diff --git a/.gitattributes b/.gitattributes index e69de29bb..7b6da41b6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Prebuilt Android engine binaries. These are large and opaque, so all of them +# are stored via Git LFS. Note this only governs how git stores *new* writes: +# blobs already committed as plain objects stay that way in history until the +# file is next written (or the history is rewritten with `git lfs migrate`). +# +# CI must check out with LFS enabled (actions/checkout `lfs: true`), otherwise +# the android build links against pointer files and fails at link time. +platforms/android/test-app/**/libs/** filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/bytecode-compilers.yml b/.github/workflows/bytecode-compilers.yml new file mode 100644 index 000000000..7bd368a97 --- /dev/null +++ b/.github/workflows/bytecode-compilers.yml @@ -0,0 +1,322 @@ +name: Build bytecode compilers + +# Builds the per-host bytecode compiler CLIs for every engine that supports +# ahead-of-time bytecode, by cloning each engine's UPSTREAM repo (not the +# android-runtime vendored sources) and building the CLI there. +# +# Output: one artifact per (engine, host), named `bytecode-compiler--`, +# each containing `//` so the downloads merge straight into +# platforms/android/tools/bytecode-compiler/bin/. +# +# The final `bundle` job merges them all into a single `bytecode-compiler-bin` +# artifact laid out exactly like platforms/android/tools/bytecode-compiler/bin/ — download it, unzip, +# and drop its contents into platforms/android/tools/bytecode-compiler/bin/ as-is. +# +# NOTE ON COMPATIBILITY: the produced bytecode must be loadable by the engine +# library the runtime ships. Hermes/PrimJS runtimes link prebuilt binaries and +# QuickJS-NG compiles from the vendored source, so the refs below must be kept in +# step with what the runtime actually bundles. Pin these to exact commits once +# validated instead of moving branches. +on: + workflow_dispatch: + inputs: + hermes_ref: + # MUST match UPSTREAM_COMMIT in build-hermes, which is what produced the + # prebuilt both platforms link. See scripts/download_hermes.sh. + description: "facebook/hermes ref (match the prebuilt release)" + default: "10a40b5f1de81d007cba6afe567c4ba7a0b45f5d" + quickjs_ng_ref: + # MUST match the vendored source in vendor/quickjs/source_ng, or + # JS_ReadObject rejects the blob: the compiler writes its own BC_VERSION + # and the runtime accepts only its own. d950d55e == BC_VERSION 27, which + # is what the vendored quickjs.c defines. + description: "quickjs-ng/quickjs ref (match vendor/quickjs/source_ng)" + default: "d950d55e950dd7994a96c669c31efa967b8b79f3" + primjs_ref: + description: "lynx-family/primjs ref" + default: "9c1fe150179b86f0ae5c6fbe670af7d76eca9df6" + +env: + HERMES_REPO: https://github.com/facebook/hermes + QUICKJS_NG_REPO: https://github.com/quickjs-ng/quickjs + PRIMJS_REPO: https://github.com/lynx-family/primjs + +jobs: + # --------------------------------------------------------------------------- + # Hermes — `hermesc` already emits a loadable HBC blob; no shim needed. + # --------------------------------------------------------------------------- + hermes: + name: hermes (${{ matrix.host }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - { host: linux-x64, runner: ubuntu-24.04 } + - { host: linux-arm64, runner: ubuntu-24.04-arm } + - { host: darwin-x64, runner: macos-15-intel } + - { host: darwin-arm64, runner: macos-14 } + - { host: win32-x64, runner: windows-2022 } + # win32-arm64 dropped: hermes' Boost.Context ARM64 assembler + CMake's + # ASM_ARMASM linker support fail on windows-11-arm. The win32-x64 + # hermesc runs on ARM Windows via x64 emulation (HBC output is portable). + steps: + - name: Install toolchain (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build build-essential python3 + - name: Install toolchain (macOS) + if: runner.os == 'macOS' + run: brew install cmake ninja + - name: Install toolchain (Windows) + if: runner.os == 'Windows' + run: choco install ninja -y + + - name: Clone Hermes + shell: bash + # Fetch by ref so an exact commit SHA works (git clone --branch only + # accepts branch/tag names). GitHub allows fetching arbitrary SHAs. + run: | + set -euxo pipefail + ref="${{ github.event.inputs.hermes_ref || '10a40b5f1de81d007cba6afe567c4ba7a0b45f5d' }}" + git init hermes + git -C hermes remote add origin "$HERMES_REPO" + git -C hermes fetch --depth 1 origin "$ref" + git -C hermes -c advice.detachedHead=false checkout FETCH_HEAD + + - name: Build hermesc (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + set -euxo pipefail + cmake -S hermes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DHERMES_BUILD_APPLE_FRAMEWORK=OFF + cmake --build build --target hermesc + + - name: Build hermesc (Windows) + if: runner.os == 'Windows' + shell: bash + run: | + set -euxo pipefail + # Use the default (Visual Studio) generator; Ninja+MSVC also works if the + # VS dev environment is on PATH. + cmake -S hermes -B build -DCMAKE_BUILD_TYPE=Release + cmake --build build --target hermesc --config Release + + - name: Stage binary + shell: bash + run: | + set -euxo pipefail + mkdir -p "out/hermes/${{ matrix.host }}" + bin="$(find build -type f \( -name hermesc -o -name hermesc.exe \) | head -n1)" + test -n "$bin" + cp "$bin" "out/hermes/${{ matrix.host }}/" + if [ "${{ runner.os }}" = "macOS" ]; then + codesign --sign - --force "out/hermes/${{ matrix.host }}/$(basename "$bin")" + codesign --verify --verbose "out/hermes/${{ matrix.host }}/$(basename "$bin")" + fi + + - uses: actions/upload-artifact@v4 + with: + name: bytecode-compiler-hermes-${{ matrix.host }} + # Root the artifact at `out/` so it carries the full + # `hermes//` path and merges straight into bin/. + path: out + if-no-files-found: error + + # --------------------------------------------------------------------------- + # QuickJS-NG — CMake build of libqjs, then our shim (same JS_* API). + # + # There is no bellard/quickjs job: the runtime dropped that tree, and its + # QUICKJS engine is now an alias for QUICKJS_NG (see + # platforms/android/test-app/runtime/build.gradle). + # --------------------------------------------------------------------------- + quickjs-ng: + name: quickjs-ng (${{ matrix.host }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - { host: linux-x64, runner: ubuntu-24.04 } + - { host: linux-arm64, runner: ubuntu-24.04-arm } + - { host: darwin-x64, runner: macos-15-intel } + - { host: darwin-arm64, runner: macos-14 } + - { host: win32-x64, runner: windows-2022 } + # - { host: win32-arm64, runner: windows-11-arm } + steps: + - uses: actions/checkout@v4 + - name: Install toolchain (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y cmake ninja-build build-essential + - name: Install toolchain (macOS) + if: runner.os == 'macOS' + run: brew install cmake ninja + - name: Install toolchain (Windows) + if: runner.os == 'Windows' + run: choco install ninja llvm -y + + - name: Clone QuickJS-NG + shell: bash + run: | + set -euxo pipefail + ref="${{ github.event.inputs.quickjs_ng_ref || 'd950d55e950dd7994a96c669c31efa967b8b79f3' }}" + git init quickjs-ng + git -C quickjs-ng remote add origin "$QUICKJS_NG_REPO" + git -C quickjs-ng fetch --depth 1 origin "$ref" + git -C quickjs-ng -c advice.detachedHead=false checkout FETCH_HEAD + + - name: Build lib + shim + shell: bash + env: + SHIM: ${{ github.workspace }}/platforms/android/tools/bytecode-compiler/native/qjs-compile.c + run: | + set -euxo pipefail + # Note: the workflow lives at the repository root, so the shim path is + # platforms/android/-prefixed; the tool tree itself is under Android. + # Build the shim as a CMake target so it uses the SAME toolchain + CRT + # as the engine lib. Linking a separately-compiled (clang) shim against + # the MSVC-built qjs.lib fails with __imp_*/CRT-mismatch link errors on + # Windows. The 'qjs' target propagates its public include dirs and libm. + cp "$SHIM" quickjs-ng/nsbc-quickjs-ng.c + cat >> quickjs-ng/CMakeLists.txt <<'CMAKE' + + add_executable(nsbc-quickjs-ng nsbc-quickjs-ng.c) + target_link_libraries(nsbc-quickjs-ng PRIVATE qjs) + target_compile_definitions(nsbc-quickjs-ng PRIVATE NSBC_MAGIC=\"NSBCNGS\") + CMAKE + cmake -S quickjs-ng -B quickjs-ng/build -DCMAKE_BUILD_TYPE=Release + cmake --build quickjs-ng/build --config Release --target nsbc-quickjs-ng + out="out/quickjs-ng/${{ matrix.host }}"; mkdir -p "$out" + bin="$(find quickjs-ng/build -type f \( -name 'nsbc-quickjs-ng' -o -name 'nsbc-quickjs-ng.exe' \) | head -n1)" + test -n "$bin"; cp "$bin" "$out/" + if [ "${{ runner.os }}" = "macOS" ]; then + codesign --sign - --force "$out/nsbc-quickjs-ng" + codesign --verify --verbose "$out/nsbc-quickjs-ng" + fi + [ "${{ runner.os }}" = "Windows" ] || "$out/nsbc-quickjs-ng" || true + + - uses: actions/upload-artifact@v4 + with: + name: bytecode-compiler-quickjs-ng-${{ matrix.host }} + # Root the artifact at `out/` so it carries the full + # `quickjs-ng//` path and merges straight into bin/. + path: out + if-no-files-found: error + + # --------------------------------------------------------------------------- + # PrimJS — build via its own toolchain: `hab sync` fetches gn + ninja + a + # bundled clang + sysroot, then GN/ninja build the engine. We add our LEPUS_* + # shim as a sibling GN executable (reusing the qjs-cli engine deps). + # + # Unix only, and NOT linux-arm64: PrimJS's envsetup.sh aborts on Windows, and + # its DEPS only provides a linux sysroot for x86_64. So: linux-x64 + macOS. + # + # Validated locally on darwin-arm64 (hab sync -> gn gen -> ninja nsbc-primjs + # produces a working CLI). Linux uses the same flow. + # --------------------------------------------------------------------------- + primjs: + name: primjs (${{ matrix.host }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - { host: linux-x64, runner: ubuntu-24.04 } + - { host: darwin-x64, runner: macos-15-intel } + - { host: darwin-arm64, runner: macos-14 } + steps: + - uses: actions/checkout@v4 + - name: Install prerequisites (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y python3 curl + + - name: Clone PrimJS + shell: bash + run: | + set -euxo pipefail + ref="${{ github.event.inputs.primjs_ref || '9c1fe150179b86f0ae5c6fbe670af7d76eca9df6' }}" + git init primjs + git -C primjs remote add origin "$PRIMJS_REPO" + git -C primjs fetch --depth 1 origin "$ref" + git -C primjs -c advice.detachedHead=false checkout FETCH_HEAD + + - name: Add shim as a GN target + shell: bash + env: + SHIM: ${{ github.workspace }}/platforms/android/tools/bytecode-compiler/native/primjs-compile.c + run: | + set -euxo pipefail + # Build as C++ (.cc); the LEPUS_* API is extern "C" so linkage is fine. + cp "$SHIM" primjs/tools/qjs-cli/nsbc-primjs.cc + # Add the target to the ROOT BUILD.gn (always loaded, so it's always + # generated) linking ONLY the core engine (//src:quickjs_lib). We avoid + # qjs_wasm_binding — its WASM/JSC code doesn't build under the bundled + # toolchain's -Werror, and our shim doesn't need it. (Validated locally.) + cat >> primjs/BUILD.gn <<'GN' + + executable("nsbc-primjs") { + cflags = [ "-Wno-c99-designator" ] + sources = [ "//tools/qjs-cli/nsbc-primjs.cc" ] + include_dirs = [ "//include" ] + deps = [ "//src:quickjs_lib" ] + } + GN + + - name: Build (hab sync + gn + ninja) + shell: bash + working-directory: primjs + run: | + set -euxo pipefail + # hab (in-repo) fetches gn, ninja, the bundled clang toolchain + sysroot. + source tools/envsetup.sh + hab sync . + gn gen out/Default --args="is_debug=false" + ninja -C out/Default nsbc-primjs + out="../out/primjs/${{ matrix.host }}"; mkdir -p "$out" + cp out/Default/nsbc-primjs "$out/nsbc-primjs" + if [ "${{ runner.os }}" = "macOS" ]; then + codesign --sign - --force "$out/nsbc-primjs" + codesign --verify --verbose "$out/nsbc-primjs" + fi + "$out/nsbc-primjs" || true # usage line (exit 2) proves it links & runs + + - uses: actions/upload-artifact@v4 + with: + name: bytecode-compiler-primjs-${{ matrix.host }} + # Root the artifact at `out/` so it carries the full + # `primjs//` path and merges straight into bin/. + path: out + if-no-files-found: error + + # --------------------------------------------------------------------------- + # Bundle — merge every per-(engine, host) artifact into a single artifact laid + # out exactly like platforms/android/tools/bytecode-compiler/bin/, ready to download-and-paste. + # --------------------------------------------------------------------------- + bundle: + name: bundle bin/ + runs-on: ubuntu-24.04 + needs: [hermes, quickjs-ng, primjs] + # Run even if some engine/host builds failed, so a partial bin/ is still + # published rather than nothing. + if: always() + steps: + - name: Download all compiler artifacts + uses: actions/download-artifact@v4 + with: + # Every per-job artifact already carries an `//` + # path; merge-multiple overlays them into one tree = the bin/ layout. + pattern: bytecode-compiler-* + path: bin + merge-multiple: true + + - name: Show merged layout + run: find bin -type f | sort + + - name: Restore executable permissions + shell: bash + run: find bin -type f -path '*/darwin-*/*' -exec chmod +x {} \; + + - uses: actions/upload-artifact@v4 + with: + name: bytecode-compiler-bin + path: bin + if-no-files-found: error diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 627aeb287..1b2db8d98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,13 @@ jobs: - name: Generate macOS metadata run: npm run metagen macos + - name: Apply QuickJS patches + # vendor/quickjs/source_ng is a pristine submodule checkout at its pinned + # commit; the NativeScript engine changes the Apple build needs (e.g. + # JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE for node:vm) live in + # vendor/quickjs/patches and are applied into the work tree here. + run: npm run patch-quickjs + - name: Build NativeScript run: npm run build @@ -52,6 +59,6 @@ jobs: IOS_BUILD_TIMEOUT_MS: "600000" IOS_TEST_TIMEOUT_MS: "600000" IOS_TEST_INACTIVITY_TIMEOUT_MS: "180000" - IOS_TEST_VERBOSE_SPECS: "1" + IOS_LOG_JUNIT: "1" IOS_SIMCTL_QUERY_TIMEOUT_MS: "10000" run: npm run test:ios diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 06f4b0710..ec1d9fd57 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -111,6 +111,12 @@ jobs: echo NPM_VERSION=$NPM_VERSION >> $GITHUB_OUTPUT echo NPM_TAG=$NPM_TAG >> $GITHUB_OUTPUT echo BUILD_ENGINE=$BUILD_ENGINE >> $GITHUB_OUTPUT + - name: Apply QuickJS patches + # vendor/quickjs/source_ng is a pristine submodule checkout at its pinned + # commit; the NativeScript engine changes the Apple build needs (e.g. + # JS_EVAL_FLAG_COMPILE_ONLY_NO_RESOLVE for node:vm) live in + # vendor/quickjs/patches and are applied into the work tree here. + run: npm run patch-quickjs - name: Build run: ./scripts/build_all_ios.sh --${{ steps.npm_version_output.outputs.BUILD_ENGINE }} - name: Upload npm package artifact diff --git a/.github/workflows/npm_trusted_release.yml b/.github/workflows/npm_trusted_release.yml index c446b32b5..9807e84ea 100644 --- a/.github/workflows/npm_trusted_release.yml +++ b/.github/workflows/npm_trusted_release.yml @@ -1,30 +1,46 @@ name: NPM Trusted Release -# Publishes one or more NativeScript iOS npm packages -# (@nativescript/ios-v8, @nativescript/ios-hermes, @nativescript/ios-jsc, -# @nativescript/ios-quickjs, @nativescript/react-native) via npm trusted -# publishing (OIDC). +# Publishes NativeScript runtime npm packages via npm trusted publishing (OIDC). +# +# iOS engine packages: @nativescript/ios-{v8,hermes,jsc,quickjs} +# Android engine packages: @nativescript/android-{v8,hermes,jsc,quickjs-ng,primjs} +# React Native package: @nativescript/react-native # # Each package must be configured on npmjs.com with a trusted publisher that -# points at this repository + workflow + environment. With `engine: all`, the -# workflow fans out across the four iOS engine packages via a matrix; use -# `engine: react-native` to publish @nativescript/react-native. +# points at this repository + workflow + environment. +# +# `engines` accepts any comma/space-separated combination (e.g. "v8, quickjs"), +# `all` for every engine a platform supports, or `react-native`. The `ios` and +# `android` checkboxes pick which platforms build the selected engines; primjs +# is Android-only. +# +# The Android runtime lives on a separate branch until it merges to main. +# Dispatch this workflow from main and set `build-ref` to that branch (e.g. +# android-react-native): the run then uses this workflow definition but builds +# and publishes from that branch's sources. on: workflow_dispatch: inputs: - engine: - description: "Package to release (engine package, react-native, or 'all' for every iOS engine)" + engines: + description: "Engines to release, comma/space separated: v8, quickjs, jsc, hermes, primjs — or 'all'. Use 'react-native' for @nativescript/react-native." required: true - type: choice + type: string default: v8 - options: - - v8 - - hermes - - jsc - - quickjs - - react-native - - all + ios: + description: "Build the selected engines for iOS (@nativescript/ios-)" + required: false + type: boolean + default: true + android: + description: "Build the selected engines for Android (@nativescript/android-)" + required: false + type: boolean + default: false + build-ref: + description: "Git ref to build from (defaults to the ref the workflow runs on). Use android-react-native for Android builds until it merges." + required: false + type: string release-type: description: "Version bump (patch/minor/major publish to 'latest'; prerelease uses 'preid' as the dist-tag)" required: false @@ -56,11 +72,13 @@ on: concurrency: # Avoid overlapping publishes on the same ref/package selection. - group: npm-trusted-release-${{ github.ref }}-${{ inputs.engine }} + group: npm-trusted-release-${{ github.ref }}-${{ inputs.engines }}-${{ inputs.ios }}-${{ inputs.android }} cancel-in-progress: false env: XCODE_VERSION: "26.2.0" + # V8 14.9's headers need std::atomic_ref (libc++ from LLVM 19), so NDK r29+. + ANDROID_NDK_VERSION: "29.0.14206865" jobs: matrix: @@ -68,41 +86,128 @@ jobs: runs-on: ubuntu-latest permissions: {} outputs: - targets: ${{ steps.compute.outputs.targets }} + apple_targets: ${{ steps.compute.outputs.apple_targets }} + android_targets: ${{ steps.compute.outputs.android_targets }} + publish_targets: ${{ steps.compute.outputs.publish_targets }} steps: - name: Compute matrix id: compute env: - ENGINE: ${{ inputs.engine }} + ENGINES: ${{ inputs.engines }} + IOS: ${{ inputs.ios }} + ANDROID: ${{ inputs.android }} run: | set -euo pipefail - case "$ENGINE" in - all) - echo 'targets=["v8","hermes","jsc","quickjs"]' >> "$GITHUB_OUTPUT" - ;; - v8|hermes|jsc|quickjs|react-native) - printf 'targets=["%s"]\n' "$ENGINE" >> "$GITHUB_OUTPUT" - ;; - *) - echo "Unsupported engine: $ENGINE" >&2 - exit 1 - ;; - esac - build: + raw=$(printf '%s' "$ENGINES" | tr '[:upper:]' '[:lower:]' | tr ',' ' ') + + IOS_ENGINES="v8 hermes jsc quickjs" + ANDROID_ENGINES="v8 hermes jsc quickjs primjs" + + apple=() + android=() + + contains() { + local x="$1"; shift + local e + for e in "$@"; do [ "$e" = "$x" ] && return 0; done + return 1 + } + add_apple() { contains "$1" ${apple[@]+"${apple[@]}"} || apple+=("$1"); } + add_android() { contains "$1" ${android[@]+"${android[@]}"} || android+=("$1"); } + + # The Android quickjs package keeps the upstream quickjs-ng name. + android_pkg_for() { + case "$1" in + quickjs) echo quickjs-ng ;; + *) echo "$1" ;; + esac + } + + for token in $raw; do + [ -n "$token" ] || continue + [ "$token" = "quickjs-ng" ] && token=quickjs + case "$token" in + all) + if [ "$IOS" != "true" ] && [ "$ANDROID" != "true" ]; then + echo "::error::'all' requires at least one platform (ios and/or android) to be checked." + exit 1 + fi + if [ "$IOS" = "true" ]; then + for e in $IOS_ENGINES; do add_apple "ios-$e"; done + fi + if [ "$ANDROID" = "true" ]; then + for e in $ANDROID_ENGINES; do add_android "android-$(android_pkg_for "$e")"; done + fi + ;; + react-native) + add_apple react-native + ;; + v8|hermes|jsc|quickjs|primjs) + matched=false + if [ "$IOS" = "true" ] && [ "$token" != "primjs" ]; then + add_apple "ios-$token" + matched=true + fi + if [ "$ANDROID" = "true" ]; then + add_android "android-$(android_pkg_for "$token")" + matched=true + fi + if [ "$matched" != "true" ]; then + if [ "$token" = "primjs" ] && [ "$IOS" = "true" ]; then + echo "::error::primjs is Android-only; check the android box to release @nativescript/android-primjs." + else + echo "::error::Engine '$token' selected but no platform checked. Enable ios and/or android." + fi + exit 1 + fi + if [ "$token" = "primjs" ] && [ "$IOS" = "true" ]; then + echo "::notice::primjs has no iOS package; releasing the Android package only." + fi + ;; + *) + echo "::error::Unknown engine '$token'. Expected v8, quickjs, jsc, hermes, primjs, react-native, or all." + exit 1 + ;; + esac + done + + if [ ${#apple[@]} -eq 0 ] && [ ${#android[@]} -eq 0 ]; then + echo "::error::No release targets resolved from engines='$ENGINES' (ios=$IOS, android=$ANDROID)." + exit 1 + fi + + to_json() { + if [ $# -eq 0 ]; then printf '[]'; return; fi + local out="[" first=true e + for e in "$@"; do + $first || out+="," + out+="\"$e\"" + first=false + done + printf '%s]' "$out" + } + + all_targets=(${apple[@]+"${apple[@]}"} ${android[@]+"${android[@]}"}) + { + echo "apple_targets=$(to_json ${apple[@]+"${apple[@]}"})" + echo "android_targets=$(to_json ${android[@]+"${android[@]}"})" + echo "publish_targets=$(to_json ${all_targets[@]+"${all_targets[@]}"})" + } >> "$GITHUB_OUTPUT" + echo "Apple targets: $(to_json ${apple[@]+"${apple[@]}"})" + echo "Android targets: $(to_json ${android[@]+"${android[@]}"})" + + build-apple: name: Build ${{ matrix.target }} needs: matrix + if: ${{ needs.matrix.outputs.apple_targets != '[]' }} runs-on: macos-26 permissions: contents: read strategy: fail-fast: false matrix: - target: ${{ fromJson(needs.matrix.outputs.targets) }} - outputs: - # Per-target outputs aren't natively supported with matrices, so each job - # uploads its computed metadata alongside the tarball artifact. - placeholder: noop + target: ${{ fromJson(needs.matrix.outputs.apple_targets) }} steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 @@ -113,8 +218,12 @@ jobs: xcode-version: ${{ env.XCODE_VERSION }} - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: + ref: ${{ inputs.build-ref || github.ref }} fetch-depth: 0 submodules: recursive + # react-native also builds the Android tools jars, which compile + # against LFS-stored jars under platforms/android/test-app/**/libs. + lfs: ${{ matrix.target == 'react-native' }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 24 @@ -128,10 +237,10 @@ jobs: npm install python3 -m pip install --upgrade pip six if ! command -v ld64.lld >/dev/null; then - brew list lld || brew install lld + brew list lld &>/dev/null || brew install lld fi if ! command -v cmake >/dev/null; then - brew list cmake || brew install cmake + brew list cmake &>/dev/null || brew install cmake fi if [ ! -x /usr/local/bin/cmake ]; then sudo mkdir -p /usr/local/bin @@ -159,11 +268,12 @@ jobs: tarball_basename="nativescript-react-native" npm_tag_target="react-native" else - pkg_dir="packages/ios-${target}" - package_name="@nativescript/ios-${target}" - tarball_basename="nativescript-ios-${target}" - npm_tag_target="ios-${target}" - echo "IOS_VARIANT=ios-${target}" >> "$GITHUB_ENV" + engine="${target#ios-}" + pkg_dir="packages/ios-${engine}" + package_name="@nativescript/ios-${engine}" + tarball_basename="nativescript-ios-${engine}" + npm_tag_target="ios-${engine}" + echo "IOS_VARIANT=ios-${engine}" >> "$GITHUB_ENV" fi pushd "$pkg_dir" >/dev/null @@ -195,21 +305,60 @@ jobs: echo "Exact prerelease publishes must include a prerelease identifier (for example 9.0.0-preview.0)." >&2 exit 1 fi - echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT" - echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" - echo "PACKAGE_DIR=$pkg_dir" >> "$GITHUB_OUTPUT" - echo "PACKAGE_NAME=$package_name" >> "$GITHUB_OUTPUT" - echo "TARBALL_BASENAME=$tarball_basename" >> "$GITHUB_OUTPUT" + { + echo "NPM_VERSION=$NPM_VERSION" + echo "NPM_TAG=$NPM_TAG" + echo "PACKAGE_DIR=$pkg_dir" + echo "PACKAGE_NAME=$package_name" + echo "TARBALL_BASENAME=$tarball_basename" + } >> "$GITHUB_OUTPUT" echo "Resolved $package_name@$NPM_VERSION (tag: $NPM_TAG)" - - name: Build iOS engine (--${{ matrix.target }}) + - name: Apply QuickJS patches + # A build-ref that vendors QuickJS needs the NativeScript engine changes + # from vendor/quickjs/patches (e.g. JS_WeakRef_Deref / JS_NewString16 + # used by vendor/quickjs/quickjs-api.c) applied onto the pristine + # submodule checkout before the iOS quickjs build. Refs without the + # vendored engine have nothing to patch. + if: ${{ matrix.target != 'react-native' }} + run: | + if [ -f scripts/apply_quickjs_patches.js ]; then + npm run patch-quickjs + else + echo "No vendored QuickJS in this ref; nothing to patch." + fi + - name: Build iOS engine (${{ matrix.target }}) if: ${{ matrix.target != 'react-native' }} env: TARGET: ${{ matrix.target }} - run: ./scripts/build_all_ios.sh "--${TARGET}" + run: ./scripts/build_all_ios.sh "--${TARGET#ios-}" + # The package ships both halves. The Android half is generated -- both + # android/tools (the build-time jars) and android/src/main/java-runtime + # are gitignored -- so without these steps npm pack produces a tarball + # with an Android directory that has no runtime and no tools in it. + - name: Verify Android runtime sources + if: ${{ matrix.target == 'react-native' }} + run: | + if [ ! -d platforms/android/test-app ]; then + echo "::error::The checked-out ref has no Android runtime, so @nativescript/react-native would publish without its Android half. Set build-ref to a branch that contains it (e.g. android-react-native)." + exit 1 + fi + - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + if: ${{ matrix.target == 'react-native' }} + with: + distribution: temurin + java-version: 17 - name: Build @nativescript/react-native if: ${{ matrix.target == 'react-native' }} run: | ./scripts/build_all_react_native.sh + # Staging only: copies the runtime C++ and com.tns.* Java into the + # package. No SDK or NDK involved -- the consuming app compiles them. + ./scripts/build_react_native_android.sh + # Builds the metadata/dts/static-binding jars through the test-app + # Gradle build, which configures Android modules and so needs the SDK + # the runner image ships. + ./scripts/build_react_native_android_tools.sh + # Stages the iOS half and packs; run last, it is what produces the tarball. ./scripts/build_react_native_turbomodule.sh - name: Record metadata shell: bash @@ -249,18 +398,221 @@ jobs: name: NativeScript-dSYMs-${{ matrix.target }} path: dist/dSYMs + build-android: + name: Build ${{ matrix.target }} + needs: matrix + if: ${{ needs.matrix.outputs.android_targets != '[]' }} + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.matrix.outputs.android_targets) }} + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 + with: + egress-policy: audit + - name: Free disk space + run: sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup /opt/hostedtoolcache/CodeQL || true + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + ref: ${{ inputs.build-ref || github.ref }} + fetch-depth: 0 + submodules: recursive + # The prebuilt engine binaries under platforms/android/test-app/**/libs + # are stored in Git LFS; without this the build links pointer files. + lfs: true + - name: Verify Android runtime sources + run: | + if [ ! -f scripts/build_all_android.sh ] || [ ! -d platforms/android ]; then + echo "::error::The checked-out ref has no Android runtime. Set build-ref to a branch that contains it (e.g. android-react-native)." + exit 1 + fi + - uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + with: + distribution: temurin + java-version: 17 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 24 + registry-url: "https://registry.npmjs.org" + - name: Install Dependencies + run: npm install + - name: Install Android NDK + # sdkmanager is not on PATH on the hosted ubuntu runners; call it from + # ANDROID_HOME. Skip the install when the image already ships this NDK. + run: | + if [ ! -d "${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" ]; then + echo y | "${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" --install "ndk;${ANDROID_NDK_VERSION}" >/dev/null + fi + - name: Prepare Android build + run: ./scripts/setup_android.sh + - name: Bump version + id: bump + shell: bash + env: + RELEASE_TYPE: ${{ inputs.release-type }} + PACKAGE_VERSION: ${{ inputs.version }} + PREID: ${{ inputs.preid }} + NPM_TAG_OVERRIDE: ${{ inputs.npm-tag }} + TARGET: ${{ matrix.target }} + run: | + set -euo pipefail + release_type="$RELEASE_TYPE" + package_version="$PACKAGE_VERSION" + preid="$PREID" + npm_tag_override="$NPM_TAG_OVERRIDE" + pkg="${TARGET#android-}" + pkg_dir="packages/android-${pkg}" + package_name="@nativescript/android-${pkg}" + tarball_basename="nativescript-android-${pkg}" + npm_tag_target="android-${pkg}" + case "$pkg" in + v8) gradle_engine="V8-13" ;; + quickjs-ng) gradle_engine="QUICKJS_NG" ;; + hermes) gradle_engine="HERMES" ;; + jsc) gradle_engine="JSC" ;; + primjs) gradle_engine="PRIMJS" ;; + *) + echo "::error::Unknown Android engine package '$pkg'." + exit 1 + ;; + esac + if [ ! -f "$pkg_dir/package.json" ]; then + echo "::error::Missing $pkg_dir/package.json on this ref." + exit 1 + fi + + pushd "$pkg_dir" >/dev/null + if [ -n "$package_version" ]; then + npm version "$package_version" --no-git-tag-version >/dev/null + elif [ "$release_type" = "prerelease" ]; then + npm version prerelease --preid "$preid" --no-git-tag-version >/dev/null + else + npm version "$release_type" --no-git-tag-version >/dev/null + fi + NPM_VERSION=$(node -e "console.log(require('./package.json').version)") + popd >/dev/null + + NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node ./scripts/get-npm-tag.js "$npm_tag_target") + if [ -n "$npm_tag_override" ]; then + case "$npm_tag_override" in + *[[:space:]]*) + echo "Invalid npm tag override '$npm_tag_override': dist-tags cannot contain whitespace." >&2 + exit 1 + ;; + esac + if printf '%s\n' "$npm_tag_override" | grep -Eq '^[0-9]+(\.[0-9]+)*$'; then + echo "Invalid npm tag override '$npm_tag_override': dist-tags must not look like semver versions." >&2 + exit 1 + fi + NPM_TAG="$npm_tag_override" + fi + if [ -n "$package_version" ] && [ "$release_type" = "prerelease" ] && [ -z "$npm_tag_override" ] && [ "$NPM_TAG" = "latest" ]; then + echo "Exact prerelease publishes must include a prerelease identifier (for example 9.0.0-preview.0)." >&2 + exit 1 + fi + { + echo "NPM_VERSION=$NPM_VERSION" + echo "NPM_TAG=$NPM_TAG" + echo "PACKAGE_DIR=$pkg_dir" + echo "PACKAGE_NAME=$package_name" + echo "TARBALL_BASENAME=$tarball_basename" + echo "GRADLE_ENGINE=$gradle_engine" + } >> "$GITHUB_OUTPUT" + echo "Resolved $package_name@$NPM_VERSION (tag: $NPM_TAG, gradle engine: $gradle_engine)" + - name: Build Android runtime (${{ steps.bump.outputs.GRADLE_ENGINE }}) + env: + GIT_COMMIT: ${{ github.sha }} + GRADLE_ENGINE: ${{ steps.bump.outputs.GRADLE_ENGINE }} + run: ./scripts/build_all_android.sh "--engine=${GRADLE_ENGINE}" --binding=napi "-PndkVersion=${ANDROID_NDK_VERSION}" + - name: Pack npm package + shell: bash + env: + GRADLE_ENGINE: ${{ steps.bump.outputs.GRADLE_ENGINE }} + PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} + PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} + NPM_VERSION: ${{ steps.bump.outputs.NPM_VERSION }} + run: | + set -euo pipefail + # Mirrors the slug the gradle build derives (see platforms/android/build.gradle). + slug=$(printf '%s' "$GRADLE_ENGINE" | tr '[:upper:]-' '[:lower:]_') + [ "$slug" = "quickjs_ng" ] && slug=quickjs + dist_dir="dist/android_${slug}_napi" + if [ ! -f "$dist_dir/package.json" ]; then + echo "::error::Expected staged package at $dist_dir; the gradle build did not produce it." + exit 1 + fi + # The gradle build stages the generic @nativescript/android package; + # stamp the per-engine name and the bumped version before packing. + node -e ' + const fs = require("fs"); + const [, path, name, version] = process.argv; + const pkg = JSON.parse(fs.readFileSync(path, "utf8")); + pkg.name = name; + pkg.version = version; + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + "\n"); + ' "$dist_dir/package.json" "$PACKAGE_NAME" "$NPM_VERSION" + mkdir -p "$PACKAGE_DIR/dist" + (cd "$dist_dir" && npm pack --pack-destination "$GITHUB_WORKSPACE/$PACKAGE_DIR/dist") + - name: Record metadata + shell: bash + env: + TARGET: ${{ matrix.target }} + PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} + PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} + NPM_VERSION: ${{ steps.bump.outputs.NPM_VERSION }} + NPM_TAG: ${{ steps.bump.outputs.NPM_TAG }} + TARBALL_BASENAME: ${{ steps.bump.outputs.TARBALL_BASENAME }} + run: | + set -euo pipefail + package_dir="$PACKAGE_DIR" + tarball_file="${TARBALL_BASENAME}-${NPM_VERSION}.tgz" + if [ ! -f "$package_dir/dist/$tarball_file" ]; then + echo "::error::Expected tarball $package_dir/dist/$tarball_file was not produced." + exit 1 + fi + cat > "$package_dir/dist/release-meta.json" <- + ${{ !cancelled() && + needs.matrix.result == 'success' && + needs.build-apple.result != 'failure' && needs.build-apple.result != 'cancelled' && + needs.build-android.result != 'failure' && needs.build-android.result != 'cancelled' && + (needs.build-apple.result == 'success' || needs.build-android.result == 'success') }} runs-on: ubuntu-latest environment: name: ${{ inputs.dry-run && 'npm-publish-dry-run' || 'npm-publish' }} strategy: fail-fast: false matrix: - target: ${{ fromJson(needs.matrix.outputs.targets) }} + target: ${{ fromJson(needs.matrix.outputs.publish_targets) }} permissions: contents: read id-token: write @@ -299,10 +651,12 @@ jobs: NPM_TAG=$(node -e "console.log(require('./$meta').tag)") PACKAGE_NAME=$(node -e "console.log(require('./$meta').package_name)") TARBALL=$(node -e "console.log(require('./$meta').tarball)") - echo "NPM_VERSION=$NPM_VERSION" >> "$GITHUB_OUTPUT" - echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT" - echo "PACKAGE_NAME=$PACKAGE_NAME" >> "$GITHUB_OUTPUT" - echo "TARBALL=$TARBALL" >> "$GITHUB_OUTPUT" + { + echo "NPM_VERSION=$NPM_VERSION" + echo "NPM_TAG=$NPM_TAG" + echo "PACKAGE_NAME=$PACKAGE_NAME" + echo "TARBALL=$TARBALL" + } >> "$GITHUB_OUTPUT" - name: Publish package (OIDC trusted publishing) if: ${{ vars.USE_NPM_TOKEN != 'true' }} shell: bash @@ -354,29 +708,39 @@ jobs: if: always() needs: - matrix - - build + - build-apple + - build-android - publish runs-on: ubuntu-latest permissions: {} steps: - name: Print summary env: - PACKAGE_SELECTION: ${{ inputs.engine }} + ENGINES: ${{ inputs.engines }} + IOS: ${{ inputs.ios }} + ANDROID: ${{ inputs.android }} + BUILD_REF: ${{ inputs.build-ref }} RELEASE_TYPE: ${{ inputs.release-type }} PACKAGE_VERSION: ${{ inputs.version }} PREID: ${{ inputs.preid }} NPM_TAG_OVERRIDE: ${{ inputs.npm-tag }} DRY_RUN: ${{ inputs.dry-run }} - TARGETS: ${{ needs.matrix.outputs.targets }} - BUILD_RESULT: ${{ needs.build.result }} + APPLE_TARGETS: ${{ needs.matrix.outputs.apple_targets }} + ANDROID_TARGETS: ${{ needs.matrix.outputs.android_targets }} + BUILD_APPLE_RESULT: ${{ needs.build-apple.result }} + BUILD_ANDROID_RESULT: ${{ needs.build-android.result }} PUBLISH_RESULT: ${{ needs.publish.result }} run: | - echo "Package selection: $PACKAGE_SELECTION" - echo "Release type: $RELEASE_TYPE" - echo "Exact version: $PACKAGE_VERSION" - echo "Preid: $PREID" - echo "NPM tag override: $NPM_TAG_OVERRIDE" - echo "Dry run: $DRY_RUN" - echo "Targets: $TARGETS" - echo "Build result: $BUILD_RESULT" - echo "Publish result: $PUBLISH_RESULT" + echo "Engines: $ENGINES" + echo "Platforms: ios=$IOS android=$ANDROID" + echo "Build ref: ${BUILD_REF:-$GITHUB_REF}" + echo "Release type: $RELEASE_TYPE" + echo "Exact version: $PACKAGE_VERSION" + echo "Preid: $PREID" + echo "NPM tag override: $NPM_TAG_OVERRIDE" + echo "Dry run: $DRY_RUN" + echo "Apple targets: $APPLE_TARGETS" + echo "Android targets: $ANDROID_TARGETS" + echo "Apple build result: $BUILD_APPLE_RESULT" + echo "Android build result: $BUILD_ANDROID_RESULT" + echo "Publish result: $PUBLISH_RESULT" diff --git a/.gitignore b/.gitignore index d8af2d700..f317bdf6b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ package-lock.json v8_build .npmrc /Frameworks/ +/.kiro/ +/opencode.json /llvm/ @@ -52,10 +54,10 @@ v8_build .cipd/ # project template -/templates/ios/.build_env_vars.sh -/templates/ios/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/ -/templates/visionos/.build_env_vars.sh -/templates/visionos/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +/platforms/apple/templates/ios/.build_env_vars.sh +/platforms/apple/templates/ios/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/ +/platforms/apple/templates/visionos/.build_env_vars.sh +/platforms/apple/templates/visionos/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist .cache/ @@ -66,11 +68,41 @@ packages/*/metadata-json SwiftBindgen # Generated Objective-C/C dispatch wrappers -NativeScript/ffi/napi/GeneratedSignatureDispatch.inc -NativeScript/ffi/napi/GeneratedSignatureDispatch.inc.stamp +NativeScript/ffi/**/GeneratedSignatureDispatch.inc +NativeScript/ffi/**/GeneratedSignatureDispatch.inc.stamp +NativeScript/ffi/**/GeneratedGsdSignatureDispatch.inc +NativeScript/ffi/**/GeneratedGsdSignatureDispatch.inc.stamp + +# Packaged native framework artifacts +packages/*/NativeScript.xcframework/ # React Native TurboModule package staging packages/react-native/dist/ packages/react-native/ios/vendor/ packages/react-native/metadata/ -packages/react-native/native-api-jsi/ +packages/react-native/native-api/ + +# Prebuilt V8 static libs, fetched by scripts/download_v8.sh rather than +# committed -- they are ~380 MB. The Apple xcframework is already excluded via +# /Frameworks/ above. The shared V8 headers under vendor/v8 ARE tracked: they +# are text, and the build needs them without a network round trip. +/platforms/android/test-app/runtime/src/main/libs/v8/ + +# V8's internal headers (~27 MB, 2400 files), fetched by download_v8.sh for the +# v8_inspector sources. Unlike vendor/v8/include these are not tracked: they are +# large, entirely unedited, and pinned to the same release as the binaries. +/vendor/v8/src/ +/vendor/v8/third_party/ + +# Prebuilt Hermes libs, fetched by scripts/download_hermes.sh from the same +# release as the Apple xcframework. Committing them let the .so files drift out +# of step with the headers. +/platforms/android/test-app/runtime/src/main/libs/hermes/ + +# @nativescript/react-native Android module build output +packages/react-native/android/.cxx/ +packages/react-native/android/build/ +# Staged by scripts/build_react_native_android_tools.sh at package time +packages/react-native/android/tools/ +# Staged by scripts/build_react_native_android.sh at package time +packages/react-native/android/src/main/java-runtime/ diff --git a/.gitmodules b/.gitmodules index fa45c83c7..8299eb02c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,9 @@ path = libffi url = https://github.com/NativeScript/libffi.git branch = darind/v8-ios +[submodule "vendor/quickjs/source_ng"] + path = vendor/quickjs/source_ng + url = https://github.com/quickjs-ng/quickjs + # The NativeScript patch is applied into the work tree by + # scripts/apply_quickjs_patches.js, so the submodule is always dirty. + ignore = dirty diff --git a/NativeScript/CMakeLists.txt b/NativeScript/CMakeLists.txt index b9386f462..b8e494301 100644 --- a/NativeScript/CMakeLists.txt +++ b/NativeScript/CMakeLists.txt @@ -1,7 +1,11 @@ cmake_minimum_required(VERSION 3.15) # Metadata -project(NativeScript CXX OBJCXX) +# C is required for the quickjs engine sources (quickjs.c, libregexp.c, …) and +# quickjs-api.c. It used to be enabled as a side effect of add_subdirectory()ing +# mimalloc; without it CMake silently drops every .c file from the target and the +# build fails at link with undefined JS_* symbols. +project(NativeScript C CXX OBJCXX) set(NAME NativeScript) set(VERSION 0.1.0) @@ -20,12 +24,12 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}") # Arguments set(TARGET_PLATFORM "macos" CACHE STRING "Target platform for the Objective-C bridge") set(TARGET_ENGINE "v8" CACHE STRING "Target JS engine for the NativeScript runtime") -set(NS_FFI_BACKEND "auto" CACHE STRING "FFI backend: auto, napi, or direct") +set(NS_FFI_BACKEND "auto" CACHE STRING "FFI backend: auto, napi, v8, jsc, quickjs, or hermes") set(NS_GSD_BACKEND "auto" CACHE STRING "Generated signature dispatch backend: auto, v8, jsc, quickjs, hermes, napi, or none") set(METADATA_SIZE 0 CACHE STRING "Size of embedded metadata in bytes") set(BUILD_CLI_BINARY OFF CACHE BOOL "Build the NativeScript CLI binary") set(BUILD_MACOS_NODE_API OFF CACHE BOOL "Build the NativeScript macOS Node API dylib") -set_property(CACHE NS_FFI_BACKEND PROPERTY STRINGS auto napi direct) +set_property(CACHE NS_FFI_BACKEND PROPERTY STRINGS auto napi v8 jsc quickjs hermes) set_property(CACHE NS_GSD_BACKEND PROPERTY STRINGS auto v8 jsc quickjs hermes napi none) if (BUILD_MACOS_NODE_API) @@ -111,7 +115,12 @@ elseif(TARGET_ENGINE STREQUAL "v8") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -stdlib=libc++ -std=c++20 -DTARGET_ENGINE_V8") elseif(TARGET_ENGINE STREQUAL "quickjs") set(TARGET_ENGINE_QUICKJS TRUE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++20 -DTARGET_ENGINE_QUICKJS") + # The shared quickjs backend selects between the quickjs-ng and bellard APIs + # with __QJS_NG__ / __QUICKJS_NG__. Only quickjs-ng is vendored now, so both + # platforms must define them or the bellard branches get compiled against an + # ng header (JS_IsArray, JS_FreeValue, … then mismatch on arity). + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D__QJS_NG__ -D__QUICKJS_NG__") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++20 -DTARGET_ENGINE_QUICKJS -D__QJS_NG__ -D__QUICKJS_NG__") elseif(TARGET_ENGINE STREQUAL "jsc") set(TARGET_ENGINE_JSC TRUE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++20 -DTARGET_ENGINE_JSC") @@ -139,36 +148,46 @@ message(STATUS "GENERIC_NAPI = ${GENERIC_NAPI}") if(NS_FFI_BACKEND STREQUAL "auto") if(GENERIC_NAPI OR TARGET_ENGINE_NONE) set(NS_EFFECTIVE_FFI_BACKEND "napi") - elseif(TARGET_ENGINE_HERMES OR TARGET_ENGINE_V8 OR TARGET_ENGINE_JSC OR TARGET_ENGINE_QUICKJS) - set(NS_EFFECTIVE_FFI_BACKEND "direct") + elseif(TARGET_ENGINE_HERMES) + set(NS_EFFECTIVE_FFI_BACKEND "hermes") + elseif(TARGET_ENGINE_V8) + set(NS_EFFECTIVE_FFI_BACKEND "v8") + elseif(TARGET_ENGINE_JSC) + set(NS_EFFECTIVE_FFI_BACKEND "jsc") + elseif(TARGET_ENGINE_QUICKJS) + set(NS_EFFECTIVE_FFI_BACKEND "quickjs") else() set(NS_EFFECTIVE_FFI_BACKEND "napi") endif() -elseif(NS_FFI_BACKEND STREQUAL "napi" OR NS_FFI_BACKEND STREQUAL "direct") +elseif(NS_FFI_BACKEND STREQUAL "napi" OR + NS_FFI_BACKEND STREQUAL "v8" OR + NS_FFI_BACKEND STREQUAL "jsc" OR + NS_FFI_BACKEND STREQUAL "quickjs" OR + NS_FFI_BACKEND STREQUAL "hermes") set(NS_EFFECTIVE_FFI_BACKEND "${NS_FFI_BACKEND}") else() message(FATAL_ERROR "Unknown NS_FFI_BACKEND: ${NS_FFI_BACKEND}") endif() -if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct" AND +if(NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND (GENERIC_NAPI OR TARGET_ENGINE_NONE OR BUILD_MACOS_NODE_API)) - message(FATAL_ERROR "NS_FFI_BACKEND=direct requires an embedded JS runtime build") + message(FATAL_ERROR + "NS_FFI_BACKEND=${NS_EFFECTIVE_FFI_BACKEND} requires an embedded JS runtime build") endif() -message(STATUS "NS_FFI_BACKEND = ${NS_FFI_BACKEND} (${NS_EFFECTIVE_FFI_BACKEND})") - -if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct" AND - NOT (NS_GSD_BACKEND STREQUAL "auto" OR NS_GSD_BACKEND STREQUAL "none")) +if(NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND + NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "${TARGET_ENGINE}") message(FATAL_ERROR - "NS_GSD_BACKEND is only used by the Node-API FFI backend. " - "Use NS_GSD_BACKEND=auto or none with NS_FFI_BACKEND=direct.") + "NS_FFI_BACKEND=${NS_EFFECTIVE_FFI_BACKEND} requires TARGET_ENGINE=${NS_EFFECTIVE_FFI_BACKEND}") endif() +message(STATUS "NS_FFI_BACKEND = ${NS_FFI_BACKEND} (${NS_EFFECTIVE_FFI_BACKEND})") + if(NS_GSD_BACKEND STREQUAL "auto") - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") - set(NS_EFFECTIVE_GSD_BACKEND "none") - else() + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") set(NS_EFFECTIVE_GSD_BACKEND "napi") + else() + set(NS_EFFECTIVE_GSD_BACKEND "${NS_EFFECTIVE_FFI_BACKEND}") endif() elseif(NS_GSD_BACKEND STREQUAL "v8" OR NS_GSD_BACKEND STREQUAL "jsc" OR @@ -200,81 +219,92 @@ if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND "NS_FFI_BACKEND=napi is the pure Node-API FFI backend and only supports " "NS_GSD_BACKEND=napi or none.") endif() +if(NOT NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi" AND + NS_EFFECTIVE_GSD_BACKEND STREQUAL "napi") + message(FATAL_ERROR + "NS_FFI_BACKEND=${NS_EFFECTIVE_FFI_BACKEND} cannot use NS_GSD_BACKEND=napi. " + "Use the matching engine backend or none.") +endif() message(STATUS "NS_GSD_BACKEND = ${NS_GSD_BACKEND} (${NS_EFFECTIVE_GSD_BACKEND})") # Set up sources include_directories( ./ - ffi/shared ../metadata-generator/include napi/common libffi/${LIBFFI_BUILD}/include ) set(FFI_SHARED_SOURCE_FILES - ffi/shared/Tasks.cpp + jsi/shared/Tasks.cpp ) set(FFI_NAPI_SOURCE_FILES - ffi/napi/AutoreleasePool.mm - ffi/napi/Protocol.mm - ffi/napi/ObjCBridge.mm - ffi/napi/Block.mm - ffi/napi/Class.mm - ffi/napi/Closure.mm - ffi/napi/ClassMember.mm - ffi/napi/Cif.mm - ffi/napi/TypeConv.mm - ffi/napi/Util.mm - ffi/napi/Struct.mm - ffi/napi/ObjectRef.mm - ffi/napi/JSObject.mm - ffi/napi/Enum.mm - ffi/napi/Variable.mm - ffi/napi/Object.mm - ffi/napi/CFunction.mm - ffi/napi/Interop.mm - ffi/napi/InlineFunctions.mm - ffi/napi/ClassBuilder.mm + ffi/objc/napi/AutoreleasePool.mm + ffi/objc/napi/Protocol.mm + ffi/objc/napi/ObjCBridge.mm + ffi/objc/napi/Block.mm + ffi/objc/napi/Class.mm + ffi/objc/napi/Closure.mm + ffi/objc/napi/ClassMember.mm + ffi/objc/napi/Cif.mm + ffi/objc/napi/TypeConv.mm + ffi/objc/napi/Util.mm + ffi/objc/napi/Struct.mm + ffi/objc/napi/ObjectRef.mm + ffi/objc/napi/JSObject.mm + ffi/objc/napi/Enum.mm + ffi/objc/napi/Variable.mm + ffi/objc/napi/Object.mm + ffi/objc/napi/CFunction.mm + ffi/objc/napi/Interop.mm + ffi/objc/napi/InlineFunctions.mm + ffi/objc/napi/ClassBuilder.mm ) -set(FFI_DIRECT_SHARED_SOURCE_FILES - ffi/shared/direct/EmbeddedMetadata.mm +set(FFI_ENGINE_SHARED_SOURCE_FILES + ffi/objc/shared/MetadataState.mm ) -set(FFI_HERMES_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/hermes/jsi/NativeApiJsi.mm +set(FFI_HERMES_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + ffi/objc/hermes/NativeApiJsi.mm ) -set(FFI_V8_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/v8/NativeApiV8.mm - ffi/v8/NativeApiV8HostObjects.mm - ffi/v8/NativeApiV8Runtime.mm - ffi/v8/NativeApiV8Value.mm +set(FFI_V8_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + # Platform-neutral engine layer, shared with the Android runtime. + jsi/v8/V8HostObjects.cpp + jsi/v8/V8Runtime.cpp + jsi/v8/V8Value.cpp + # Apple-only ObjC bridge on top of it. + ffi/objc/v8/NativeApiV8.mm ) -set(FFI_JSC_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/jsc/NativeApiJSC.mm - ffi/jsc/NativeApiJSCHostObjects.mm - ffi/jsc/NativeApiJSCRuntime.mm - ffi/jsc/NativeApiJSCValue.mm +set(FFI_JSC_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + # Platform-neutral engine layer, shared with the Android runtime. + jsi/jsc/JSCHostObjects.cpp + jsi/jsc/JSCRuntime.cpp + jsi/jsc/JSCValue.cpp + # Apple-only ObjC bridge on top of it. + ffi/objc/jsc/NativeApiJSC.mm ) -set(FFI_QUICKJS_DIRECT_SOURCE_FILES - ${FFI_DIRECT_SHARED_SOURCE_FILES} - ffi/quickjs/NativeApiQuickJSHostObjects.mm - ffi/quickjs/NativeApiQuickJS.mm - ffi/quickjs/NativeApiQuickJSRuntime.mm - ffi/quickjs/NativeApiQuickJSValue.mm +set(FFI_QUICKJS_ENGINE_SOURCE_FILES + ${FFI_ENGINE_SHARED_SOURCE_FILES} + # Platform-neutral engine layer, shared with the Android runtime. + jsi/quickjs/QuickJSHostObjects.cpp + jsi/quickjs/QuickJSRuntime.cpp + jsi/quickjs/QuickJSValue.cpp + # Apple-only ObjC bridge on top of it. + ffi/objc/quickjs/NativeApiQuickJS.mm ) set(SOURCE_FILES ${FFI_SHARED_SOURCE_FILES} - runtime/NativeScriptException.mm + runtime/apple/NativeScriptException.mm ) if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") @@ -287,140 +317,145 @@ endif() if(ENABLE_JS_RUNTIME) set(SOURCE_FILES ${SOURCE_FILES} - runtime/modules/console/Console.cpp - runtime/Runtime.cpp - runtime/modules/worker/Worker.mm - runtime/modules/worker/MessageJSON.cpp - runtime/modules/worker/MessageV8.cpp - runtime/modules/worker/ConcurrentQueue.cpp - runtime/modules/worker/WorkerImpl.mm - runtime/modules/worker/WorkerImpl.mm - runtime/modules/module/ModuleInternal.cpp - runtime/modules/node/Node.cpp - runtime/modules/node/FS.cpp - runtime/modules/node/Path.cpp - runtime/modules/node/Process.cpp - runtime/modules/node/VM.cpp - runtime/modules/performance/Performance.cpp - runtime/ThreadSafeFunction.mm - runtime/Bundle.mm - runtime/modules/timers/Timers.mm - runtime/modules/app/App.mm - runtime/modules/web/Web.mm - runtime/NativeScript.mm - runtime/RuntimeConfig.cpp + runtime/apple/modules/console/Console.cpp + runtime/apple/Runtime.cpp + runtime/apple/modules/worker/Worker.mm + runtime/apple/modules/worker/MessageJSON.cpp + runtime/apple/modules/worker/MessageV8.cpp + runtime/apple/modules/worker/ConcurrentQueue.cpp + runtime/apple/modules/worker/WorkerImpl.mm + runtime/apple/modules/worker/WorkerImpl.mm + runtime/apple/modules/module/ModuleInternal.cpp + runtime/apple/modules/node/Node.cpp + runtime/apple/modules/node/FS.cpp + runtime/apple/modules/node/Path.cpp + runtime/apple/modules/node/Process.cpp + runtime/apple/modules/node/VM.cpp + runtime/apple/modules/performance/Performance.cpp + runtime/apple/ThreadSafeFunction.mm + runtime/apple/Bundle.mm + runtime/apple/modules/timers/Timers.mm + runtime/apple/modules/app/App.mm + runtime/apple/modules/web/Web.mm + runtime/apple/NativeScript.mm + runtime/apple/RuntimeConfig.cpp runtime/modules/url/ada/ada.cpp runtime/modules/url/URL.cpp runtime/modules/url/URLSearchParams.cpp ) if(TARGET_ENGINE_V8) + # V8's headers come from the pinned release via scripts/download_v8.sh, the + # same set Android uses. Both spellings are needed: from the napi + # backend, and "src/inspector/..." from the console/inspector code, which + # spells paths relative to the V8 checkout root. + set(V8_VENDOR_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../vendor/v8") + if(NOT EXISTS "${V8_VENDOR_DIR}/src/inspector/v8-console-message.h") + message(FATAL_ERROR + "V8 headers missing at ${V8_VENDOR_DIR}. Run scripts/download_v8.sh.") + endif() + include_directories( napi/v8 - napi/v8/v8_inspector + ${V8_VENDOR_DIR} + ${V8_VENDOR_DIR}/v8_inspector + ${V8_VENDOR_DIR}/include + # V8 14.9's internals include Abseil as . + ${V8_VENDOR_DIR}/third_party/abseil-cpp ) set(SOURCE_FILES ${SOURCE_FILES} - napi/v8/v8-api.cpp - napi/v8/v8-module-loader.cpp + ${V8_VENDOR_DIR}/v8-api.cpp + ${V8_VENDOR_DIR}/v8-module-loader.cpp napi/v8/jsr.cpp - napi/v8/SimpleAllocator.cpp + ${V8_VENDOR_DIR}/SimpleAllocator.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "v8") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_V8_DIRECT_SOURCE_FILES} + ${FFI_V8_ENGINE_SOURCE_FILES} ) endif() elseif(TARGET_ENGINE_HERMES) - set(HERMES_HEADERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../Frameworks/hermes-headers") + set(HERMES_HEADERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../vendor/hermes/include") include_directories( napi/hermes ) - if(EXISTS "${HERMES_HEADERS_DIR}/jsi/jsi.h" AND EXISTS "${HERMES_HEADERS_DIR}/hermes/hermes.h") - include_directories( - ${HERMES_HEADERS_DIR} - ) - else() - include_directories( - napi/hermes/include - napi/hermes/include/hermes - napi/hermes/include/jsi - ) + # Fetched by scripts/download_hermes.sh alongside hermes.xcframework, not + # committed -- these headers must match the prebuilt binary, and a stale + # in-tree copy silently compiling against the wrong ABI is worse than a + # missing one. + if(NOT EXISTS "${HERMES_HEADERS_DIR}/jsi/jsi.h") + message(FATAL_ERROR + "Hermes headers missing at ${HERMES_HEADERS_DIR}. Run scripts/download_hermes.sh.") endif() + include_directories( + ${HERMES_HEADERS_DIR} + ) + set(SOURCE_FILES ${SOURCE_FILES} napi/hermes/jsr.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "hermes") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_HERMES_DIRECT_SOURCE_FILES} + ${FFI_HERMES_ENGINE_SOURCE_FILES} ) endif() elseif(TARGET_ENGINE_QUICKJS) - set(MI_BUILD_OBJECT OFF) - set(MI_OVERRIDE OFF) - set(MI_BUILD_SHARED OFF) - set(MI_BUILD_TESTS OFF) - set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") - add_subdirectory(napi/quickjs/mimalloc-dev mimalloc) - include_directories( napi/quickjs - napi/quickjs/source + ${CMAKE_CURRENT_SOURCE_DIR}/../vendor/quickjs/source_ng napi/common - mimalloc - napi/quickjs/mimalloc-dev/include ) set(SOURCE_FILES ${SOURCE_FILES} - # quickjs - napi/quickjs/source/cutils.c - napi/quickjs/source/libregexp.c - napi/quickjs/source/libbf.c - napi/quickjs/source/libunicode.c - napi/quickjs/source/quickjs.c + # quickjs-ng, NativeScript-patched, vendored at the repo root and shared + ../vendor/quickjs/source_ng/libregexp.c + ../vendor/quickjs/source_ng/libunicode.c + ../vendor/quickjs/source_ng/quickjs.c + ../vendor/quickjs/source_ng/dtoa.c # napi - napi/quickjs/quickjs-api.c + ../vendor/quickjs/quickjs-api.c napi/quickjs/jsr.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "quickjs") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_QUICKJS_DIRECT_SOURCE_FILES} + ${FFI_QUICKJS_ENGINE_SOURCE_FILES} ) endif() elseif(TARGET_ENGINE_JSC) include_directories( napi/jsc - napi/jsc/include + ../vendor/jsc napi/common ) set(SOURCE_FILES ${SOURCE_FILES} - napi/jsc/jsc-api.cpp + ../vendor/jsc/jsc-api.cpp napi/jsc/jsr.cpp ) - if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") + if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "jsc") set(SOURCE_FILES ${SOURCE_FILES} - ${FFI_JSC_DIRECT_SOURCE_FILES} + ${FFI_JSC_ENGINE_SOURCE_FILES} ) endif() @@ -482,16 +517,13 @@ target_sources( "NativeScript.h" ) -if(TARGET_ENGINE_V8 AND TARGET_PLATFORM_IOS) - # iOS V8 slices are built with pointer compression enabled. Keep embedder - # build flags in sync to satisfy V8::Initialize() build config checks. - target_compile_definitions( - ${NAME} - PRIVATE - V8_COMPRESS_POINTERS - V8_31BIT_SMIS_ON_64BIT_ARCH - ) -endif() +# NOTE: the Apple V8 slices are built with v8_enable_pointer_compression=false +# (see scripts/matrix/build-ios.sh in v8-buildscripts), so the embedder must NOT +# declare V8_COMPRESS_POINTERS -- V8::Initialize() aborts on a mismatch in +# either direction. This previously defined it for iOS, which was correct for +# the old 14.3 xcframework but would abort at startup against 14.9. The Android +# slices differ: they leave the gn arg at its default, which is ON for 64-bit. + if(ENABLE_JS_RUNTIME) target_compile_definitions(${NAME} PRIVATE ENABLE_JS_RUNTIME) @@ -511,40 +543,48 @@ elseif(TARGET_ENGINE_JSC) target_compile_definitions(${NAME} PRIVATE TARGET_ENGINE_JSC) endif() -set(NS_GSD_BACKEND_V8_VALUE 0) -set(NS_GSD_BACKEND_JSC_VALUE 0) -set(NS_GSD_BACKEND_QUICKJS_VALUE 0) set(NS_GSD_BACKEND_HERMES_VALUE 0) set(NS_GSD_BACKEND_NAPI_VALUE 0) -set(NS_FFI_BACKEND_DIRECT_VALUE 0) +set(NS_GSD_BACKEND_PREPARED_VALUE 0) set(NS_FFI_BACKEND_NAPI_VALUE 0) +set(NS_FFI_BACKEND_V8_VALUE 0) +set(NS_FFI_BACKEND_JSC_VALUE 0) +set(NS_FFI_BACKEND_QUICKJS_VALUE 0) +set(NS_FFI_BACKEND_HERMES_VALUE 0) if(NS_EFFECTIVE_GSD_BACKEND STREQUAL "v8") - set(NS_GSD_BACKEND_V8_VALUE 1) + set(NS_GSD_BACKEND_PREPARED_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "jsc") - set(NS_GSD_BACKEND_JSC_VALUE 1) + set(NS_GSD_BACKEND_PREPARED_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "quickjs") - set(NS_GSD_BACKEND_QUICKJS_VALUE 1) + set(NS_GSD_BACKEND_PREPARED_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "hermes") set(NS_GSD_BACKEND_HERMES_VALUE 1) elseif(NS_EFFECTIVE_GSD_BACKEND STREQUAL "napi") set(NS_GSD_BACKEND_NAPI_VALUE 1) endif() -if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "direct") - set(NS_FFI_BACKEND_DIRECT_VALUE 1) -elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") +if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") set(NS_FFI_BACKEND_NAPI_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "v8") + set(NS_FFI_BACKEND_V8_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "jsc") + set(NS_FFI_BACKEND_JSC_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "quickjs") + set(NS_FFI_BACKEND_QUICKJS_VALUE 1) +elseif(NS_EFFECTIVE_FFI_BACKEND STREQUAL "hermes") + set(NS_FFI_BACKEND_HERMES_VALUE 1) endif() target_compile_definitions(${NAME} PRIVATE - NS_GSD_BACKEND_V8=${NS_GSD_BACKEND_V8_VALUE} - NS_GSD_BACKEND_JSC=${NS_GSD_BACKEND_JSC_VALUE} - NS_GSD_BACKEND_QUICKJS=${NS_GSD_BACKEND_QUICKJS_VALUE} NS_GSD_BACKEND_HERMES=${NS_GSD_BACKEND_HERMES_VALUE} NS_GSD_BACKEND_NAPI=${NS_GSD_BACKEND_NAPI_VALUE} - NS_FFI_BACKEND_DIRECT=${NS_FFI_BACKEND_DIRECT_VALUE} + NS_GSD_BACKEND_PREPARED=${NS_GSD_BACKEND_PREPARED_VALUE} NS_FFI_BACKEND_NAPI=${NS_FFI_BACKEND_NAPI_VALUE} + NS_FFI_BACKEND_V8=${NS_FFI_BACKEND_V8_VALUE} + NS_FFI_BACKEND_JSC=${NS_FFI_BACKEND_JSC_VALUE} + NS_FFI_BACKEND_QUICKJS=${NS_FFI_BACKEND_QUICKJS_VALUE} + NS_FFI_BACKEND_HERMES=${NS_FFI_BACKEND_HERMES_VALUE} ) set(FRAMEWORK_VERSION_VALUE "${VERSION}") @@ -657,13 +697,13 @@ if(TARGET_ENGINE_V8) # Prefer universal sim slice if present set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") if(NOT EXISTS "${V8_SLICE_DIR}") - set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") # fallback + set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64-simulator/libv8_monolith.framework") endif() elseif(TARGET_PLATFORM STREQUAL "ios") # Prefer universal sim slice if present set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") if(NOT EXISTS "${V8_SLICE_DIR}") - set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") # fallback + set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/ios-arm64/libv8_monolith.framework") endif() elseif(TARGET_PLATFORM STREQUAL "visionos-sim") set(V8_SLICE_DIR "${V8_XCFRAMEWORK}/xrsimulator-arm64/libv8_monolith.framework") @@ -714,14 +754,6 @@ elseif(TARGET_PLATFORM_IOS) ) endif() -if(TARGET_ENGINE_QUICKJS) - target_link_libraries( - ${NAME} - PRIVATE - mimalloc-static - ) -endif() - if(GENERIC_NAPI) target_link_options( ${NAME} diff --git a/NativeScript/cli/main.cpp b/NativeScript/cli/main.cpp index f9ea139c0..f72be8600 100644 --- a/NativeScript/cli/main.cpp +++ b/NativeScript/cli/main.cpp @@ -5,12 +5,12 @@ #include #include -#include "runtime/NativeScriptException.h" -#include "runtime/Bundle.h" -#include "runtime/Runtime.h" -#include "runtime/RuntimeConfig.h" +#include "runtime/apple/NativeScriptException.h" +#include "runtime/apple/Bundle.h" +#include "runtime/apple/Runtime.h" +#include "runtime/apple/RuntimeConfig.h" #include "segappend.h" -#include "ffi/shared/Tasks.h" +#include "ffi/objc/shared/Tasks.h" #include "BundleLoader.h" using namespace nativescript; diff --git a/NativeScript/ffi/hermes/jsi/NativeApiJsi.h b/NativeScript/ffi/hermes/jsi/NativeApiJsi.h deleted file mode 100644 index 82df76143..000000000 --- a/NativeScript/ffi/hermes/jsi/NativeApiJsi.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef NATIVE_API_JSI_H -#define NATIVE_API_JSI_H - -#include -#include -#include - -#include - -namespace nativescript { - -class NativeApiJsiScheduler { - public: - virtual ~NativeApiJsiScheduler() = default; - virtual void invokeOnJS(std::function task) = 0; - virtual void invokeOnUI(std::function task) = 0; -}; - -struct NativeApiJsiConfig { - const char* metadataPath = nullptr; - const void* metadataPtr = nullptr; - const char* globalName = "__nativeScriptNativeApi"; - std::shared_ptr scheduler = nullptr; - std::function)> nativeInvocationInvoker = nullptr; - std::function)> nativeCallbackInvoker = nullptr; - std::function)> jsThreadCallbackInvoker = nullptr; - bool invokeCallbacksOnNativeCallerThread = false; - bool installGlobalSymbols = false; -}; - -facebook::jsi::Object CreateNativeApiJSI( - facebook::jsi::Runtime& runtime, - const NativeApiJsiConfig& config = NativeApiJsiConfig{}); - -void InstallNativeApiJSI( - facebook::jsi::Runtime& runtime, - const NativeApiJsiConfig& config = NativeApiJsiConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSI( - facebook::jsi::Runtime* runtime, const char* metadataPath); - -#endif // NATIVE_API_JSI_H diff --git a/NativeScript/ffi/hermes/jsi/NativeApiJsi.mm b/NativeScript/ffi/hermes/jsi/NativeApiJsi.mm deleted file mode 100644 index 70a80bbfe..000000000 --- a/NativeScript/ffi/hermes/jsi/NativeApiJsi.mm +++ /dev/null @@ -1,89 +0,0 @@ -#include "NativeApiJsi.h" - -#ifdef TARGET_ENGINE_HERMES - -#import -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace nativescript { -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -#include "jsi/NativeApiJsiHostObjects.h" -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSI(facebook::jsi::Runtime* runtime, - const char* metadataPath) { - if (runtime == nullptr) { - return; - } - nativescript::NativeApiJsiConfig config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiJSI(*runtime, config); -} - -#endif // TARGET_ENGINE_HERMES diff --git a/NativeScript/ffi/hermes/jsi/README.md b/NativeScript/ffi/hermes/jsi/README.md deleted file mode 100644 index ca07222b5..000000000 --- a/NativeScript/ffi/hermes/jsi/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Native API JSI bridge - -This directory contains the Hermes-first JSI entrypoint for NativeScript Native -API access. - -The backend is split by FFI responsibility: - -- `../../shared/jsi/NativeApiJsiBridge.h` owns metadata indexing, symbol lookup, scheduler - state, and bridge lifetime caches. -- `../../shared/jsi/NativeApiJsiHostObjects.h` owns class, object, protocol, pointer, - reference, struct, and union host objects. -- `../../shared/jsi/NativeApiJsiCallbacks.h` owns signatures, libffi callback trampolines, - JS blocks, and native function pointer callback lifetime. -- `../../shared/jsi/NativeApiJsiConversion.h` owns JSI/native type conversion and the - `interop` helper surface. -- `../../shared/jsi/NativeApiJsiInvocation.h` owns constants, enums, C function calls, - function pointer calls, and Objective-C selector dispatch. -- `../../shared/jsi/NativeApiJsiHostObject.h` owns the public API host object exposed to JS. -- `../../shared/jsi/NativeApiJsiInstall.h` owns runtime/global installation. - -The core installer is engine-host agnostic: - -```cpp -nativescript::NativeApiJsiConfig config; -config.metadataPath = metadataPath; -config.metadataPtr = metadataPtr; -nativescript::InstallNativeApiJSI(runtime, config); -``` - -NativeScript's Hermes runtime installs this automatically as -`globalThis.__nativeScriptNativeApi`. - -React Native integrations should include `NativeApiJsiReactNative.h` from a -TurboModule implementation and pass the module's JS/UI `CallInvoker`s: - -```cpp -nativescript::InstallReactNativeNativeApiJSI( - runtime, jsInvoker, uiInvoker, metadataPath, metadataPtr); -``` - -The React Native adapter is intentionally only a scheduler/config shim. The -native API host object, metadata loading, primitive C function dispatch, -Objective-C class/object handles, and selector invocation live in the shared -JSI implementation so they can be used by both NativeScript Hermes and a React -Native TurboModule without going through Node-API. - -The direct JSI backend is still moving toward full NativeScript bridge parity. -It covers the metadata-backed Objective-C class/function/constant/enum paths -needed by the React Native TurboModule, plus metadata-backed structs/unions, -primitive array/vector value marshalling, JS blocks, C function pointer -callbacks, protocol wrappers, pointer/reference helpers, and the core `interop` -helpers (`Pointer`, `Reference`, `sizeof`, `alloc`, `free`, `adopt`, -`handleof`, `stringFromCString`, `bufferFromData`, and `addProtocol`). Struct -and union constructors, plus protocol symbols, are installed on `globalThis` -along with `interop` so common NativeScript-style calls such as -`CGRect({ origin, size })`, `interop.sizeof(CGRect)`, and -`interop.handleof(value)` work through JSI. - -The remaining RN FFI-suite skip is the explicit `interop.addMethod` decorator -hook. JavaScript-defined Objective-C subclasses created through `.extend(...)` -use the JSI class-builder path and are covered by the React Native compatibility -suite. diff --git a/NativeScript/ffi/jni/jsi/Engine.cpp b/NativeScript/ffi/jni/jsi/Engine.cpp new file mode 100644 index 000000000..2fce367b1 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/Engine.cpp @@ -0,0 +1,284 @@ +#include "Engine.h" + +#include +#include + +using namespace tns; +using namespace tns::js_util; + +namespace { + +std::mutex g_builtinsMutex; +// Keyed by JsRuntime::identity(): a host callback receives a freshly +// constructed Runtime wrapper, so &rt is not stable and keying on it would +// build (and leak) a whole Builtins set per callback. +std::unordered_map> g_builtins; + +// `a === b` and `a instanceof b` have no engine:: entry point and no builtin to +// borrow, so they come from two one-line scripts evaluated once per runtime. +const char* const kStrictEqualsSource = "(function(a,b){return a===b;})"; +const char* const kInstanceOfSource = "(function(o,c){return o instanceof c;})"; + +JsFunction evaluateFunction(JsRuntime& rt, const char* source, + const char* sourceURL) { + JsValue result = rt.evaluateJavaScript( + std::make_shared(std::string(source)), sourceURL); + return result.asObject(rt).asFunction(rt); +} + +std::unique_ptr createBuiltins(JsRuntime& rt) { + auto builtins = std::make_unique(); + JsObject global = rt.global(); + + builtins->objectCtor = global.getPropertyAsObject(rt, "Object"); + builtins->defineProperty = + builtins->objectCtor.getPropertyAsFunction(rt, "defineProperty"); + builtins->getPrototypeOf = + builtins->objectCtor.getPropertyAsFunction(rt, "getPrototypeOf"); + builtins->setPrototypeOf = + builtins->objectCtor.getPropertyAsFunction(rt, "setPrototypeOf"); + builtins->objectCreate = builtins->objectCtor.getPropertyAsFunction(rt, "create"); + builtins->objectKeys = builtins->objectCtor.getPropertyAsFunction(rt, "keys"); + builtins->hasOwnProperty = builtins->objectCtor.getPropertyAsObject(rt, "prototype") + .getPropertyAsFunction(rt, "hasOwnProperty"); + + JsObject reflect = global.getPropertyAsObject(rt, "Reflect"); + builtins->deleteProperty = reflect.getPropertyAsFunction(rt, "deleteProperty"); + + builtins->numberCtor = global.getPropertyAsObject(rt, "Number"); + builtins->isInteger = builtins->numberCtor.getPropertyAsFunction(rt, "isInteger"); + + builtins->stringCtor = global.getPropertyAsObject(rt, "String"); + builtins->booleanCtor = global.getPropertyAsObject(rt, "Boolean"); + builtins->dateCtor = global.getPropertyAsObject(rt, "Date"); + builtins->dataViewCtor = global.getPropertyAsObject(rt, "DataView"); + + JsObject arrayCtor = global.getPropertyAsObject(rt, "Array"); + builtins->isArray = arrayCtor.getPropertyAsFunction(rt, "isArray"); + + JsObject arrayBufferCtor = global.getPropertyAsObject(rt, "ArrayBuffer"); + builtins->isView = arrayBufferCtor.getPropertyAsFunction(rt, "isView"); + + builtins->errorCtor = global.getPropertyAsFunction(rt, "Error"); + builtins->stringCoerce = global.getPropertyAsFunction(rt, "String"); + + builtins->strictEquals = + evaluateFunction(rt, kStrictEqualsSource, ""); + builtins->instanceOf = + evaluateFunction(rt, kInstanceOfSource, ""); + + return builtins; +} + +bool callPredicate(JsRuntime& rt, const JsFunction& fn, const JsValue& lhs, + const JsValue& rhs) { + const JsValue args[] = {lhs, rhs}; + JsValue result = fn.call(rt, args, static_cast(2)); + return result.isBool() && result.getBool(); +} + +} // namespace + +Builtins& Builtins::of(JsRuntime& rt) { + std::lock_guard lock(g_builtinsMutex); + auto it = g_builtins.find(rt.identity()); + if (it != g_builtins.end()) return *it->second; + auto inserted = g_builtins.emplace(rt.identity(), createBuiltins(rt)); + return *inserted.first->second; +} + +void Builtins::dispose(JsRuntime& rt) { + std::lock_guard lock(g_builtinsMutex); + g_builtins.erase(rt.identity()); +} + +JsValue js_util::getPrototypeOf(JsRuntime& rt, const JsValue& object) { + if (!object.isObject()) return undefined(); + const JsValue args[] = {object}; + return Builtins::of(rt).getPrototypeOf.call(rt, args, static_cast(1)); +} + +void js_util::setPrototypeOf(JsRuntime& rt, const JsValue& object, + const JsValue& prototype) { + if (!object.isObject()) return; + const JsValue args[] = {object, prototype}; + Builtins::of(rt).setPrototypeOf.call(rt, args, static_cast(2)); +} + +bool js_util::strict_equal(JsRuntime& rt, const JsValue& lhs, + const JsValue& rhs) { + return callPredicate(rt, Builtins::of(rt).strictEquals, lhs, rhs); +} + +bool js_util::instance_of(JsRuntime& rt, const JsValue& value, + const JsValue& ctor) { + return callPredicate(rt, Builtins::of(rt).instanceOf, value, ctor); +} + +bool js_util::delete_property(JsRuntime& rt, const JsValue& object, + const JsValue& key) { + return callPredicate(rt, Builtins::of(rt).deleteProperty, object, key); +} + +void js_util::define_property_value(JsRuntime& rt, const JsObject& object, + const char* propertyName, + const JsValue& value, bool enumerable, + bool configurable, bool writable) { + JsObject descriptor(rt); + descriptor.setProperty(rt, "value", value); + descriptor.setProperty(rt, "enumerable", enumerable); + descriptor.setProperty(rt, "configurable", configurable); + descriptor.setProperty(rt, "writable", writable); + + const JsValue args[] = {JsValue(rt, object), to_js_string(rt, propertyName), + JsValue(rt, descriptor)}; + Builtins::of(rt).defineProperty.call(rt, args, static_cast(3)); +} + +void js_util::define_property_get_set(JsRuntime& rt, const JsObject& object, + const char* propertyName, + const JsFunction* getter, + const JsFunction* setter, bool enumerable, + bool configurable) { + JsObject descriptor(rt); + if (getter != nullptr) descriptor.setProperty(rt, "get", *getter); + if (setter != nullptr) descriptor.setProperty(rt, "set", *setter); + descriptor.setProperty(rt, "enumerable", enumerable); + descriptor.setProperty(rt, "configurable", configurable); + + const JsValue args[] = {JsValue(rt, object), to_js_string(rt, propertyName), + JsValue(rt, descriptor)}; + Builtins::of(rt).defineProperty.call(rt, args, static_cast(3)); +} + +bool js_util::has_own_property(JsRuntime& rt, const JsObject& object, + const char* propertyName) { + const JsValue args[] = {to_js_string(rt, propertyName)}; + JsValue result = Builtins::of(rt).hasOwnProperty.callWithThis( + rt, object, args, static_cast(1)); + return result.isBool() && result.getBool(); +} + +JsValue js_util::valueOf(JsRuntime& rt, const JsValue& value) { + if (!value.isObject()) return value; + JsObject object = value.asObjectBorrowed(rt); + JsValue fn = object.getProperty(rt, "valueOf"); + if (!fn.isObject()) return value; + JsObject fnObject = fn.asObjectBorrowed(rt); + if (!fnObject.isFunction(rt)) return value; + return fnObject.asFunction(rt).callWithThis(rt, object, nullptr, 0); +} + +bool js_util::is_number_object(JsRuntime& rt, const JsValue& value) { + return instance_of(rt, value, JsValue(rt, Builtins::of(rt).numberCtor)); +} + +bool js_util::is_string_object(JsRuntime& rt, const JsValue& value) { + return instance_of(rt, value, JsValue(rt, Builtins::of(rt).stringCtor)); +} + +bool js_util::is_boolean_object(JsRuntime& rt, const JsValue& value) { + return instance_of(rt, value, JsValue(rt, Builtins::of(rt).booleanCtor)); +} + +bool js_util::is_date(JsRuntime& rt, const JsValue& value) { + return instance_of(rt, value, JsValue(rt, Builtins::of(rt).dateCtor)); +} + +bool js_util::is_dataview(JsRuntime& rt, const JsValue& value) { + return instance_of(rt, value, JsValue(rt, Builtins::of(rt).dataViewCtor)); +} + +// napi_is_typedarray has no builtin equivalent; ArrayBuffer.isView is true for +// every typed array plus DataView, so the DataView case is subtracted. +bool js_util::is_typedarray(JsRuntime& rt, const JsValue& value) { + const JsValue args[] = {value}; + JsValue result = + Builtins::of(rt).isView.call(rt, args, static_cast(1)); + if (!(result.isBool() && result.getBool())) return false; + return !is_dataview(rt, value); +} + +bool js_util::is_array(JsRuntime& rt, const JsValue& value) { + const JsValue args[] = {value}; + JsValue result = + Builtins::of(rt).isArray.call(rt, args, static_cast(1)); + return result.isBool() && result.getBool(); +} + +bool js_util::is_float(JsRuntime& rt, const JsValue& value) { + const JsValue args[] = {value}; + JsValue result = + Builtins::of(rt).isInteger.call(rt, args, static_cast(1)); + return !(result.isBool() && result.getBool()); +} + +JsValue js_util::object_create_from(JsRuntime& rt, const JsValue& prototype) { + const JsValue args[] = {prototype}; + return Builtins::of(rt).objectCreate.call(rt, args, static_cast(1)); +} + +JsFunction js_util::set_function(JsRuntime& rt, JsObject& object, + const char* name, + engine::HostFunctionType callback) { + JsFunction fn = JsFunction::createFromHostFunction( + rt, JsPropNameID::forAscii(rt, name), 0, std::move(callback)); + object.setProperty(rt, name, fn); + return fn; +} + +void js_util::inherits(JsRuntime& rt, const JsObject& ctor, + const JsObject& superCtor) { + setPrototypeOf(rt, ctor.getProperty(rt, "prototype"), + superCtor.getProperty(rt, "prototype")); + setPrototypeOf(rt, JsValue(rt, ctor), JsValue(rt, superCtor)); +} + +bool js_util::is_error(JsRuntime& rt, const JsValue& value) { + return instance_of(rt, value, JsValue(rt, Builtins::of(rt).errorCtor)); +} + +// napi_coerce_to_string has no engine:: entry point; String(value) is the same +// abstract operation and works for every value kind, including symbols. +std::string js_util::coerce_to_string(JsRuntime& rt, const JsValue& value) { + if (value.isString()) return value.asString(rt).utf8(rt); + const JsValue args[] = {value}; + JsValue result = + Builtins::of(rt).stringCoerce.call(rt, args, static_cast(1)); + return result.isString() ? result.asString(rt).utf8(rt) : std::string(); +} + +JsValue js_util::create_error(JsRuntime& rt, const std::string& message, + const char* code) { + // Rebuild the original error type from a "Error: " prefix. + // + // A JSError that carries its thrown value keeps its constructor for free, but + // one built from a message alone would otherwise always come back as a plain + // Error. Hermes is where this shows: it reports a *compile* failure as a + // JSINativeException rather than a JS throw, so jsi/hermes tags the message + // "SyntaxError: ..." precisely so the type can be restored here. Every other + // engine raises a real SyntaxError with a value and never reaches this path. + // The Require specs assert the type ("main started SyntaxError main ended"). + JsFunction ctor = Builtins::of(rt).errorCtor; + std::string text = message; + static const char* kErrorNames[] = {"SyntaxError", "TypeError", "RangeError", + "ReferenceError", "EvalError", "URIError"}; + for (const char* name : kErrorNames) { + const std::string prefix = std::string(name) + ": "; + if (text.rfind(prefix, 0) != 0) continue; + JsValue candidate = rt.global().getProperty(rt, name); + if (candidate.isObject() && candidate.asObjectBorrowed(rt).isFunction(rt)) { + ctor = candidate.asObject(rt).asFunction(rt); + text = text.substr(prefix.size()); + } + break; + } + + const JsValue args[] = {to_js_string(rt, text)}; + JsValue error = ctor.callAsConstructor(rt, args, static_cast(1)); + if (code != nullptr && error.isObject()) { + JsObject errorObject = error.asObject(rt); + errorObject.setProperty(rt, "code", to_js_string(rt, code)); + } + return error; +} diff --git a/NativeScript/ffi/jni/jsi/Engine.h b/NativeScript/ffi/jni/jsi/Engine.h new file mode 100644 index 000000000..bfb69ac41 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/Engine.h @@ -0,0 +1,200 @@ +#ifndef NS_FFI_JNI_JSI_ENGINE_H +#define NS_FFI_JNI_JSI_ENGINE_H + +// The jsi tree's counterpart to js_native_api.h + native_api_util.h: it picks +// the nativescript::engine backend for the engine being built and supplies the +// handful of JS operations the engine layer deliberately does not expose +// (defineProperty, instanceof, prototype access, delete, strict equality). +// +// Those are absent from engine:: because they are not primitives on any of the +// five engines in the same way -- they are language operations reachable from +// the global object. Rather than push five implementations down, they are +// resolved once per runtime from the JS builtins and cached (see Builtins). + +#if defined(TARGET_ENGINE_V8) +#include "jsi/v8/V8Runtime.h" +#elif defined(TARGET_ENGINE_JSC) +#include "jsi/jsc/JSCRuntime.h" +#elif defined(TARGET_ENGINE_QUICKJS) +#include "jsi/quickjs/QuickJSRuntime.h" +#elif defined(TARGET_ENGINE_HERMES) +#include "jsi/hermes/HermesRuntime.h" +#else +#error "The jsi JNI bridge needs a TARGET_ENGINE_* definition." +#endif + +#include +#include +#include +#include + +namespace engine = ::nativescript::engine; + +namespace tns { + +using JsRuntime = engine::Runtime; +using JsValue = engine::Value; +using JsObject = engine::Object; +using JsFunction = engine::Function; +using JsArray = engine::Array; +using JsString = engine::String; +using JsPropNameID = engine::PropNameID; +using JsError = engine::JSError; + +namespace js_util { + +// Resolved once per runtime. Every entry is an owned engine handle, so they +// must be released before the runtime is torn down -- QuickJS asserts an empty +// gc object list in JS_FreeRuntime and aborts otherwise. dispose() is called +// from the runtime's teardown path. +struct Builtins { + JsFunction defineProperty; + JsFunction getPrototypeOf; + JsFunction setPrototypeOf; + JsFunction objectCreate; + JsFunction objectKeys; + JsFunction deleteProperty; + JsFunction hasOwnProperty; + JsFunction isInteger; + JsFunction isArray; + JsFunction isView; + JsFunction strictEquals; + JsFunction instanceOf; + JsObject numberCtor; + JsObject stringCtor; + JsObject booleanCtor; + JsObject dateCtor; + JsObject dataViewCtor; + JsObject objectCtor; + JsFunction errorCtor; + JsFunction stringCoerce; + + static Builtins& of(JsRuntime& rt); + static void dispose(JsRuntime& rt); +}; + +inline JsObject global(JsRuntime& rt) { return rt.global(); } + +inline bool is_undefined(const JsValue& value) { return value.isUndefined(); } + +inline bool is_null(const JsValue& value) { return value.isNull(); } + +inline bool is_null_or_undefined(const JsValue& value) { + return value.isUndefined() || value.isNull(); +} + +inline bool is_object(const JsValue& value) { return value.isObject(); } + +inline JsValue undefined() { return JsValue::undefined(); } + +inline JsValue null() { return JsValue::null(); } + +inline bool get_bool(const JsValue& value) { + return value.isBool() ? value.getBool() : false; +} + +inline double get_number(const JsValue& value) { + return value.isNumber() ? value.getNumber() : 0; +} + +inline int32_t get_int32(const JsValue& value) { + return static_cast(get_number(value)); +} + +// asString(rt).utf8(rt) built an owning engine::String -- a heap allocation and +// a persistent engine handle -- for a value that was read and dropped in the +// same expression. Value::utf8 reads the handle in place. See the declaration +// in jsi/v8/V8Runtime.h. +inline std::string get_string_value(JsRuntime& rt, const JsValue& value) { + return value.utf8(rt); +} + +inline JsValue to_js_string(JsRuntime& rt, const std::string& value) { + return JsValue::createStringFromUtf8(rt, value.data(), value.size()); +} + +inline JsValue to_js_string(JsRuntime& rt, const char* value) { + const char* text = value != nullptr ? value : ""; + return JsValue::createStringFromUtf8(rt, text, std::strlen(text)); +} + +inline JsValue get_property(JsRuntime& rt, const JsValue& object, + const char* propertyName) { + if (!object.isObject()) return undefined(); + return object.asObjectBorrowed(rt).getProperty(rt, propertyName); +} + +inline bool has_property(JsRuntime& rt, const JsValue& object, + const char* propertyName) { + if (!object.isObject()) return false; + return object.asObjectBorrowed(rt).hasProperty(rt, propertyName); +} + +// Object::hasProperty walks the prototype chain; napi_has_own_property does +// not, and the callers that used it are distinguishing an own member from an +// inherited one. +bool has_own_property(JsRuntime& rt, const JsObject& object, const char* propertyName); + +inline JsValue get_prototype(JsRuntime& rt, const JsValue& object) { + return get_property(rt, object, "prototype"); +} + +inline void set_prototype(JsRuntime& rt, JsObject& object, + const JsValue& prototype) { + object.setProperty(rt, "prototype", prototype); +} + +JsValue getPrototypeOf(JsRuntime& rt, const JsValue& object); +void setPrototypeOf(JsRuntime& rt, const JsValue& object, + const JsValue& prototype); + +// napi_util::get__proto__ reads the "__proto__" accessor; going through +// Object.getPrototypeOf instead gives the same answer for every object the +// runtime handles and does not depend on Object.prototype being intact. +inline JsValue get__proto__(JsRuntime& rt, const JsValue& object) { + return getPrototypeOf(rt, object); +} + +bool strict_equal(JsRuntime& rt, const JsValue& lhs, const JsValue& rhs); +bool instance_of(JsRuntime& rt, const JsValue& value, const JsValue& ctor); +bool delete_property(JsRuntime& rt, const JsValue& object, const JsValue& key); + +void define_property_value(JsRuntime& rt, const JsObject& object, + const char* propertyName, const JsValue& value, + bool enumerable = true, bool configurable = true, + bool writable = true); + +void define_property_get_set(JsRuntime& rt, const JsObject& object, + const char* propertyName, + const JsFunction* getter, + const JsFunction* setter, + bool enumerable = true, bool configurable = true); + +JsValue valueOf(JsRuntime& rt, const JsValue& value); + +bool is_number_object(JsRuntime& rt, const JsValue& value); +bool is_string_object(JsRuntime& rt, const JsValue& value); +bool is_boolean_object(JsRuntime& rt, const JsValue& value); +bool is_date(JsRuntime& rt, const JsValue& value); +bool is_dataview(JsRuntime& rt, const JsValue& value); +bool is_typedarray(JsRuntime& rt, const JsValue& value); +bool is_array(JsRuntime& rt, const JsValue& value); +bool is_float(JsRuntime& rt, const JsValue& value); +bool is_error(JsRuntime& rt, const JsValue& value); + +std::string coerce_to_string(JsRuntime& rt, const JsValue& value); + +JsValue create_error(JsRuntime& rt, const std::string& message, + const char* code = nullptr); + +JsValue object_create_from(JsRuntime& rt, const JsValue& prototype); + +JsFunction set_function(JsRuntime& rt, JsObject& object, const char* name, + engine::HostFunctionType callback); + +void inherits(JsRuntime& rt, const JsObject& ctor, const JsObject& superCtor); + +} // namespace js_util +} // namespace tns + +#endif // NS_FFI_JNI_JSI_ENGINE_H diff --git a/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp new file mode 100644 index 000000000..4638f8985 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.cpp @@ -0,0 +1,1800 @@ +// +// Created by Ammar Ahmed on 20/09/2024. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "JEnv.h" +#include "CallbackHandlers.h" +#include "Util.h" +#include "JniLocalRef.h" +#include "MetadataNode.h" +#include "MethodCache.h" +#include "ArgConverter.h" +#include "JsArgConverter.h" +#include "GlobalHelpers.h" +#if !defined(NS_JSI_HOST_RUNTIME) +#include "ModuleInternal.h" +#endif +#if !defined(NS_JSI_HOST_RUNTIME) +#include "WorkerWrapper.h" +#endif +#include + +#ifdef USE_MIMALLOC + +#include "mimalloc.h" + +#endif + +using namespace std; +using namespace tns; + + +namespace { +bool EnsureMetadataReturnType(MetadataEntry *entry) { + if (entry == nullptr) { + return false; + } + + auto &returnType = entry->getReturnType(); + if (!returnType.empty()) { + return true; + } + + const auto &signature = entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen == std::string::npos || closeParen + 1 >= signature.size()) { + return false; + } + + const auto candidate = signature.substr(closeParen + 1); + switch (candidate[0]) { + case 'V': case 'Z': case 'B': case 'C': case 'S': + case 'I': case 'J': case 'F': case 'D': + if (candidate.size() != 1) return false; + break; + case 'L': + if (candidate.size() < 3 || candidate.back() != ';') return false; + break; + case '[': + if (candidate.size() < 2) return false; + break; + default: + return false; + } + + returnType = candidate; + return true; +} +// Converts a NativeScriptException into a JS throw, the way every callback in +// this tree has to. The napi tree needed no equivalent: there a native error +// was reported with napi_throw and a plain return, so nothing could unwind out +// of a callback. Here a C++ throw is the mechanism, and an escapee unwinds +// through the engine's own frames. +template +JsValue Guarded(JsRuntime &rt, Fn &&fn) { + try { + return fn(); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException(std::string("Error: c++ exception!")).ReThrowToJs(rt); + } +} +} // namespace + +void CallbackHandlers::Init(JsRuntime &rt) { + JEnv jEnv; + + JAVA_LANG_STRING = jEnv.FindClass("java/lang/String"); + assert(JAVA_LANG_STRING != nullptr); + + RUNTIME_CLASS = jEnv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + RESOLVE_CLASS_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveClass", + "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Z)Ljava/lang/Class;"); + assert(RESOLVE_CLASS_METHOD_ID != nullptr); + + CURRENT_OBJECTID_FIELD_ID = jEnv.GetFieldID(RUNTIME_CLASS, "currentObjectId", "I"); + assert(CURRENT_OBJECTID_FIELD_ID != nullptr); + + MAKE_INSTANCE_STRONG_ID = jEnv.GetMethodID(RUNTIME_CLASS, "makeInstanceStrong", + "(Ljava/lang/Object;I)V"); + assert(MAKE_INSTANCE_STRONG_ID != nullptr); + + GET_TYPE_METADATA = jEnv.GetStaticMethodID(RUNTIME_CLASS, "getTypeMetadata", + "(Ljava/lang/String;I)[Ljava/lang/String;"); + assert(GET_TYPE_METADATA != nullptr); + + ENABLE_VERBOSE_LOGGING_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "enableVerboseLogging", + "()V"); + assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); + + DISABLE_VERBOSE_LOGGING_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "disableVerboseLogging", + "()V"); + assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); + + MetadataNode::Init(rt); + + MethodCache::Init(); +} + +JsValue CallbackHandlers::CallJavaMethod(JsRuntime &rt, const JsValue &caller, const string &className, + const string &methodName, MetadataEntry *entry, + bool isFromInterface, bool isStatic, bool isConstructorCall, + const JsValue *argv, size_t argc, + ObjectManager *objectManager, + bool metadataSignatureIsUnambiguous) { + + JEnv jEnv; + jclass clazz; + jmethodID mid; + string *sig = nullptr; + string *returnType = nullptr; + auto retType = MethodReturnType::Unknown; + MethodCache::CacheMethodInfo mi; + bool isSuper = false; + + if (metadataSignatureIsUnambiguous && entry != nullptr && + !EnsureMetadataReturnType(entry)) { + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + + if (metadataSignatureIsUnambiguous && entry != nullptr && entry->memberId == nullptr) { + JEnv metadataEnv; + auto metadataClass = metadataEnv.FindClass(className); + if (metadataClass != nullptr) { + jmethodID metadataMethod; + jclass resolvedMetadataClass = metadataClass; + if (isStatic && isFromInterface) { + auto methodAndClassPair = metadataEnv.GetInterfaceStaticMethodIDAndJClass( + className, methodName, entry->getSig()); + metadataMethod = methodAndClassPair.first; + if (methodAndClassPair.second != nullptr) { + resolvedMetadataClass = methodAndClassPair.second; + } + } else { + metadataMethod = isStatic + ? metadataEnv.GetStaticMethodID(metadataClass, methodName, + entry->getSig()) + : metadataEnv.GetMethodID(metadataClass, methodName, + entry->getSig()); + } + if (metadataMethod != nullptr) { + entry->memberId = reinterpret_cast(metadataMethod); + entry->clazz = resolvedMetadataClass; + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } + + if ((entry != nullptr) && (entry->getIsResolved() || metadataSignatureIsUnambiguous)) { + auto &entrySignature = entry->getSig(); + isStatic = entry->isStatic; + + if (entry->memberId == nullptr) { + clazz = jEnv.FindClass(className); + + if (clazz == nullptr) { + MetadataNode *callerNode = MetadataNode::GetNodeFromHandle(rt, caller); + const string callerClassName = callerNode->GetName(); + + DEBUG_WRITE("Cannot resolve class: %s while calling method: %s callerClassName: %s", + className.c_str(), methodName.c_str(), callerClassName.c_str()); + clazz = jEnv.FindClass(callerClassName); + if (clazz == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve caller's class name: %s", callerClassName.c_str()); + return js_util::undefined(); + } + + if (isStatic) { + if (isFromInterface) { + auto methodAndClassPair = jEnv.GetInterfaceStaticMethodIDAndJClass( + className, + methodName, + entrySignature); + entry->memberId = methodAndClassPair.first; + clazz = methodAndClassPair.second; + } else { + entry->memberId = jEnv.GetStaticMethodID(clazz, methodName, entrySignature); + } + } else { + entry->memberId = jEnv.GetMethodID(clazz, methodName, entrySignature); + } + + if (entry->memberId == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve a method %s on caller class: %s", + methodName.c_str(), callerClassName.c_str()); + return js_util::undefined(); + } + } else { + if (isStatic) { + if (isFromInterface) { + auto methodAndClassPair = jEnv.GetInterfaceStaticMethodIDAndJClass( + className, + methodName, entrySignature); + entry->memberId = methodAndClassPair.first; + clazz = methodAndClassPair.second; + } else { + entry->memberId = jEnv.GetStaticMethodID(clazz, methodName, entrySignature); + } + } else { + entry->memberId = jEnv.GetMethodID(clazz, methodName, entrySignature); + } + + if (entry->memberId == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve a method %s on class: %s", methodName.c_str(), + className.c_str()); + return js_util::undefined(); + } + } + entry->clazz = clazz; + } + + mid = reinterpret_cast(entry->memberId); + clazz = entry->clazz; + sig = &entry->getSig(); + returnType = &entry->getReturnType(); + retType = entry->getRetType(); + } else { + DEBUG_WRITE("Resolving method: %s on className %s", methodName.c_str(), className.c_str()); + + clazz = jEnv.FindClass(className); + if (clazz != nullptr) { + mi = MethodCache::ResolveMethodSignature(rt, className, methodName, argc, argv, isStatic); + if (mi.mid == nullptr) { + DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d", + className.c_str(), methodName.c_str(), isStatic, isSuper); + return js_util::undefined(); + } + } else { + MetadataNode *callerNode = MetadataNode::GetNodeFromHandle(rt, caller); + const string callerClassName = callerNode->GetName(); + DEBUG_WRITE("Resolving method on caller class: %s.%s on className %s", + callerClassName.c_str(), methodName.c_str(), className.c_str()); + mi = MethodCache::ResolveMethodSignature(rt, callerClassName, methodName, argc, argv, + isStatic); + if (mi.mid == nullptr) { + DEBUG_WRITE( + "Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d, callerClass=%s", + className.c_str(), methodName.c_str(), isStatic, isSuper, + callerClassName.c_str()); + return js_util::undefined(); + } + } + + clazz = mi.clazz; + mid = mi.mid; + sig = &mi.signature; + returnType = &mi.returnType; + retType = mi.retType; + } + + if (!isStatic) { + DEBUG_WRITE("CallJavaMethod on instance %s", methodName.c_str()); + } else { + DEBUG_WRITE("CallJavaMethod on class %s", methodName.c_str()); + } + + // The caller (MethodCallback) passes a cached ObjectManager*; only fall back + // to the locked runtime map lookup when invoked without one. Resolved + // before the converter so object-arg conversion can reuse it too. + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + JsArgConverter argConverter = (entry != nullptr && entry->isExtensionFunction) + ? JsArgConverter(rt, caller, argv, argc, *sig, entry, (JNIEnv *) jEnv, objectManager) + : JsArgConverter(rt, argv, argc, false, *sig, entry, (JNIEnv *) jEnv, objectManager); + + + if (!argConverter.IsValid()) { + JsArgConverter::Error err = argConverter.GetError(); + throw NativeScriptException(err.msg); + } + + JniLocalRef callerJavaObject; + + jvalue *javaArgs = argConverter.ToArgs(); + + if (!isStatic) { + int objectId = -1; + + callerJavaObject = objectManager->GetJavaObjectByJsObject(caller, &objectId, &isSuper); + + if (callerJavaObject.IsNull()) { + stringstream ss; + + if (isConstructorCall) { + ss << "No java object found on which to call \"" << methodName + << "\" method. It is possible your Javascript object is not linked with the corresponding Java class. Try passing context(this) to the constructor function."; + } else { + ss << "Failed calling " << methodName << " on a " << className + << " instance. The JavaScript instance no longer has available Java instance counterpart."; + } + throw NativeScriptException(ss.str()); + } + } + + switch (retType) { + case MethodReturnType::Void: { + if (isStatic) { + jEnv.CallStaticVoidMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + jEnv.CallNonvirtualVoidMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + jEnv.CallVoidMethodA(callerJavaObject, mid, javaArgs); + } + return js_util::undefined(); + } + case MethodReturnType::Boolean: { + jboolean result; + if (isStatic) { + result = jEnv.CallStaticBooleanMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualBooleanMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallBooleanMethodA(callerJavaObject, mid, javaArgs); + } + + return JsValue(result != 0); + } + case MethodReturnType::Byte: { + jbyte result; + if (isStatic) { + result = jEnv.CallStaticByteMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualByteMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallByteMethodA(callerJavaObject, mid, javaArgs); + } + + return JsValue((int) result); + } + case MethodReturnType::Char: { + jchar result; + if (isStatic) { + result = jEnv.CallStaticCharMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualCharMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallCharMethodA(callerJavaObject, mid, javaArgs); + } + + // The napi tree round-trips the jchar through a jstring and takes one + // byte of its UTF-8 form, which truncates anything outside ASCII. + // engine::String is UTF-8 only, so the transcode is explicit here and + // matches every other jchar path in this tree. + return ArgConverter::convertToJsString(rt, &result, 1); + } + case MethodReturnType::Short: { + jshort result; + if (isStatic) { + result = jEnv.CallStaticShortMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualShortMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallShortMethodA(callerJavaObject, mid, javaArgs); + } + + return JsValue((int) result); + } + case MethodReturnType::Int: { + jint result; + if (isStatic) { + result = jEnv.CallStaticIntMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualIntMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallIntMethodA(callerJavaObject, mid, javaArgs); + } + return JsValue((int) result); + } + case MethodReturnType::Long: { + jlong result; + if (isStatic) { + result = jEnv.CallStaticLongMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualLongMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallLongMethodA(callerJavaObject, mid, javaArgs); + } + return ArgConverter::ConvertFromJavaLong(rt, result); + } + case MethodReturnType::Float: { + jfloat result; + if (isStatic) { + result = jEnv.CallStaticFloatMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualFloatMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallFloatMethodA(callerJavaObject, mid, javaArgs); + } + return JsValue((double) result); + } + case MethodReturnType::Double: { + jdouble result; + if (isStatic) { + result = jEnv.CallStaticDoubleMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualDoubleMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallDoubleMethodA(callerJavaObject, mid, javaArgs); + } + return JsValue((double) result); + } + case MethodReturnType::String: { + jobject result = nullptr; + + if (isStatic) { + result = jEnv.CallStaticObjectMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallObjectMethodA(callerJavaObject, mid, javaArgs); + } + + if (result != nullptr) { + JsValue returnValue = ArgConverter::jstringToJsString(rt, + static_cast(result)); + jEnv.DeleteLocalRef(result); + return returnValue; + } + + return js_util::null(); + } + case MethodReturnType::Object: { + jobject result = nullptr; + + if (isStatic) { + result = jEnv.CallStaticObjectMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallObjectMethodA(callerJavaObject, mid, javaArgs); + } + + if (result == nullptr) { + return js_util::null(); + } + + JsValue returnValue; + + // A declared array return can never be a java.lang.String, so skip + // the per-return IsInstanceOf JNI probe on the array-return hot path. + // Non-array Object/CharSequence returns can be polymorphic Strings, + // so those still need the check. + bool isArrayReturn = returnType != nullptr && !returnType->empty() && + (*returnType)[0] == '['; + auto isString = !isArrayReturn && jEnv.IsInstanceOf(result, JAVA_LANG_STRING); + + if (isString) { + returnValue = ArgConverter::jstringToJsString(rt, (jstring) result); + } else { + jint javaObjectID = objectManager->GetOrCreateObjectId(result); + returnValue = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (js_util::is_null_or_undefined(returnValue)) { + returnValue = objectManager->CreateJSWrapper(javaObjectID, *returnType, + result); + } + } + + jEnv.DeleteLocalRef(result); + + return returnValue; + } + default: { + assert(false); + return js_util::undefined(); + } + } +} + + +bool CallbackHandlers::RegisterInstance(JsRuntime &rt, const JsValue &jsObject, + const std::string &fullClassName, + const ArgsWrapper &argWrapper, + const JsValue &implementationObject, + bool isInterface, + JsValue *jsThisProxy, + const std::string &baseClassName, + MetadataNode *node) { + bool success; + + DEBUG_WRITE("RegisterInstance called for '%s'", fullClassName.c_str()); + + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + jclass generatedJavaClass = ResolveClass(rt, baseClassName, fullClassName, + implementationObject, + isInterface); + + int javaObjectID = objectManager->GenerateNewObjectID(); + + objectManager->Link(jsObject, javaObjectID, nullptr, node); + + // resolve constructor + auto mi = MethodCache::ResolveConstructorSignature(rt, argWrapper, fullClassName, + generatedJavaClass, isInterface); + + // while the "instance" is being created, if an exception is thrown during the construction + // this scope will guarantee the "javaObjectID" will be set to -1 and won't have an invalid value + jobject instance; + { + JavaObjectIdScope objIdScope(jEnv, CURRENT_OBJECTID_FIELD_ID, runtime->GetJavaRuntime(), + javaObjectID); + + if (argWrapper.type == ArgType::Interface) { + instance = jEnv.NewObject(generatedJavaClass, mi.mid); + } else { + // resolve arguments before passing them on to the constructor + JsArgConverter argConverter(rt, argWrapper.argv, argWrapper.argc, mi.signature); + auto ctorArgs = argConverter.ToArgs(); + + instance = jEnv.NewObjectA(generatedJavaClass, mi.mid, ctorArgs); + } + } + + // Set runtimeId field on interface and extended classes + if (runtime->GetId() != 0 && + (isInterface || !js_util::is_null_or_undefined(implementationObject))) { + jfieldID runtimeIdField; + auto itFound = jclass_to_runtimeId_cache.find(generatedJavaClass); + if (itFound != jclass_to_runtimeId_cache.end()) { + runtimeIdField = itFound->second; + } else { + runtimeIdField = jEnv.GetFieldID(generatedJavaClass, "runtimeId", "I"); + jclass_to_runtimeId_cache.emplace(generatedJavaClass, runtimeIdField); + } + if (runtimeIdField != nullptr) { + jint runtimeId = runtime->GetId(); // Assuming GetId() returns the current runtime's id + DEBUG_WRITE("Setting runtimeId %d on instance of %s", runtimeId, fullClassName.c_str()); + jEnv.SetIntField(instance, runtimeIdField, runtimeId); + } + } + + jEnv.CallVoidMethod(runtime->GetJavaRuntime(), MAKE_INSTANCE_STRONG_ID, instance, javaObjectID); + + // Reuse the runtime we already resolved instead of re-querying via rt. + runtime->AdjustAmountOfExternalAllocatedMemory(); + runtime->TryCallGC(); + + JniLocalRef localInstance(instance); + success = !localInstance.IsNull(); + + if (success) { + // ResolveClass already cached this exact (global) jclass under + // fullClassName, so reuse it instead of a redundant FindClass lookup. + objectManager->SetJavaClass(jsObject, generatedJavaClass); + *jsThisProxy = objectManager->GetOrCreateProxy(javaObjectID, jsObject); + } else { + DEBUG_WRITE_FORCE("RegisterInstance failed with null new instance class: %s", + fullClassName.c_str()); + } + + return success; +} + +jclass CallbackHandlers::ResolveClass(JsRuntime &rt, const string &baseClassName, + const string &fullClassName, + const JsValue &implementationObject, bool isInterface) { + JEnv jEnv; + jclass globalRefToGeneratedClass = jEnv.CheckForClassInCache(fullClassName); + + if (globalRefToGeneratedClass == nullptr) { + + // get needed arguments in order to load binding + JniLocalRef javaBaseClassName(jEnv.NewStringUTF(baseClassName.c_str())); + JniLocalRef javaFullClassName(jEnv.NewStringUTF(fullClassName.c_str())); + + jobjectArray methodOverrides = GetMethodOverrides(rt, jEnv, implementationObject); + + jobjectArray implementedInterfaces = GetImplementedInterfaces(rt, jEnv, + implementationObject); + + auto runtime = Runtime::GetRuntime(rt); + + // create or load generated binding (java class) + jclass generatedClass = (jclass) jEnv.CallObjectMethod(runtime->GetJavaRuntime(), + RESOLVE_CLASS_METHOD_ID, + (jstring) javaBaseClassName, + (jstring) javaFullClassName, + methodOverrides, + implementedInterfaces, + isInterface); + + globalRefToGeneratedClass = jEnv.InsertClassIntoCache(fullClassName, generatedClass); + + jEnv.DeleteGlobalRef(methodOverrides); + jEnv.DeleteGlobalRef(implementedInterfaces); + } + + return globalRefToGeneratedClass; +} + +// Called by ExtendMethodCallback when extending a class +string CallbackHandlers::ResolveClassName(JsRuntime &rt, jclass &clazz) { + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + auto className = objectManager->GetClassName(clazz); + return className; +} + +JsValue CallbackHandlers::GetArrayElement(JsRuntime &rt, const JsValue &array, + uint32_t index, const string &arraySignature, + ObjectManager *objectManager, jobject arrayObject) { + return arrayElementAccessor.GetArrayElement(rt, array, index, arraySignature, + objectManager, arrayObject); +} + +void CallbackHandlers::SetArrayElement(JsRuntime &rt, const JsValue &array, + uint32_t index, + const string &arraySignature, const JsValue &value, + ObjectManager *objectManager, jobject arrayObject) { + + arrayElementAccessor.SetArrayElement(rt, array, index, arraySignature, value, + objectManager, arrayObject); +} + +JsValue CallbackHandlers::GetJavaField(JsRuntime &rt, const JsValue &caller, + FieldCallbackData *fieldData, + ObjectManager *objectManager, + JniLocalRef targetJavaObject) { + return fieldAccessor.GetJavaField(rt, caller, fieldData, objectManager, + std::move(targetJavaObject)); +} + +void CallbackHandlers::SetJavaField(JsRuntime &rt, const JsValue &target, + const JsValue &value, FieldCallbackData *fieldData, + ObjectManager *objectManager, + JniLocalRef targetJavaObject) { + fieldAccessor.SetJavaField(rt, target, value, fieldData, objectManager, + std::move(targetJavaObject)); +} + +void CallbackHandlers::AdjustAmountOfExternalAllocatedMemory(JsRuntime &rt) { + auto runtime = Runtime::GetRuntime(rt); + runtime->AdjustAmountOfExternalAllocatedMemory(); + runtime->TryCallGC(); +} + +JsValue CallbackHandlers::CreateJSWrapper(JsRuntime &rt, jint javaObjectID, + const string &typeName) { + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + + return objectManager->CreateJSWrapper(javaObjectID, typeName); +} + +// The two Collect* helpers below define which names take part in a binding. +// Content-keyed class names hash exactly these sets, so the static binding +// generator has to reproduce them from the bundle byte for byte -- a name that +// is collected here but not there (or the reverse) yields two different class +// names for one extend() site, which fails at runtime as LookedUpClassNotFound. +// Keeping the JNI arrays and the hash on one implementation is what makes that +// contract checkable in a single place. +vector +CallbackHandlers::CollectImplementedInterfaceNames(JsRuntime &rt, + const JsValue &implementationObject) { + vector interfaceNames; + + if (!implementationObject.isObject()) { + return interfaceNames; + } + + auto prop = implementationObject.asObjectBorrowed(rt).getProperty(rt, "interfaces"); + + if (js_util::is_array(rt, prop)) { + auto array = prop.asObjectBorrowed(rt).getArray(rt); + size_t length = array.size(rt); + + for (size_t j = 0; j < length; j++) { + auto element = array.getValueAtIndexBorrowed(rt, j); + + if (element.isObject()) { + auto node = MetadataNode::GetTypeMetadataName(rt, element); + + node = Util::ReplaceAll(node, std::string("/"), std::string(".")); + + interfaceNames.push_back(node); + } + } + } + + return interfaceNames; +} + +vector +CallbackHandlers::CollectMethodOverrideNames(JsRuntime &rt, + const JsValue &implementationObject, + bool functionsOnly) { + vector methodNames; + + if (!implementationObject.isObject()) { + return methodNames; + } + + auto implObject = implementationObject.asObjectBorrowed(rt); + + // Object::getPropertyNames walks the prototype chain, where the napi tree + // asked for napi_key_own_only | napi_key_all_properties. Object's own + // getOwnPropertyNames is exactly that set (own, enumerable or not), so it is + // used rather than the engine helper. + auto getOwnPropertyNames = + js_util::Builtins::of(rt).objectCtor.getPropertyAsFunction(rt, "getOwnPropertyNames"); + const JsValue nameArgs[] = {JsValue(rt, implObject)}; + auto propNamesValue = getOwnPropertyNames.call(rt, nameArgs, (size_t) 1); + auto propNames = propNamesValue.asObjectBorrowed(rt).getArray(rt); + + size_t length = propNames.size(rt); + + for (size_t i = 0; i < length; i++) { + auto element = propNames.getValueAtIndexBorrowed(rt, i); + auto name = ArgConverter::ConvertToString(rt, element); + + if (name == "super") { + continue; + } + + // Runtime bookkeeping stamped onto the implementation object, not + // something the user wrote. getOwnPropertyNames returns non-enumerable + // properties too, so these have to be filtered by name. + if (name == EXTEND_CTOR_CACHE_KEY || name == CLASS_IMPLEMENTATION_OBJECT) { + continue; + } + + // Written onto the prototype by ts_helpers' __extends before it defers + // to extend(), so every TypeScript-transpiled subclass carries them. + // They are invisible to the static binding generator, which reads the + // class body from source -- hashing them put the two sides permanently + // out of step and sent every such class down the on-device dex path. + if (name == PROP_KEY_TS_PARENT || name == PROP_KEY_TS_CHILD) { + continue; + } + + if (!functionsOnly) { + // Hashing, not binding. The generator reads the implementation object + // out of the bundle, where it cannot tell a function from any other + // value -- `{ foo: someIdentifier }` is a method it must assume, and + // a statically-typed guess either way would disagree with what this + // enumeration sees. Taking every name instead makes the two sides + // agree by construction on everything the generator can parse. + // + // The three exclusions below are the names the generator provably + // never puts in the method field: `super` and `constructor` are + // artifacts of how the implementation object is built (ts_helpers + // extends with the class prototype, which always carries a + // constructor), and an array-valued `interfaces` is routed to its own + // field instead. + if (name == "constructor") { + continue; + } + + string lowered = name; + std::transform(lowered.begin(), lowered.end(), lowered.begin(), + [](unsigned char c) { return std::tolower(c); }); + + auto value = implObject.getProperty(rt, element); + bool isInterfacesDeclaration = lowered == "interfaces" && js_util::is_array(rt, value); + if (isInterfacesDeclaration) { + continue; + } + + methodNames.push_back(name); + continue; + } + + auto method = implObject.getProperty(rt, element); + + bool methodFound = method.isObject() && method.asObjectBorrowed(rt).isFunction(rt); + + if (methodFound) { + methodNames.push_back(name); + } + } + + return methodNames; +} + +jobjectArray +CallbackHandlers::GetImplementedInterfaces(JsRuntime &rt, JEnv &jEnv, + const JsValue &implementationObject) { + auto names = CollectImplementedInterfaceNames(rt, implementationObject); + + vector interfacesToImplement; + interfacesToImplement.reserve(names.size()); + for (const auto &name: names) { + interfacesToImplement.push_back(jEnv.NewStringUTF(name.c_str())); + } + + int interfacesCount = interfacesToImplement.size(); + + jobjectArray implementedInterfaces = CallbackHandlers::GetJavaStringArray(jEnv, + interfacesCount); + for (int i = 0; i < interfacesCount; i++) { + jEnv.SetObjectArrayElement(implementedInterfaces, i, interfacesToImplement[i]); + } + + for (int i = 0; i < interfacesCount; i++) { + jEnv.DeleteLocalRef(interfacesToImplement[i]); + } + + return implementedInterfaces; +} + +jobjectArray +CallbackHandlers::GetMethodOverrides(JsRuntime &rt, JEnv &jEnv, + const JsValue &implementationObject) { + auto names = CollectMethodOverrideNames(rt, implementationObject); + + vector methodNames; + methodNames.reserve(names.size()); + for (const auto &name: names) { + methodNames.push_back(jEnv.NewStringUTF(name.c_str())); + } + + int methodCount = methodNames.size(); + + jobjectArray methodOverrides = CallbackHandlers::GetJavaStringArray(jEnv, methodCount); + for (int i = 0; i < methodCount; i++) { + jEnv.SetObjectArrayElement(methodOverrides, i, methodNames[i]); + } + + for (int i = 0; i < methodCount; i++) { + jEnv.DeleteLocalRef(methodNames[i]); + } + + return methodOverrides; +} + +JsValue CallbackHandlers::RunOnMainThreadCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + assert(argc == 1); + assert(args[0].isObject() && args[0].asObjectBorrowed(rt).isFunction(rt)); + + uint64_t key = ++count_; + bool inserted; + + std::tie(std::ignore, inserted) = cache_.try_emplace(key, Runtime::GetRuntime(rt), rt, + args[0]); + assert(inserted && "Main thread callback ID should not be duplicated"); + + auto value = Callback(key); + auto size = sizeof(Callback); + auto wrote = write(Runtime::GetWriter(), &value, size); + + return js_util::undefined(); + }); +} + +int CallbackHandlers::RunOnMainThreadFdCallback(int fd, int events, void *data) { + struct Callback value; + auto size = sizeof(Callback); + ssize_t nr = read(fd, &value, sizeof(value)); + + auto key = value.id_; + + auto it = cache_.find(key); + if (it == cache_.end()) { + return 1; + } + + tns::Runtime *runtime = it->second.runtime_; + JsRuntime &rt = runtime->GetJSRuntime(); + + JSScope scope(runtime->GetEngineHost()); + + // Copy the callback out before erasing: the entry owns the handle, and + // erasing it releases it. + JsValue cb(rt, it->second.callback_); + + auto global = rt.global(); + + cache_.erase(it); + + try { + cb.asObject(rt).asFunction(rt).callWithThis(rt, global); + } catch (JsError &e) { + DEBUG_WRITE("Error calling JavaScript callback: %s", e.what()); + } + + return 1; +} + +JsValue CallbackHandlers::LogMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + if (argc > 0) { + if (args[0].isString()) { + std::string message = args[0].asString(rt).utf8(rt); + DEBUG_WRITE("%s", message.c_str()); + } + } + } + catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } + + return js_util::undefined(); +} + +JsValue CallbackHandlers::DrainMicrotaskCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + rt.drainMicrotasks(); + return js_util::undefined(); +} + +JsValue CallbackHandlers::TimeCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double duration = nano.time_since_epoch().count(); + return JsValue(duration); +} + +JsValue +CallbackHandlers::ReleaseNativeCounterpartCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + if (argc != 1) { + throw JsError(rt, "Unexpected arguments count!"); + } + + if (!args[0].isObject()) { + throw JsError(rt, "Argument is not an object!"); + } + + Runtime::GetRuntime(rt)->GetObjectManager()->ReleaseNativeObject(rt, args[0]); + return js_util::undefined(); + }); +} + +JsValue +CallbackHandlers::DumpReferenceTablesMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + DumpReferenceTablesMethod(); + return js_util::undefined(); +} + +void CallbackHandlers::DumpReferenceTablesMethod() { + try { + JEnv jEnv; + jclass vmDbgClass = jEnv.FindClass("dalvik/system/VMDebug"); + if (vmDbgClass != nullptr) { + jmethodID mid = jEnv.GetStaticMethodID(vmDbgClass, "dumpReferenceTables", "()V"); + if (mid != 0) { + jEnv.CallStaticVoidMethod(vmDbgClass, mid); + } + } + } + catch (NativeScriptException &e) { + // e.ReThrowToJs(rt); + } + 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(); + } +} + +JsValue +CallbackHandlers::EnableVerboseLoggingMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + tns::LogEnabled = true; + JEnv jEnv; + jEnv.CallVoidMethod(Runtime::GetRuntime(rt)->GetJavaRuntime(), + ENABLE_VERBOSE_LOGGING_METHOD_ID); + } + catch (NativeScriptException &e) { + // e.ReThrowToJs(rt); + } + 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(); + } + return js_util::undefined(); +} + +JsValue +CallbackHandlers::DisableVerboseLoggingMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + tns::LogEnabled = false; + JEnv jEnv; + jEnv.CallVoidMethod(Runtime::GetRuntime(rt)->GetJavaRuntime(), + DISABLE_VERBOSE_LOGGING_METHOD_ID); + } + catch (NativeScriptException &e) { + // e.ReThrowToJs(rt); + } + 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(); + } + return js_util::undefined(); +} + +JsValue CallbackHandlers::ExitMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + auto msg = argc > 0 ? ArgConverter::ConvertToString(rt, args[0]) : std::string(); + DEBUG_WRITE_FATAL("FORCE EXIT: %s", msg.c_str()); + exit(-1); + return js_util::undefined(); +} + +void CallbackHandlers::CreateGlobalCastFunctions(JsRuntime &rt) { + auto global = rt.global(); + castFunctions.CreateGlobalCastFunctions(rt, global); +} + +vector CallbackHandlers::GetTypeMetadata(const string &name, int index) { + JEnv env; + + string canonicalName = Util::ConvertFromJniToCanonicalName(name); + + JniLocalRef className(env.NewStringUTF(canonicalName.c_str())); + jint idx = index; + + JniLocalRef pubApi( + env.CallStaticObjectMethod(RUNTIME_CLASS, GET_TYPE_METADATA, (jstring) className, idx)); + + jsize length = env.GetArrayLength(pubApi); + + assert(length > 0); + + vector result; + + for (jsize i = 0; i < length; i++) { + JniLocalRef s(env.GetObjectArrayElement(pubApi, i)); + const char *pc = env.GetStringUTFChars(s, nullptr); + result.push_back(string(pc)); + env.ReleaseStringUTFChars(s, pc); + } + + return result; +} + +JsValue CallbackHandlers::CallJSMethod(JsRuntime &rt, JNIEnv *_jEnv, + const JsValue &jsObject, jclass claz, + const string &methodName, int javaObjectId, + jobjectArray args) { + JEnv jEnv(_jEnv); + + auto runtime = Runtime::GetRuntime(rt); + JsValue method = runtime->js_method_cache->getCachedMethod(javaObjectId, methodName); + if (method.isUndefined()) { + method = jsObject.asObjectBorrowed(rt).getProperty(rt, methodName); + if (method.isObject() && method.asObjectBorrowed(rt).isFunction(rt)) { + runtime->js_method_cache->cacheMethod(javaObjectId, methodName, method); + } + } + + if (!method.isObject() || !method.asObjectBorrowed(rt).isFunction(rt)) { + stringstream ss; + ss << "Cannot find method '" << methodName << "' implementation"; + throw NativeScriptException(ss.str()); + } + + DEBUG_WRITE("Calling JS Method %s", methodName.c_str()); + + auto fn = method.asObjectBorrowed(rt).asFunction(rt); + auto receiver = jsObject.asObjectBorrowed(rt); + + // The napi tree bracketed the call with napi_is_exception_pending to notice + // a throw. engine:: propagates a JS throw as a JSError, so the equivalent is + // to let it unwind and convert it here. + try { + int argc = jEnv.GetArrayLength(args) / 3; + if (argc > 0) { + JsValue *jsArgs = nullptr; + JsValue stack_args[8]; + std::unique_ptr heap_args; + if (argc <= 8) { + jsArgs = stack_args; + } else { + heap_args = std::make_unique(argc); + jsArgs = heap_args.get(); + } + ArgConverter::ConvertJavaArgsToJsArgs(rt, args, argc, jsArgs); + return fn.callWithThis(rt, receiver, jsArgs, (size_t) argc); + } + + return fn.callWithThis(rt, receiver); + } catch (JsError &e) { + if (e.value() != nullptr) { + throw NativeScriptException(rt, *e.value(), "Error calling js method: " + methodName); + } + throw NativeScriptException("Error calling js method: " + methodName + ": " + e.what()); + } +} + +JsValue CallbackHandlers::FindClass(JsRuntime &rt, const char *name) { + JEnv jEnv; + jclass javaClass = jEnv.FindClass(name); + if (jEnv.ExceptionCheck() == JNI_FALSE) { + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + + jint javaObjectID = objectManager->GetOrCreateObjectId(javaClass); + JsValue clazz = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (js_util::is_null_or_undefined(clazz)) { + clazz = objectManager->CreateJSWrapper(javaObjectID, "Ljava/lang/Class;", javaClass); + } + + return clazz; + } + return js_util::undefined(); +} + +int CallbackHandlers::GetArrayLength(JsRuntime &rt, const JsValue &arr) { + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + auto javaArr = objectManager->GetJavaObjectByJsObjectFast(arr); + + auto length = jEnv.GetArrayLength(javaArr); + + return length; +} + +jobjectArray CallbackHandlers::GetJavaStringArray(JEnv &jEnv, int length) { + if (length > CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH) { + stringstream ss; + ss << "You are trying to override more methods than the limit of " + << CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH; + throw NativeScriptException(ss.str()); + } + + JniLocalRef tmpArr(jEnv.NewObjectArray(length, JAVA_LANG_STRING, nullptr)); + return (jobjectArray) jEnv.NewGlobalRef(tmpArr); +} + +CallbackHandlers::func_AChoreographer_getInstance AChoreographer_getInstance_; + +CallbackHandlers::func_AChoreographer_postFrameCallback AChoreographer_postFrameCallback_; +CallbackHandlers::func_AChoreographer_postFrameCallbackDelayed AChoreographer_postFrameCallbackDelayed_; + +CallbackHandlers::func_AChoreographer_postFrameCallback64 AChoreographer_postFrameCallback64_; +CallbackHandlers::func_AChoreographer_postFrameCallbackDelayed64 AChoreographer_postFrameCallbackDelayed64_; + +void CallbackHandlers::FrameCallbackCacheEntry::execute(double ts, void *data) { + if (data == nullptr) { + return; + } + + auto entry = static_cast(data); + if (entry->shouldRemoveBeforeCall()) { + frameCallbackCache_.erase(entry->id); // invalidates *entry + return; + } + + tns::Runtime *runtime = entry->runtime; + JsRuntime &rt = runtime->GetJSRuntime(); + JSScope scope(runtime->GetEngineHost()); + + JsValue cb(rt, entry->callback); + auto global = rt.global(); + + entry->markUnscheduled(); + + const JsValue args[] = {JsValue(ts)}; + try { + cb.asObject(rt).asFunction(rt).callWithThis(rt, global, args, (size_t) 1); + } catch (JsError &e) { + DEBUG_WRITE("Error in frame callback: %s", e.what()); + } + + // check if we should remove it (it should be both unscheduled and removed) + if (entry->shouldRemoveAfterCall()) { + frameCallbackCache_.erase(entry->id); // invalidates *entry + } +} + +void CallbackHandlers::PostCallback(JsRuntime &rt, const JsValue *args, size_t argc, + CallbackHandlers::FrameCallbackCacheEntry *entry) { + ALooper_prepare(0); + auto instance = AChoreographer_getInstance_(); + bool hasDelay = argc > 1 && args[1].isNumber(); + + if (android_get_device_api_level() >= 29) { + if (hasDelay) { + auto delayValue = (uint32_t) js_util::get_number(args[1]); + AChoreographer_postFrameCallbackDelayed64_(instance, entry->frameCallback64_, entry, + delayValue); + } else { + AChoreographer_postFrameCallback64_(instance, entry->frameCallback64_, entry); + } + } else { + if (hasDelay) { + auto delayValue = (int64_t) js_util::get_number(args[1]); + AChoreographer_postFrameCallbackDelayed_(instance, entry->frameCallback_, entry, + static_cast(delayValue)); + } else { + AChoreographer_postFrameCallback_(instance, entry->frameCallback_, entry); + } + } +} + +JsValue CallbackHandlers::PostFrameCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + if (android_get_device_api_level() >= 24) { + InitChoreographer(); + + if (argc < 1 || !args[0].isObject() || !args[0].asObjectBorrowed(rt).isFunction(rt)) { + throw JsError(rt, "Frame callback argument is not a function"); + } + + auto func = args[0].asObjectBorrowed(rt); + + auto pId = func.getPropertyBorrowed(rt, "_postFrameCallbackId"); + if (pId.isNumber()) { + auto id = (uint64_t) js_util::get_number(pId); + auto cb = frameCallbackCache_.find(id); + if (cb != frameCallbackCache_.end()) { + bool shouldReschedule = !cb->second.isScheduled(); + cb->second.markScheduled(); + if (shouldReschedule) { + PostCallback(rt, args, argc, &cb->second); + } + return js_util::undefined(); + } + } + + uint64_t key = ++frameCallbackCount_; + + func.setProperty(rt, "_postFrameCallbackId", JsValue((double) key)); + + auto [val, inserted] = frameCallbackCache_.try_emplace(key, Runtime::GetRuntime(rt), rt, + args[0], key); + assert(inserted && "Frame callback ID should not be duplicated"); + + val->second.markScheduled(); + PostCallback(rt, args, argc, &val->second); + + } + return js_util::undefined(); + }); +} + +JsValue CallbackHandlers::RemoveFrameCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + if (android_get_device_api_level() >= 24) { + InitChoreographer(); + + if (argc < 1 || !args[0].isObject() || !args[0].asObjectBorrowed(rt).isFunction(rt)) { + throw JsError(rt, "Frame callback argument is not a function"); + } + + auto func = args[0].asObjectBorrowed(rt); + + auto pId = func.getPropertyBorrowed(rt, "_postFrameCallbackId"); + + if (pId.isNumber()) { + auto id = (uint64_t) js_util::get_number(pId); + auto cb = frameCallbackCache_.find(id); + if (cb != frameCallbackCache_.end()) { + cb->second.markRemoved(); + } + } + } + return js_util::undefined(); + }); +} + +void CallbackHandlers::InitChoreographer() { + if (AChoreographer_getInstance_ == nullptr) { + void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (lib != nullptr) { + AChoreographer_getInstance_ = reinterpret_cast( + dlsym(lib, "AChoreographer_getInstance")); + AChoreographer_postFrameCallback_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallback")); + AChoreographer_postFrameCallbackDelayed_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallbackDelayed")); + + assert(AChoreographer_getInstance_); + assert(AChoreographer_postFrameCallback_); + assert(AChoreographer_postFrameCallbackDelayed_); + + if (android_get_device_api_level() >= 29) { + AChoreographer_postFrameCallback64_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallback64")); + AChoreographer_postFrameCallbackDelayed64_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallbackDelayed64")); + + assert(AChoreographer_postFrameCallback64_); + assert(AChoreographer_postFrameCallbackDelayed64_); + } + } + } +} + +void CallbackHandlers::RemoveEnvEntries(JsRuntime &rt) { + // Erasing while iterating (as the napi tree does) invalidates the iterator + // on the very entry the loop then advances; collect first, erase after. + tns::Runtime *runtime = Runtime::GetRuntimeUnchecked(rt); + std::vector staleCallbacks; + for (auto &item: cache_) { + if (item.second.runtime_ == runtime) { + staleCallbacks.push_back(item.first); + } + } + for (auto key: staleCallbacks) { + cache_.erase(key); + } + + std::vector staleFrameCallbacks; + for (auto &item: frameCallbackCache_) { + if (item.second.runtime == runtime) { + staleFrameCallbacks.push_back(item.first); + } + } + for (auto key: staleFrameCallbacks) { + frameCallbackCache_.erase(key); + } +} + +// Worker +// +// A Worker gets its own VM, which a guest cannot build -- the runtime it is +// attached to belongs to the embedder. Excluded from the guest build along with +// WorkerWrapper itself; React Native has its own answer for background work. +#if !defined(NS_JSI_HOST_RUNTIME) + +JsValue CallbackHandlers::NewThreadCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + // The napi tree rejected a plain `Worker(...)` call by checking + // napi_get_new_target. engine:: exposes no new.target, and + // createFromHostConstructor does NOT imply a construct call -- V8's + // ConstructorBehavior::kAllow permits both, and the other backends + // likewise route a plain call here. What distinguishes the two on every + // backend is the receiver: a construct call gets a fresh object built + // from the constructor's prototype, while a plain call gets undefined + // (strict) or the global object (sloppy). + if (js_util::is_null_or_undefined(thisVal) || + js_util::strict_equal(rt, thisVal, JsValue(rt, rt.global()))) { + throw NativeScriptException("Worker should be called as a constructor!"); + } + + if (argc != 1) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL/URL OBJECT to the file"); + } + + if (!args[0].isString() && !args[0].isObject()) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL/URL OBJECT to the file"); + } + + JsValue workerFilePath; + if (args[0].isObject()) { + workerFilePath = args[0].asObjectBorrowed(rt).getProperty(rt, "href"); + if (js_util::is_null_or_undefined(workerFilePath)) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL to the file"); + } + } else { + workerFilePath = JsValue(rt, args[0]); + } + + auto frames = tns::BuildStacktraceFrames(rt, nullptr, 1); + string currentExecutingScriptNameStr = + frames.size() < 3 ? frames[0].filename : frames[2].filename; + + auto lastForwardSlash = currentExecutingScriptNameStr.find_last_of("/"); + auto currentDir = currentExecutingScriptNameStr.substr(0, lastForwardSlash + 1); + std::string fileSchema("file://"); + if (currentDir.compare(0, fileSchema.length(), fileSchema) == 0) { + currentDir = currentDir.substr(fileSchema.length()); + } + + std::string workerPath = ArgConverter::ConvertToString(rt, workerFilePath); + + if (workerPath.compare(0, fileSchema.length(), fileSchema) == 0) { + workerPath = workerPath.substr(fileSchema.length()); + auto workerPathPrefix = workerPath.substr(0, 1) == "/" ? "~" : "~/"; + workerPath = workerPathPrefix + workerPath; + } + + DEBUG_WRITE("Worker Path: %s, Current Dir: %s", workerPath.c_str(), currentDir.c_str()); + + + + // Will throw if path is invalid or doesn't exist + ModuleInternal::CheckFileExists(rt, workerPath, currentDir); + + // Resolve the JNI handles used by the worker thread bootstrap while we + // are still on the parent (main, for the first worker) thread. + WorkerWrapper::EnsureJniCached(); + + auto workerId = WorkerWrapper::NextWorkerId(); + auto jsThis = thisVal.asObjectBorrowed(rt); + jsThis.setProperty(rt, "workerId", JsValue((int) workerId)); + + DEBUG_WRITE("Called Worker constructor id=%d", workerId); + + // THREAD_PRIORITY_BACKGROUND (android.os.Process) == 10 + const int kThreadPriorityBackground = 10; + auto wrapper = std::make_shared(rt, workerId, workerPath, currentDir, + kThreadPriorityBackground, thisVal); + WorkerWrapper::Insert(workerId, wrapper); + wrapper->Start(); + + auto error = GlobalHelpers::CreateError(rt, ""); + jsThis.setProperty(rt, "__stack__", error.getProperty(rt, "stack")); + + return JsValue(rt, thisVal); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c exception!")); + nsEx.ReThrowToJs(rt); + } +} + +JsValue +CallbackHandlers::WorkerObjectPostMessageCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + if (argc != 1) { + NativeScriptException exception( + "Failed to execute 'postMessage' on 'Worker': 1 argument required."); + throw exception; + } + + auto jsThis = thisVal.asObjectBorrowed(rt); + + auto isTerminated = jsThis.getPropertyBorrowed(rt, "isTerminated"); + if (isTerminated.isBool() && isTerminated.getBool()) { + return js_util::undefined(); + } + + std::string msg = tns::JsonStringifyObject(rt, args[0], false); + + // get worker's ID that is associated with this Worker object + auto jsId = jsThis.getPropertyBorrowed(rt, "workerId"); + auto id = js_util::get_int32(jsId); + + auto wrapper = WorkerWrapper::GetById(id); + if (wrapper != nullptr) { + wrapper->PostMessage(std::make_shared( + worker::Message::MakeData(std::move(msg)))); + } + + DEBUG_WRITE( + "MAIN: WorkerObjectPostMessageCallback called postMessage on Worker object(id=%d)", + id); + } catch (NativeScriptException &ex) { + ex.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } + return js_util::undefined(); +} + +JsValue +CallbackHandlers::WorkerGlobalPostMessageCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + if (argc != 1) { + throw JsError(rt, + "Failed to execute 'postMessage' on WorkerGlobalScope: 1 argument required."); + } + + // The napi tree drained a pending exception here before stringifying. + // engine:: has no pending-exception state -- a throw is already unwinding + // as a JSError -- so there is nothing to drain. + + std::string msg = tns::JsonStringifyObject(rt, args[0], false); + + auto wrapper = WorkerWrapper::FromRuntime(rt); + if (wrapper != nullptr) { + wrapper->PostMessageToParent(std::make_shared( + worker::Message::MakeData(std::move(msg)))); + } + + DEBUG_WRITE("WORKER: WorkerGlobalPostMessageCallback called."); + } catch (NativeScriptException &ex) { + ex.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } + + return js_util::undefined(); +} + +JsValue CallbackHandlers::WorkerObjectTerminateCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + DEBUG_WRITE("WORKER: WorkerObjectTerminateCallback called."); + + try { + auto thiz = thisVal.asObjectBorrowed(rt); + + auto jsId = thiz.getPropertyBorrowed(rt, "workerId"); + int32_t id = js_util::get_int32(jsId); + + auto isTerminated = thiz.getPropertyBorrowed(rt, "isTerminated"); + if (isTerminated.isBool() && isTerminated.getBool()) { + return js_util::undefined(); + } + + thiz.setProperty(rt, "isTerminated", true); + + auto wrapper = WorkerWrapper::GetById(id); + if (wrapper != nullptr) { + wrapper->Terminate(); + } + } catch (NativeScriptException &ex) { + ex.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } + + return js_util::undefined(); +} + +JsValue CallbackHandlers::WorkerGlobalCloseCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + DEBUG_WRITE("WORKER: WorkerThreadCloseCallback called."); + + try { + auto global = rt.global(); + + auto isTerminated = global.getPropertyBorrowed(rt, "isTerminating"); + if (isTerminated.isBool() && isTerminated.getBool()) { + return js_util::undefined(); + } + + global.setProperty(rt, "isTerminating", true); + + auto callback = global.getProperty(rt, "onclose"); + if (callback.isObject() && callback.asObjectBorrowed(rt).isFunction(rt)) { + try { + callback.asObjectBorrowed(rt).asFunction(rt).callWithThis(rt, global); + } catch (JsError &error) { + CallWorkerScopeOnErrorHandle(rt, error); + } + } + + auto wrapper = WorkerWrapper::FromRuntime(rt); + if (wrapper != nullptr) { + wrapper->Close(); + } + } catch (NativeScriptException &ex) { + ex.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } + + return js_util::undefined(); +} + +namespace { + // Pulls (message, stack, frames) out of a thrown value the way the napi tree + // did: an object carries them as properties, anything else is coerced. + struct ErrorDetails { + std::string message; + std::string stack; + std::vector frames; + }; + + ErrorDetails DescribeThrown(JsRuntime &rt, const JsValue &error) { + ErrorDetails details; + if (error.isObject()) { + details.frames = tns::BuildStacktraceFrames(rt, &error, 1); + auto object = error.asObjectBorrowed(rt); + auto message = object.getProperty(rt, "message"); + auto stack = object.getProperty(rt, "stack"); + details.message = message.isString() ? message.asString(rt).utf8(rt) : std::string(); + details.stack = stack.isString() ? stack.asString(rt).utf8(rt) : std::string(); + } else { + details.message = js_util::coerce_to_string(rt, error); + } + return details; + } +} + +void CallbackHandlers::CallWorkerScopeOnErrorHandle(JsRuntime &rt, const JsError &error) { + try { + auto global = rt.global(); + + auto callback = global.getProperty(rt, "onerror"); + + // A message-only JSError has no JS payload to hand to onerror, so one is + // built from its text; the napi tree always had a napi_value here. + JsValue errorValue = error.value() != nullptr + ? JsValue(rt, *error.value()) + : JsValue(rt, GlobalHelpers::CreateError(rt, error.what())); + + ErrorDetails details = DescribeThrown(rt, errorValue); + + if (callback.isObject() && callback.asObjectBorrowed(rt).isFunction(rt)) { + JsValue result; + bool threw = false; + ErrorDetails pending; + + const JsValue args[] = {errorValue}; + try { + result = callback.asObjectBorrowed(rt).asFunction(rt) + .callWithThis(rt, global, args, (size_t) 1); + } catch (JsError &perror) { + threw = true; + JsValue pendingValue = perror.value() != nullptr + ? JsValue(rt, *perror.value()) + : JsValue(rt, GlobalHelpers::CreateError(rt, perror.what())); + pending = DescribeThrown(rt, pendingValue); + } + + if (threw) { + auto line = 0; + std::string filename; + if (!pending.frames.empty()) { + line = pending.frames[0].line; + filename = pending.frames[0].filename; + } + auto wrapper = WorkerWrapper::FromRuntime(rt); + if (wrapper != nullptr) { + wrapper->PassUncaughtExceptionFromWorkerToParent( + pending.message, filename, pending.stack, line); + } + } else if (!js_util::is_null_or_undefined(result)) { + if (js_util::get_bool(result)) { + return; + } + } + } + + auto line = 0; + std::string filename; + if (!details.frames.empty()) { + line = details.frames[0].line; + filename = details.frames[0].filename; + } + auto wrapper = WorkerWrapper::FromRuntime(rt); + if (wrapper != nullptr) { + wrapper->PassUncaughtExceptionFromWorkerToParent( + details.message, filename, details.stack, line); + } + + + } catch (NativeScriptException &ex) { + ex.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } +} +#endif + + +robin_hood::unordered_map CallbackHandlers::cache_; +robin_hood::unordered_map CallbackHandlers::jclass_to_runtimeId_cache; + +robin_hood::unordered_map CallbackHandlers::frameCallbackCache_; + +std::atomic_int64_t CallbackHandlers::count_ = {0}; +std::atomic_uint64_t CallbackHandlers::frameCallbackCount_ = {0}; + +short CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH = 100; +jclass CallbackHandlers::RUNTIME_CLASS = nullptr; +jclass CallbackHandlers::JAVA_LANG_STRING = nullptr; +jfieldID CallbackHandlers::CURRENT_OBJECTID_FIELD_ID = nullptr; +jmethodID CallbackHandlers::RESOLVE_CLASS_METHOD_ID = nullptr; +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; + +NumericCasts CallbackHandlers::castFunctions; + +ArrayElementAccessor CallbackHandlers::arrayElementAccessor; + +FieldAccessor CallbackHandlers::fieldAccessor; diff --git a/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h new file mode 100644 index 000000000..628dd1d28 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/callbackhandlers/CallbackHandlers.h @@ -0,0 +1,383 @@ +#ifndef CALLBACKHANDLERS_H_ +#define CALLBACKHANDLERS_H_ + +#include +#include +#include +#include "JEnv.h" +#include "ArgsWrapper.h" +#include "MetadataEntry.h" +#include "FieldCallbackData.h" +#include "MetadataTreeNode.h" +#include "NumericCasts.h" +#include "FieldAccessor.h" +#include "ArrayElementAccessor.h" +#include "ObjectManager.h" +#include "robin_hood.h" +#include +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +namespace tns { + class CallbackHandlers { + public: + static void Init(JsRuntime &rt); + + static JsValue + CreateJSWrapper(JsRuntime &rt, jint javaObjectID, const std::string &typeName); + + static bool RegisterInstance(JsRuntime &rt, const JsValue &jsObject, + const std::string &fullClassName, + const ArgsWrapper &argWrapper, + const JsValue &implementationObject, + bool isInterface, + JsValue *jsThisProxy, + const std::string &baseClassName = std::string(), + MetadataNode *node = nullptr); + + static jclass ResolveClass(JsRuntime &rt, const std::string &baseClassName, + const std::string &fullClassName, + const JsValue &implementationObject, + bool isInterface); + + static std::string ResolveClassName(JsRuntime &rt, jclass &clazz); + + static JsValue + GetArrayElement(JsRuntime &rt, const JsValue &array, uint32_t index, + const std::string &arraySignature, + ObjectManager *objectManager = nullptr, jobject arrayObject = nullptr); + + static void + SetArrayElement(JsRuntime &rt, const JsValue &array, uint32_t index, + const std::string &arraySignature, const JsValue &value, + ObjectManager *objectManager = nullptr, jobject arrayObject = nullptr); + + static int GetArrayLength(JsRuntime &rt, const JsValue &arr); + + // `isConstructorCall` replaces the napi tree's napi_get_new_target probe, + // which needed the napi_callback_info. engine:: host functions do not + // carry a new.target, so the one caller that cared (MethodCallback, for + // an error message) passes what it already knows. + static JsValue + CallJavaMethod(JsRuntime &rt, const JsValue &caller, const std::string &className, + const std::string &methodName, MetadataEntry *entry, bool isFromInterface, + bool isStatic, bool isConstructorCall, const JsValue *argv, size_t argc, + ObjectManager *objectManager = nullptr, + bool metadataSignatureIsUnambiguous = false); + + static JsValue + CallJSMethod(JsRuntime &rt, JNIEnv *jEnv, const JsValue &jsObject, jclass claz, + const std::string &methodName, int javaObjectId, jobjectArray args); + + static JsValue + GetJavaField(JsRuntime &rt, const JsValue &caller, + FieldCallbackData *fieldData, ObjectManager *objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); + + static void SetJavaField(JsRuntime &rt, const JsValue &target, + const JsValue &value, FieldCallbackData *fieldData, + ObjectManager *objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); + + static JsValue RunOnMainThreadCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static int RunOnMainThreadFdCallback(int fd, int events, void *data); + + static JsValue LogMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue TimeCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue DumpReferenceTablesMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue DrainMicrotaskCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static void DumpReferenceTablesMethod(); + + static JsValue ExitMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static void CreateGlobalCastFunctions(JsRuntime &rt); + + static std::vector GetTypeMetadata(const std::string &name, int index); + + /* + * Gets all methods in the implementation object, and packs them in a jobjectArray + * to pass them to Java Land, so that their corresponding Java callbacks are written when + * the dexFactory generates the class + */ + static jobjectArray + GetMethodOverrides(JsRuntime &rt, JEnv &jEnv, const JsValue &implementationObject); + + /* + * Gets all interfaces declared in the 'interfaces' array inside the implementation object, + * and packs them in a jobjectArray to pass them to Java Land, so that they may be + * implemented when the dexFactory generates the corresponding class + */ + static jobjectArray + GetImplementedInterfaces(JsRuntime &rt, JEnv &jEnv, const JsValue &implementationObject); + + /* + * The name sets the two Get* helpers above are built from. Exposed so that + * content-keyed class names can hash the same sets the generated binding is + * built out of, without a second copy of the filtering rules. + */ + // functionsOnly selects what the names are for: true builds the array the + // dex generator overrides against, false the set a content-keyed class + // name hashes. The two differ deliberately -- see the implementation. + static std::vector + CollectMethodOverrideNames(JsRuntime &rt, const JsValue &implementationObject, + bool functionsOnly = true); + + static std::vector + CollectImplementedInterfaceNames(JsRuntime &rt, const JsValue &implementationObject); + + static JsValue EnableVerboseLoggingMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue DisableVerboseLoggingMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ReleaseNativeCounterpartCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue FindClass(JsRuntime &rt, const char *name); + +// Worker. Not built for a guest -- see the definitions in CallbackHandlers.cpp. +#if !defined(NS_JSI_HOST_RUNTIME) + static JsValue NewThreadCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + /* + * main -> worker messaging + * Fired when a Worker instance's postMessage is called + */ + static JsValue WorkerObjectPostMessageCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + /* + * worker -> main thread messaging + * Fired when a Worker script's "postMessage" is called + */ + static JsValue WorkerGlobalPostMessageCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + /* + * Fired when a Worker instance's terminate is called (cooperatively + * stops the worker thread's looper) + */ + static JsValue WorkerObjectTerminateCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + /* + * Fired when a Worker script's close is called + */ + static JsValue WorkerGlobalCloseCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + /* + * Is called when an unhandled exception is thrown inside the worker + * Will execute 'onerror' if one is provided inside the Worker Scope + * Will make the exception "bubble up" through to the parent, to be handled by the Worker Object + * if 'onerror' isn't implemented or returns false + * + * Takes the caught JSError rather than the napi tree's napi_value: an + * engine:: throw arrives as a JSError, and its payload (which is what the + * napi version received) is JSError::value(). + */ + static void CallWorkerScopeOnErrorHandle(JsRuntime &rt, const JsError &error); +#endif + + static JsValue PostFrameCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue RemoveFrameCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static void RemoveEnvEntries(JsRuntime &rt); + + struct AChoreographer; + + typedef void (*AChoreographer_frameCallback)(long frameTimeNanos, void *data); + + typedef void (*AChoreographer_frameCallback64)(int64_t frameTimeNanos, void *data); + + typedef AChoreographer *(*func_AChoreographer_getInstance)(); + + typedef void (*func_AChoreographer_postFrameCallback)( + AChoreographer *choreographer, AChoreographer_frameCallback callback, + void *data); + + typedef void (*func_AChoreographer_postFrameCallback64)( + AChoreographer *choreographer, AChoreographer_frameCallback64 callback, + void *data); + + typedef void (*func_AChoreographer_postFrameCallbackDelayed)( + AChoreographer *choreographer, AChoreographer_frameCallback callback, + void *data, long delayMillis); + + typedef void (*func_AChoreographer_postFrameCallbackDelayed64)( + AChoreographer *choreographer, AChoreographer_frameCallback64 callback, + void *data, uint32_t delayMillis); + + // The napi tree also declared `lastCallId` / `lastCallValue` here. Both + // were dead -- defined once, never read -- and lastCallValue as an owned + // engine handle in a process-wide static would outlive its runtime, so + // they are not carried over. + + private: + CallbackHandlers() { + } + + static void AdjustAmountOfExternalAllocatedMemory(JsRuntime &rt); + + /* + * Helper method that creates a java string array for sending strings over JNI + */ + static jobjectArray GetJavaStringArray(JEnv &jEnv, int length); + + static short MAX_JAVA_STRING_ARRAY_LENGTH; + + static jclass RUNTIME_CLASS; + + static jclass JAVA_LANG_STRING; + + static jmethodID RESOLVE_CLASS_METHOD_ID; + + static jfieldID CURRENT_OBJECTID_FIELD_ID; + + static jmethodID MAKE_INSTANCE_STRONG_ID; + + static jmethodID GET_TYPE_METADATA; + + static jmethodID ENABLE_VERBOSE_LOGGING_METHOD_ID; + + static jmethodID DISABLE_VERBOSE_LOGGING_METHOD_ID; + + static NumericCasts castFunctions; + + static ArrayElementAccessor arrayElementAccessor; + + static FieldAccessor fieldAccessor; + + struct JavaObjectIdScope { + JavaObjectIdScope(JEnv &_jEnv, jfieldID fieldId, jobject runtime, int javaObjectId) + : jEnv(_jEnv), _fieldID(fieldId), _runtime(runtime) { + jEnv.SetIntField(_runtime, _fieldID, javaObjectId); + } + + ~JavaObjectIdScope() { + jEnv.SetIntField(_runtime, _fieldID, -1); + } + + private: + JEnv jEnv; + jfieldID _fieldID; + jobject _runtime; + }; + + static std::atomic_int64_t count_; + + struct Callback { + Callback() {} + + Callback(uint64_t id) + : id_(id) { + } + + uint64_t id_; + }; + + // An owned engine::Function replaces the napi_ref: it survives handle + // scopes and is released when the entry is erased, so there is no + // destructor left to write. + // + // The owning tns::Runtime is stored rather than the engine::Runtime the + // callback was registered from: that one is a stack temporary built for + // the host call (see engine::Runtime::identity()), so its address would + // dangle by the time this entry runs. + struct CacheEntry { + CacheEntry(tns::Runtime *runtime, JsRuntime &rt, const JsValue &callback) + : runtime_(runtime), callback_(rt, callback) { + } + + tns::Runtime *runtime_; + JsValue callback_; + }; + + static robin_hood::unordered_map cache_; + + static robin_hood::unordered_map jclass_to_runtimeId_cache; + + static std::atomic_uint64_t frameCallbackCount_; + + struct FrameCallbackCacheEntry { + // See CacheEntry: the owning tns::Runtime, not the call-scoped + // engine::Runtime wrapper. + FrameCallbackCacheEntry(tns::Runtime *runtime, JsRuntime &_rt, + const JsValue &callback_, uint64_t aId) + : runtime(runtime), callback(_rt, callback_), id(aId) { + } + + tns::Runtime *runtime; + JsValue callback; + uint64_t id; + + bool isScheduled() { + return scheduled; + } + + void markScheduled() { + scheduled = true; + removed = false; + } + + void markRemoved() { + // we can never unschedule a callback, so we just mark it as removed + removed = true; + } + + AChoreographer_frameCallback frameCallback_ = [](long ts, void *data) { + execute((double) ts, data); + }; + + AChoreographer_frameCallback64 frameCallback64_ = [](int64_t ts, void *data) { + execute((double) ts, data); + }; + + static void execute(double ts, void *data); + + private: + bool removed = false; + bool scheduled = false; + + void markUnscheduled() { + scheduled = false; + removed = true; + } + + bool shouldRemoveBeforeCall() { + return removed; + } + + bool shouldRemoveAfterCall() { + return !scheduled && removed; + } + }; + + static robin_hood::unordered_map frameCallbackCache_; + + static void InitChoreographer(); + + static void PostCallback(JsRuntime &rt, const JsValue *args, size_t argc, + FrameCallbackCacheEntry *entry); + + }; +} + +#endif /* CALLBACKHANDLERS_H_ */ diff --git a/NativeScript/ffi/jni/jsi/constants/Constants.cpp b/NativeScript/ffi/jni/jsi/constants/Constants.cpp new file mode 100644 index 000000000..b957a7eea --- /dev/null +++ b/NativeScript/ffi/jni/jsi/constants/Constants.cpp @@ -0,0 +1,12 @@ +/* + * Constants.cpp + * + * Created on: Nov 6, 2015 + * Author: gatanasov + */ + +#include "Constants.h" + +std::string Constants::APP_ROOT_FOLDER_PATH = ""; +bool Constants::CACHE_COMPILED_CODE = false; + diff --git a/NativeScript/ffi/jni/jsi/constants/Constants.h b/NativeScript/ffi/jni/jsi/constants/Constants.h new file mode 100644 index 000000000..8a0772f0d --- /dev/null +++ b/NativeScript/ffi/jni/jsi/constants/Constants.h @@ -0,0 +1,42 @@ +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#include + +#define PROP_KEY_EXTEND "extend" +#define PROP_KEY_NULLOBJECT "null" +#define PROP_KEY_NULL_NODE_NAME "nullNode" +#define PROP_KEY_VALUEOF "valueOf" +#define PROP_KEY_CLASS "class" +#define PRIVATE_TYPE_NAME "#typename" +#define CLASS_IMPLEMENTATION_OBJECT "t::ClassImplementationObject" +// Identifies an implementation object across repeated extend() calls, so the +// extended constructor can be cached per implementation rather than per +// generated class name -- content-keyed names are shared by design. +#define EXTEND_CTOR_CACHE_KEY "t::ExtendCtorCacheKey" +#define PROP_KEY_SUPER "super" +#define PROP_KEY_SUPERVALUE "supervalue" +#define PRIVATE_JSINFO "#js_info" +#define PRIVATE_CALLSUPER "#supercall" +#define PRIVATE_IS_NAPI "#is_napi" +#define PROP_KEY_TOSTRING "toString" +#define PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT "__isPrototypeImplementationObject" +// Stamped onto the implementation object by ts_helpers' __extends, not written +// by the user. They exist only after that helper has run, so the static binding +// generator cannot see them -- see CollectMethodOverrideNames. +#define PROP_KEY_TS_PARENT "__parent" +#define PROP_KEY_TS_CHILD "__child" + +class Constants { + public: + const static char CLASS_NAME_LOCATION_SEPARATOR = '_'; + + static std::string APP_ROOT_FOLDER_PATH; + static bool CACHE_COMPILED_CODE; + + private: + Constants() { + } +}; + +#endif /* CONSTANTS_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp new file mode 100644 index 000000000..9f1d895a2 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.cpp @@ -0,0 +1,304 @@ +#include "ArgConverter.h" +#include "ObjectManager.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "NumericCasts.h" +#include "NativeScriptAssert.h" +#include +#ifdef USE_MIMALLOC +#include "mimalloc.h" +#endif + + +using namespace std; +using namespace tns; + +namespace { +// The napi version reads new.target to recover the prototype when a constructor +// callback is handed a null `this`. engine:: has no new.target, so the +// constructor's own prototype is captured when the function is created and +// passed in here instead; for a `new Ctor()` call the two are the same object. +JsValue EnsurePlainConstructorThis(JsRuntime &rt, const JsValue &jsThis, + const JsValue &prototype) { + if (!js_util::is_null_or_undefined(jsThis)) { + return jsThis; + } + + JsObject receiver(rt); + + if (!js_util::is_null_or_undefined(prototype)) { + js_util::setPrototypeOf(rt, JsValue(rt, receiver), prototype); + } + + return JsValue(rt, receiver); +} +} + +void ArgConverter::Init(JsRuntime &rt) { + auto cache = GetTypeLongCache(rt); + + JsFunction longNumberCtorFunc = JsFunction::createFromHostConstructor( + rt, JsPropNameID::forAscii(rt, "NativeScriptLongNumber"), 0, + [](JsRuntime &rt, const JsValue &jsThis, const JsValue *argv, size_t argc) { + return ArgConverter::NativeScriptLongFunctionCallback(rt, jsThis, argv, argc); + }); + + JsValue longNumberPrototypeValue = js_util::get_prototype(rt, JsValue(rt, longNumberCtorFunc)); + if (!longNumberPrototypeValue.isObject()) { + return; + } + JsObject longNumberPrototype = longNumberPrototypeValue.asObject(rt); + + js_util::set_function(rt, longNumberPrototype, "valueOf", + ArgConverter::NativeScriptLongValueOfFunctionCallback); + js_util::set_function(rt, longNumberPrototype, "toString", + ArgConverter::NativeScriptLongToStringFunctionCallback); + + cache->LongNumberCtorFunc = longNumberCtorFunc; + + JsValue nanValue(numeric_limits::quiet_NaN()); + + JsObject global = rt.global(); + JsFunction numCtor = global.getPropertyAsFunction(rt, "Number"); + + const JsValue args[] = {nanValue}; + JsValue nanObject = numCtor.callAsConstructor(rt, args, static_cast(1)); + if (!nanObject.isObject()) { + return; + } + + cache->NanNumberObject = nanObject.asObject(rt); +} + +JsValue ArgConverter::NativeScriptLongValueOfFunctionCallback(JsRuntime &rt, + const JsValue &jsThis, + const JsValue *argv, size_t argc) { + try { + return JsValue(numeric_limits::quiet_NaN()); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } +} + +JsValue ArgConverter::NativeScriptLongToStringFunctionCallback(JsRuntime &rt, + const JsValue &jsThis, + const JsValue *argv, size_t argc) { + try { + return js_util::get_property(rt, jsThis, "value"); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } +} + +JsValue ArgConverter::NativeScriptLongFunctionCallback(JsRuntime &rt, const JsValue &jsThis, + const JsValue *argv, size_t argc) { + try { + auto cache = GetTypeLongCache(rt); + JsValue receiverValue = EnsurePlainConstructorThis( + rt, jsThis, js_util::get_prototype(rt, JsValue(rt, cache->LongNumberCtorFunc))); + if (!receiverValue.isObject()) { + return js_util::undefined(); + } + JsObject receiver = receiverValue.asObject(rt); + + receiver.setProperty(rt, "javaLong", true); + + NumericCasts::MarkAsLong(rt, receiver, argc > 0 ? argv[0] : js_util::undefined()); + + receiver.setProperty(rt, "prototype", JsValue(rt, cache->NanNumberObject)); + return JsValue(rt, receiver); + + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } +} + +void ArgConverter::ConvertJavaArgsToJsArgs(JsRuntime &rt, jobjectArray args, size_t argc, + JsValue *arr) { + JEnv jenv; + + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + + int jArrayIndex = 0; + for (int i = 0; i < argc; i++) { + JniLocalRef argTypeIDObj(jenv.GetObjectArrayElement(args, jArrayIndex++)); + JniLocalRef arg(jenv.GetObjectArrayElement(args, jArrayIndex++)); + JniLocalRef argJavaClassPath(jenv.GetObjectArrayElement(args, jArrayIndex++)); + + Type argTypeID = (Type) JType::IntValue(jenv, argTypeIDObj); + + JsValue jsArg; + switch (argTypeID) { + case Type::Boolean: + jsArg = JsValue(JType::BooleanValue(jenv, arg) == JNI_TRUE); + break; + case Type::Char: + jsArg = jcharToJsString(rt, JType::CharValue(jenv, arg)); + break; + case Type::Byte: + jsArg = JsValue((double) JType::ByteValue(jenv, arg)); + break; + case Type::Short: + jsArg = JsValue((double) JType::ShortValue(jenv, arg)); + break; + case Type::Int: + jsArg = JsValue((double) JType::IntValue(jenv, arg)); + break; + case Type::Long: + jsArg = JsValue((double) JType::LongValue(jenv, arg)); + break; + case Type::Float: + jsArg = JsValue((double) JType::FloatValue(jenv, arg)); + break; + case Type::Double: + jsArg = JsValue((double) JType::DoubleValue(jenv, arg)); + break; + case Type::String: + jsArg = jstringToJsString(rt, (jstring) arg); + break; + case Type::JsObject: { + jint javaObjectID = JType::IntValue(jenv, arg); + jsArg = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (js_util::is_null_or_undefined(jsArg)) { + string argClassName = jstringToString(ObjectToString(argJavaClassPath)); + argClassName = Util::ConvertFromCanonicalToJniName(argClassName); + jsArg = objectManager->CreateJSWrapper(javaObjectID, argClassName); + } + break; + } + case Type::Null: + jsArg = js_util::null(); + break; + } + + arr[i] = jsArg; + } + +} + +JsValue ArgConverter::ConvertFromJavaLong(JsRuntime &rt, jlong value) { + long long longValue = value; + + if ((-JS_LONG_LIMIT < longValue) && (longValue < JS_LONG_LIMIT)) { + return JsValue((double) longValue); + } + + auto cache = GetTypeLongCache(rt); + char strNumber[24]; + sprintf(strNumber, "%lld", longValue); + const JsValue args[] = {convertToJsString(rt, std::string(strNumber))}; + + return cache->LongNumberCtorFunc.callAsConstructor(rt, args, static_cast(1)); +} + +int64_t ArgConverter::ConvertToJavaLong(JsRuntime &rt, const JsValue &value) { + JsValue valueProp = js_util::get_property(rt, value, "value"); + if (!valueProp.isString()) { + return 0; + } + + string num = js_util::get_string_value(rt, valueProp); + + int64_t longValue = atoll(num.c_str()); + + return longValue; +} + +ArgConverter::TypeLongOperationsCache *ArgConverter::GetTypeLongCache(JsRuntime &rt) { + const void *key = rt.identity(); + auto cache = s_type_long_operations_cache.Get(key); + if (cache) return cache; + cache = new TypeLongOperationsCache; + s_type_long_operations_cache.Insert(key, cache); + return cache; +} + +JsValue ArgConverter::convertToJsString(JsRuntime &rt, const jchar *data, int length) { + if (data == nullptr || length <= 0) { + return convertToJsString(rt, std::string()); + } + + // Strict UTF-16 -> UTF-8, matching what napi_create_string_utf16 did inside + // the engine. Unpaired surrogates are emitted as U+FFFD rather than dropped, + // so a lone jchar (Type::Char, which is exactly one code unit) still yields a + // one-character JS string. + std::string utf8; + utf8.reserve((size_t) length); + for (int i = 0; i < length; i++) { + uint32_t cp = data[i]; + if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < length) { + uint32_t low = data[i + 1]; + if (low >= 0xDC00 && low <= 0xDFFF) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + i++; + } else { + cp = 0xFFFD; + } + } else if (cp >= 0xD800 && cp <= 0xDFFF) { + cp = 0xFFFD; + } + + if (cp < 0x80) { + utf8.push_back((char) cp); + } else if (cp < 0x800) { + utf8.push_back((char) (0xC0 | (cp >> 6))); + utf8.push_back((char) (0x80 | (cp & 0x3F))); + } else if (cp < 0x10000) { + utf8.push_back((char) (0xE0 | (cp >> 12))); + utf8.push_back((char) (0x80 | ((cp >> 6) & 0x3F))); + utf8.push_back((char) (0x80 | (cp & 0x3F))); + } else { + utf8.push_back((char) (0xF0 | (cp >> 18))); + utf8.push_back((char) (0x80 | ((cp >> 12) & 0x3F))); + utf8.push_back((char) (0x80 | ((cp >> 6) & 0x3F))); + utf8.push_back((char) (0x80 | (cp & 0x3F))); + } + } + + return convertToJsString(rt, utf8); +} + +u16string ArgConverter::ConvertToUtf16String(JsRuntime &rt, const JsValue &s) { + if (!s.isString()) { + return {}; + } else { + auto utf16str = Util::ConvertFromUtf8ToUtf16(js_util::get_string_value(rt, s)); + + return utf16str; + } +} + +void ArgConverter::onDisposeRuntime(JsRuntime &rt) { + const void *key = rt.identity(); + auto cache = s_type_long_operations_cache.Get(key); + s_type_long_operations_cache.Remove(key); + delete cache; +} + +tns::ConcurrentMap ArgConverter::s_type_long_operations_cache; diff --git a/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h new file mode 100644 index 000000000..2c1ddb390 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArgConverter.h @@ -0,0 +1,138 @@ +/* + * ArgConverter.h + * + * Created on: Jan 29, 2014 + * Author: slavchev + */ + +#ifndef ARGCONVERTER_H_ +#define ARGCONVERTER_H_ + +#include "Runtime.h" +#include "NativeScriptAssert.h" +#include "JEnv.h" +#include +#include + +namespace tns { + + class ArgConverter { + public: + static void Init(JsRuntime &rt); + + static void ConvertJavaArgsToJsArgs(JsRuntime &rt, jobjectArray args, size_t length, + JsValue *arr); + + static JsValue ConvertFromJavaLong(JsRuntime &rt, jlong value); + + static int64_t ConvertToJavaLong(JsRuntime &rt, const JsValue &value); + + static JsValue jstringToJsString(JsRuntime &rt, jstring value) { + if (value == nullptr) return js_util::null(); + + JEnv jenv; + auto chars = jenv.GetStringUTFChars(value, JNI_FALSE); + auto length = jenv.GetStringUTFLength(value); + auto jsString = convertToJsString(rt, chars, length); + jenv.ReleaseStringUTFChars(value, chars); + + return jsString; + } + + static std::string jstringToString(jstring value) { + if (value == nullptr) { + return {}; + } + + JEnv jenv; + + jboolean f = JNI_FALSE; + auto chars = jenv.GetStringUTFChars(value, &f); + std::string s(chars); + jenv.ReleaseStringUTFChars(value, chars); + + return s; + } + + inline static std::string ConvertToString(JsRuntime &rt, const JsValue &s) { + if (!s.isString()) { + return {}; + } else { + return js_util::get_string_value(rt, s); + } + } + + static std::u16string ConvertToUtf16String(JsRuntime &rt, const JsValue &s); + + inline static jstring ConvertToJavaString(JsRuntime &rt, const JsValue &jsValue) { + JEnv jenv; + return jenv.NewStringUTF(js_util::get_string_value(rt, jsValue).c_str()); + } + + // engine::String is UTF-8 only, so a UTF-16 payload (every jchar/jstring + // that reaches here) is transcoded on the way in rather than handed to a + // napi_create_string_utf16 equivalent. + static JsValue convertToJsString(JsRuntime &rt, const jchar *data, int length); + + inline static JsValue convertToJsString(JsRuntime &rt, const std::string &s) { + return JsValue::createStringFromUtf8(rt, s.data(), s.size()); + } + + // Every Java string that reaches JS goes through here, and the value is + // handed straight back to the engine by the callback that produced it. + // Value::createStringFromUtf8 is the non-owning creation: on V8 it costs + // neither the make_shared nor the global handle that + // String::createFromUtf8 does. See jsi/v8/V8Runtime.h for the contract. + inline static JsValue convertToJsString(JsRuntime &rt, const char *data, int length) { + return JsValue::createStringFromUtf8(rt, data, (size_t) length); + } + + inline static JsValue + ConvertToJsUTF16String(JsRuntime &rt, const std::u16string &utf16string) { + return convertToJsString(rt, reinterpret_cast(utf16string.data()), + (int) utf16string.length()); + } + + static void onDisposeRuntime(JsRuntime &rt); + + private: + + // TODO: plamen5kov: rewrite logic for java long number operations in javascript (java long -> javascript number operations check) + static const long long JS_LONG_LIMIT = ((long long) 1) << 53; + + struct TypeLongOperationsCache { + JsFunction LongNumberCtorFunc; + JsObject NanNumberObject; + }; + + static TypeLongOperationsCache *GetTypeLongCache(JsRuntime &rt); + + inline static jstring ObjectToString(jobject object) { + return (jstring) object; + } + + inline static JsValue jcharToJsString(JsRuntime &rt, jchar value) { + return convertToJsString(rt, &value, 1); + } + + static JsValue NativeScriptLongFunctionCallback(JsRuntime &rt, const JsValue &jsThis, + const JsValue *argv, size_t argc); + + static JsValue NativeScriptLongValueOfFunctionCallback(JsRuntime &rt, + const JsValue &jsThis, + const JsValue *argv, size_t argc); + + static JsValue NativeScriptLongToStringFunctionCallback(JsRuntime &rt, + const JsValue &jsThis, + const JsValue *argv, size_t argc); + + /* + * "s_type_long_operations_cache" used to keep function + * dealing with operations concerning java long -> javascript number. + */ + // Keyed by JsRuntime::identity(); &rt is not stable across callbacks. + static tns::ConcurrentMap s_type_long_operations_cache; + }; +} + +#endif /* ARGCONVERTER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/ArgsWrapper.h b/NativeScript/ffi/jni/jsi/conversion/ArgsWrapper.h new file mode 100644 index 000000000..7287b6501 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArgsWrapper.h @@ -0,0 +1,30 @@ +/* + * ArgsWrapper.h + * + * Created on: Dec 20, 2013 + * Author: slavchev + */ + +#ifndef ARGSWRAPPER_H_ +#define ARGSWRAPPER_H_ +#include "Engine.h" + +namespace tns { +enum class ArgType { + Class, + Interface +}; + +struct ArgsWrapper { + public: + ArgsWrapper(const JsValue* argv_, size_t argc_, ArgType t) + : + argv(argv_), argc(argc_), type(t) { + } + const JsValue* argv; + size_t argc; + ArgType type; +}; +} + +#endif /* ARGSWRAPPER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/ArrayBufferHelper.cpp b/NativeScript/ffi/jni/jsi/conversion/ArrayBufferHelper.cpp new file mode 100644 index 000000000..7067854d2 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArrayBufferHelper.cpp @@ -0,0 +1,152 @@ +#include "ArrayBufferHelper.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include +#include + +using namespace tns; + +namespace { + // engine::ArrayBuffer takes ownership of a MutableBuffer instead of the napi + // tree's (data, length, finalize) triple, so the two backing stores below are + // what the two napi_create_external_arraybuffer calls become: one that only + // points at the direct buffer Java already owns, and one that owns its copy. + class BorrowedBuffer : public engine::MutableBuffer { + public: + BorrowedBuffer(uint8_t *data, size_t size) : m_data(data), m_size(size) {} + + size_t size() const override { return m_size; } + + uint8_t *data() override { return m_data; } + + private: + uint8_t *m_data; + size_t m_size; + }; + + class OwnedBuffer : public engine::MutableBuffer { + public: + explicit OwnedBuffer(size_t size) : m_data(new uint8_t[size]), m_size(size) {} + + ~OwnedBuffer() override { delete[] m_data; } + + size_t size() const override { return m_size; } + + uint8_t *data() override { return m_data; } + + private: + uint8_t *m_data; + size_t m_size; + }; +} + +ArrayBufferHelper::ArrayBufferHelper() + : m_objectManager(nullptr), m_ByteBufferClass(nullptr), m_isDirectMethodID(nullptr), + m_remainingMethodID(nullptr), m_getMethodID(nullptr) { +} + +void ArrayBufferHelper::CreateConvertFunctions(JsRuntime &rt, JsObject &global, + ObjectManager *objectManager) { + m_objectManager = objectManager; + + auto arrBufferCtor = global.getProperty(rt, "ArrayBuffer"); + if (!arrBufferCtor.isObject()) { + return; + } + + auto ctorObject = arrBufferCtor.asObject(rt); + js_util::set_function(rt, ctorObject, "from", + [this](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + try { + return CreateFromCallbackImpl(rt, args, argc); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException(std::string("Error: c++ exception!")) + .ReThrowToJs(rt); + } + }); +} + +JsValue ArrayBufferHelper::CreateFromCallbackImpl(JsRuntime &rt, const JsValue *args, size_t argc) { + if (argc != 1) { + throw NativeScriptException("Wrong number of arguments (1 expected)"); + } + + const JsValue &argObj = args[0]; + + if (!argObj.isObject()) { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + auto obj = m_objectManager->GetJavaObjectByJsObject(argObj); + + if (obj.IsNull()) { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + JEnv jEnv; + + if (m_ByteBufferClass == nullptr) { + m_ByteBufferClass = jEnv.FindClass("java/nio/ByteBuffer"); + assert(m_ByteBufferClass != nullptr); + } + + auto isByteBuffer = jEnv.IsInstanceOf(obj, m_ByteBufferClass); + + if (!isByteBuffer) { + throw NativeScriptException("Wrong type of argument (ByteBuffer expected)"); + } + + if (m_isDirectMethodID == nullptr) { + m_isDirectMethodID = jEnv.GetMethodID(m_ByteBufferClass, "isDirect", "()Z"); + assert(m_isDirectMethodID != nullptr); + } + + auto ret = jEnv.CallBooleanMethod(obj, m_isDirectMethodID); + + auto isDirectBuffer = ret == JNI_TRUE; + + std::shared_ptr buffer; + + if (isDirectBuffer) { + auto data = jEnv.GetDirectBufferAddress(obj); + auto size = jEnv.GetDirectBufferCapacity(obj); + + buffer = std::make_shared(static_cast(data), (size_t) size); + } else { + if (m_remainingMethodID == nullptr) { + m_remainingMethodID = jEnv.GetMethodID(m_ByteBufferClass, "remaining", "()I"); + assert(m_remainingMethodID != nullptr); + } + + int bufferRemainingSize = jEnv.CallIntMethod(obj, m_remainingMethodID); + + if (m_getMethodID == nullptr) { + m_getMethodID = jEnv.GetMethodID(m_ByteBufferClass, "get", + "([BII)Ljava/nio/ByteBuffer;"); + assert(m_getMethodID != nullptr); + } + + jbyteArray byteArray = jEnv.NewByteArray(bufferRemainingSize); + jEnv.CallObjectMethod(obj, m_getMethodID, byteArray, 0, bufferRemainingSize); + + auto byteArrayElements = jEnv.GetByteArrayElements(byteArray, 0); + + auto owned = std::make_shared((size_t) bufferRemainingSize); + memcpy(owned->data(), byteArrayElements, bufferRemainingSize); + buffer = owned; + + jEnv.ReleaseByteArrayElements(byteArray, byteArrayElements, 0); + } + + engine::ArrayBuffer arrayBuffer(rt, std::move(buffer)); + arrayBuffer.setProperty(rt, "nativeObject", argObj); + + return JsValue(rt, arrayBuffer); +} diff --git a/NativeScript/ffi/jni/jsi/conversion/ArrayBufferHelper.h b/NativeScript/ffi/jni/jsi/conversion/ArrayBufferHelper.h new file mode 100644 index 000000000..d55ba45ec --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArrayBufferHelper.h @@ -0,0 +1,27 @@ +#ifndef ARRAYBUFFERHELPER_H_ +#define ARRAYBUFFERHELPER_H_ + +#include "ObjectManager.h" + +namespace tns { + class ArrayBufferHelper { + public: + ArrayBufferHelper(); + + void CreateConvertFunctions(JsRuntime &rt, JsObject &global, ObjectManager* objectManager); + + private: + + JsValue CreateFromCallbackImpl(JsRuntime &rt, const JsValue* args, size_t argc); + + ObjectManager* m_objectManager; + + jclass m_ByteBufferClass; + jmethodID m_isDirectMethodID; + jmethodID m_remainingMethodID; + jmethodID m_getMethodID; + }; +} + + +#endif /* ARRAYBUFFERHELPER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.cpp b/NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.cpp new file mode 100644 index 000000000..625ba4ca9 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.cpp @@ -0,0 +1,233 @@ +#include "ArrayElementAccessor.h" +#include "JsArgToArrayConverter.h" +#include "ArgConverter.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +using namespace std; +using namespace tns; + +JsValue ArrayElementAccessor::GetArrayElement(JsRuntime &rt, const JsValue &array, uint32_t index, + const string& arraySignature, + ObjectManager* objectManager, jobject arrayObject) { + JEnv jenv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + // The caller may hand us the already-resolved Java array (single probe per + // loop instead of per element); otherwise resolve it here. + JniLocalRef localArr; + jobject arr; + if (arrayObject != nullptr) { + arr = arrayObject; + } else { + localArr = objectManager->GetJavaObjectByJsObject(array); + assertNonNullNativeArray(localArr); + arr = localArr; + } + + const jsize startIndex = index; + const jsize length = 1; + + // Dispatch on the element-type char (no substr allocation, no string-compare + // chain). Primitive element values are created inline. + switch (arraySignature[1]) { + case 'Z': { + jboolean v; + jenv.GetBooleanArrayRegion((jbooleanArray) arr, startIndex, length, &v); + return JsValue((bool) v); + } + case 'B': { + jbyte v; + jenv.GetByteArrayRegion((jbyteArray) arr, startIndex, length, &v); + return JsValue((int) v); + } + case 'C': { + jchar v; + jenv.GetCharArrayRegion((jcharArray) arr, startIndex, length, &v); + // The napi tree round-trips the jchar through a jstring and then takes + // one byte of its UTF-8 form, which truncates anything outside ASCII. + // engine::String is UTF-8 only, so the transcode is explicit here and + // is the same one every other jchar path in this tree uses. + return ArgConverter::convertToJsString(rt, &v, 1); + } + case 'S': { + jshort v; + jenv.GetShortArrayRegion((jshortArray) arr, startIndex, length, &v); + return JsValue((int) v); + } + case 'I': { + jint v; + jenv.GetIntArrayRegion((jintArray) arr, startIndex, length, &v); + return JsValue((int) v); + } + case 'J': { + jlong v; + jenv.GetLongArrayRegion((jlongArray) arr, startIndex, length, &v); + return JsValue((double) v); + } + case 'F': { + jfloat v; + jenv.GetFloatArrayRegion((jfloatArray) arr, startIndex, length, &v); + return JsValue((double) v); + } + case 'D': { + jdouble v; + jenv.GetDoubleArrayRegion((jdoubleArray) arr, startIndex, length, &v); + return JsValue((double) v); + } + default: { // 'L' object or '[' nested array + jobject result = jenv.GetObjectArrayElement((jobjectArray) arr, index); + // Pass the element signature as a string_view into arraySignature (drop + // the leading '[') instead of allocating a fresh substring per element. + JsValue value = ConvertToJsValue(rt, objectManager, jenv, + std::string_view(arraySignature).substr(1), &result); + jenv.DeleteLocalRef(result); + return value; + } + } +} + +void ArrayElementAccessor::SetArrayElement(JsRuntime &rt, const JsValue &array, uint32_t index, + const string& arraySignature, const JsValue &value, + ObjectManager* objectManager, jobject arrayObject) { + JEnv jenv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + JniLocalRef localArr; + jobject arr; + if (arrayObject != nullptr) { + arr = arrayObject; + } else { + localArr = objectManager->GetJavaObjectByJsObject(array); + assertNonNullNativeArray(localArr); + arr = localArr; + } + + // Dispatch on the element-type char (no substr allocation, no string-compare + // chain). + switch (arraySignature[1]) { + case 'Z': { //bool + jboolean v = static_cast(js_util::get_bool(value)); + jenv.SetBooleanArrayRegion((jbooleanArray) arr, index, 1, &v); + break; + } + case 'B': { //byte + jbyte v = static_cast(js_util::get_int32(value)); + jenv.SetByteArrayRegion((jbyteArray) arr, index, 1, &v); + break; + } + case 'C': { //char + string str = js_util::get_string_value(rt, value); + JniLocalRef s(jenv.NewString(reinterpret_cast(str.c_str()), 1)); + jboolean isCopy = false; + const char* singleChar = jenv.GetStringUTFChars(s, &isCopy); + jchar v = *singleChar; + jenv.ReleaseStringUTFChars(s, singleChar); + jenv.SetCharArrayRegion((jcharArray) arr, index, 1, &v); + break; + } + case 'S': { //short + jshort v = static_cast(js_util::get_int32(value)); + jenv.SetShortArrayRegion((jshortArray) arr, index, 1, &v); + break; + } + case 'I': { //int + jint v = static_cast(js_util::get_int32(value)); + jenv.SetIntArrayRegion((jintArray) arr, index, 1, &v); + break; + } + case 'J': { //long + jlong v = static_cast(js_util::get_number(value)); + jenv.SetLongArrayRegion((jlongArray) arr, index, 1, &v); + break; + } + case 'F': { //float + jfloat v = static_cast(js_util::get_number(value)); + jenv.SetFloatArrayRegion((jfloatArray) arr, index, 1, &v); + break; + } + case 'D': { //double + jdouble v = static_cast(js_util::get_number(value)); + jenv.SetDoubleArrayRegion((jdoubleArray) arr, index, 1, &v); + break; + } + default: { //string or object + if (value.isObject() || value.isString()) { + JsArgToArrayConverter argConverter(rt, value, false, (int) Type::Null, objectManager); + if (argConverter.IsValid()) { + jobject objectElementValue = argConverter.GetConvertedArg(); + jenv.SetObjectArrayElement((jobjectArray) arr, index, objectElementValue); + } else { + JsArgToArrayConverter::Error err = argConverter.GetError(); + throw NativeScriptException(string(err.msg)); + } + } else { + throw NativeScriptException(string("Cannot assign primitive value to array of objects.")); + } + break; + } + } +} + +JsValue ArrayElementAccessor::ConvertToJsValue(JsRuntime &rt, ObjectManager* objectManager, + JEnv& jenv, std::string_view elementSignature, + const void* value) { + switch (elementSignature[0]) { + case 'Z': + return JsValue((bool) *(jboolean*) value); + case 'B': + return JsValue((int) *(jbyte*) value); + case 'C': + return js_util::to_js_string(rt, std::string((const char*) value, 1)); + case 'S': + return JsValue((int) *(jshort*) value); + case 'I': + return JsValue((int) *(jint*) value); + case 'J': + return JsValue((double) *(jlong*) value); + case 'F': + return JsValue((double) *(jfloat*) value); + case 'D': + return JsValue((double) *(jdouble*) value); + default: { + if (nullptr != (*(jobject*) value)) { + bool isString = elementSignature == "Ljava/lang/String;"; + + if (isString) { + return ArgConverter::jstringToJsString(rt, *(jstring *) value); + } + + jint javaObjectID = objectManager->GetOrCreateObjectId(*(jobject*) value); + JsValue jsValue = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (js_util::is_null_or_undefined(jsValue)) { + string className; + if (elementSignature[0] == '[') { + className = Util::JniClassPathToCanonicalName(string(elementSignature)); + } else { + className = objectManager->GetClassName(*(jobject*) value); + } + + jsValue = objectManager->CreateJSWrapper(javaObjectID, className); + } + + return jsValue; + } + + return js_util::null(); + } + } +} + +void ArrayElementAccessor::assertNonNullNativeArray(tns::JniLocalRef& arrayReference) { + if(arrayReference.IsNull()){ + throw NativeScriptException("Failed calling indexer operator on native array. The JavaScript instance no longer has available Java instance counterpart."); + } +} diff --git a/NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.h b/NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.h new file mode 100644 index 000000000..c8812d1eb --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArrayElementAccessor.h @@ -0,0 +1,36 @@ +#ifndef ARRAYELEMENTACCESSOR_H_ +#define ARRAYELEMENTACCESSOR_H_ + +#include "JEnv.h" +#include "JniLocalRef.h" +#include "Engine.h" +#include +#include +#include "ObjectManager.h" + + +namespace tns { + class ArrayElementAccessor { + public: + // `objectManager` and `arrayObject` may be supplied pre-resolved by the + // caller (e.g. the host object's indexed get/set trap or an array-loop + // helper) to avoid a locked runtime lookup and re-resolving the Java array + // on every element. Both fall back to resolving internally when omitted. + JsValue GetArrayElement(JsRuntime &rt, const JsValue &array, uint32_t index, + const std::string& arraySignature, + ObjectManager* objectManager = nullptr, + jobject arrayObject = nullptr); + + void SetArrayElement(JsRuntime &rt, const JsValue &array, uint32_t index, + const std::string& arraySignature, const JsValue &value, + ObjectManager* objectManager = nullptr, + jobject arrayObject = nullptr); + + private: + JsValue ConvertToJsValue(JsRuntime &rt, ObjectManager* objectManager, JEnv& jEnv, + std::string_view elementSignature, const void* value); + void assertNonNullNativeArray(tns::JniLocalRef& arrayReference); + }; +} + +#endif /* ARRAYELEMENTACCESSOR_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/ArrayHelper.cpp b/NativeScript/ffi/jni/jsi/conversion/ArrayHelper.cpp new file mode 100644 index 000000000..05c632b11 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArrayHelper.cpp @@ -0,0 +1,141 @@ +#include "ArrayHelper.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +ArrayHelper::ArrayHelper() { +} + +void ArrayHelper::Init(JsRuntime &rt) { + JEnv jenv; + + RUNTIME_CLASS = jenv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + CREATE_ARRAY_HELPER = jenv.GetStaticMethodID(RUNTIME_CLASS, "createArrayHelper", "(Ljava/lang/String;I)Ljava/lang/Object;"); + assert(CREATE_ARRAY_HELPER != nullptr); + + auto global = rt.global(); + auto arrayConstructor = global.getProperty(rt, "Array"); + if (!arrayConstructor.isObject()) { + return; + } + + auto ctorObject = arrayConstructor.asObject(rt); + js_util::set_function(rt, ctorObject, "create", CreateJavaArrayCallback); +} + +JsValue ArrayHelper::CreateJavaArrayCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + try { + return CreateJavaArray(rt, args, argc); + } catch (NativeScriptException& e) { + e.ReThrowToJs(rt); + } catch (JsError&) { + throw; + } catch (std::exception& e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } +} + +JsValue ArrayHelper::CreateJavaArray(JsRuntime &rt, const JsValue *args, size_t argc) { + if (argc != 2) { + throw JsError(rt, "Expect two parameters."); + } + + const JsValue &type = args[0]; + const JsValue &length = args[1]; + + JniLocalRef array; + + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + + if (type.isString()) { + if (!length.isNumber()) { + throw JsError(rt, "Expect integer value as a second argument."); + } + + if (js_util::is_float(rt, length)) { + throw JsError(rt, "Expect integer value as a second argument. It is a float"); + } + + int32_t len = js_util::get_int32(length); + if (len < 0) { + throw JsError(rt, "Expect non-negative integer value as a second argument."); + } + + string typeName = ArgConverter::ConvertToString(rt, type); + array = JniLocalRef(CreateArrayByClassName(typeName, len)); + } else if (type.isObject()) { + if (!length.isNumber()) { + throw JsError(rt, "Expect integer value as a second argument."); + } + + if (js_util::is_float(rt, length)) { + throw JsError(rt, "Expect integer value as a second argument."); + } + + int32_t len = js_util::get_int32(length); + if (len < 0) { + throw JsError(rt, "Expect non-negative integer value as a second argument."); + } + + auto classVal = type.asObjectBorrowed(rt).getProperty(rt, "class"); + + if (classVal.isUndefined()) { + throw JsError(rt, "Expect known class as a second argument."); + } + + auto c = objectManager->GetJavaObjectByJsObject(classVal); + + JEnv jenv; + array = jenv.NewObjectArray(len, static_cast(c), nullptr); + } else { + throw JsError(rt, "Expect primitive type name or class function as a first argument"); + } + + jint javaObjectID = objectManager->GetOrCreateObjectId(array); + return objectManager->CreateJSWrapper(javaObjectID, "" /* ignored */, array); +} + +jobject ArrayHelper::CreateArrayByClassName(const string& typeName, int length) { + JEnv jEnv; + jobject array; + + if (typeName == "char") { + array = jEnv.NewCharArray(length); + } else if (typeName == "boolean") { + array = jEnv.NewBooleanArray(length); + } else if (typeName == "byte") { + array = jEnv.NewByteArray(length); + } else if (typeName == "short") { + array = jEnv.NewShortArray(length); + } else if (typeName == "int") { + array = jEnv.NewIntArray(length); + } else if (typeName == "long") { + array = jEnv.NewLongArray(length); + } else if (typeName == "float") { + array = jEnv.NewFloatArray(length); + } else if (typeName == "double") { + array = jEnv.NewDoubleArray(length); + } else { + JniLocalRef s(jEnv.NewStringUTF(typeName.c_str())); + array = jEnv.CallStaticObjectMethod(RUNTIME_CLASS, CREATE_ARRAY_HELPER, (jstring)s, length); + } + + return array; +} + +jclass ArrayHelper::RUNTIME_CLASS = nullptr; +jmethodID ArrayHelper::CREATE_ARRAY_HELPER = nullptr; diff --git a/NativeScript/ffi/jni/jsi/conversion/ArrayHelper.h b/NativeScript/ffi/jni/jsi/conversion/ArrayHelper.h new file mode 100644 index 000000000..006f6a27d --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/ArrayHelper.h @@ -0,0 +1,29 @@ +#ifndef ARRAYHELPER_H_ +#define ARRAYHELPER_H_ + +#include "Engine.h" +#include "ObjectManager.h" +#include + +namespace tns { +class ArrayHelper { + public: + static void Init(JsRuntime &rt); + + private: + ArrayHelper(); + + static JsValue CreateJavaArrayCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue CreateJavaArray(JsRuntime &rt, const JsValue *args, size_t argc); + + static jobject CreateArrayByClassName(const std::string& typeName, int length); + + static jclass RUNTIME_CLASS; + + static jmethodID CREATE_ARRAY_HELPER; +}; +} + +#endif /* ARRAYHELPER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/JsArgConverter.cpp b/NativeScript/ffi/jni/jsi/conversion/JsArgConverter.cpp new file mode 100644 index 000000000..be1cacb50 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/JsArgConverter.cpp @@ -0,0 +1,744 @@ +#include "JsArgConverter.h" +#include "ObjectManager.h" +#include "JniSignatureParser.h" +#include "JsArgToArrayConverter.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "MetadataNode.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +namespace { + // The napi tree reads the element type from napi_get_typedarray_info. There is + // no engine:: equivalent -- typed arrays are not part of the engine contract -- + // so the element type is taken from the view's own constructor name, which is + // the same information by another route and needs no per-engine support. + tns::BufferCastType GetBufferCastType(JsRuntime &rt, const JsValue &view) { + auto ctor = view.asObjectBorrowed(rt).getProperty(rt, "constructor"); + if (!ctor.isObject()) return tns::BufferCastType::Byte; + auto name = ctor.asObjectBorrowed(rt).getProperty(rt, "name"); + if (!name.isString()) return tns::BufferCastType::Byte; + std::string n = name.asString(rt).utf8(rt); + + if (n == "Int16Array" || n == "Uint16Array") return tns::BufferCastType::Short; + if (n == "Int32Array" || n == "Uint32Array") return tns::BufferCastType::Int; + if (n == "Float32Array") return tns::BufferCastType::Float; + if (n == "Float64Array") return tns::BufferCastType::Double; + if (n == "BigInt64Array" || n == "BigUint64Array") return tns::BufferCastType::Long; + return tns::BufferCastType::Byte; + } +} + +JsArgConverter::JsArgConverter(JsRuntime &rt, const JsValue &caller, const JsValue *args, size_t argc, + const std::string &methodSignature, MetadataEntry *entry, JNIEnv *jniEnv, + ObjectManager *objectManager) + : m_rt(&rt), m_jniEnv(jniEnv), m_objectManager(objectManager), m_isValid(true), + m_error(Error()) { + int providedArgumentsLength = argc; + m_argsLen = 1 + providedArgumentsLength; + + if (m_argsLen > 0) { + if ((entry != nullptr) && (entry->getIsResolved())) { + if (entry->parsedSig.empty()) { + JniSignatureParser parser(methodSignature); + entry->parsedSig = parser.Parse(); + } + m_tokens = &entry->parsedSig; + } else { + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + m_tokens = &m_ownedTokens; + } + + m_isValid = ConvertArg(rt, caller, 0); + + if (!m_isValid) { + throw NativeScriptException("Error while converting argument!"); + } + + for (size_t i = 0; i < providedArgumentsLength; i++) { + m_isValid = ConvertArg(rt, args[i], i + 1); + + if (!m_isValid) { + break; + } + } + } +} + +JsArgConverter::JsArgConverter(JsRuntime &rt, const JsValue *args, size_t argc, + bool hasImplementationObject, const std::string &methodSignature, + MetadataEntry *entry, JNIEnv *jniEnv, ObjectManager *objectManager) + : m_rt(&rt), m_jniEnv(jniEnv), m_objectManager(objectManager), m_isValid(true), + m_error(Error()) { + m_argsLen = !hasImplementationObject ? argc : argc - 1; + + if (m_argsLen > 0) { + if ((entry != nullptr) && (entry->getIsResolved())) { + if (entry->parsedSig.empty()) { + JniSignatureParser parser(methodSignature); + entry->parsedSig = parser.Parse(); + } + m_tokens = &entry->parsedSig; + } else { + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + m_tokens = &m_ownedTokens; + } + + for (size_t i = 0; i < m_argsLen; i++) { + m_isValid = ConvertArg(rt, args[i], i); + + if (!m_isValid) { + break; + } + } + } +} + +JsArgConverter::JsArgConverter(JsRuntime &rt, const JsValue *args, size_t argc, + const std::string &methodSignature) + : m_rt(&rt), m_isValid(true), m_error(Error()) { + m_argsLen = argc; + + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + m_tokens = &m_ownedTokens; + + for (size_t i = 0; i < m_argsLen; i++) { + m_isValid = ConvertArg(rt, args[i], i); + + if (!m_isValid) { + break; + } + } +} + +bool JsArgConverter::ConvertArg(JsRuntime &rt, const JsValue &arg, int index) { + bool success = false; + + char buff[1024]; + buff[0] = '\0'; + + const auto &typeSignature = (*m_tokens)[index]; + + // Record only the failing index up front (cheap). The default diagnostic + // string is built lazily in GetError() from m_tokens[index], so the common + // success path pays no per-argument string allocation. + m_error.index = index; + + if (arg.isObject()) { + if (js_util::is_array(rt, arg)) { + success = typeSignature[0] == '['; + + if (success) { + success = ConvertJavaScriptArray(rt, arg, index); + } + + if (!success) { + snprintf(buff, sizeof(buff), "Cannot convert array to %s at index %d", + typeSignature.c_str(), index); + } + } else { + auto objectManager = m_objectManager != nullptr + ? m_objectManager + : Runtime::GetRuntime(rt)->GetObjectManager(); + + bool isHostObject = objectManager->IsHostObject(arg); + + CastType castType = isHostObject ? CastType::None + : NumericCasts::GetCastType(rt, arg); + + JsValue castValue; + if (castType != CastType::None) { + castValue = NumericCasts::GetCastValue(rt, arg); + } + + JniLocalRef obj; + + JEnv jEnv = GetJEnv(); + + switch (castType) { + case CastType::Char: + if (castValue.isString()) { + string value = ArgConverter::ConvertToString(rt, castValue); + m_args[index].c = (jchar) value[0]; + success = true; + } + break; + + case CastType::Byte: + if (castValue.isString()) { + string strValue = ArgConverter::ConvertToString(rt, castValue); + int byteArg = atoi(strValue.c_str()); + jbyte value = (jbyte) byteArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (castValue.isNumber()) { + jbyte value = (jbyte) js_util::get_int32(castValue); + success = ConvertFromCastFunctionObject(value, index); + } + + break; + + case CastType::Short: + if (castValue.isString()) { + string strValue = ArgConverter::ConvertToString(rt, castValue); + int shortArg = atoi(strValue.c_str()); + jshort value = (jshort) shortArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (castValue.isNumber()) { + jshort value = (jshort) js_util::get_int32(castValue); + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Long: + if (castValue.isString()) { + string strValue = ArgConverter::ConvertToString(rt, castValue); + jlong value = (jlong) atoll(strValue.c_str()); + success = ConvertFromCastFunctionObject(value, index); + } else if (castValue.isNumber()) { + jlong value = (jlong) js_util::get_number(castValue); + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Float: + if (castValue.isNumber()) { + jfloat value = (jfloat) js_util::get_number(castValue); + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Double: + if (castValue.isNumber()) { + jdouble value = (jdouble) js_util::get_number(castValue); + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::None: { + obj = objectManager->GetJavaObjectByJsObject(arg); + + if (obj.IsNull()) { + bool isArrayBuffer = arg.asObjectBorrowed(rt).isArrayBuffer(rt); + bool isTypedArray = false; + bool isDataView = false; + + if (!isArrayBuffer) { + isTypedArray = js_util::is_typedarray(rt, arg); + if (!isTypedArray) { + isDataView = js_util::is_dataview(rt, arg); + } + } + + if (isArrayBuffer || isDataView || isTypedArray) { + obj = JsArgConverter::GetByteBuffer(rt, arg, isArrayBuffer, + isTypedArray, isDataView); + } + } + + if (!isHostObject) { + if (MetadataNode::GetNullNode(rt, arg) != nullptr) { + SetConvertedObject(index, nullptr); + success = true; + break; + } + } + + success = !obj.IsNull(); + + if (success) { + SetConvertedObject(index, obj.Move(), obj.IsGlobal()); + } else { + if (js_util::is_number_object(rt, arg)) { + success = ConvertJavaScriptNumber(rt, arg, index, true); + break; + } else if (js_util::is_string_object(rt, arg)) { + JsValue stringValue = js_util::valueOf(rt, arg); + success = ConvertJavaScriptString(rt, stringValue, index); + break; + } else if (js_util::is_boolean_object(rt, arg)) { + JsValue boolValue = js_util::valueOf(rt, arg); + success = ConvertJavaScriptBoolean(rt, boolValue, index); + break; + } + + if (!success) { + snprintf(buff, sizeof(buff), "Cannot convert object to %s at index %d", + typeSignature.c_str(), index); + } + } + break; + } + + default: + throw NativeScriptException("Unsupported cast type"); + } + } + } else if (arg.isNumber()) { + success = ConvertJavaScriptNumber(rt, arg, index, false); + + if (!success) { + snprintf(buff, sizeof(buff), "Cannot convert number to %s at index %d", + typeSignature.c_str(), index); + } + } else if (arg.isBool()) { + success = ConvertJavaScriptBoolean(rt, arg, index); + + if (!success) { + snprintf(buff, sizeof(buff), "Cannot convert boolean to %s at index %d", + typeSignature.c_str(), index); + } + } else if (arg.isString()) { + success = ConvertJavaScriptString(rt, arg, index); + + if (!success) { + snprintf(buff, sizeof(buff), "Cannot convert string to %s at index %d", + typeSignature.c_str(), index); + } + } else if (arg.isUndefined() || arg.isNull()) { + SetConvertedObject(index, nullptr); + success = true; + } else { + SetConvertedObject(index, nullptr); + success = false; + } + + if (!success) { + m_error.index = index; + // Keep the seeded default when no specific message was formatted (buff + // untouched), avoiding a garbage/empty message. + if (buff[0] != '\0') { + m_error.msg = string(buff); + } + } + + return success; +} + + +void JsArgConverter::SetConvertedObject(int index, jobject obj, bool isGlobal) { + m_args[index].l = obj; + if ((obj != nullptr) && !isGlobal) { + m_args_refs[m_args_refs_size++] = index; + } +} + +bool JsArgConverter::ConvertJavaScriptNumber(JsRuntime &rt, const JsValue &jsValue, int index, + bool isNumberObject) { + bool success = true; + + jvalue value = {0}; + + const auto &typeSignature = (*m_tokens)[index]; + + const char typePrefix = typeSignature[0]; + + double number = isNumberObject ? js_util::get_number(js_util::valueOf(rt, jsValue)) + : js_util::get_number(jsValue); + + switch (typePrefix) { + case 'B': // byte + value.b = (jbyte) (int32_t) number; + break; + case 'S': // short + value.s = (jshort) (int32_t) number; + break; + case 'I': // int + value.i = (jint) (int32_t) number; + break; + case 'J': // long + value.j = (jlong) (int64_t) number; + break; + case 'F': // float + value.f = (jfloat) number; + break; + case 'D': // double + value.d = (jdouble) number; + break; + default: + success = false; + break; + } + + if (success) { + m_args[index] = value; + } + + return success; +} + +bool JsArgConverter::ConvertJavaScriptBoolean(JsRuntime &rt, const JsValue &jsValue, int index) { + bool success; + + const auto &typeSignature = (*m_tokens)[index]; + + if (typeSignature == "Z") { + m_args[index].z = js_util::get_bool(jsValue) ? JNI_TRUE : JNI_FALSE; + success = true; + } else { + success = false; + } + + return success; +} + +bool JsArgConverter::ConvertJavaScriptString(JsRuntime &rt, const JsValue &jsValue, int index) { + jstring stringObject = ArgConverter::ConvertToJavaString(rt, jsValue); + SetConvertedObject(index, stringObject); + return true; +} + +bool JsArgConverter::ConvertJavaScriptArray(JsRuntime &rt, const JsValue &jsArr, int index) { + bool success = true; + + jarray arr = nullptr; + + auto jsArray = jsArr.asObjectBorrowed(rt).getArray(rt); + const jsize arrLength = (jsize) jsArray.size(rt); + + const auto &arraySignature = (*m_tokens)[index]; + + std::string elementType = arraySignature.substr(1); + + const char elementTypePrefix = elementType[0]; + + jclass elementClass; + std::string strippedClassName; + + JEnv jenv = GetJEnv(); + switch (elementTypePrefix) { + case 'Z': { + arr = jenv.NewBooleanArray(arrLength); + std::vector bools(arrLength); + for (jsize i = 0; i < arrLength; i++) { + bools[i] = (jboolean) js_util::get_bool(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetBooleanArrayRegion((jbooleanArray) arr, 0, arrLength, bools.data()); + break; + } + case 'B': { + arr = jenv.NewByteArray(arrLength); + std::vector bytes(arrLength); + for (jsize i = 0; i < arrLength; i++) { + bytes[i] = (jbyte) js_util::get_int32(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetByteArrayRegion((jbyteArray) arr, 0, arrLength, bytes.data()); + break; + } + case 'C': { + arr = jenv.NewCharArray(arrLength); + std::vector chars(arrLength); + for (jsize i = 0; i < arrLength; i++) { + std::string str = js_util::get_string_value(rt, + jsArray.getValueAtIndexBorrowed(rt, i)); + chars[i] = str.empty() ? (jchar) 0 : (jchar) str[0]; + } + jenv.SetCharArrayRegion((jcharArray) arr, 0, arrLength, chars.data()); + break; + } + case 'S': { + arr = jenv.NewShortArray(arrLength); + std::vector shorts(arrLength); + for (jsize i = 0; i < arrLength; i++) { + shorts[i] = (jshort) js_util::get_int32(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetShortArrayRegion((jshortArray) arr, 0, arrLength, shorts.data()); + break; + } + case 'I': { + arr = jenv.NewIntArray(arrLength); + std::vector ints(arrLength); + for (jsize i = 0; i < arrLength; i++) { + ints[i] = (jint) js_util::get_int32(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetIntArrayRegion((jintArray) arr, 0, arrLength, ints.data()); + break; + } + case 'J': { + arr = jenv.NewLongArray(arrLength); + std::vector longs(arrLength); + for (jsize i = 0; i < arrLength; i++) { + longs[i] = (jlong) js_util::get_number(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetLongArrayRegion((jlongArray) arr, 0, arrLength, longs.data()); + break; + } + case 'F': { + arr = jenv.NewFloatArray(arrLength); + std::vector floats(arrLength); + for (jsize i = 0; i < arrLength; i++) { + floats[i] = (jfloat) js_util::get_number(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetFloatArrayRegion((jfloatArray) arr, 0, arrLength, floats.data()); + break; + } + case 'D': { + arr = jenv.NewDoubleArray(arrLength); + std::vector doubles(arrLength); + for (jsize i = 0; i < arrLength; i++) { + doubles[i] = (jdouble) js_util::get_number(jsArray.getValueAtIndexBorrowed(rt, i)); + } + jenv.SetDoubleArrayRegion((jdoubleArray) arr, 0, arrLength, doubles.data()); + break; + } + case 'L': + strippedClassName = elementType.substr(1, elementType.length() - 2); + elementClass = jenv.FindClass(strippedClassName); + arr = jenv.NewObjectArray(arrLength, elementClass, nullptr); + for (jsize i = 0; i < arrLength; i++) { + JsValue element = jsArray.getValueAtIndex(rt, i); + JsArgToArrayConverter c(rt, element, false, (int) Type::Null, + m_objectManager != nullptr + ? m_objectManager + : Runtime::GetRuntime(rt)->GetObjectManager()); + jobject o = c.GetConvertedArg(); + jenv.SetObjectArrayElement((jobjectArray) arr, (int) i, o); + } + break; + default: + success = false; + break; + } + + if (success) { + SetConvertedObject(index, arr); + } + + return success; +} + + +template +bool JsArgConverter::ConvertFromCastFunctionObject(T value, int index) { + bool success = false; + + const auto &typeSignature = (*m_tokens)[index]; + + const char typeSignaturePrefix = typeSignature[0]; + + switch (typeSignaturePrefix) { + case 'B': + m_args[index].b = (jbyte) value; + success = true; + break; + + case 'S': + m_args[index].s = (jshort) value; + success = true; + break; + + case 'I': + m_args[index].i = (jint) value; + success = true; + break; + + case 'J': + m_args[index].j = (jlong) value; + success = true; + break; + + case 'F': + m_args[index].f = (jfloat) value; + success = true; + break; + + case 'D': + m_args[index].d = (jdouble) value; + success = true; + break; + + default: + success = false; + break; + } + + return success; +} + +int JsArgConverter::Length() const { + return m_argsLen; +} + +bool JsArgConverter::IsValid() const { + return m_isValid; +} + +jvalue *JsArgConverter::ToArgs() { + return m_args; +} + +JsArgConverter::Error JsArgConverter::GetError() const { + Error e = m_error; + // Build the default diagnostic lazily (only when an error is actually + // queried and no specific message was already formatted on the failure path). + if (e.index >= 0 && e.msg.empty() && m_tokens != nullptr && + e.index < (int) m_tokens->size()) { + e.msg = "Cannot convert argument at index " + std::to_string(e.index) + + " to " + (*m_tokens)[e.index]; + } + return e; +} + +JsArgConverter::~JsArgConverter() { + if (m_argsLen > 0) { + JEnv env = GetJEnv(); + for (int i = 0; i < m_args_refs_size; i++) { + int index = m_args_refs[i]; + if (index != -1) { + env.DeleteLocalRef(m_args[index].l); + } + } + } +} + +JniLocalRef JsArgConverter::GetByteBuffer(JsRuntime &rt, const JsValue &object, bool isArrayBuffer, + bool isTypedArray, bool isDataView) { + JEnv jEnv; + + BufferCastType bufferCastType = tns::BufferCastType::Byte; + size_t offset = 0; + size_t length = 0; + uint8_t *data = nullptr; + + if (isTypedArray || isDataView) { + // The napi tree reads the view's data pointer (which already points at + // the view's start) and then adds byteOffset again. Going through the + // backing ArrayBuffer instead makes `data + offset` the view start + // exactly once; the two agree wherever byteOffset is 0. + auto view = object.asObjectBorrowed(rt); + auto bufferValue = view.getProperty(rt, "buffer"); + if (!bufferValue.isObject()) { + return JniLocalRef(); + } + auto arrayBuffer = bufferValue.asObject(rt).getArrayBuffer(rt); + data = arrayBuffer.data(rt); + offset = (size_t) js_util::get_number(view.getPropertyBorrowed(rt, "byteOffset")); + + if (isTypedArray) { + length = arrayBuffer.size(rt); + bufferCastType = GetBufferCastType(rt, object); + } else { + length = (size_t) js_util::get_number(view.getPropertyBorrowed(rt, "byteLength")); + } + } else if (isArrayBuffer) { + auto arrayBuffer = object.asObjectBorrowed(rt).getArrayBuffer(rt); + data = arrayBuffer.data(rt); + length = arrayBuffer.size(rt); + } + + jobject directBuffer; + + if (isDataView || isTypedArray) { + directBuffer = jEnv.NewDirectByteBuffer(data + offset, length); + } else { + directBuffer = jEnv.NewDirectByteBuffer(data, length); + } + + + auto directBufferClazz = jEnv.GetObjectClass(directBuffer); + + auto byteOrderId = BYTE_ORDER_METHOD_ID; + + if (!BYTE_ORDER_METHOD_ID) { + byteOrderId = jEnv.GetMethodID(directBufferClazz, "order", + "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); + BYTE_ORDER_METHOD_ID = byteOrderId; + } + + auto byteOrderClazz = jEnv.FindClass("java/nio/ByteOrder"); + + auto byteOrderEnumId = BYTE_ORDER_ENUM_ID; + + if (!byteOrderEnumId) { + byteOrderEnumId = jEnv.GetStaticMethodID(byteOrderClazz, + "nativeOrder", + "()Ljava/nio/ByteOrder;"); + BYTE_ORDER_ENUM_ID = byteOrderEnumId; + } + + auto nativeByteOrder = jEnv.CallStaticObjectMethodA(byteOrderClazz, + byteOrderEnumId, + nullptr); + + directBuffer = jEnv.CallObjectMethod(directBuffer, byteOrderId, + nativeByteOrder); + + jobject buffer; + + if (bufferCastType == BufferCastType::Short) { + auto id = AS_SHORT_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asShortBuffer", + "()Ljava/nio/ShortBuffer;"); + AS_SHORT_BUFFER = id; + } + + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Int) { + auto id = AS_INT_BUFFER; + + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asIntBuffer", + "()Ljava/nio/IntBuffer;"); + AS_INT_BUFFER = id; + } + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Long) { + auto id = AS_LONG_BUFFER; + + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asLongBuffer", + "()Ljava/nio/LongBuffer;"); + AS_LONG_BUFFER = id; + } + + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Float) { + + auto id = AS_FLOAT_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asFloatBuffer", + "()Ljava/nio/FloatBuffer;"); + AS_FLOAT_BUFFER = id; + } + + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Double) { + + auto id = AS_DOUBLE_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asDoubleBuffer", + "()Ljava/nio/DoubleBuffer;"); + AS_DOUBLE_BUFFER = id; + } + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else { + buffer = directBuffer; + } + + buffer = jEnv.NewGlobalRef(buffer); + + ObjectManager *objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + + int id = objectManager->GetOrCreateObjectId(buffer); + auto clazz = jEnv.GetObjectClass(buffer); + + ObjectManager::MarkObject(rt, object); + + objectManager->Link(object, id, clazz); + + return objectManager->GetJavaObjectByJsObject(object); +} + +jmethodID JsArgConverter::BYTE_ORDER_METHOD_ID = nullptr; +jmethodID JsArgConverter::BYTE_ORDER_ENUM_ID = nullptr; +jmethodID JsArgConverter::AS_SHORT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_LONG_BUFFER = nullptr; +jmethodID JsArgConverter::AS_FLOAT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_INT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_DOUBLE_BUFFER = nullptr; diff --git a/NativeScript/ffi/jni/jsi/conversion/JsArgConverter.h b/NativeScript/ffi/jni/jsi/conversion/JsArgConverter.h new file mode 100644 index 000000000..ebf9e5bda --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/JsArgConverter.h @@ -0,0 +1,113 @@ +#ifndef JSARGCONVERTER_H_ +#define JSARGCONVERTER_H_ + +#include +#include +#include "JEnv.h" +#include "Runtime.h" +#include "MetadataEntry.h" + +namespace tns { + + enum class BufferCastType { + Byte, + Short, + Int, + Long, + Float, + Double + }; + + class JsArgConverter { + public: + + JsArgConverter(JsRuntime &rt, const JsValue &caller, const JsValue* args, size_t argc, const std::string& methodSignature, MetadataEntry* entry, JNIEnv* jniEnv = nullptr, ObjectManager* objectManager = nullptr); + + JsArgConverter(JsRuntime &rt, const JsValue* args, size_t argc, bool hasImplementationObject, const std::string& methodSignature, MetadataEntry* entry, JNIEnv* jniEnv = nullptr, ObjectManager* objectManager = nullptr); + + JsArgConverter(JsRuntime &rt, const JsValue* args, size_t argc, const std::string& methodSignature); + + ~JsArgConverter(); + + jvalue* ToArgs(); + + int Length() const; + + bool IsValid() const; + + struct Error; + + Error GetError() const; + + struct Error { + Error() : + index(-1), msg(std::string()) { + } + + int index; + std::string msg; + }; + + static JniLocalRef GetByteBuffer(JsRuntime &rt, const JsValue &object, bool isArrayBuffer, bool isTypedArray, bool isDataView); + + + + static jmethodID BYTE_ORDER_METHOD_ID; + static jmethodID BYTE_ORDER_ENUM_ID; + static jmethodID AS_SHORT_BUFFER; + static jmethodID AS_INT_BUFFER; + static jmethodID AS_LONG_BUFFER; + static jmethodID AS_FLOAT_BUFFER; + static jmethodID AS_DOUBLE_BUFFER; + private: + + bool ConvertArg(JsRuntime &rt, const JsValue &arg, int index); + + bool ConvertJavaScriptArray(JsRuntime &rt, const JsValue &jsArr, int index); + + bool ConvertJavaScriptNumber(JsRuntime &rt, const JsValue &jsValue, int index, bool isNumberObject); + + bool ConvertJavaScriptBoolean(JsRuntime &rt, const JsValue &jsValue, int index); + + bool ConvertJavaScriptString(JsRuntime &rt, const JsValue &jsValue, int index); + + void SetConvertedObject(int index, jobject obj, bool isGlobal = false); + + + template + bool ConvertFromCastFunctionObject(T value, int index); + + JsRuntime* m_rt; + + // Current thread's JNIEnv* threaded down from the caller (avoids + // re-querying the JavaVM via GetEnv); nullptr => construct locally. + JNIEnv* m_jniEnv = nullptr; + + // Returns a JEnv reusing the threaded JNIEnv* when available. + inline JEnv GetJEnv() const { + return m_jniEnv != nullptr ? JEnv(m_jniEnv, JEnv::Adopt::Trusted) : JEnv(); + } + + // Cached ObjectManager threaded from the caller (avoids a locked + // runtime lookup per object-typed argument). + ObjectManager* m_objectManager = nullptr; + + int m_argsLen; + + bool m_isValid; + + jvalue m_args[255]; + int m_args_refs[255]; + int m_args_refs_size = 0; + + // Parsed argument-type tokens. On the common path this points directly at + // the MetadataEntry's cached `parsedSig` (no copy); only the entry-less / + // unresolved fallback owns its tokens in m_ownedTokens. + const std::vector* m_tokens = nullptr; + std::vector m_ownedTokens; + + Error m_error; + }; +} + +#endif /* JSARGCONVERTER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/JsArgToArrayConverter.cpp b/NativeScript/ffi/jni/jsi/conversion/JsArgToArrayConverter.cpp new file mode 100644 index 000000000..9995bafdd --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/JsArgToArrayConverter.cpp @@ -0,0 +1,395 @@ +#include "JsArgToArrayConverter.h" +#include +#include "ObjectManager.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "MetadataNode.h" +#include "JsArgConverter.h" + +using namespace std; +using namespace tns; + +JsArgToArrayConverter::JsArgToArrayConverter(JsRuntime &rt, const JsValue &arg, + bool isImplementationObject, int classReturnType, + ObjectManager* objectManager) + : m_arr(nullptr), m_argsAsObject(nullptr), m_argsLen(0), m_isValid(false), m_error(Error()), + m_return_type(classReturnType) { + m_objectManager = objectManager; + if (!isImplementationObject) { + m_argsLen = 1; + m_argsAsObject = (m_argsLen <= INLINE_CAPACITY) ? m_inlineArgs : new jobject[m_argsLen]; + memset(m_argsAsObject, 0, m_argsLen * sizeof(jobject)); + + m_isValid = ConvertArg(rt, arg, 0); + } +} + +JsArgToArrayConverter::JsArgToArrayConverter(JsRuntime &rt, size_t argc, const JsValue *argv, + bool hasImplementationObject) + : m_arr(nullptr), m_argsAsObject(nullptr), m_argsLen(0), m_isValid(false), m_error(Error()), + m_return_type(static_cast(Type::Null)) { + m_argsLen = !hasImplementationObject ? argc : argc - 2; + + bool success = true; + + if (m_argsLen > 0) { + m_argsAsObject = (m_argsLen <= INLINE_CAPACITY) ? m_inlineArgs : new jobject[m_argsLen]; + memset(m_argsAsObject, 0, m_argsLen * sizeof(jobject)); + + for (int i = 0; i < m_argsLen; i++) { + success = ConvertArg(rt, argv[i], i); + + if (!success) { + break; + } + } + } + + m_isValid = success; +} + +bool JsArgToArrayConverter::ConvertArg(JsRuntime &rt, const JsValue &arg, int index) { + bool success = false; + // Error text is built only on failure (avoids a per-call stringstream). + std::string errMsg; + + // Seed a default diagnostic: the early `return false` paths below do not + // reach the error-population tail, and the caller loop stops at the first + // failing argument, so GetError() always carries a non-empty, indexed + // message even on those paths. + m_error.index = index; + m_error.msg = "Cannot marshal JavaScript argument at index " + + std::to_string(index) + " to Java type."; + + JEnv jEnv; + + Type returnType = JType::getClassType(m_return_type); + + if (arg.isUndefined() || arg.isNull()) { + SetConvertedObject(jEnv, index, nullptr); + success = true; + } else if (arg.isNumber()) { + double d = js_util::get_number(arg); + int64_t i = (int64_t) d; + + bool isWholeNumber = d == i; + + if (isWholeNumber) { + jobject obj; + + if ((INT_MIN <= i) && (i <= INT_MAX) && + (returnType == Type::Int || returnType == Type::Null)) { + obj = JType::NewInt(jEnv, (jint) i); + } else { + obj = JType::NewLong(jEnv, (jlong) d); + } + + SetConvertedObject(jEnv, index, obj); + success = true; + } else { + jobject obj; + + if ((FLT_MIN <= d) && (d <= FLT_MAX) && + (returnType == Type::Float || returnType == Type::Null)) { + obj = JType::NewFloat(jEnv, (jfloat) d); + } else { + obj = JType::NewDouble(jEnv, (jdouble) d); + } + + SetConvertedObject(jEnv, index, obj); + success = true; + } + } else if (arg.isBool()) { + auto javaObject = JType::NewBoolean(jEnv, js_util::get_bool(arg)); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } else if (arg.isString()) { + auto stringObject = ArgConverter::ConvertToJavaString(rt, arg); + SetConvertedObject(jEnv, index, stringObject); + success = true; + } else if (arg.isObject()) { + const JsValue &jsObj = arg; + + auto objectManager = m_objectManager != nullptr + ? m_objectManager + : Runtime::GetRuntime(rt)->GetObjectManager(); + + bool isHostObject = objectManager->IsHostObject(jsObj); + + CastType castType = isHostObject ? CastType::None + : NumericCasts::GetCastType(rt, jsObj); + + JsValue castValue; + jchar charValue; + jbyte byteValue; + jshort shortValue; + jlong longValue; + jfloat floatValue; + jdouble doubleValue; + jobject javaObject; + JniLocalRef obj; + + switch (castType) { + case CastType::Char: + castValue = NumericCasts::GetCastValue(rt, jsObj); + charValue = '\0'; + if (!castValue.isUndefined()) { + string str = ArgConverter::ConvertToString(rt, castValue); + charValue = (jchar) str[0]; + } + javaObject = JType::NewChar(jEnv, charValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Byte: + castValue = NumericCasts::GetCastValue(rt, jsObj); + byteValue = 0; + + if (!castValue.isUndefined()) { + if (castValue.isString()) { + string value = ArgConverter::ConvertToString(rt, castValue); + int byteArg = atoi(value.c_str()); + byteValue = (jbyte) byteArg; + } else { + byteValue = (jbyte) js_util::get_int32(castValue); + } + } + + javaObject = JType::NewByte(jEnv, byteValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Short: + castValue = NumericCasts::GetCastValue(rt, jsObj); + shortValue = 0; + if (!castValue.isUndefined()) { + if (castValue.isString()) { + string value = ArgConverter::ConvertToString(rt, castValue); + int shortArg = atoi(value.c_str()); + shortValue = (jshort) shortArg; + } else { + shortValue = (jshort) js_util::get_int32(castValue); + } + } + + javaObject = JType::NewShort(jEnv, shortValue); + + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Long: + castValue = NumericCasts::GetCastValue(rt, jsObj); + longValue = 0; + if (!castValue.isUndefined()) { + if (castValue.isString()) { + auto strValue = ArgConverter::ConvertToString(rt, castValue); + longValue = atoll(strValue.c_str()); + } else { + longValue = (jlong) js_util::get_number(castValue); + } + } + javaObject = JType::NewLong(jEnv, longValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Float: + castValue = NumericCasts::GetCastValue(rt, jsObj); + floatValue = 0; + if (!castValue.isUndefined()) { + floatValue = (jfloat) js_util::get_number(castValue); + } + javaObject = JType::NewFloat(jEnv, floatValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Double: + castValue = NumericCasts::GetCastValue(rt, jsObj); + doubleValue = 0; + if (!castValue.isUndefined()) { + doubleValue = (jdouble) js_util::get_number(castValue); + } + javaObject = JType::NewDouble(jEnv, doubleValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::None: { + obj = objectManager->GetJavaObjectByJsObject(jsObj); + + if (obj.IsNull()) { + bool isArrayBuffer = jsObj.asObjectBorrowed(rt).isArrayBuffer(rt); + bool isTypedArray = false; + bool isDataView = false; + + if (!isArrayBuffer) { + isTypedArray = js_util::is_typedarray(rt, jsObj); + if (!isTypedArray) { + isDataView = js_util::is_dataview(rt, jsObj); + } + } + + if (isArrayBuffer || isDataView || isTypedArray) { + obj = JsArgConverter::GetByteBuffer(rt, jsObj, isArrayBuffer, isTypedArray, + isDataView); + } + } + + if (!isHostObject) { + MetadataNode *node = MetadataNode::GetNullNode(rt, jsObj); + if (node != nullptr) { + auto type = node->GetName(); + auto nullObjName = "com/tns/NullObject"; + auto nullObjCtorSig = "(Ljava/lang/Class;)V"; + + jclass nullClazz = jEnv.FindClass(nullObjName); + jmethodID ctor = jEnv.GetMethodID(nullClazz, "", nullObjCtorSig); + jclass clazzToNull = jEnv.FindClass(type); + jobject nullObjType = jEnv.NewObject(nullClazz, ctor, clazzToNull); + + if (nullObjType != nullptr) { + SetConvertedObject(jEnv, index, nullObjType, false); + } else { + SetConvertedObject(jEnv, index, nullptr); + } + + return true; + } + } + + success = !obj.IsNull(); + if (success) { + SetConvertedObject(jEnv, index, obj.Move(), obj.IsGlobal()); + } else { + if (js_util::is_number_object(rt, arg)) { + JsValue numValue = js_util::valueOf(rt, arg); + if (js_util::is_float(rt, numValue)) { + javaObject = JType::NewFloat(jEnv, + (jfloat) js_util::get_number(numValue)); + } else { + javaObject = JType::NewInt(jEnv, (jint) js_util::get_int32(numValue)); + } + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } else if (js_util::is_string_object(rt, arg)) { + JsValue stringValue = js_util::valueOf(rt, arg); + javaObject = ArgConverter::ConvertToJavaString(rt, stringValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } else if (js_util::is_boolean_object(rt, arg)) { + JsValue boolValue = js_util::valueOf(rt, arg); + javaObject = JType::NewBoolean(jEnv, js_util::get_bool(boolValue)); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } + + if (!success) { + stringstream s; + s << "Cannot marshal JavaScript argument " + << js_util::coerce_to_string(rt, jsObj) << " at index " << index + << " to Java type."; + errMsg = s.str(); + } + } + break; + } + + default: + throw NativeScriptException("Unsupported cast type"); + } + } else { + errMsg = "Cannot marshal JavaScript argument at index " + std::to_string(index) + + " to Java type."; + success = false; + } + + if (!success) { + m_error.index = index; + // Keep the seeded default when no specific message was built. + if (!errMsg.empty()) { + m_error.msg = std::move(errMsg); + } + } + + return success; +} + +jobject JsArgToArrayConverter::GetConvertedArg() { + return (m_argsLen > 0) ? m_argsAsObject[0] : nullptr; +} + +void JsArgToArrayConverter::SetConvertedObject(JEnv &env, int index, jobject obj, bool isGlobal) { + m_argsAsObject[index] = obj; + if ((obj != nullptr) && !isGlobal) { + m_storedIndexes.push_back(index); + } +} + +int JsArgToArrayConverter::Length() const { + return m_argsLen; +} + +bool JsArgToArrayConverter::IsValid() const { + return m_isValid; +} + +JsArgToArrayConverter::Error JsArgToArrayConverter::GetError() const { + return m_error; +} + +jobjectArray JsArgToArrayConverter::ToJavaArray() { + if ((m_arr == nullptr) && (m_argsLen > 0)) { + if (m_argsLen >= JsArgToArrayConverter::MAX_JAVA_PARAMS_COUNT) { + stringstream ss; + ss << "You are trying to override more than the MAX_JAVA_PARAMS_COUNT: " + << JsArgToArrayConverter::MAX_JAVA_PARAMS_COUNT; + throw NativeScriptException(ss.str()); + } + + JEnv jEnv; + + if (JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS == nullptr) { + JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS = jEnv.FindClass("java/lang/Object"); + } + + JniLocalRef tmpArr( + jEnv.NewObjectArray(m_argsLen, JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS, + nullptr)); + m_arr = (jobjectArray) jEnv.NewGlobalRef(tmpArr); + + for (int i = 0; i < m_argsLen; i++) { + jEnv.SetObjectArrayElement(m_arr, i, m_argsAsObject[i]); + } + } + + return m_arr; +} + +JsArgToArrayConverter::~JsArgToArrayConverter() { + if (m_argsLen > 0) { + JEnv env; + + env.DeleteGlobalRef(m_arr); + + int length = m_storedIndexes.size(); + for (int i = 0; i < length; i++) { + int index = m_storedIndexes[i]; + env.DeleteLocalRef(m_argsAsObject[index]); + } + + if (m_argsAsObject != m_inlineArgs) { + delete[] m_argsAsObject; + } + } +} + +jclass JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS = nullptr; diff --git a/NativeScript/ffi/jni/jsi/conversion/JsArgToArrayConverter.h b/NativeScript/ffi/jni/jsi/conversion/JsArgToArrayConverter.h new file mode 100644 index 000000000..8df7d8813 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/JsArgToArrayConverter.h @@ -0,0 +1,78 @@ +#ifndef JSARGTOARRAYCONVERTER_H_ +#define JSARGTOARRAYCONVERTER_H_ + +#include "JEnv.h" +#include "JniLocalRef.h" +#include "Engine.h" +#include +#include + +namespace tns { +class ObjectManager; + +class JsArgToArrayConverter { + public: + JsArgToArrayConverter(JsRuntime &rt, size_t argc, const JsValue* argv, bool hasImplementationObject); + + // `objectManager` may be supplied pre-resolved (avoids a locked runtime + // lookup); falls back to resolving internally when omitted. + JsArgToArrayConverter(JsRuntime &rt, const JsValue &arg, bool isImplementationObject, int classReturnType, + ObjectManager* objectManager = nullptr); + + ~JsArgToArrayConverter(); + + jobjectArray ToJavaArray(); + + jobject GetConvertedArg(); + + int Length() const; + + bool IsValid() const; + + struct Error; + + Error GetError() const; + + struct Error { + Error() : + index(-1), msg(std::string()) { + } + + int index; + std::string msg; + }; + + private: + bool ConvertArg(JsRuntime &rt, const JsValue &arg, int index); + + void SetConvertedObject(JEnv& env, int index, jobject obj, bool isGlobal = false); + + int m_argsLen; + + int m_return_type; + + bool m_isValid; + + Error m_error; + + std::vector m_storedIndexes; + + jobject* m_argsAsObject; + + // Inline storage for the common small-arity case (esp. the single-arg + // path used per object-array element); heap only when larger. + static const int INLINE_CAPACITY = 8; + jobject m_inlineArgs[INLINE_CAPACITY]; + + // Cached ObjectManager threaded from the caller. + ObjectManager* m_objectManager = nullptr; + + jobjectArray m_arr; + + short MAX_JAVA_PARAMS_COUNT = 256; + + static jclass JAVA_LANG_OBJECT_CLASS; +}; +} + +#endif /* JSARGTOARRAYCONVERTER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/conversion/NumericCasts.cpp b/NativeScript/ffi/jni/jsi/conversion/NumericCasts.cpp new file mode 100644 index 000000000..0aa7796be --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/NumericCasts.cpp @@ -0,0 +1,142 @@ +#include "NumericCasts.h" +#include "NativeScriptAssert.h" +#include "Util.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +void NumericCasts::CreateGlobalCastFunctions(JsRuntime &rt, JsObject &globalObject) { + js_util::set_function(rt, globalObject, "long", MarkAsLongCallback); + js_util::set_function(rt, globalObject, "byte", MarkAsByteCallback); + js_util::set_function(rt, globalObject, "short", MarkAsShortCallback); + js_util::set_function(rt, globalObject, "double", MarkAsDoubleCallback); + js_util::set_function(rt, globalObject, "float", MarkAsFloatCallback); + js_util::set_function(rt, globalObject, "char", MarkAsCharCallback); +} + +void NumericCasts::MarkAsLong(JsRuntime &rt, JsObject &object, const JsValue &value) { + MarkJsObject(rt, object, CastType::Long, value); +} + +JsValue NumericCasts::MarkAsLongCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count) { + if (count != 1) { + throw JsError(rt, "long(x) should be called with single parameter"); + } + + if (!args[0].isString() && !args[0].isNumber()) { + throw JsError(rt, + "long(x) should be called with single parameter containing a long number representation"); + } + + JsObject cast(rt); + MarkJsObject(rt, cast, CastType::Long, args[0]); + return JsValue(rt, cast); +} + +JsValue NumericCasts::MarkAsByteCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count) { + if (count != 1) { + throw JsError(rt, "byte(x) should be called with single parameter"); + } + + if (!args[0].isString() && !args[0].isNumber() && + !js_util::is_number_object(rt, args[0]) && !js_util::is_string_object(rt, args[0])) { + throw JsError(rt, + "byte(x) should be called with single parameter containing a byte number representation"); + } + + JsValue value = args[0].isNumber() + ? JsValue(rt, args[0]) + : js_util::to_js_string(rt, js_util::coerce_to_string(rt, args[0])); + + JsObject cast(rt); + MarkJsObject(rt, cast, CastType::Byte, value); + return JsValue(rt, cast); +} + +JsValue NumericCasts::MarkAsShortCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count) { + if (count != 1) { + throw JsError(rt, "short(x) should be called with single parameter"); + } + + if (!args[0].isString() && !args[0].isNumber() && + !js_util::is_number_object(rt, args[0]) && !js_util::is_string_object(rt, args[0])) { + throw JsError(rt, + "short(x) should be called with single parameter containing a byte number representation"); + } + + JsValue value = args[0].isNumber() + ? JsValue(rt, args[0]) + : js_util::to_js_string(rt, js_util::coerce_to_string(rt, args[0])); + + JsObject cast(rt); + MarkJsObject(rt, cast, CastType::Short, value); + return JsValue(rt, cast); +} + +JsValue NumericCasts::MarkAsCharCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count) { + if (count != 1) { + throw JsError(rt, "char(x) should be called with single parameter"); + } + + if (!args[0].isString()) { + throw JsError(rt, + "char(x) should be called with single parameter containing a char representation"); + } + + if (args[0].asString(rt).utf8(rt).size() != 1) { + throw JsError(rt, + "char(x) should be called with single parameter containing a single char"); + } + + JsObject cast(rt); + MarkJsObject(rt, cast, CastType::Char, args[0]); + return JsValue(rt, cast); +} + +JsValue NumericCasts::MarkAsFloatCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count) { + if (count != 1) { + throw JsError(rt, "float(x) should be called with single parameter"); + } + + if (!args[0].isNumber()) { + throw JsError(rt, + "float(x) should be called with single parameter containing a float number representation"); + } + + JsObject cast(rt); + MarkJsObject(rt, cast, CastType::Float, args[0]); + return JsValue(rt, cast); +} + +JsValue NumericCasts::MarkAsDoubleCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count) { + if (count != 1) { + throw JsError(rt, "double(x) should be called with single parameter"); + } + + if (!args[0].isNumber()) { + throw JsError(rt, + "double(x) should be called with single parameter containing a double number representation"); + } + + JsObject cast(rt); + MarkJsObject(rt, cast, CastType::Double, args[0]); + return JsValue(rt, cast); +} + +void +NumericCasts::MarkJsObject(JsRuntime &rt, JsObject &object, CastType castType, + const JsValue &value) { + object.setProperty(rt, s_castMarker, JsValue(static_cast(castType))); + object.setProperty(rt, "value", value); +} + +const char *NumericCasts::s_castMarker = "t::cast"; diff --git a/NativeScript/ffi/jni/jsi/conversion/NumericCasts.h b/NativeScript/ffi/jni/jsi/conversion/NumericCasts.h new file mode 100644 index 000000000..a5df84b6c --- /dev/null +++ b/NativeScript/ffi/jni/jsi/conversion/NumericCasts.h @@ -0,0 +1,74 @@ +#ifndef NUMERICCASTS_H_ +#define NUMERICCASTS_H_ + +#include "Engine.h" +#include "Runtime.h" +#include + +namespace tns { + enum class CastType { + None, + Char, + Byte, + Short, + Long, + Float, + Double + }; + + class NumericCasts { + public: + void CreateGlobalCastFunctions(JsRuntime &rt, JsObject &globalObject); + + inline static CastType GetCastType(JsRuntime &rt, const JsValue &object) { + CastType ret = CastType::None; + + if (!object.isObject()) return ret; + + // The napi tree short-circuits host objects here, because reading a + // named property off one was a napi_get_named_property that the proxy + // could answer expensively. A cast marker is only ever set on a plain + // object created by MarkJsObject, and the host proxy's get trap + // forwards an unknown name straight to its target, so the read below + // returns undefined for a host object and reaches the same answer. + auto hidden = object.asObjectBorrowed(rt).getPropertyBorrowed(rt, s_castMarker); + if (hidden.isNumber()) { + ret = static_cast(static_cast(hidden.getNumber())); + } + + return ret; + } + + inline static JsValue GetCastValue(JsRuntime &rt, const JsValue &object) { + return object.asObjectBorrowed(rt).getProperty(rt, "value"); + } + + static void MarkAsLong(JsRuntime &rt, JsObject &object, const JsValue &value); + + private: + static JsValue MarkAsLongCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count); + + static JsValue MarkAsByteCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count); + + static JsValue MarkAsShortCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count); + + static JsValue MarkAsCharCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count); + + static JsValue MarkAsFloatCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count); + + static JsValue MarkAsDoubleCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t count); + + static void + MarkJsObject(JsRuntime &rt, JsObject &object, CastType castType, const JsValue &value); + + static const char *s_castMarker; + }; +} + +#endif /* NUMERICCASTS_H_ */ diff --git a/NativeScript/ffi/jni/jsi/exceptions/NativeScriptAssert.h b/NativeScript/ffi/jni/jsi/exceptions/NativeScriptAssert.h new file mode 100644 index 000000000..dc0ad3c91 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/exceptions/NativeScriptAssert.h @@ -0,0 +1,22 @@ +/* + * nativescriptassert.h + * + * Created on: 12.11.2013 + * Author: blagoev + */ + +#ifndef NATIVESCRIPTASSERT_H_ +#define NATIVESCRIPTASSERT_H_ + +#include + +namespace tns { +extern bool LogEnabled; + +#define DEBUG_WRITE(fmt, args...) if (tns::LogEnabled) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +// #define DEBUG_WRITE(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +#define DEBUG_WRITE_FORCE(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +#define DEBUG_WRITE_FATAL(fmt, args...) __android_log_print(ANDROID_LOG_FATAL, "TNS.Native", fmt, ##args) +} + +#endif /* NATIVESCRIPTASSERT_H_ */ diff --git a/NativeScript/ffi/jni/jsi/exceptions/NativeScriptException.cpp b/NativeScript/ffi/jni/jsi/exceptions/NativeScriptException.cpp new file mode 100644 index 000000000..a9b87784d --- /dev/null +++ b/NativeScript/ffi/jni/jsi/exceptions/NativeScriptException.cpp @@ -0,0 +1,395 @@ +#include "Util.h" +#include "NativeScriptException.h" +#include "ArgConverter.h" +#include "NativeScriptAssert.h" +#include "Runtime.h" +#include "ObjectManager.h" +#include + +using namespace std; +using namespace tns; + +NativeScriptException::NativeScriptException(JEnv& env) + : m_javascriptException(nullptr) { + jthrowable thrw = env.ExceptionOccurred(); + m_javaException = JniLocalRef(thrw); + env.ExceptionClear(); + DEBUG_WRITE("%s, %s", GetExceptionMessage(env, m_javaException).c_str(), GetExceptionStackTrace(env, m_javaException).c_str()); +} + +NativeScriptException::NativeScriptException(const string& message) + : m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message) { + + DEBUG_WRITE("%s", m_message.c_str()); +} + +NativeScriptException::NativeScriptException(const string& message, const string& stackTrace) + : m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message), m_stackTrace(stackTrace) { + + DEBUG_WRITE("%s, %s ", m_message.c_str(), m_stackTrace.c_str()); +} + +NativeScriptException::NativeScriptException(JsRuntime& rt, const JsValue& error, const string& message) + : m_javaException(JniLocalRef()) { + m_javascriptException = std::make_shared(rt, error); + m_message = GetErrorMessage(rt, error, message); + m_stackTrace = GetErrorStackTrace(rt, error); + m_fullMessage = GetFullMessage(rt, error, m_message); +} + +NativeScriptException::NativeScriptException(JsRuntime& rt, const JsError& error, + const string& message) + : m_javaException(JniLocalRef()) { + if (error.value() != nullptr) { + const JsValue& value = *error.value(); + m_javascriptException = std::make_shared(rt, value); + m_message = GetErrorMessage(rt, value, message); + m_stackTrace = GetErrorStackTrace(rt, value); + m_fullMessage = GetFullMessage(rt, value, m_message); + return; + } + // Keep any "Error: " tag at the FRONT of the composed message, so + // js_util::create_error can rebuild that constructor when this is rethrown. + // Hermes reports a compile failure as a native exception rather than a JS + // throw, and jsi/hermes tags it "SyntaxError: ..." for exactly this reason; + // burying the tag mid-message would turn it back into a plain Error, and + // the Require specs read e.name. + std::string what = error.what(); + std::string tag; + for (const char* name : {"SyntaxError", "TypeError", "RangeError", + "ReferenceError", "EvalError", "URIError"}) { + const std::string prefix = std::string(name) + ": "; + if (what.rfind(prefix, 0) == 0) { + tag = prefix; + what = what.substr(prefix.size()); + break; + } + } + m_message = tag + (message.empty() ? what : message + "\n" + what); + m_stackTrace = error.stack(); + m_fullMessage = m_message; +} + +void NativeScriptException::ReThrowToJs(JsRuntime& rt) { + // Fallback message used if the rich error object cannot be materialized — + // ReThrowToJs must always throw, otherwise the failing Java call silently + // appears to succeed to JS. + const std::string& fallback = !m_fullMessage.empty() ? m_fullMessage + : !m_message.empty() ? m_message + : std::string("Unknown native error."); + + JsValue errObj; + + if (m_javascriptException != nullptr) { + errObj = *m_javascriptException; + if (errObj.isObject()) { + JsObject errorObject = errObj.asObject(rt); + if (!m_fullMessage.empty()) { + errorObject.setProperty(rt, "fullMessage", + ArgConverter::convertToJsString(rt, m_fullMessage)); + } else if (!m_message.empty()) { + errorObject.setProperty(rt, "fullMessage", + ArgConverter::convertToJsString(rt, m_message)); + } + } + } else if (!m_fullMessage.empty()) { + errObj = js_util::create_error(rt, m_fullMessage); + } else if (!m_message.empty()) { + errObj = js_util::create_error(rt, m_message); + } else if (!m_javaException.IsNull()) { + errObj = WrapJavaToJsException(rt); + } else { + errObj = js_util::create_error(rt, "No javascript exception or message provided."); + } + + throw JsError(rt, fallback, errObj, m_stackTrace); +} + +void NativeScriptException::ReThrowToJava(JsRuntime* rt) { + if (rt) { + JSScope scope(*rt); + } + jthrowable ex = nullptr; + JEnv jEnv; + + if (!m_javaException.IsNull()) { + // Static lookup avoids needing the runtime/ObjectManager here, which may + // be unavailable while an exception is being rethrown to Java. + std::string excClassName = ObjectManager::GetClassName((jobject)m_javaException); + + if (excClassName == "com/tns/NativeScriptException") { + ex = m_javaException; + } else { + JniLocalRef msg(jEnv.NewStringUTF("Java Error!")); + JniLocalRef stack(jEnv.NewStringUTF("")); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, (jstring)stack, (jobject)m_javaException)); + } + } else if (m_javascriptException != nullptr && rt != nullptr) { + JsValue errObj = *m_javascriptException; + if (errObj.isObject()) { + auto exObj = TryGetJavaThrowableObject(jEnv, *rt, errObj); + ex = (jthrowable)exObj.Move(); + } + + JniLocalRef msg(jEnv.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(jEnv.NewStringUTF(m_stackTrace.c_str())); + + if (ex == nullptr) { + // The napi tree hands Java the napi_ref itself; here Java gets its own + // owned handle, released by WrapJavaToJsException when it comes back. + // Ownership is then unambiguous: this exception object keeps its + // shared_ptr and Java keeps the copy it was given. + auto* javaOwnedValue = new JsValue(*rt, errObj); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)stackTrace, reinterpret_cast(javaOwnedValue))); + } else { + auto excClassName = ObjectManager::GetClassName(ex); + if (excClassName != "com/tns/NativeScriptException") { + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, (jstring)stackTrace, ex)); + } + } + } else if (!m_message.empty()) { + JniLocalRef msg(jEnv.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(jEnv.NewStringUTF(m_stackTrace.c_str())); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)stackTrace, (jlong)0)); + } else { + JniLocalRef msg(jEnv.NewStringUTF("No java exception or message provided.")); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)nullptr, (jlong)0)); + } + jEnv.Throw(ex); +} + +void NativeScriptException::Init() { + JEnv jenv; + + RUNTIME_CLASS = jenv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + THROWABLE_CLASS = jenv.FindClass("java/lang/Throwable"); + assert(THROWABLE_CLASS != nullptr); + + NATIVESCRIPTEXCEPTION_CLASS = jenv.FindClass("com/tns/NativeScriptException"); + assert(NATIVESCRIPTEXCEPTION_CLASS != nullptr); + + NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = jenv.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;J)V"); + assert(NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID != nullptr); + + NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = jenv.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V"); + assert(NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID != nullptr); + + NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = jenv.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getStackTraceAsString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID != nullptr); + + NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = jenv.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getMessage", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID != nullptr); +} + +// ON UNCAUGHT EXCEPTION +void NativeScriptException::OnUncaughtError(JsRuntime& rt, const JsValue& error) { + string errorMessage = GetErrorMessage(rt, error); + string stackTrace = GetErrorStackTrace(rt, error); + + NativeScriptException e(errorMessage, stackTrace); + e.ReThrowToJava(&rt); +} + +void NativeScriptException::CallJsFuncWithErr(JsRuntime& rt, const JsValue& errObj, bool isDiscarded) { + JsObject global = rt.global(); + + JsValue handler = isDiscarded ? global.getProperty(rt, "__onDiscardedError") + : global.getProperty(rt, "__onUncaughtError"); + + if (handler.isObject() && handler.asObjectBorrowed(rt).isFunction(rt)) { + const JsValue args[] = {errObj}; + handler.asObjectBorrowed(rt).asFunction(rt).callWithThis(rt, global, args, 1); + } +} + +JsValue NativeScriptException::WrapJavaToJsException(JsRuntime& rt) { + JsValue errObj; + + JEnv jenv; + + string excClassName = ObjectManager::GetClassName((jobject)m_javaException); + if (excClassName == "com/tns/NativeScriptException") { + jfieldID fieldID = jenv.GetFieldID(jenv.GetObjectClass(m_javaException), "jsValueAddress", "J"); + jlong addr = jenv.GetLongField(m_javaException, fieldID); + + if (addr != 0) { + auto pv = reinterpret_cast(addr); + errObj = *pv; + delete pv; + } else { + errObj = GetJavaExceptionFromEnv(rt, m_javaException, jenv); + } + } else { + errObj = GetJavaExceptionFromEnv(rt, m_javaException, jenv); + } + + return errObj; +} + +JsValue NativeScriptException::GetJavaExceptionFromEnv(JsRuntime& rt, const JniLocalRef& exc, JEnv& jenv) { + auto errMsg = GetExceptionMessage(jenv, exc); + auto stackTrace = GetExceptionStackTrace(jenv, exc); + DEBUG_WRITE("Error during java interop errorMessage: %s\n stackTrace:\n %s", errMsg.c_str(), stackTrace.c_str()); + + auto objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + + JsValue errObj = js_util::create_error(rt, errMsg, "0"); + if (!errObj.isObject()) { + return errObj; + } + JsObject errorObject = errObj.asObject(rt); + + jint javaObjectID = objectManager->GetOrCreateObjectId((jobject)exc); + auto nativeExceptionObject = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (js_util::is_null_or_undefined(nativeExceptionObject)) { + string className = objectManager->GetClassName((jobject)exc); + nativeExceptionObject = objectManager->CreateJSWrapper(javaObjectID, className); + } + + errorObject.setProperty(rt, "nativeException", nativeExceptionObject); + + string jsStackTraceMessage = GetErrorStackTrace(rt, errObj); + errorObject.setProperty(rt, "stack", ArgConverter::convertToJsString(rt, jsStackTraceMessage)); + errorObject.setProperty(rt, "stackTrace", + ArgConverter::convertToJsString(rt, jsStackTraceMessage + stackTrace)); + + return errObj; +} + +string NativeScriptException::GetFullMessage(JsRuntime& rt, const JsValue& error, const string& jsExceptionMessage) { + if (!js_util::is_error(rt, error)) { + return jsExceptionMessage; + } + + stringstream ss; + ss << jsExceptionMessage; + + string stackTraceMessage = GetErrorStackTrace(rt, error); + + ss << endl << "StackTrace: " << endl << stackTraceMessage << endl; + + string loggedMessage = ss.str(); + + PrintErrorMessage(loggedMessage); + + return loggedMessage; +} + +JniLocalRef NativeScriptException::TryGetJavaThrowableObject(JEnv& env, JsRuntime& rt, const JsValue& jsObj) { + JniLocalRef javaThrowableObject; + + auto objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + + auto javaObj = objectManager->GetJavaObjectByJsObject(jsObj); + JniLocalRef objClass; + + if (!javaObj.IsNull()) { + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } else { + JsValue nativeEx = js_util::get_property(rt, jsObj, "nativeException"); + if (js_util::is_object(nativeEx)) { + javaObj = objectManager->GetJavaObjectByJsObject(nativeEx); + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } + } + + auto isThrowable = !objClass.IsNull() ? env.IsAssignableFrom(objClass, THROWABLE_CLASS) : JNI_FALSE; + + if (isThrowable == JNI_TRUE) { + javaThrowableObject = JniLocalRef(env.NewLocalRef(javaObj)); + } + + return javaThrowableObject; +} + +void NativeScriptException::PrintErrorMessage(const string& errorMessage) { + stringstream ss(errorMessage); + string line; + while (getline(ss, line, '\n')) { + DEBUG_WRITE("%s", line.c_str()); + } +} + +string NativeScriptException::GetErrorMessage(JsRuntime& rt, const JsValue& error, const string& prependMessage) { + if (!js_util::is_error(rt, error)) { + return js_util::coerce_to_string(rt, error); + } + + JsValue message = js_util::get_property(rt, error, "message"); + + string mes = ArgConverter::ConvertToString(rt, message); + + stringstream ss; + + if (!prependMessage.empty()) { + ss << prependMessage << endl; + } + + string errMessage; + bool hasFullErrorMessage = false; + JsValue fullMessage = js_util::get_property(rt, error, "fullMessage"); + if (fullMessage.isString()) { + hasFullErrorMessage = true; + errMessage = ArgConverter::ConvertToString(rt, fullMessage); + ss << errMessage; + } + + if (!mes.empty()) { + if (hasFullErrorMessage) { + ss << endl; + } + ss << mes; + } + + return ss.str(); +} + +string NativeScriptException::GetErrorStackTrace(JsRuntime& rt, const JsValue& error) { + stringstream ss; + + if (!js_util::is_error(rt, error)) return ""; + + JsValue stack = js_util::get_property(rt, error, "stack"); + + string stackStr = ArgConverter::ConvertToString(rt, stack); + ss << stackStr; + + return ss.str(); +} + +string NativeScriptException::GetExceptionMessage(JEnv& env, jthrowable exception) { + string errMsg; + JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID, exception)); + + const char* msgStr = env.GetStringUTFChars(msg, nullptr); + + errMsg.append(msgStr); + + env.ReleaseStringUTFChars(msg, msgStr); + + return errMsg; +} + +string NativeScriptException::GetExceptionStackTrace(JEnv& env, jthrowable exception) { + string errStackTrace; + JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID, exception)); + + const char* msgStr = env.GetStringUTFChars(msg, nullptr); + + errStackTrace.append(msgStr); + + env.ReleaseStringUTFChars(msg, msgStr); + + return errStackTrace; +} + +jclass NativeScriptException::RUNTIME_CLASS = nullptr; +jclass NativeScriptException::THROWABLE_CLASS = nullptr; +jclass NativeScriptException::NATIVESCRIPTEXCEPTION_CLASS = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = nullptr; diff --git a/NativeScript/ffi/jni/jsi/exceptions/NativeScriptException.h b/NativeScript/ffi/jni/jsi/exceptions/NativeScriptException.h new file mode 100644 index 000000000..9df16db86 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/exceptions/NativeScriptException.h @@ -0,0 +1,135 @@ +#ifndef NATIVESCRIPTEXCEPTION_H_ +#define NATIVESCRIPTEXCEPTION_H_ + +#include + +#include "Engine.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "ObjectManager.h" + +namespace tns { +// Derives from std::exception, which the napi tree's copy does not need to. +// There, a native error was reported by calling napi_throw and returning; here +// a C++ throw IS the mechanism, so a NativeScriptException that escapes a host +// callback unwinds through the engine's C++ frames. The engine trampolines +// catch JSError and std::exception; without this base an escapee matches +// neither and terminates the process instead of surfacing as a JS error. +class NativeScriptException : public std::exception { + public: + /* + * Generates a NativeScriptException with java error from environment + */ + NativeScriptException(JEnv& env); + + /* + * Generates a NativeScriptException with given message + */ + NativeScriptException(const std::string& message); + + /* + * Generates a NativeScriptException with given message and stackTrace + */ + NativeScriptException(const std::string& message, const std::string& stackTrace); + + /* + * Generates a NativeScriptException with javascript error from the runtime and a prepend message if any + */ + NativeScriptException(JsRuntime& rt, const JsValue& error, const std::string& message = ""); + + /* + * Generates a NativeScriptException from a caught engine::JSError. This is + * how a JS throw reaches native code here, where the napi tree read a + * pending exception with napi_get_and_clear_last_exception; the JSError + * carries the thrown value when the engine had one, and only its message + * when it did not. + */ + NativeScriptException(JsRuntime& rt, const JsError& error, const std::string& message = ""); + + // The napi counterpart (ReThrowToNapi) sets a pending exception and + // returns, so callers followed it with `return nullptr`. engine:: signals + // JS errors by throwing, and the engine's host-function wrapper converts a + // JSError back into a JS throw carrying the original value -- so this does + // not return, and any statement after a call to it is unreachable. + [[noreturn]] void ReThrowToJs(JsRuntime& rt); + void ReThrowToJava(JsRuntime* rt); + + // The stored message, for logging uncaught native exceptions. + const char* what() const noexcept override { return m_message.c_str(); } + + static void Init(); + + /* + * This handler is attached to the runtime to handle uncaught javascript exceptions. + */ + static void OnUncaughtError(JsRuntime& rt, const JsValue& error); + + /* + * Calls the global "__onUncaughtError" or "__onDiscardedError" if such is provided + */ + static void CallJsFuncWithErr(JsRuntime& rt, const JsValue& errObj, bool isDiscarded); + + private: + /* + * Try to get native exception or NativeScriptException from js object + */ + JniLocalRef TryGetJavaThrowableObject(JEnv& env, JsRuntime& rt, const JsValue& jsObj); + + /* + * Gets java exception message from jthrowable + */ + std::string GetExceptionMessage(JEnv& env, jthrowable exception); + + /* + * Gets java exception stack trace from jthrowable + */ + std::string GetExceptionStackTrace(JEnv& env, jthrowable exception); + + /* + * Gets the member m_javaException, wraps it and creates a javascript error object from it + */ + JsValue WrapJavaToJsException(JsRuntime& rt); + + /* + * Gets all the information from a java exception and puts it in a javascript error object + */ + JsValue GetJavaExceptionFromEnv(JsRuntime& rt, const JniLocalRef& exc, JEnv& jenv); + + /* + * Gets all the information from a js message and an js error object and puts it in a string + */ + static std::string GetErrorMessage(JsRuntime& rt, const JsValue& error, const std::string& prependMessage = ""); + + /* + * Generates string stack trace from js StackTrace + */ + static std::string GetErrorStackTrace(JsRuntime& rt, const JsValue& stackTrace); + + /* + * Adds a prepend message to the normal message process + */ + std::string GetFullMessage(JsRuntime& rt, const JsValue& error, const std::string& jsExceptionMessage); + + // A napi_ref in the napi tree. An owned engine::Value is already the + // persistent handle a reference was, so the refcount goes away; shared_ptr + // keeps the exception copyable, which `catch (NativeScriptException& e)` + // followed by a rethrow relies on. + std::shared_ptr m_javascriptException; + JniLocalRef m_javaException; + std::string m_message; + std::string m_stackTrace; + std::string m_fullMessage; + + static jclass RUNTIME_CLASS; + static jclass THROWABLE_CLASS; + static jclass NATIVESCRIPTEXCEPTION_CLASS; + static jmethodID NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID; + + static void PrintErrorMessage(const std::string& errorMessage); +}; +} + +#endif /* NATIVESCRIPTEXCEPTION_H_ */ diff --git a/NativeScript/ffi/jni/jsi/finalizer/FinalizerQueue.cpp b/NativeScript/ffi/jni/jsi/finalizer/FinalizerQueue.cpp new file mode 100644 index 000000000..0a298c680 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/finalizer/FinalizerQueue.cpp @@ -0,0 +1,153 @@ +#include "FinalizerQueue.h" +#include "JEnv.h" +#include "NativeScriptException.h" +#include "NativeScriptAssert.h" +#include "Runtime.h" +#include + +using namespace tns; + +jclass FinalizerQueue::HANDLER_CLASS = nullptr; +jmethodID FinalizerQueue::HANDLER_CTOR = nullptr; +jmethodID FinalizerQueue::HANDLER_SCHEDULE = nullptr; +jmethodID FinalizerQueue::HANDLER_RELEASE = nullptr; + +FinalizerQueue::FinalizerQueue(JsRuntime *rt) : rt_(rt) { + JEnv jEnv; + if (HANDLER_CLASS == nullptr) { + HANDLER_CLASS = jEnv.FindClass("com/tns/FinalizerHandler"); + assert(HANDLER_CLASS != nullptr); + HANDLER_CTOR = jEnv.GetMethodID(HANDLER_CLASS, "", "(J)V"); + HANDLER_SCHEDULE = jEnv.GetMethodID(HANDLER_CLASS, "schedule", "()V"); + HANDLER_RELEASE = jEnv.GetMethodID(HANDLER_CLASS, "release", "()V"); + } + + // Bind a FinalizerHandler to the current (runtime) thread's Looper. + jobject localHandler = jEnv.NewObject(HANDLER_CLASS, HANDLER_CTOR, + reinterpret_cast(this)); + handler_ = jEnv.NewGlobalRef(localHandler); +} + +FinalizerQueue::~FinalizerQueue() { + Destroy(); +} + +void FinalizerQueue::Post(Finalize cb, void *data, void *hint) { + if (cb == nullptr) { + return; + } + + bool runInline = false; + jobject handler = nullptr; // captured under the lock so Destroy can't free it mid-use + { + std::lock_guard lock(mutex_); + if (stopped_) { + // Teardown: the loop is no longer draining us. Fall back to running the + // cleanup inline (matches the pre-deferral behavior for this edge). + runInline = true; + } else { + queue_.push_back({cb, data, hint}); + // Only wake the loop on the empty -> non-empty transition; further posts + // ride the already-scheduled drain. + if (!scheduled_) { + scheduled_ = true; + handler = handler_; + } + } + } + + if (runInline) { + cb(*rt_, data, hint); + return; + } + + if (handler != nullptr) { + JEnv jEnv; + jEnv.CallVoidMethod(handler, HANDLER_SCHEDULE); + } +} + +void FinalizerQueue::Drain() { + // Take the whole batch under the lock, then run callbacks outside it: a + // callback may free objects whose GC finalizers Post() again, and that must + // not deadlock on the queue mutex. Re-posted work sets scheduled_ = true and + // wakes the loop for the next tick. + std::vector batch; + { + std::lock_guard lock(mutex_); + scheduled_ = false; + batch.swap(queue_); + } + + // No per-callback handle scope here. An engine::Value owns its handle, so a + // value a callback materialises is rooted by the Value itself rather than by + // an enclosing scope; the engine entry (JSScope, opened in + // nativeDrainFinalizers) supplies the isolate/context the engine calls need. + for (auto &entry: batch) { + if (entry.cb != nullptr) { + entry.cb(*rt_, entry.data, entry.hint); + } + } +} + +void FinalizerQueue::Destroy() { + // Mark stopped and detach the handler under the lock, so a concurrent Post + // (possible from a background JS thread's GC) either observes stopped_ and + // runs inline, or has already captured the handler before we release it. + std::vector batch; + jobject handler = nullptr; + { + std::lock_guard lock(mutex_); + if (stopped_) { + return; + } + stopped_ = true; + handler = handler_; + handler_ = nullptr; + batch.swap(queue_); + } + + if (handler != nullptr) { + JEnv jEnv; + jEnv.CallVoidMethod(handler, HANDLER_RELEASE); + jEnv.DeleteGlobalRef(handler); + } + + // Run whatever was still queued while the runtime is valid; finalizers that + // fire during the subsequent teardown then run inline via PostFinalizer. + for (auto &entry: batch) { + if (entry.cb != nullptr) { + entry.cb(*rt_, entry.data, entry.hint); + } + } +} + +void tns::PostFinalizer(JsRuntime &rt, FinalizerQueue::Finalize cb, void *data, void *hint) { + Runtime::PostFinalizer(rt, cb, data, hint); +} + +// Reverse-native for com.tns.FinalizerHandler.nativeDrainFinalizers (bound by +// symbol name). Runs on the runtime thread at a safe, post-GC message-loop tick. +extern "C" JNIEXPORT void JNICALL +Java_com_tns_FinalizerHandler_nativeDrainFinalizers(JNIEnv *jniEnv, jclass clazz, jlong queuePtr) { + auto *queue = reinterpret_cast(queuePtr); + if (queue == nullptr) { + return; + } + try { + // Enter the JS scope (lock + isolate/context) before running any callback, + // since they call into the engine. + JSScope scope(*queue->Rt()); + queue->Drain(); + } catch (NativeScriptException &e) { + e.ReThrowToJava(nullptr); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} diff --git a/NativeScript/ffi/jni/jsi/finalizer/FinalizerQueue.h b/NativeScript/ffi/jni/jsi/finalizer/FinalizerQueue.h new file mode 100644 index 000000000..6d5f550c1 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/finalizer/FinalizerQueue.h @@ -0,0 +1,79 @@ +#ifndef TEST_APP_FINALIZER_QUEUE_H +#define TEST_APP_FINALIZER_QUEUE_H + +#include +#include +#include +#include "Engine.h" + +namespace tns { + /** + * Engine-agnostic deferral for finalizer cleanup that must touch the JS heap + * (e.g. releasing an owned engine handle), which is illegal from inside a GC + * finalizer on every engine (V8's InvokeFinalizerFromGC; a reentrant + * JS_FreeValue during a QuickJS sweep corrupts the collector; etc.). + * + * A finalizer calls FinalizerQueue::Post — which only allocates and appends, + * never touching the JS heap, so it is safe to run mid-GC on any thread. The + * queued callbacks are drained on the runtime thread's Java message loop (see + * com.tns.FinalizerHandler), a point guaranteed to be outside any GC sweep + * with JS unwound to the host. + * + * Owned by Runtime; there is one per runtime. Post is thread-safe; Drain + * and Destroy run on the runtime thread. + */ + class FinalizerQueue { + public: + using Finalize = void (*)(JsRuntime &rt, void *data, void *hint); + + // Binds a com.tns.FinalizerHandler to the CURRENT thread's Looper, so this + // MUST be constructed on the runtime thread. + explicit FinalizerQueue(JsRuntime *rt); + + ~FinalizerQueue(); + + // Schedules cb(rt, data, hint) to run at the next message-loop tick. + // Thread-safe and safe to call from inside a GC finalizer (no JS-heap + // interaction). A no-op cb is ignored. + void Post(Finalize cb, void *data, void *hint); + + // Runs all currently-queued callbacks. Invoked from FinalizerHandler on the + // runtime thread; the caller opens a JS scope first. + void Drain(); + + // Releases the Java handler and runs any still-queued callbacks inline. + // Must run on the runtime thread while the runtime is still valid. + // Idempotent. + void Destroy(); + + JsRuntime *Rt() const { return rt_; } + + private: + struct Entry { + Finalize cb; + void *data; + void *hint; + }; + + JsRuntime *rt_; + std::mutex mutex_; + std::vector queue_; + bool scheduled_ = false; + bool stopped_ = false; + jobject handler_ = nullptr; // global ref to com.tns.FinalizerHandler + + // Cached (process-wide) FinalizerHandler JNI ids. + static jclass HANDLER_CLASS; + static jmethodID HANDLER_CTOR; + static jmethodID HANDLER_SCHEDULE; + static jmethodID HANDLER_RELEASE; + }; + + // Convenience wrapper: defers cb(rt, data, hint) to the owning runtime's + // post-GC finalizer drain. Safe to call from inside a GC finalizer. Falls back + // to running inline if the runtime is unavailable/tearing down. Lets callers + // (e.g. modules) defer without pulling in the heavy Runtime.h. + void PostFinalizer(JsRuntime &rt, FinalizerQueue::Finalize cb, void *data, void *hint); +} + +#endif //TEST_APP_FINALIZER_QUEUE_H diff --git a/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp b/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp new file mode 100644 index 000000000..9b87b90f8 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/global/GlobalHelpers.cpp @@ -0,0 +1,221 @@ +#include "GlobalHelpers.h" +#include "ArgConverter.h" +#include "CallbackHandlers.h" +#include "Constants.h" +#include "JEnv.h" +#include "NativeScriptException.h" +#include +#include "robin_hood.h" +#include "Util.h" +#include + +using namespace std; +using namespace tns; + +// Keyed by JsRuntime::identity(); &rt is not stable across callbacks. +static robin_hood::unordered_map rtToPersistentSmartJSONStringify = + robin_hood::unordered_map(); + +static JsFunction *GetSmartJSONStringifyFunction(JsRuntime &rt) { + auto it = rtToPersistentSmartJSONStringify.find(rt.identity()); + if (it != rtToPersistentSmartJSONStringify.end()) { + return &it->second; + } + + const char * smartStringifyFunctionScript = R"( + (function () { + function smartStringify(object, handleCirculars) { + if (!handleCirculars) { + return JSON.stringify(object, null, 2); + } + + const seen = []; + var replacer = function (key, value) { + if (value != null && typeof value == "object") { + if (seen.indexOf(value) >= 0) { + if (key) { + return "[Circular]"; + } + return; + } + seen.push(value); + } + return value; + }; + return JSON.stringify(object, replacer, 2); + } + return smartStringify; +})(); +)"; + + JsValue result; + try { + result = rt.evaluateJavaScript( + std::make_shared(smartStringifyFunctionScript), + ""); + } catch (JsError &) { + return nullptr; + } + + if (!result.isObject()) { + return nullptr; + } + + auto object = result.asObject(rt); + if (!object.isFunction(rt)) { + return nullptr; + } + + auto emplaced = rtToPersistentSmartJSONStringify.emplace(rt.identity(), object.asFunction(rt)); + return &emplaced.first->second; +} + + + +std::string tns::JsonStringifyObject(JsRuntime &rt, const JsValue &value, + bool handleCircularReferences) { + if (value.isUndefined()) { + return ""; + } + + JsFunction *smartJSONStringifyFunction = GetSmartJSONStringifyFunction(rt); + std::string result; + if (smartJSONStringifyFunction != nullptr) { + const JsValue args[] = {JsValue(rt, value), JsValue(handleCircularReferences)}; + try { + JsValue resultValue = smartJSONStringifyFunction->call(rt, args, (size_t) 2); + result = ArgConverter::ConvertToString(rt, resultValue); + } catch (JsError &e) { + if (e.value() != nullptr) { + throw NativeScriptException(rt, *e.value(), "Error converting object to json"); + } + throw NativeScriptException("Error converting object to json"); + } + } + + return result; +} + +JsValue tns::JsonParseString(JsRuntime &rt, const std::string &value) { + auto global = rt.global(); + auto jsonValue = global.getProperty(rt, "JSON"); + if (!jsonValue.isObject()) { + return js_util::undefined(); + } + auto json = jsonValue.asObject(rt); + auto parse = json.getPropertyAsFunction(rt, "parse"); + + const JsValue args[] = {ArgConverter::convertToJsString(rt, value)}; + try { + return parse.callWithThis(rt, json, args, (size_t) 1); + } catch (JsError &e) { + if (e.value() != nullptr) { + throw NativeScriptException(rt, *e.value(), "Error converting json string to object"); + } + throw NativeScriptException("Error converting json string to object"); + } +} + +std::vector tns::BuildStacktraceFrames(JsRuntime &rt, const JsValue *error, + int size) { + std::vector frames; + JsValue stack; + if (error != nullptr) { + if (!error->isObject()) return frames; + stack = error->asObjectBorrowed(rt).getProperty(rt, "stack"); + } else { + // The napi tree branched on __HERMES__ / __PRIMJS__ to build the carrier + // error, because napi_create_error was not usable on all of them. + // Invoking the Error constructor works identically on every engine and + // needs no per-engine branch. + // + // It must be a *call* into the constructor, not an evaluated + // `new Error()` script: evaluating one pushes the script's own frame + // onto the stack, so every frame index shifts by one. That is not + // cosmetic -- MetadataNode::GetExtendLocation builds generated class + // names out of frames[0], and it produced names like + // "Button1__1_-59_" that no dex proxy exists for. + JsValue err; + try { + err = JsValue(rt, GlobalHelpers::CreateError(rt, "")); + } catch (JsError &) { + return frames; + } + if (!err.isObject()) return frames; + stack = err.asObject(rt).getProperty(rt, "stack"); + } + + if (js_util::is_null_or_undefined(stack)) return frames; + + string stackTrace = js_util::get_string_value(rt, stack); + vector stackLines; + Util::SplitString(stackTrace, "\n", stackLines); + + // Source modules carry a full "file://…" URL in every frame, so this matches + // those exactly as before (also covers JSC's "func@file://…:line:col" form). + const regex schemeRegex(R"((file:.*):(\d+):(\d+))"); +#ifdef NS_BYTECODE_ENABLED + // Bytecode modules embed an app-relative source name (e.g. "shared/index.js") + // at compile time — see tools/bytecode-compiler/compile-bytecode.js — because + // the device-absolute path can't be baked in ahead of time. Those frames have + // no scheme, so match a "(path:line:col)" (or leading-space) form and rebuild + // the full runtime URL from the app root. A leading "(" or space anchors the + // path so a function name is never glued on; "@" stays a valid path char so + // scoped modules (tns_modules/@nativescript/…) survive. Bytecode engines + // (Hermes/QuickJS/PrimJS) all emit the parenthesised V8-style frame, so the + // "@"-delimited (JSC) form never reaches here — and JSC has no bytecode. + const regex bareRegex(R"RE([(\s]([^\s():]+):(\d+):(\d+))RE"); +#endif + +#if defined(NS_JSI_HOST_RUNTIME) + // A guest's modules come from the embedder's bundler, and its frames carry + // no "file:" scheme -- Metro serves "http://10.0.2.2:8081/index.bundle?..." + // in development and an "index.android.bundle" path in release. Without a + // match there are no frames at all, and MetadataNode::GetExtendLocation then + // has no call site to name an unnamed extend() after, so every one of them + // collapses onto the same generated class. + // + // Anchored the same way as the bytecode form, and the scheme pattern above + // still runs first, so nothing changes for a frame that does carry one. + const regex hostRegex(R"RE([(\s@]([A-Za-z][A-Za-z\d+.-]*://[^\s()]+|[^\s()@]+):(\d+):(\d+))RE"); +#endif + + int current = 0; + for (auto &frame : stackLines) { + smatch match; + std::string filePath; + if (regex_search(frame, match, schemeRegex)) { + filePath = match[1].str(); + } +#ifdef NS_BYTECODE_ENABLED + else if (regex_search(frame, match, bareRegex)) { + filePath = "file://" + Constants::APP_ROOT_FOLDER_PATH + match[1].str(); + } +#endif +#if defined(NS_JSI_HOST_RUNTIME) + else if (regex_search(frame, match, hostRegex)) { + filePath = match[1].str(); + } +#endif + else { + continue; + } + current++; + frames.emplace_back(stoi(match[2].str()), + stoi(match[3].str()), + filePath, + frame); + if (current == size) break; + } + return frames; +} + +JsObject tns::GlobalHelpers::CreateError(JsRuntime &rt, const std::string &message) { + auto errorCtor = rt.global().getPropertyAsFunction(rt, "Error"); + const JsValue args[] = {js_util::to_js_string(rt, message)}; + return errorCtor.callAsConstructor(rt, args, (size_t) 1).asObject(rt); +} + +void tns::GlobalHelpers::onDisposeRuntime(JsRuntime &rt) { + rtToPersistentSmartJSONStringify.erase(rt.identity()); +} diff --git a/NativeScript/ffi/jni/jsi/global/GlobalHelpers.h b/NativeScript/ffi/jni/jsi/global/GlobalHelpers.h new file mode 100644 index 000000000..7dbc37b1d --- /dev/null +++ b/NativeScript/ffi/jni/jsi/global/GlobalHelpers.h @@ -0,0 +1,41 @@ +#ifndef JSI_GLOBALHELPERS_H_ +#define JSI_GLOBALHELPERS_H_ + +#include "jni.h" +#include "Engine.h" +#include +#include +#include +#include + +namespace tns { +std::string JsonStringifyObject(JsRuntime& rt, const JsValue& value, bool handleCircularReferences = true); + +JsValue JsonParseString(JsRuntime& rt, const std::string& value); + +struct JsStacktraceFrame { + JsStacktraceFrame(): line(0), col(0) {} + JsStacktraceFrame( + int _line, int _col, std::string _filename, std::string _text + ): line(_line), col(_col), filename(std::move(_filename)), text(std::move(_text)) {} + + int line; + int col; + std::string filename; + std::string text; +}; + +// `error` is optional: pass nullptr to capture the current stack instead. +std::vector BuildStacktraceFrames(JsRuntime& rt, const JsValue* error, int size); + +namespace GlobalHelpers { + // `new Error(message)`. The napi tree's Console reached for napi_create_error + // here; engine:: has no error factory (JSError is the C++ carrier, not a + // constructor), so the Error constructor is called through the runtime. + JsObject CreateError(JsRuntime& rt, const std::string& message); + + void onDisposeRuntime(JsRuntime& rt); +} +} + +#endif /* JSI_GLOBALHELPERS_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp b/NativeScript/ffi/jni/jsi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp new file mode 100644 index 000000000..fdbe50d60 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp @@ -0,0 +1,11 @@ +#include "DesugaredInterfaceCompanionClassNameResolver.h" + +std::string DesugaredInterfaceCompanionClassNameResolver::resolveD8InterfaceCompanionClassName( + const std::string& interfaceName) { + return interfaceName + D8_COMPANION_CLASS_SUFFIX; +} + +std::string DesugaredInterfaceCompanionClassNameResolver::resolveBazelInterfaceCompanionClassName( + const std::string& interfaceName) { + return interfaceName + BAZEL_COMPANION_CLASS_SUFFIX; +} diff --git a/NativeScript/ffi/jni/jsi/jni/DesugaredInterfaceCompanionClassNameResolver.h b/NativeScript/ffi/jni/jsi/jni/DesugaredInterfaceCompanionClassNameResolver.h new file mode 100644 index 000000000..052dc8952 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/DesugaredInterfaceCompanionClassNameResolver.h @@ -0,0 +1,22 @@ +#ifndef TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H +#define TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H + + +#include + +class DesugaredInterfaceCompanionClassNameResolver { + +public: + std::string resolveD8InterfaceCompanionClassName(const std::string& interfaceName); + + std::string resolveBazelInterfaceCompanionClassName(const std::string& interfaceName); + +private: + const std::string BAZEL_COMPANION_CLASS_SUFFIX = "$$CC"; + const std::string D8_COMPANION_CLASS_SUFFIX = "$-CC"; + + +}; + + +#endif //TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H diff --git a/NativeScript/ffi/jni/jsi/jni/DirectBuffer.cpp b/NativeScript/ffi/jni/jsi/jni/DirectBuffer.cpp new file mode 100644 index 000000000..1b3e6f8a3 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/DirectBuffer.cpp @@ -0,0 +1,56 @@ +#include "DirectBuffer.h" +#include "JniLocalRef.h" + +using namespace tns; + +DirectBuffer::DirectBuffer(uint32_t length) { + m_length = length; + + m_data = new int[m_length]; + + m_end = m_data + m_length; + + Reset(); + + int capacity = m_length * sizeof(int); + + JEnv env; + JniLocalRef buff(env.NewDirectByteBuffer(m_data, capacity)); + + m_buff = env.NewGlobalRef(buff); +} + +DirectBuffer::operator jobject() const { + return m_buff; +} + +int* DirectBuffer::GetData() const { + return m_data; +} + +int DirectBuffer::Length() const { + return m_length; +} + +int DirectBuffer::Size() const { + return m_pos - m_data; +} + +void DirectBuffer::Reset() { + m_pos = m_data; +} + +bool DirectBuffer::Write(int value) { + bool canWrite = m_pos < m_end; + if (canWrite) { + int bigEndianInt = __builtin_bswap32(value); + *(m_pos++) = bigEndianInt; + } + return canWrite; +} + +DirectBuffer::~DirectBuffer() { + JEnv env; + env.DeleteGlobalRef(m_buff); + delete[] m_data; +} diff --git a/NativeScript/ffi/jni/jsi/jni/DirectBuffer.h b/NativeScript/ffi/jni/jsi/jni/DirectBuffer.h new file mode 100644 index 000000000..4de8a8a63 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/DirectBuffer.h @@ -0,0 +1,30 @@ +#ifndef DIRECTBUFFER_H_ +#define DIRECTBUFFER_H_ + +#include "JEnv.h" + +namespace tns { +class DirectBuffer { + public: + DirectBuffer(uint32_t capacity = 65536); + ~DirectBuffer(); + + operator jobject() const; + + int* GetData() const; + int Length() const; + int Size() const; + + void Reset(); + bool Write(int value); + + private: + jobject m_buff; + int* m_data; + jlong m_length; + int* m_pos; + int* m_end; +}; +} + +#endif /* DIRECTBUFFER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/File.cpp b/NativeScript/ffi/jni/jsi/jni/File.cpp new file mode 100644 index 000000000..0dad918da --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/File.cpp @@ -0,0 +1,108 @@ +/* + * File.cpp + * + * Created on: Jun 24, 2015 + * Author: gatanasov + */ + +#include "File.h" +#include +#include +#include +#include + +using namespace std; + +namespace tns { + +string File::ReadText(const string& filePath) { + int len; + bool isNew; + const char* content = ReadText(filePath, len, isNew); + + string s(content, len); + + if (isNew) { + delete[] content; + } + + return s; +} + +void* File::ReadBinary(const string& filePath, int& length) { + length = 0; + + auto file = fopen(filePath.c_str(), READ_BINARY); + if (!file) { + return nullptr; + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + rewind(file); + + uint8_t* data = new uint8_t[length]; + fread(data, sizeof(uint8_t), length, file); + fclose(file); + + return data; +} + +bool File::WriteBinary(const string& filePath, const void* data, int length) { + auto file = fopen(filePath.c_str(), WRITE_BINARY); + if (!file) { + return false; + } + + auto writtenBytes = fwrite(data, sizeof(uint8_t), length, file); + fclose(file); + + return writtenBytes == length; +} + +const char* File::ReadText(const string& filePath, int& charLength, bool& isNew) { + FILE* file = fopen(filePath.c_str(), "rb"); + fseek(file, 0, SEEK_END); + + charLength = ftell(file); + isNew = charLength > BUFFER_SIZE; + + rewind(file); + + if (isNew) { + char* newBuffer = new char[charLength]; + fread(newBuffer, 1, charLength, file); + fclose(file); + + return newBuffer; + } + + fread(Buffer, 1, charLength, file); + fclose(file); + + return Buffer; +} + +std::unique_ptr File::ReadFile(const std::string &filePath, int &length, int extraBuffer) { + FILE *file = fopen(filePath.c_str(), "rb"); + if (!file) { + std::stringstream ss; + ss << "metadata file (" << filePath << ") couldn't be opened! (Error: " << errno << ") "; +// throw NativeScriptException(ss.str()); + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + std::unique_ptr buffer(new char[length + extraBuffer]); + rewind(file); + fread(buffer.get(), 1, length, file); + fclose(file); + + return buffer; + } + +char* File::Buffer = new char[BUFFER_SIZE]; + +const char* File::WRITE_BINARY = "wb"; +const char* File::READ_BINARY = "rb"; +} diff --git a/NativeScript/ffi/jni/jsi/jni/File.h b/NativeScript/ffi/jni/jsi/jni/File.h new file mode 100644 index 000000000..de9509282 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/File.h @@ -0,0 +1,29 @@ +/* + * File.h + * + * Created on: Jun 24, 2015 + * Author: gatanasov + */ + +#ifndef JNI_FILE_H_ +#define JNI_FILE_H_ + +#include + +namespace tns { +class File { + public: + static const char* ReadText(const std::string& filePath, int& length, bool& isNew); + static std::string ReadText(const std::string& filePath); + static bool WriteBinary(const std::string& filePath, const void* inData, int length); + static void* ReadBinary(const std::string& filePath, int& length); + static std::unique_ptr ReadFile(const std::string &filePath, int &length, int extraBuffer = 0); +private: + static const int BUFFER_SIZE = 1024 * 1024; + static char* Buffer; + static const char* WRITE_BINARY; + static const char* READ_BINARY; +}; +} + +#endif /* JNI_FILE_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/JEnv.cpp b/NativeScript/ffi/jni/jsi/jni/JEnv.cpp new file mode 100644 index 000000000..190b2817a --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JEnv.cpp @@ -0,0 +1,901 @@ +#include "JEnv.h" +#include +#include "Util.h" +#include "DesugaredInterfaceCompanionClassNameResolver.h" +#include "NativeScriptException.h" + +using namespace tns; +using namespace std; + +JEnv::JEnv() + : m_env(nullptr) { + JNIEnv *env = nullptr; + jint ret = s_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + + if ((ret != JNI_OK) || (env == nullptr)) { + ret = s_jvm->AttachCurrentThread(&env, nullptr); + assert(ret == JNI_OK); + assert(env != nullptr); + } + + m_env = env; +} + +JEnv::JEnv(JNIEnv *jniEnv) { + jint ret = s_jvm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6); + + if ((ret != JNI_OK) || (jniEnv == nullptr)) { + ret = s_jvm->AttachCurrentThread(&jniEnv, nullptr); + assert(ret == JNI_OK); + assert(jniEnv != nullptr); + } + + m_env = jniEnv; +} + +JEnv::~JEnv() { +} + +JEnv::operator JNIEnv* () const { + return m_env; +} + +jmethodID JEnv::GetMethodID(jclass clazz, const string &name, const string &sig) { + jmethodID mid = m_env->GetMethodID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return mid; +} + +jmethodID JEnv::GetStaticMethodID(jclass clazz, const string &name, const string &sig) { + jmethodID mid = m_env->GetStaticMethodID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return mid; +} + +jfieldID JEnv::GetFieldID(jclass clazz, const string &name, const string &sig) { + jfieldID fid = m_env->GetFieldID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return fid; +} + +jfieldID JEnv::GetStaticFieldID(jclass clazz, const string &name, const string &sig) { + jfieldID fid = m_env->GetStaticFieldID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return fid; +} + +void JEnv::CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + m_env->CallStaticVoidMethodA(clazz, methodID, args); + CheckForJavaException(); +} + +void JEnv::CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + m_env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args); + CheckForJavaException(); +} + +void JEnv::CallVoidMethodA(jobject obj, jmethodID methodID, jvalue *args) { + m_env->CallVoidMethodA(obj, methodID, args); + CheckForJavaException(); +} + +jboolean JEnv::CallStaticBooleanMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallStaticBooleanMethodA(clazz, methodID, args); + CheckForJavaException(); + return jbl; +} + +jboolean +JEnv::CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallNonvirtualBooleanMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jbl; +} + +jboolean JEnv::CallBooleanMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallBooleanMethodA(obj, methodID, args); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::CallStaticByteMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallStaticByteMethodA(clazz, methodID, args); + CheckForJavaException(); + return jbt; +} + +jbyte JEnv::CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallNonvirtualByteMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jbt; +} + +jbyte JEnv::CallByteMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallByteMethodA(obj, methodID, args); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::CallStaticCharMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallStaticCharMethodA(clazz, methodID, args); + CheckForJavaException(); + return jch; +} + +jchar JEnv::CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallNonvirtualCharMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jch; +} + +jchar JEnv::CallCharMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallCharMethodA(obj, methodID, args); + CheckForJavaException(); + return jch; +} + +jshort JEnv::CallStaticShortMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallStaticShortMethodA(clazz, methodID, args); + CheckForJavaException(); + return jsh; + +} + +jshort +JEnv::CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallNonvirtualShortMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jsh; +} + +jshort JEnv::CallShortMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallShortMethodA(obj, methodID, args); + CheckForJavaException(); + return jsh; +} + +jint JEnv::CallStaticIntMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallStaticIntMethodA(clazz, methodID, args); + CheckForJavaException(); + return ji; + +} + +jint JEnv::CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallNonvirtualIntMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return ji; +} + +jint JEnv::CallIntMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallIntMethodA(obj, methodID, args); + CheckForJavaException(); + return ji; +} + +jlong JEnv::CallStaticLongMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallStaticLongMethodA(clazz, methodID, args); + CheckForJavaException(); + return jl; +} + +jlong JEnv::CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallNonvirtualLongMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jl; +} + +jlong JEnv::CallLongMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallLongMethodA(obj, methodID, args); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::CallStaticFloatMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallStaticFloatMethodA(clazz, methodID, args); + CheckForJavaException(); + return jfl; +} + +jfloat +JEnv::CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallNonvirtualFloatMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jfl; +} + +jfloat JEnv::CallFloatMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallFloatMethodA(obj, methodID, args); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::CallStaticDoubleMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallStaticDoubleMethodA(clazz, methodID, args); + CheckForJavaException(); + return jdb; +} + +jdouble +JEnv::CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallNonvirtualDoubleMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jdb; +} + +jdouble JEnv::CallDoubleMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallDoubleMethodA(obj, methodID, args); + CheckForJavaException(); + return jdb; +} + +jobject JEnv::CallStaticObjectMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallStaticObjectMethodA(clazz, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject +JEnv::CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallNonvirtualObjectMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject JEnv::CallObjectMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallObjectMethodA(obj, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject JEnv::GetStaticObjectField(jclass clazz, jfieldID fieldID) { + jobject jo = m_env->GetStaticObjectField(clazz, fieldID); + CheckForJavaException(); + return jo; +} + +jboolean JEnv::GetStaticBooleanField(jclass clazz, jfieldID fieldID) { + jboolean jbl = m_env->GetStaticBooleanField(clazz, fieldID); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::GetStaticByteField(jclass clazz, jfieldID fieldID) { + jbyte jbt = m_env->GetStaticByteField(clazz, fieldID); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::GetStaticCharField(jclass clazz, jfieldID fieldID) { + jchar jch = m_env->GetStaticCharField(clazz, fieldID); + CheckForJavaException(); + return jch; +} + +jshort JEnv::GetStaticShortField(jclass clazz, jfieldID fieldID) { + jshort jsh = m_env->GetStaticShortField(clazz, fieldID); + CheckForJavaException(); + return jsh; +} + +jint JEnv::GetStaticIntField(jclass clazz, jfieldID fieldID) { + jint ji = m_env->GetStaticIntField(clazz, fieldID); + CheckForJavaException(); + return ji; +} + +jlong JEnv::GetStaticLongField(jclass clazz, jfieldID fieldID) { + jlong jl = m_env->GetStaticLongField(clazz, fieldID); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::GetStaticFloatField(jclass clazz, jfieldID fieldID) { + jfloat jfl = m_env->GetStaticFloatField(clazz, fieldID); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::GetStaticDoubleField(jclass clazz, jfieldID fieldID) { + jdouble jd = m_env->GetStaticDoubleField(clazz, fieldID); + CheckForJavaException(); + return jd; +} + +void JEnv::SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) { + m_env->SetStaticObjectField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value) { + m_env->SetStaticBooleanField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value) { + m_env->SetStaticByteField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value) { + m_env->SetStaticCharField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value) { + m_env->SetStaticShortField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticIntField(jclass clazz, jfieldID fieldID, jint value) { + m_env->SetStaticIntField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value) { + m_env->SetStaticLongField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value) { + m_env->SetStaticFloatField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value) { + m_env->SetStaticDoubleField(clazz, fieldID, value); + CheckForJavaException(); +} + +jobject JEnv::GetObjectField(jobject obj, jfieldID fieldID) { + jobject jo = m_env->GetObjectField(obj, fieldID); + CheckForJavaException(); + return jo; +} + +jboolean JEnv::GetBooleanField(jobject obj, jfieldID fieldID) { + jboolean jbl = m_env->GetBooleanField(obj, fieldID); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::GetByteField(jobject obj, jfieldID fieldID) { + jbyte jbt = m_env->GetByteField(obj, fieldID); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::GetCharField(jobject obj, jfieldID fieldID) { + jchar jch = m_env->GetCharField(obj, fieldID); + CheckForJavaException(); + return jch; +} + +jshort JEnv::GetShortField(jobject obj, jfieldID fieldID) { + jshort jsh = m_env->GetShortField(obj, fieldID); + CheckForJavaException(); + return jsh; +} + +jint JEnv::GetIntField(jobject obj, jfieldID fieldID) { + jint ji = m_env->GetIntField(obj, fieldID); + CheckForJavaException(); + return ji; +} + +jlong JEnv::GetLongField(jobject obj, jfieldID fieldID) { + jlong jl = m_env->GetLongField(obj, fieldID); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::GetFloatField(jobject obj, jfieldID fieldID) { + jfloat jfl = m_env->GetFloatField(obj, fieldID); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::GetDoubleField(jobject obj, jfieldID fieldID) { + jdouble jd = m_env->GetDoubleField(obj, fieldID); + CheckForJavaException(); + return jd; +} + +void JEnv::SetObjectField(jobject obj, jfieldID fieldID, jobject value) { + m_env->SetObjectField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) { + m_env->SetBooleanField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetByteField(jobject obj, jfieldID fieldID, jbyte value) { + m_env->SetByteField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetCharField(jobject obj, jfieldID fieldID, jchar value) { + m_env->SetCharField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetShortField(jobject obj, jfieldID fieldID, jshort value) { + m_env->SetShortField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetIntField(jobject obj, jfieldID fieldID, jint value) { + m_env->SetIntField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetLongField(jobject obj, jfieldID fieldID, jlong value) { + m_env->SetLongField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetFloatField(jobject obj, jfieldID fieldID, jfloat value) { + m_env->SetFloatField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) { + m_env->SetDoubleField(obj, fieldID, value); + CheckForJavaException(); +} + +jstring JEnv::NewString(const jchar *unicodeChars, jsize len) { + jstring jst = m_env->NewString(unicodeChars, len); + CheckForJavaException(); + return jst; +} + +jstring JEnv::NewStringUTF(const char *bytes) { + jstring jst = m_env->NewStringUTF(bytes); + CheckForJavaException(); + return jst; +} + +jobjectArray JEnv::NewObjectArray(jsize length, jclass elementClass, jobject initialElement) { + jobjectArray joa = m_env->NewObjectArray(length, elementClass, initialElement); + CheckForJavaException(); + return joa; +} + +jobject JEnv::GetObjectArrayElement(jobjectArray array, jsize index) { + jobject jo = m_env->GetObjectArrayElement(array, index); + CheckForJavaException(); + return jo; +} + +void JEnv::SetObjectArrayElement(jobjectArray array, jsize index, jobject value) { + m_env->SetObjectArrayElement(array, index, value); + CheckForJavaException(); +} + +const char *JEnv::GetStringUTFChars(jstring str, jboolean *isCopy) { + const char *cc = m_env->GetStringUTFChars(str, isCopy); + CheckForJavaException(); + return cc; +} + +void JEnv::ReleaseStringUTFChars(jstring str, const char *utf) { + m_env->ReleaseStringUTFChars(str, utf); + CheckForJavaException(); +} + +const jchar *JEnv::GetStringChars(jstring str, jboolean *isCopy) { + const jchar *cjc = m_env->GetStringChars(str, isCopy); + CheckForJavaException(); + return cjc; +} + +void JEnv::ReleaseStringChars(jstring str, const jchar *chars) { + m_env->ReleaseStringChars(str, chars); + CheckForJavaException(); +} + +const int JEnv::GetStringLength(jstring str) { + const int ci = m_env->GetStringLength(str); + CheckForJavaException(); + return ci; +} + +const int JEnv::GetStringUTFLength(jstring str) { + const int ci = m_env->GetStringUTFLength(str); + CheckForJavaException(); + return ci; +} + +void JEnv::GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) { + m_env->GetStringUTFRegion(str, start, len, buf); + CheckForJavaException(); +} + +jint JEnv::Throw(jthrowable obj) { + return m_env->Throw(obj); +} + +jint JEnv::ThrowNew(jclass clazz, const string &message) { + return m_env->ThrowNew(clazz, message.c_str()); +} + +jthrowable JEnv::ExceptionOccurred() { + jthrowable jt = m_env->ExceptionOccurred(); + return jt; +} + +void JEnv::ExceptionDescribe() { + m_env->ExceptionDescribe(); + CheckForJavaException(); +} + +void JEnv::ExceptionClear() { + m_env->ExceptionClear(); +} + +jboolean JEnv::IsInstanceOf(jobject obj, jclass clazz) { + jboolean jbl = m_env->IsInstanceOf(obj, clazz); + CheckForJavaException(); + return jbl; +} + +jobjectRefType JEnv::GetObjectRefType(jobject obj) { + jobjectRefType ort = m_env->GetObjectRefType(obj); + CheckForJavaException(); + return ort; +} + +jobject JEnv::NewGlobalRef(jobject obj) { + jobject jo = m_env->NewGlobalRef(obj); +// CheckForJavaException(); + return jo; +} + +jweak JEnv::NewWeakGlobalRef(jobject obj) { + jweak jw = m_env->NewWeakGlobalRef(obj); + CheckForJavaException(); + return jw; +} + +void JEnv::DeleteGlobalRef(jobject globalRef) { + m_env->DeleteGlobalRef(globalRef); + CheckForJavaException(); +} + +void JEnv::DeleteWeakGlobalRef(jweak obj) { + m_env->DeleteWeakGlobalRef(obj); + CheckForJavaException(); +} + +jobject JEnv::NewLocalRef(jobject ref) { + jobject jo = m_env->NewLocalRef(ref); + CheckForJavaException(); + return jo; +} + +void JEnv::DeleteLocalRef(jobject localRef) { + m_env->DeleteLocalRef(localRef); +} + +jbyteArray JEnv::NewByteArray(jsize length) { + jbyteArray jba = m_env->NewByteArray(length); + CheckForJavaException(); + return jba; +} + +jbooleanArray JEnv::NewBooleanArray(jsize length) { + jbooleanArray jba = m_env->NewBooleanArray(length); + CheckForJavaException(); + return jba; +} + +jcharArray JEnv::NewCharArray(jsize length) { + jcharArray jca = m_env->NewCharArray(length); + CheckForJavaException(); + return jca; +} + +jshortArray JEnv::NewShortArray(jsize length) { + jshortArray jsa = m_env->NewShortArray(length); + CheckForJavaException(); + return jsa; +} + +jintArray JEnv::NewIntArray(jsize length) { + jintArray jia = m_env->NewIntArray(length); + CheckForJavaException(); + return jia; +} + +jlongArray JEnv::NewLongArray(jsize length) { + jlongArray jla = m_env->NewLongArray(length); + CheckForJavaException(); + return jla; +} + +jfloatArray JEnv::NewFloatArray(jsize length) { + jfloatArray jfa = m_env->NewFloatArray(length); + CheckForJavaException(); + return jfa; +} + +jdoubleArray JEnv::NewDoubleArray(jsize length) { + jdoubleArray jda = m_env->NewDoubleArray(length); + CheckForJavaException(); + return jda; +} + +jbyte *JEnv::GetByteArrayElements(jbyteArray array, jboolean *isCopy) { + jbyte *jbt = m_env->GetByteArrayElements(array, isCopy); + CheckForJavaException(); + return jbt; +} + +void JEnv::ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode) { + m_env->ReleaseByteArrayElements(array, elems, mode); + CheckForJavaException(); +} + +void JEnv::GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf) { + m_env->GetBooleanArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf) { + m_env->GetByteArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf) { + m_env->GetCharArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf) { + m_env->GetShortArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf) { + m_env->GetIntArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +jint *JEnv::GetIntArrayElements(jintArray array, jboolean *isCopy) { + jint *jin = m_env->GetIntArrayElements(array, isCopy); + CheckForJavaException(); + return jin; +} + +void JEnv::GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf) { + m_env->GetLongArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf) { + m_env->GetFloatArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf) { + m_env->GetDoubleArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf) { + m_env->SetByteArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf) { + m_env->SetBooleanArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf) { + m_env->SetCharArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf) { + m_env->SetShortArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf) { + m_env->SetIntArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf) { + m_env->SetLongArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf) { + m_env->SetFloatArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf) { + m_env->SetDoubleArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +jclass JEnv::FindClass(const string &className) { + jclass global_class = CheckForClassInCache(className); + + if (global_class == nullptr) { + auto classIsMissing = CheckForClassMissingCache(className); + // class is missing. Set the same JNI error we had when we tried to find it the first time + if (classIsMissing != nullptr) { + m_env->Throw(classIsMissing); + return nullptr; + } + jclass tmp = m_env->FindClass(className.c_str()); + + if (m_env->ExceptionCheck() == JNI_TRUE) { + m_env->ExceptionClear(); + string cannonicalClassName = Util::ConvertFromJniToCanonicalName(className); + jstring s = m_env->NewStringUTF(cannonicalClassName.c_str()); + tmp = static_cast(m_env->CallStaticObjectMethod(RUNTIME_CLASS, + GET_CACHED_CLASS_METHOD_ID, s)); + + m_env->DeleteLocalRef(s); + // we failed our static class check + // if we continue, we will crash (C++ level) + // so just return null and let the runtime deal with the NativeScriptException + if (m_env->ExceptionCheck() == JNI_TRUE) { + auto tmpException = m_env->ExceptionOccurred(); + m_env->ExceptionClear(); + m_env->Throw(InsertClassIntoMissingCache(className, tmpException)); + return nullptr; + } + } + + global_class = InsertClassIntoCache(className, tmp); + } + + return global_class; +} + +jclass JEnv::CheckForClassInCache(const string &className) { + jclass global_class = nullptr; + auto itFound = s_classCache.find(className); + + if (itFound != s_classCache.end()) { + global_class = itFound->second; + } + + return global_class; +} + +jclass JEnv::InsertClassIntoCache(const string &className, jclass &tmp) { + auto global_class = reinterpret_cast(m_env->NewGlobalRef(tmp)); + s_classCache.emplace(className, global_class); + m_env->DeleteLocalRef(tmp); + + return global_class; +} + +jthrowable JEnv::CheckForClassMissingCache(const string &className) { + jthrowable throwable = nullptr; + auto itFound = s_missingClasses.find(className); + + if (itFound != s_missingClasses.end()) { + throwable = itFound->second; + } + + return throwable; +} + +jthrowable JEnv::InsertClassIntoMissingCache(const string &className,const jthrowable &tmp) { + auto throwable = reinterpret_cast(m_env->NewGlobalRef(tmp)); + s_missingClasses.emplace(className, throwable); + m_env->DeleteLocalRef(tmp); + + return throwable; +} + +jobject JEnv::NewDirectByteBuffer(void *address, jlong capacity) { + jobject jo = m_env->NewDirectByteBuffer(address, capacity); + CheckForJavaException(); + return jo; +} + +void *JEnv::GetDirectBufferAddress(jobject buf) { + void *v = m_env->GetDirectBufferAddress(buf); + CheckForJavaException(); + return v; +} + +jlong JEnv::GetDirectBufferCapacity(jobject buf) { + jlong jl = m_env->GetDirectBufferCapacity(buf); + CheckForJavaException(); + return jl; +} + +jboolean JEnv::IsAssignableFrom(jclass clazz1, jclass clazz2) { + jboolean jbl = m_env->IsAssignableFrom(clazz1, clazz2); + CheckForJavaException(); + return jbl; +} + +void JEnv::Init(JavaVM *jvm) { + assert(jvm != nullptr); + s_jvm = jvm; + + JEnv env; + RUNTIME_CLASS = env.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + GET_CACHED_CLASS_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "getCachedClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + assert(GET_CACHED_CLASS_METHOD_ID != nullptr); +} + +jclass JEnv::GetObjectClass(jobject obj) { + jclass jcl = m_env->GetObjectClass(obj); + CheckForJavaException(); + return jcl; +} + +jsize JEnv::GetArrayLength(jarray array) { + jsize jsz = m_env->GetArrayLength(array); + CheckForJavaException(); + return jsz; +} + +//recursion if we put: CheckForJavaException(); +//in this method +jboolean JEnv::ExceptionCheck() { + return m_env->ExceptionCheck(); +} + +void JEnv::CheckForJavaException() { + if (ExceptionCheck() == JNI_TRUE) { + throw NativeScriptException(*this); + } +} + +JavaVM *JEnv::s_jvm = nullptr; +robin_hood::unordered_map JEnv::s_classCache; +robin_hood::unordered_map JEnv::s_missingClasses; +jclass JEnv::RUNTIME_CLASS = nullptr; +jmethodID JEnv::GET_CACHED_CLASS_METHOD_ID = nullptr; + +std::pair +JEnv::GetInterfaceStaticMethodIDAndJClass(const std::string &interfaceName, + const std::string &methodName, + const std::string &sig) { + + DesugaredInterfaceCompanionClassNameResolver companionClassNameResolver; + std::string possibleCalleeNames[] = {interfaceName, + companionClassNameResolver.resolveBazelInterfaceCompanionClassName( + interfaceName), + companionClassNameResolver.resolveD8InterfaceCompanionClassName( + interfaceName)}; + + for (const std::string& calleeName: possibleCalleeNames) { + jclass clazz = this->FindClass(calleeName); + + if (clazz != NULL) { + jmethodID methodId = m_env->GetStaticMethodID(clazz, methodName.c_str(), sig.c_str()); + + if (ExceptionCheck() == JNI_FALSE) { + return std::make_pair(methodId, clazz); + } + + ExceptionClear(); + } + } + + throw NativeScriptException( + "Could not call static interface method with name: " + methodName + " and signature: " + + sig + " for interface: " + interfaceName); + +} + + diff --git a/NativeScript/ffi/jni/jsi/jni/JEnv.h b/NativeScript/ffi/jni/jsi/jni/JEnv.h new file mode 100644 index 000000000..b1a544e4c --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JEnv.h @@ -0,0 +1,466 @@ +#ifndef JENV_H_ +#define JENV_H_ + +#include "jni.h" +#include "robin_hood.h" +#include + +namespace tns { + class JEnv { + public: + JEnv(); + + JEnv(JNIEnv *jniEnv); + + // Wrap an already-obtained JNIEnv* WITHOUT re-querying the JavaVM + // (no GetEnv). Use only when the pointer is known to belong to the + // current attached thread (e.g. threaded down from a callback prologue). + enum class Adopt { Trusted }; + JEnv(JNIEnv *jniEnv, Adopt) : m_env(jniEnv) {} + + ~JEnv(); + + operator JNIEnv *() const; + + jclass GetObjectClass(jobject obj); + + jsize GetArrayLength(jarray array); + + inline bool isSameObject(jobject obj1, jobject obj2) { + return m_env->IsSameObject(obj1, obj2) == JNI_TRUE; + } + + jmethodID GetMethodID(jclass clazz, const std::string &name, const std::string &sig); + + jmethodID GetStaticMethodID(jclass clazz, const std::string &name, const std::string &sig); + + std::pair GetInterfaceStaticMethodIDAndJClass( + const std::string &interfaceName, const std::string &methodName, + const std::string &sig); + + jfieldID GetFieldID(jclass clazz, const std::string &name, const std::string &sig); + + jfieldID GetStaticFieldID(jclass clazz, const std::string &name, const std::string &sig); + + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jboolean CallStaticBooleanMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jboolean + CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jboolean CallBooleanMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jbyte CallStaticByteMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jbyte + CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jbyte CallByteMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jchar CallStaticCharMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jchar + CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jchar CallCharMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jshort CallStaticShortMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jshort + CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jshort CallShortMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jint CallStaticIntMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jint CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jint CallIntMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jlong CallStaticLongMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jlong + CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jlong CallLongMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jfloat CallStaticFloatMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jfloat + CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jfloat CallFloatMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jdouble CallStaticDoubleMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jdouble + CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jdouble CallDoubleMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jobject CallStaticObjectMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jobject + CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jobject CallObjectMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID); + + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID); + + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID); + + jchar GetStaticCharField(jclass clazz, jfieldID fieldID); + + jshort GetStaticShortField(jclass clazz, jfieldID fieldID); + + jint GetStaticIntField(jclass clazz, jfieldID fieldID); + + jlong GetStaticLongField(jclass clazz, jfieldID fieldID); + + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID); + + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID); + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value); + + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value); + + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value); + + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value); + + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value); + + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value); + + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value); + + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value); + + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value); + + jobject GetObjectField(jobject obj, jfieldID fieldID); + + jboolean GetBooleanField(jobject obj, jfieldID fieldID); + + jbyte GetByteField(jobject obj, jfieldID fieldID); + + jchar GetCharField(jobject obj, jfieldID fieldID); + + jshort GetShortField(jobject obj, jfieldID fieldID); + + jint GetIntField(jobject obj, jfieldID fieldID); + + jlong GetLongField(jobject obj, jfieldID fieldID); + + jfloat GetFloatField(jobject obj, jfieldID fieldID); + + jdouble GetDoubleField(jobject obj, jfieldID fieldID); + + void SetObjectField(jobject obj, jfieldID fieldID, jobject value); + + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value); + + void SetByteField(jobject obj, jfieldID fieldID, jbyte value); + + void SetCharField(jobject obj, jfieldID fieldID, jchar value); + + void SetShortField(jobject obj, jfieldID fieldID, jshort value); + + void SetIntField(jobject obj, jfieldID fieldID, jint value); + + void SetLongField(jobject obj, jfieldID fieldID, jlong value); + + void SetFloatField(jobject obj, jfieldID fieldID, jfloat value); + + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value); + + jstring NewString(const jchar *unicodeChars, jsize len); + + jstring NewStringUTF(const char *bytes); + + jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement); + + jobject GetObjectArrayElement(jobjectArray array, jsize index); + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject value); + + const char *GetStringUTFChars(jstring str, jboolean *isCopy); + + void ReleaseStringUTFChars(jstring str, const char *utf); + + const jchar *GetStringChars(jstring str, jboolean *isCopy); + + void ReleaseStringChars(jstring str, const jchar *chars); + + const int GetStringLength(jstring str); + + const int GetStringUTFLength(jstring str); + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf); + + jint Throw(jthrowable obj); + + jint ThrowNew(jclass clazz, const std::string &message); + + jboolean ExceptionCheck(); + + jthrowable ExceptionOccurred(); + + void ExceptionDescribe(); + + void ExceptionClear(); + + jboolean IsInstanceOf(jobject obj, jclass clazz); + + jobjectRefType GetObjectRefType(jobject obj); + + jobject NewGlobalRef(jobject obj); + + jweak NewWeakGlobalRef(jobject obj); + + void DeleteGlobalRef(jobject globalRef); + + void DeleteWeakGlobalRef(jweak obj); + + jobject NewLocalRef(jobject ref); + + void DeleteLocalRef(jobject localRef); + + jbyteArray NewByteArray(jsize length); + + jbooleanArray NewBooleanArray(jsize length); + + jcharArray NewCharArray(jsize length); + + jshortArray NewShortArray(jsize length); + + jintArray NewIntArray(jsize length); + + jlongArray NewLongArray(jsize length); + + jfloatArray NewFloatArray(jsize length); + + jdoubleArray NewDoubleArray(jsize length); + + jbyte *GetByteArrayElements(jbyteArray array, jboolean *isCopy); + + + void ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode); + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf); + + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf); + + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf); + + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf); + + void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf); + + jint *GetIntArrayElements(jintArray array, jboolean *isCopy); + + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf); + + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf); + + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf); + + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf); + + void + SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf); + + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf); + + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf); + + void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf); + + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf); + + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf); + + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf); + + jclass FindClass(const std::string &className); + + /* + * The "CheckForClassInCache" will check if a class is loaded into the cache + * if it is: it returns a global reference of it + * if it is not: it will return "nullptr". + */ + jclass CheckForClassInCache(const std::string &className); + + /* + * "InsertClassIntoCache" will take care of deleting the LocalReference of passed "jclass& tmp". + * A new GlobalReference object will be created from "tmp". The function returns the global object. + */ + jclass InsertClassIntoCache(const std::string &className, jclass &tmp); + + + /* + * The "CheckForClassMissing" will check if a class has been checked and it was missing, if it is, it will return the original throwable + * this is useful for rethrowing exceptions if they were caught in the previous attempt of loading it. + * if it is not: it will return "nullptr". + */ + jthrowable CheckForClassMissingCache(const std::string &className); + + jthrowable InsertClassIntoMissingCache(const std::string &className, const jthrowable &tmp); + + jobject NewDirectByteBuffer(void *address, jlong capacity); + + void *GetDirectBufferAddress(jobject buf); + + jlong GetDirectBufferCapacity(jobject buf); + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2); + + template + void CallVoidMethod(jobject obj, jmethodID methodID, Args ... args) { + m_env->CallVoidMethod(obj, methodID, args...); + CheckForJavaException(); + } + + template + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, Args ... args) { + m_env->CallStaticVoidMethod(clazz, methodID, args...); + CheckForJavaException(); + } + + template + void CallAppFail(jclass clazz, jmethodID methodID, Args ... args) { + m_env->CallStaticVoidMethod(clazz, methodID, args...); + } + + template + jint CallStaticIntMethod(jclass clazz, jmethodID methodID, Args ... args) { + jint ji = m_env->CallStaticIntMethod(clazz, methodID, args...); + CheckForJavaException(); + return ji; + } + + template + jlong CallStaticLongMethod(jclass clazz, jmethodID methodID, Args ... args) { + jlong jd = m_env->CallStaticLongMethod(clazz, methodID, args...); + CheckForJavaException(); + return jd; + } + + template + jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID, Args ... args) { + jobject jo = m_env->CallStaticObjectMethod(clazz, methodID, args...); + CheckForJavaException(); + return jo; + } + + template + jboolean CallStaticBooleanMethod(jclass clazz, jmethodID methodID, Args ... args) { + jboolean jbl = m_env->CallStaticBooleanMethod(clazz, methodID, args...); + CheckForJavaException(); + return jbl; + } + + template + jobject CallObjectMethod(jobject obj, jmethodID methodID, Args ... args) { + jobject jo = m_env->CallObjectMethod(obj, methodID, args...); + CheckForJavaException(); + return jo; + } + + template + jboolean CallBooleanMethod(jobject obj, jmethodID methodID, Args ... args) { + jboolean jbl = m_env->CallBooleanMethod(obj, methodID, args...); + CheckForJavaException(); + return jbl; + } + + template + jchar CallCharMethod(jobject obj, jmethodID methodID, Args ... args) { + jchar jc = m_env->CallCharMethod(obj, methodID, args...); + CheckForJavaException(); + return jc; + } + + template + jbyte CallByteMethod(jobject obj, jmethodID methodID, Args ... args) { + jbyte jbt = m_env->CallByteMethod(obj, methodID, args...); + CheckForJavaException(); + return jbt; + } + + template + jshort CallShortMethod(jobject obj, jmethodID methodID, Args ... args) { + jshort jsh = m_env->CallShortMethod(obj, methodID, args...); + CheckForJavaException(); + return jsh; + } + + template + jint CallIntMethod(jobject obj, jmethodID methodID, Args ... args) { + jint ji = m_env->CallIntMethod(obj, methodID, args...); + CheckForJavaException(); + return ji; + } + + template + jlong CallLongMethod(jobject obj, jmethodID methodID, Args ... args) { + jlong jl = m_env->CallLongMethod(obj, methodID, args...); + CheckForJavaException(); + return jl; + } + + template + jfloat CallFloatMethod(jobject obj, jmethodID methodID, Args ... args) { + jfloat jf = m_env->CallFloatMethod(obj, methodID, args...); + CheckForJavaException(); + return jf; + } + + template + jdouble CallDoubleMethod(jobject obj, jmethodID methodID, Args ... args) { + jdouble jd = m_env->CallDoubleMethod(obj, methodID, args...); + CheckForJavaException(); + return jd; + } + + template + jobject NewObject(jclass clazz, jmethodID methodID, Args ... args) { + jobject jo = m_env->NewObject(clazz, methodID, args...); + CheckForJavaException(); + return jo; + + } + + jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->NewObjectA(clazz, methodID, args); + CheckForJavaException(); + return jo; + } + + static void Init(JavaVM *jvm); + + private: + void CheckForJavaException(); + + JNIEnv *m_env; + + static JavaVM *s_jvm; + + static jclass RUNTIME_CLASS; + + static jmethodID GET_CACHED_CLASS_METHOD_ID; + + static robin_hood::unordered_map s_classCache; + static robin_hood::unordered_map s_missingClasses; + }; +} + +#endif /* JENV_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/JType.cpp b/NativeScript/ffi/jni/jsi/jni/JType.cpp new file mode 100644 index 000000000..479bfb0e3 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JType.cpp @@ -0,0 +1,155 @@ +#include "JType.h" +#include "NativeScriptAssert.h" + +namespace tns { +Type JType::getClassType(int retType) { + Type classReturnType = static_cast(retType); + return classReturnType; +} + +jobject JType::NewByte(JEnv env, jbyte value) { + EnsureInstance(env, &Byte, Type::Byte); + return env.NewObject(Byte->clazz, Byte->ctor, value); +} + +jobject JType::NewChar(JEnv env, jchar value) { + EnsureInstance(env, &Char, Type::Char); + return env.NewObject(Char->clazz, Char->ctor, value); +} + +jobject JType::NewBoolean(JEnv env, jboolean value) { + EnsureInstance(env, &Boolean, Type::Boolean); + return env.NewObject(Boolean->clazz, Boolean->ctor, value); +} + +jobject JType::NewShort(JEnv env, jshort value) { + EnsureInstance(env, &Short, Type::Short); + return env.NewObject(Short->clazz, Short->ctor, value); +} + +jobject JType::NewInt(JEnv env, jint value) { + EnsureInstance(env, &Int, Type::Int); + return env.NewObject(Int->clazz, Int->ctor, value); +} + +jobject JType::NewLong(JEnv env, jlong value) { + EnsureInstance(env, &Long, Type::Long); + return env.NewObject(Long->clazz, Long->ctor, value); +} + +jobject JType::NewFloat(JEnv env, jfloat value) { + EnsureInstance(env, &Float, Type::Float); + return env.NewObject(Float->clazz, Float->ctor, value); +} + +jobject JType::NewDouble(JEnv env, jdouble value) { + EnsureInstance(env, &Double, Type::Double); + return env.NewObject(Double->clazz, Double->ctor, value); +} + +jbyte JType::ByteValue(JEnv env, jobject value) { + EnsureInstance(env, &Byte, Type::Byte); + return env.CallByteMethod(value, Byte->valueMethodId); +} + +jchar JType::CharValue(JEnv env, jobject value) { + EnsureInstance(env, &Char, Type::Char); + return env.CallCharMethod(value, Char->valueMethodId); +} + +jboolean JType::BooleanValue(JEnv env, jobject value) { + EnsureInstance(env, &Boolean, Type::Boolean); + return env.CallBooleanMethod(value, Boolean->valueMethodId); +} + +jshort JType::ShortValue(JEnv env, jobject value) { + EnsureInstance(env, &Short, Type::Short); + return env.CallShortMethod(value, Short->valueMethodId); +} + +jint JType::IntValue(JEnv env, jobject value) { + EnsureInstance(env, &Int, Type::Int); + return env.CallIntMethod(value, Int->valueMethodId); +} + +jlong JType::LongValue(JEnv env, jobject value) { + EnsureInstance(env, &Long, Type::Long); + return env.CallLongMethod(value, Long->valueMethodId); +} + +jfloat JType::FloatValue(JEnv env, jobject value) { + EnsureInstance(env, &Float, Type::Float); + return env.CallFloatMethod(value, Float->valueMethodId); +} + +jdouble JType::DoubleValue(JEnv env, jobject value) { + EnsureInstance(env, &Double, Type::Double); + return env.CallDoubleMethod(value, Double->valueMethodId); +} + +void JType::EnsureInstance(JEnv env, JType** instance, Type type) { + if ((*instance) != nullptr) { + return; + } + + *instance = new JType(); + + (*instance)->Init(env, type); +} + +void JType::Init(JEnv env, Type type) { + switch (type) { + case Type::Byte: + this->clazz = env.FindClass("java/lang/Byte"); + this->ctor = env.GetMethodID(this->clazz, "", "(B)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "byteValue", "()B"); + break; + case Type::Char: + this->clazz = env.FindClass("java/lang/Character"); + this->ctor = env.GetMethodID(this->clazz, "", "(C)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "charValue", "()C"); + break; + case Type::Boolean: + this->clazz = env.FindClass("java/lang/Boolean"); + this->ctor = env.GetMethodID(this->clazz, "", "(Z)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "booleanValue", "()Z"); + break; + case Type::Short: + this->clazz = env.FindClass("java/lang/Short"); + this->ctor = env.GetMethodID(this->clazz, "", "(S)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "shortValue", "()S"); + break; + case Type::Int: + this->clazz = env.FindClass("java/lang/Integer"); + this->ctor = env.GetMethodID(this->clazz, "", "(I)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "intValue", "()I"); + break; + case Type::Long: + this->clazz = env.FindClass("java/lang/Long"); + this->ctor = env.GetMethodID(this->clazz, "", "(J)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "longValue", "()J"); + break; + case Type::Float: + this->clazz = env.FindClass("java/lang/Float"); + this->ctor = env.GetMethodID(this->clazz, "", "(F)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "floatValue", "()F"); + break; + case Type::Double: + this->clazz = env.FindClass("java/lang/Double"); + this->ctor = env.GetMethodID(this->clazz, "", "(D)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "doubleValue", "()D"); + break; + default: + break; + } +} + +JType* JType::Byte; +JType* JType::Char; +JType* JType::Boolean; +JType* JType::Short; +JType* JType::Int; +JType* JType::Long; +JType* JType::Float; +JType* JType::Double; +} diff --git a/NativeScript/ffi/jni/jsi/jni/JType.h b/NativeScript/ffi/jni/jsi/jni/JType.h new file mode 100644 index 000000000..d3bfcf2c9 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JType.h @@ -0,0 +1,66 @@ +#ifndef JNIPRIMITIVETYPE_H_ +#define JNIPRIMITIVETYPE_H_ + +#include "JEnv.h" + +namespace tns { +enum class Type + : int { + Boolean, + Char, + Byte, + Short, + Int, + Long, + Float, + Double, + String, + JsObject, + Null +}; + +class JType { + public: + static jobject NewByte(JEnv env, jbyte value); + static jobject NewChar(JEnv env, jchar value); + static jobject NewBoolean(JEnv env, jboolean value); + static jobject NewShort(JEnv env, jshort value); + static jobject NewInt(JEnv env, jint value); + static jobject NewLong(JEnv env, jlong value); + static jobject NewFloat(JEnv env, jfloat value); + static jobject NewDouble(JEnv env, jdouble value); + + static jbyte ByteValue(JEnv env, jobject value); + static jchar CharValue(JEnv env, jobject value); + static jboolean BooleanValue(JEnv env, jobject value); + static jshort ShortValue(JEnv env, jobject value); + static jint IntValue(JEnv env, jobject value); + static jlong LongValue(JEnv env, jobject value); + static jfloat FloatValue(JEnv env, jobject value); + static jdouble DoubleValue(JEnv env, jobject value); + + static Type getClassType(int retType); + + private: + JType() { + } + + void Init(JEnv env, Type type); + static void EnsureInstance(JEnv env, JType** instance, Type type); + + jclass clazz; + jmethodID ctor; + jmethodID valueMethodId; + + static JType* Byte; + static JType* Char; + static JType* Boolean; + static JType* Short; + static JType* Int; + static JType* Long; + static JType* Float; + static JType* Double; +}; +} + +#endif /* JNIPRIMITIVETYPE_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/JniLocalRef.h b/NativeScript/ffi/jni/jsi/jni/JniLocalRef.h new file mode 100644 index 000000000..f950740cf --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JniLocalRef.h @@ -0,0 +1,122 @@ +#ifndef JNILOCALREF_H_ +#define JNILOCALREF_H_ + +#include "JEnv.h" +#include "JType.h" + +namespace tns { +class JniLocalRef { + public: + JniLocalRef() + : m_obj(nullptr), m_isGlobal(false) { + } + + JniLocalRef(jobject obj, bool isGlobal = false) + : m_obj(obj), m_isGlobal(isGlobal) { + } + + JniLocalRef(jclass obj) + : m_obj(obj), m_isGlobal(false) { + } + + JniLocalRef(JniLocalRef&& rhs) + : m_obj(rhs.m_obj), m_isGlobal(rhs.m_isGlobal) { + rhs.m_obj = nullptr; + } + + bool IsNull() const { + return m_obj == nullptr; + } + + bool IsGlobal() const { + return m_isGlobal; + } + + jobject Move() { + auto value = m_obj; + m_obj = nullptr; + return value; + } + + JniLocalRef& operator=(JniLocalRef&& rhs) { + m_obj = rhs.m_obj; + m_isGlobal = rhs.m_isGlobal; + rhs.m_obj = nullptr; + return *this; + } + + operator jobject() const { + return m_obj; + } + + operator jstring() const { + return reinterpret_cast(m_obj); + } + + operator jclass() const { + return reinterpret_cast(m_obj); + } + + operator jboolean() const { + JEnv env; + return JType::BooleanValue(env, m_obj); + } + + operator jthrowable() const { + return reinterpret_cast(m_obj); + } + + operator jarray()const { + return reinterpret_cast(m_obj); + } + + operator jbyteArray() const { + return reinterpret_cast(m_obj); + } + + operator jshortArray() const { + return reinterpret_cast(m_obj); + } + + operator jintArray() const { + return reinterpret_cast(m_obj); + } + + operator jlongArray() const { + return reinterpret_cast(m_obj); + } + + operator jfloatArray() const { + return reinterpret_cast(m_obj); + } + + operator jdoubleArray() const { + return reinterpret_cast(m_obj); + } + + operator jbooleanArray() const { + return reinterpret_cast(m_obj); + } + + operator jcharArray() const { + return reinterpret_cast(m_obj); + } + + operator jobjectArray() const { + return reinterpret_cast(m_obj); + } + + ~JniLocalRef() { + if ((m_obj != nullptr) && !m_isGlobal) { + JEnv env; + env.DeleteLocalRef(m_obj); + } + } + + private: + jobject m_obj; + bool m_isGlobal; +}; +} + +#endif /* JNILOCALREF_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/JniSignatureParser.cpp b/NativeScript/ffi/jni/jsi/jni/JniSignatureParser.cpp new file mode 100644 index 000000000..3d6df3559 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JniSignatureParser.cpp @@ -0,0 +1,102 @@ +#include "JniSignatureParser.h" + +#include + +using namespace std; +using namespace tns; + +JniSignatureParser::JniSignatureParser(const string& signature) + : m_signature(signature) { +} + +vector JniSignatureParser::Parse() { + size_t startIdx = m_signature.find_first_of('('); + + assert(startIdx != string::npos); + + size_t endIdx = m_signature.find_first_of(')'); + + assert(endIdx != string::npos); + + vector tokens = ParseParams(startIdx + 1, endIdx); + + return tokens; +} + +vector JniSignatureParser::ParseParams(int stardIdx, int endIdx) { + vector tokens; + + m_pos = stardIdx; + + while (m_pos < endIdx) { + string token = ReadNextToken(endIdx); + tokens.push_back(token); + } + + return tokens; +} + +string JniSignatureParser::ReadNextToken(int endIdx) { + string token; + + char currChar = m_signature[m_pos]; + + int idx; + bool endFound; + bool testNextChar = true; + + switch (currChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + ++m_pos; + token.push_back(currChar); + break; + + case 'L': + idx = m_signature.find(';', m_pos); + assert(idx != string::npos); + token = m_signature.substr(m_pos, idx - m_pos + 1); + m_pos = idx + 1; + break; + + case '[': + idx = m_pos; + endFound = false; + while (!endFound && (idx < endIdx)) { + currChar = m_signature[idx++]; + if (testNextChar) { + switch (currChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + endFound = true; + break; + } + testNextChar = currChar == '['; + } else { + endFound = currChar == ';'; + } + } + assert(endFound); + token = m_signature.substr(m_pos, idx - m_pos); + m_pos = idx; + break; + + default: + assert(false); + break; + } + + return token; +} diff --git a/NativeScript/ffi/jni/jsi/jni/JniSignatureParser.h b/NativeScript/ffi/jni/jsi/jni/JniSignatureParser.h new file mode 100644 index 000000000..9904884ca --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/JniSignatureParser.h @@ -0,0 +1,26 @@ +#ifndef JNISIGNATUREPARSER_H_ +#define JNISIGNATUREPARSER_H_ + +#include +#include + +namespace tns { +class JniSignatureParser { + public: + JniSignatureParser(const std::string& signature); + + std::vector Parse(); + + private: + + std::vector ParseParams(int stardIdx, int endIdx); + + std::string ReadNextToken(int endIdx); + + int m_pos; + + std::string m_signature; +}; +} + +#endif /* JNISIGNATUREPARSER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/LRUCache.h b/NativeScript/ffi/jni/jsi/jni/LRUCache.h new file mode 100644 index 000000000..afceeb132 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/LRUCache.h @@ -0,0 +1,191 @@ +#ifndef LRUCACHE_H_ +#define LRUCACHE_H_ + +/* + Copyright (c) 2010-2011, Tim Day + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +namespace tns { +// Class providing fixed-size (by number of records) +// LRU-replacement cache of a function with signature +// V f(K). +// MAP should be one of std::map or std::unordered_map. +// Variadic template args used to deal with the +// different type argument signatures of those +// containers; the default comparator/hash/allocator +// will be used. +template +class LRUCache { + public: + + typedef K key_type; + typedef V value_type; + + // Key access history, most recent at back + typedef std::list key_tracker_type; + + // Key to value and key history iterator + typedef std::unordered_map< key_type, std::pair > key_to_value_type; + + // Constuctor specifies the cached function and + // the maximum number of records to be stored + LRUCache(value_type (*loadCallback)(const key_type&, void*), void (*evictCallback)(const value_type&, void*), bool (*cacheValidCallback)(const key_type&, const value_type&, void*), size_t capacity, void* state) + : m_loadCallback(loadCallback), m_capacity(capacity), m_evictCallback(evictCallback), m_cacheValidCallback(cacheValidCallback), m_state(state) { + assert(m_loadCallback != nullptr); + assert((0 < m_capacity) && (m_capacity < 10000)); + } + + // Obtain value of the cached function for k + value_type operator()(const key_type& k) { + + // Attempt to find existing record + auto it = m_key_to_value.find(k); + + if (m_cacheValidCallback != nullptr && it != m_key_to_value.end()) { + // Check if the cached value is still valid (e.g. a jweak that no + // longer points to a live object); if not, evict and treat as miss. + if (!m_cacheValidCallback(k, (*it).second.first, m_state)) { + evictKey(k); + it = m_key_to_value.end(); + } + } + + if (it == m_key_to_value.end()) { + + // We don't have it: + + // Evaluate function and create new record + const value_type v = m_loadCallback(k, m_state); + insert(k,v); + + // Return the freshly computed value + return v; + + } else { + // We do have it: + + // Update access record by moving + // accessed key to back of list + m_key_tracker.splice(m_key_tracker.end(), m_key_tracker, (*it).second.second); + + // Return the retrieved value + return (*it).second.first; + } + } + + // Obtain the cached keys, most recently used element + // at head, least recently used at tail. + // This method is provided purely to support testing. + template void get_keys(IT dst) const { + auto src = m_key_tracker.rbegin(); + + while (src != m_key_tracker.rend()) { + *dst++ = *src++; + } + } + + void clear() { + if (m_evictCallback != nullptr) { + for (auto& entry : m_key_to_value) { + m_evictCallback(entry.second.first, m_state); + } + } + m_key_to_value.clear(); + m_key_tracker.clear(); + } + + void update(const key_type& key, const value_type& value) { + jweak ref = m_loadCallback(key, m_state); + insert(key, ref); + } + + private: + + // Evict a specific key (used when a cached value is no longer valid). + void evictKey(const key_type& key) { + auto it = m_key_to_value.find(key); + if (it != m_key_to_value.end()) { + if (m_evictCallback != nullptr) { + m_evictCallback((*it).second.first, m_state); + } + m_key_tracker.erase((*it).second.second); + m_key_to_value.erase(it); + } + } + + // Record a fresh key-value pair in the cache + void insert(const key_type& k, const value_type& v) { + // Method is only called on cache misses + assert(m_key_to_value.find(k) == m_key_to_value.end()); + + // Make space if necessary + if (m_key_to_value.size() == m_capacity) { + evict(); + } + + // Record k as most-recently-used key + auto it = m_key_tracker.insert(m_key_tracker.end(), k); + + // Create the key-value entry, + // linked to the usage record. + m_key_to_value.insert(std::make_pair(k, std::make_pair(v, it))); + // No need to check return, + // given previous assert. + } + + // Purge the least-recently-used element in the cache + void evict() { + // Assert method is never called when cache is empty + assert(!m_key_tracker.empty()); + + // Identify least recently used key + auto it = m_key_to_value.find(m_key_tracker.front()); + assert(it != m_key_to_value.end()); + + if (m_evictCallback != nullptr) { + m_evictCallback((*it).second.first, m_state); + } + + // Erase both elements to completely purge record + m_key_to_value.erase(it); + m_key_tracker.pop_front(); + } + + // The function to be cached + value_type (*m_loadCallback)(const key_type&, void*); + + void (*m_evictCallback)(const value_type&, void*); + + bool (*m_cacheValidCallback)(const key_type&, const value_type&, void*); + + // Maximum number of key-value pairs to be retained + const size_t m_capacity; + + // Key access history + key_tracker_type m_key_tracker; + + // user-defined state to pass to callback + void* m_state; + + // Key-to-value lookup + key_to_value_type m_key_to_value; +}; +} + +#endif /* LRUCACHE_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jni/Logger.cpp b/NativeScript/ffi/jni/jsi/jni/Logger.cpp new file mode 100644 index 000000000..4e35d4cf9 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/Logger.cpp @@ -0,0 +1,9 @@ +#include "Logger.h" + +using namespace tns; + +Logger::Logger() { +} + +void Logger::Write() { +} diff --git a/NativeScript/ffi/jni/jsi/jni/Logger.h b/NativeScript/ffi/jni/jsi/jni/Logger.h new file mode 100644 index 000000000..6b77c4f24 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jni/Logger.h @@ -0,0 +1,14 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +namespace tns { +class Logger { + public: + Logger(); + + void Write(); + private: +}; +} + +#endif /* LOGGER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/jsonhelper/JSONObjectHelper.cpp b/NativeScript/ffi/jni/jsi/jsonhelper/JSONObjectHelper.cpp new file mode 100644 index 000000000..e9fc7ff4c --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jsonhelper/JSONObjectHelper.cpp @@ -0,0 +1,66 @@ +#include "NativeScriptException.h" +#include "JSONObjectHelper.h" +#include "ArgConverter.h" +#include +#include +#include + +using namespace tns; + +void JSONObjectHelper::RegisterFromFunction(JsRuntime& rt, const JsValue& value) { + if (!value.isObject()) { + return; + } + + auto object = value.asObjectBorrowed(rt); + + if (object.hasProperty(rt, "from")) { + return; + } + + JsValue from = CreateFromFunction(rt); + object.setProperty(rt, "from", from); +} + + +JsValue JSONObjectHelper::CreateFromFunction(JsRuntime& rt) { + static const char* source = R"((() => function from(data) { + if (!data) throw new Error("Expected one parameter"); + let store; + switch (typeof data) { + case "string": + case "boolean": + case "number": { + return data; + } + case "object": { + if (!data) { + return null; + } + + if (data instanceof Date) { + return data.toJSON(); + } + + if (Array.isArray(data)) { + store = new org.json.JSONArray(); + data.forEach((item) => store.put(from(item))); + return store; + } + + store = new org.json.JSONObject(); + Object.keys(data).forEach((key) => store.put(key, from(data[key]))); + return store; + } + default: + return null; + } + })();)"; + + try { + return rt.evaluateJavaScript(std::make_shared(source), + ""); + } catch (JsError &) { + return js_util::undefined(); + } +} diff --git a/NativeScript/ffi/jni/jsi/jsonhelper/JSONObjectHelper.h b/NativeScript/ffi/jni/jsi/jsonhelper/JSONObjectHelper.h new file mode 100644 index 000000000..04ae7e27a --- /dev/null +++ b/NativeScript/ffi/jni/jsi/jsonhelper/JSONObjectHelper.h @@ -0,0 +1,17 @@ +#ifndef JSONOBJECTHELPER_H_ +#define JSONOBJECTHELPER_H_ + +#include "Engine.h" + +namespace tns { + + class JSONObjectHelper { + public: + static void RegisterFromFunction(JsRuntime& rt, const JsValue& value); + private: + static JsValue CreateFromFunction(JsRuntime& rt); + }; + +} + +#endif //JSONOBJECTHELPER_H_ diff --git a/NativeScript/ffi/jni/jsi/metadata/FieldAccessor.cpp b/NativeScript/ffi/jni/jsi/metadata/FieldAccessor.cpp new file mode 100644 index 000000000..b7c0be426 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/FieldAccessor.cpp @@ -0,0 +1,354 @@ +#include "FieldAccessor.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +JsValue +FieldAccessor::GetJavaField(JsRuntime &rt, const JsValue &target, FieldCallbackData *fieldData, + ObjectManager *objectManager, JniLocalRef targetJavaObject) { + JEnv jEnv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + auto &fieldMetadata = fieldData->metadata; + + const auto &fieldTypeName = fieldMetadata.getSig(); + auto isStatic = fieldMetadata.isStatic; + + auto isPrimitiveType = fieldTypeName.size() == 1; + if (fieldData->fid == nullptr) { + auto isFieldArray = fieldTypeName[0] == '['; + auto fieldJniSig = isPrimitiveType + ? fieldTypeName + : (isFieldArray + ? fieldTypeName + : ("L" + fieldTypeName + ";")); + + if (isStatic) { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + fieldData->fid = jEnv.GetStaticFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + } else { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + fieldData->fid = jEnv.GetFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + } + } + + if (!isStatic) { + // The caller usually pre-resolves this (single probe); only fall back to + // resolving here when it wasn't supplied. + if (targetJavaObject.IsNull()) { + targetJavaObject = objectManager->GetJavaObjectByJsObjectFast(target); + } + + if (targetJavaObject.IsNull()) { + stringstream ss; + ss << "Cannot access property '" << fieldMetadata.getName().c_str() + << "' because there is no corresponding Java object"; + throw NativeScriptException(ss.str()); + } + + } + + + auto fieldId = fieldData->fid; + auto clazz = fieldData->clazz; + + if (isPrimitiveType) { + switch (fieldTypeName[0]) { + case 'Z': { // bool + jboolean result; + if (isStatic) { + result = jEnv.GetStaticBooleanField(clazz, fieldId); + } else { + result = jEnv.GetBooleanField(targetJavaObject, fieldId); + } + return JsValue(result == JNI_TRUE); + } + case 'B': { // byte + jbyte result; + if (isStatic) { + result = jEnv.GetStaticByteField(clazz, fieldId); + } else { + result = jEnv.GetByteField(targetJavaObject, fieldId); + } + return JsValue((int) result); + } + case 'C': { // char + jchar result; + if (isStatic) { + result = jEnv.GetStaticCharField(clazz, fieldId); + } else { + result = jEnv.GetCharField(targetJavaObject, fieldId); + } + + // The napi tree round-trips the jchar through a jstring and takes + // one byte of its UTF-8 form, which truncates anything outside + // ASCII. engine::String is UTF-8 only, so the transcode is + // explicit here and matches every other jchar path in this tree. + return ArgConverter::convertToJsString(rt, &result, 1); + } + case 'S': { // short + jshort result; + if (isStatic) { + result = jEnv.GetStaticShortField(clazz, fieldId); + } else { + result = jEnv.GetShortField(targetJavaObject, fieldId); + } + return JsValue((int) result); + } + case 'I': { // int + jint result; + if (isStatic) { + result = jEnv.GetStaticIntField(clazz, fieldId); + } else { + result = jEnv.GetIntField(targetJavaObject, fieldId); + } + + return JsValue((int) result); + } + case 'J': { // long + jlong result; + if (isStatic) { + result = jEnv.GetStaticLongField(clazz, fieldId); + } else { + result = jEnv.GetLongField(targetJavaObject, fieldId); + } + + return ArgConverter::ConvertFromJavaLong(rt, result); + } + case 'F': { // float + jfloat result; + if (isStatic) { + result = jEnv.GetStaticFloatField(clazz, fieldId); + } else { + result = jEnv.GetFloatField(targetJavaObject, fieldId); + } + return JsValue((double) result); + } + case 'D': { // double + jdouble result; + if (isStatic) { + result = jEnv.GetStaticDoubleField(clazz, fieldId); + } else { + result = jEnv.GetDoubleField(targetJavaObject, fieldId); + } + return JsValue((double) result); + } + default: { + stringstream ss; + ss << "(InternalError): in FieldAccessor::GetJavaField: Unknown field type: '" + << fieldTypeName[0] << "'"; + throw NativeScriptException(ss.str()); + } + } + } + + jobject result; + + if (isStatic) { + result = jEnv.GetStaticObjectField(clazz, fieldId); + } else { + result = jEnv.GetObjectField(targetJavaObject, fieldId); + } + + if (result == nullptr) { + return js_util::null(); + } + + JsValue fieldResult; + + bool isString = fieldTypeName == "java/lang/String"; + if (isString) { + fieldResult = ArgConverter::jstringToJsString(rt, (jstring) result); + } else { + int javaObjectID = objectManager->GetOrCreateObjectId(result); + auto objectResult = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (js_util::is_null_or_undefined(objectResult)) { + objectResult = objectManager->CreateJSWrapper(javaObjectID, fieldTypeName, result); + } + + fieldResult = objectResult; + } + jEnv.DeleteLocalRef(result); + + return fieldResult; +} + +void FieldAccessor::SetJavaField(JsRuntime &rt, const JsValue &target, const JsValue &value, + FieldCallbackData *fieldData, ObjectManager *objectManager, + JniLocalRef targetJavaObject) { + JEnv jEnv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + auto &fieldMetadata = fieldData->metadata; + + const auto &fieldTypeName = fieldMetadata.getSig(); + auto isStatic = fieldMetadata.isStatic; + + auto isPrimitiveType = fieldTypeName.size() == 1; + auto isFieldArray = fieldTypeName[0] == '['; + + if (fieldData->fid == nullptr) { + auto fieldJniSig = isPrimitiveType + ? fieldTypeName + : (isFieldArray + ? fieldTypeName + : ("L" + fieldTypeName + ";")); + + if (isStatic) { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + assert(fieldData->clazz != nullptr); + fieldData->fid = jEnv.GetStaticFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + assert(fieldData->fid != nullptr); + } else { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + assert(fieldData->clazz != nullptr); + fieldData->fid = jEnv.GetFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + assert(fieldData->fid != nullptr); + } + } + + if (!isStatic) { + // The caller usually pre-resolves this (single probe); only fall back to + // resolving here when it wasn't supplied. + if (targetJavaObject.IsNull()) { + targetJavaObject = objectManager->GetJavaObjectByJsObjectFast(target); + } + + if (targetJavaObject.IsNull()) { + stringstream ss; + ss << "Cannot access property '" << fieldMetadata.getName().c_str() + << "' because there is no corresponding Java object"; + throw NativeScriptException(ss.str()); + } + } + + auto fieldId = fieldData->fid; + auto clazz = fieldData->clazz; + + if (isPrimitiveType) { + switch (fieldTypeName[0]) { + case 'Z': { // bool + // TODO: validate value is a boolean before calling + bool boolValue = value.isBool() ? value.getBool() : false; + if (isStatic) { + jEnv.SetStaticBooleanField(clazz, fieldId, boolValue); + } else { + jEnv.SetBooleanField(targetJavaObject, fieldId, + boolValue); + } + break; + } + case 'B': { // byte + // TODO: validate value is a byte before calling + jbyte intValue = value.isNumber() ? js_util::get_int32(value) : 0; + if (isStatic) { + jEnv.SetStaticByteField(clazz, fieldId, intValue); + } else { + jEnv.SetByteField(targetJavaObject, fieldId, intValue); + } + break; + } + case 'C': { // char + std::string stringValue = js_util::get_string_value(rt, value); + JniLocalRef strValue(jEnv.NewStringUTF(stringValue.substr(0, 1).c_str())); + const char *chars = jEnv.GetStringUTFChars(strValue, 0); + + if (isStatic) { + jEnv.SetStaticCharField(clazz, fieldId, chars[0]); + } else { + jEnv.SetCharField(targetJavaObject, fieldId, chars[0]); + } + jEnv.ReleaseStringUTFChars(strValue, chars); + break; + } + case 'S': { // short + // TODO: validate value is a short before calling + short shortValue = value.isNumber() ? js_util::get_int32(value) : 0; + if (isStatic) { + jEnv.SetStaticShortField(clazz, fieldId, shortValue); + } else { + jEnv.SetShortField(targetJavaObject, fieldId, shortValue); + } + break; + } + case 'I': { // int + // TODO: validate value is a int before calling + int intValue = value.isNumber() ? js_util::get_int32(value) : 0; + if (isStatic) { + jEnv.SetStaticIntField(clazz, fieldId, intValue); + } else { + jEnv.SetIntField(targetJavaObject, fieldId, intValue); + } + break; + } + case 'J': { // long + jlong longValue = static_cast(ArgConverter::ConvertToJavaLong(rt, value)); + if (isStatic) { + jEnv.SetStaticLongField(clazz, fieldId, longValue); + } else { + jEnv.SetLongField(targetJavaObject, fieldId, longValue); + } + break; + } + case 'F': { // float + float floatValue = value.isNumber() ? js_util::get_number(value) : 0.0; + if (isStatic) { + jEnv.SetStaticFloatField(clazz, fieldId, + static_cast(floatValue)); + } else { + jEnv.SetFloatField(targetJavaObject, fieldId, + static_cast(floatValue)); + } + break; + } + case 'D': { // double + double doubleValue = value.isNumber() ? js_util::get_number(value) : 0.0; + if (isStatic) { + jEnv.SetStaticDoubleField(clazz, fieldId, doubleValue); + } else { + jEnv.SetDoubleField(targetJavaObject, fieldId, doubleValue); + } + break; + } + default: { + stringstream ss; + ss << "(InternalError): in FieldAccessor::SetJavaField: Unknown field type: '" + << fieldTypeName[0] << "'"; + throw NativeScriptException(ss.str()); + } + } + } else { + bool isString = fieldTypeName == "java/lang/String"; + JniLocalRef result; + + if (!js_util::is_null_or_undefined(value)) { + if (isString) { + // TODO: validate valie is a string; + result = ArgConverter::ConvertToJavaString(rt, value); + } else { + result = objectManager->GetJavaObjectByJsObject(value); + } + } + + if (isStatic) { + jEnv.SetStaticObjectField(clazz, fieldId, result); + } else { + jEnv.SetObjectField(targetJavaObject, fieldId, result); + } + } +} diff --git a/NativeScript/ffi/jni/jsi/metadata/FieldAccessor.h b/NativeScript/ffi/jni/jsi/metadata/FieldAccessor.h new file mode 100644 index 000000000..99a923a3f --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/FieldAccessor.h @@ -0,0 +1,27 @@ +#ifndef FIELDACCESSOR_H_ +#define FIELDACCESSOR_H_ + +#include "JEnv.h" +#include +#include "ObjectManager.h" +#include "FieldCallbackData.h" + +namespace tns { +class FieldAccessor { + public: + // `objectManager` and `targetJavaObject` may be supplied pre-resolved by + // the caller (the accessor callback) so this avoids a locked runtime + // lookup and a second host-object probe. Both fall back to resolving + // internally when omitted. + JsValue GetJavaField(JsRuntime& rt, const JsValue& target, FieldCallbackData* fieldData, + ObjectManager* objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); + + void SetJavaField(JsRuntime& rt, const JsValue& target, const JsValue& value, + FieldCallbackData* fieldData, + ObjectManager* objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); +}; +} + +#endif /* FIELDACCESSOR_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/FieldCallbackData.h b/NativeScript/ffi/jni/jsi/metadata/FieldCallbackData.h new file mode 100644 index 000000000..dc4374ce8 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/FieldCallbackData.h @@ -0,0 +1,28 @@ +#ifndef FIELDCALLBACKDATA_H_ +#define FIELDCALLBACKDATA_H_ + +#include "jni.h" +#include "Engine.h" +#include "MetadataEntry.h" + +namespace tns { + class ObjectManager; + + struct FieldCallbackData { + FieldCallbackData(MetadataEntry metadata) + : + metadata(metadata), fid(nullptr), clazz(nullptr) { + + } + + MetadataEntry metadata; + jfieldID fid; + jclass clazz; + // Cached per-runtime ObjectManager (this data is created per runtime, so + // the pointer's lifetime matches it) — avoids a locked runtime lookup. + tns::ObjectManager *objectManager = nullptr; + }; + +} + +#endif /* FIELDCALLBACKDATA_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataBuilder.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataBuilder.cpp new file mode 100644 index 000000000..02422aa70 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataBuilder.cpp @@ -0,0 +1,136 @@ +// +// Created by Ammar Ahmed on 28/09/2024. +// + +#include "MetadataBuilder.h" +#include +#include +#include +#include +#include +#include +#include +#include "NativeScriptException.h" +#include "NativeScriptAssert.h" +#include "File.h" +#include "CallbackHandlers.h" + + +using namespace tns; + +MetadataReader MetadataBuilder::BuildMetadata(const std::string &filesPath) { + timeval time1; + gettimeofday(&time1, nullptr); + + string baseDir = filesPath; + baseDir.append("/metadata"); + + DIR* dir = opendir(baseDir.c_str()); + + if(dir == nullptr){ + stringstream ss; + ss << "metadata folder couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + + // TODO: Is there a way to detect if the screen is locked as verification + // We assume based on the error that this is the only way to get this specific error here at this point + if (errno == ENOENT || errno == EACCES) { + // Log the error with error code + __android_log_print(ANDROID_LOG_ERROR, "TNS.error", "%s", ss.str().c_str()); + + // While the screen is locked after boot; we cannot access our own apps directory on Android 9+ + // So the only thing to do at this point is just exit normally w/o crashing! + + // The only reason we should be in this specific path; is if: + // 1) android:directBootAware="true" flag is set on receiver + // 2) android.intent.action.LOCKED_BOOT_COMPLETED intent is set in manifest on above receiver + // See: https://developer.android.com/guide/topics/manifest/receiver-element + // and: https://developer.android.com/training/articles/direct-boot + // This specific path occurs if you using the NativeScript-Local-Notification plugin, the + // receiver code runs fine, but the app actually doesn't need to startup. The Native code tries to + // startup because the receiver is triggered. So even though we are exiting, the receiver will have + // done its job + + _Exit(0); + } + else { + throw NativeScriptException(ss.str()); + } + } + + string nodesFile = baseDir + "/treeNodeStream.dat"; + string namesFile = baseDir + "/treeStringsStream.dat"; + string valuesFile = baseDir + "/treeValueStream.dat"; + + FILE* f = fopen(nodesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeNodeStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenNodes = ftell(f); + assert((lenNodes % sizeof(MetadataTreeNodeRawData)) == 0); + char* nodes = new char[lenNodes]; + rewind(f); + fread(nodes, 1, lenNodes, f); + fclose(f); + + const int _512KB = 524288; + + f = fopen(namesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeStringsStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenNames = ftell(f); + char* names = new char[lenNames + _512KB]; + rewind(f); + fread(names, 1, lenNames, f); + fclose(f); + + f = fopen(valuesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeValueStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenValues = ftell(f); + char* values = new char[lenValues + _512KB]; + rewind(f); + fread(values, 1, lenValues, f); + fclose(f); + + timeval time2; + gettimeofday(&time2, nullptr); + + DEBUG_WRITE("lenNodes=%d, lenNames=%d, lenValues=%d", lenNodes, lenNames, lenValues); + + long millis1 = (time1.tv_sec * 1000) + (time1.tv_usec / 1000); + long millis2 = (time2.tv_sec * 1000) + (time2.tv_usec / 1000); + + DEBUG_WRITE("time=%ld", (millis2 - millis1)); + + auto reader = BuildMetadata(lenNodes, reinterpret_cast(nodes), lenNames, reinterpret_cast(names), lenValues, reinterpret_cast(values)); + delete[] nodes; + return reader; +} + +MetadataReader MetadataBuilder::BuildMetadata(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData) { + return MetadataReader(nodesLength, nodeData, nameLength, nameData, valueLength, + valueData, CallbackHandlers::GetTypeMetadata); + + +} diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataBuilder.h b/NativeScript/ffi/jni/jsi/metadata/MetadataBuilder.h new file mode 100644 index 000000000..d6e85baa0 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataBuilder.h @@ -0,0 +1,25 @@ +// +// Created by Ammar Ahmed on 28/09/2024. +// + +#ifndef TESTAPPNAPI_METADATABUILDER_H +#define TESTAPPNAPI_METADATABUILDER_H + +#include +#include "MetadataReader.h" + +namespace tns { + + class MetadataBuilder { + public: + static MetadataReader BuildMetadata(const std::string &filesPath); + + private: + static MetadataReader + BuildMetadata(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData); + }; + +} // tns + +#endif //TESTAPPNAPI_METADATABUILDER_H diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp new file mode 100644 index 000000000..c3f8badeb --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.cpp @@ -0,0 +1,139 @@ +#include "MetadataNode.h" +#include "MetadataEntry.h" +#include "MetadataMethodInfo.h" +#include "MetadataReader.h" + +using namespace tns; + +MetadataEntry::MetadataEntry(MetadataTreeNode *m_treeNode, NodeType nodeType) : + treeNode(m_treeNode), type(nodeType), isExtensionFunction(false), isStatic(false), + isTypeMember(false), memberId(nullptr), clazz(nullptr), mi(nullptr),fi(nullptr), sfi(nullptr), + retType(MethodReturnType::Unknown), + paramCount(-1), isFinal(false), isResolved(false), retTypeParsed(false), + isFinalSet(false), isResolvedSet(false) {} + +std::string &MetadataEntry::getName() { + if (!name.empty()) return name; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Field) { + name = reader->ReadName(fi->nameOffset); + } else if (type == NodeType::StaticField) { + name = reader->ReadName(sfi->nameOffset); + } else if (type == NodeType::Method) { + name = mi.GetName(); + } + + return name; +} + +std::string &MetadataEntry::getSig() { + if (!sig.empty()) return sig; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Field) { + sig = reader->ReadTypeName(fi->nodeId); + } else if (type == NodeType::StaticField) { + sig = reader->ReadTypeName(sfi->nodeId); + } else if (type == NodeType::Method) { + uint8_t sigLength = mi.GetSignatureLength(); + if (sigLength > 0) + sig = mi.GetSignature(); + + } + + return sig; +} + +std::string &MetadataEntry::getReturnType() { + if (!returnType.empty()) return returnType; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method) { + if (mi.GetSignatureLength() > 0) { + returnType = MetadataReader::ParseReturnType(this->getSig()); + } + } else { + return returnType; + } + + return returnType; +} + +MethodReturnType MetadataEntry::getRetType() { + if (retTypeParsed && retType != MethodReturnType::Unknown) { + return retType; + } + + auto &methodReturnType = this->getReturnType(); + if (!methodReturnType.empty()) { + retType = MetadataReader::GetReturnType(methodReturnType); + retTypeParsed = retType != MethodReturnType::Unknown; + return retType; + } + + retType = MethodReturnType::Unknown; + retTypeParsed = false; + return MethodReturnType::Unknown; +} + +std::string &MetadataEntry::getDeclaringType() { + if (!declaringType.empty()) return declaringType; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::StaticField) { + declaringType = reader->ReadTypeName(sfi->declaringType); + } else if (type == NodeType::Method && isStatic) { + declaringType = mi.GetDeclaringType(); + } + + return declaringType; +} + +int MetadataEntry::getParamCount() { + if (paramCount != -1) return paramCount; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method) { + auto sigLength = mi.GetSignatureLength(); + if (sigLength > 0) { + paramCount = sigLength - 1; + } else { + paramCount = 0; + } + } + + return paramCount; +} + +bool MetadataEntry::getIsFinal() { + if (isFinalSet) return isFinal; + + if (type == NodeType::Field) { + isFinal = fi->finalModifier == MetadataTreeNode::FINAL; + } else if (type == NodeType::StaticField) { + isFinal = sfi->finalModifier == MetadataTreeNode::FINAL; + } + + isFinalSet = true; + + return isFinal; +} + +bool MetadataEntry::getIsResolved() { + if (isResolvedSet) return isResolved; + + auto reader = MetadataNode::getMetadataReader(); + if (type == NodeType::Method) { + isResolved = mi.CheckIsResolved() == 1; + } + + isResolvedSet = true; + + return isResolved; +} diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h new file mode 100644 index 000000000..1cb79b071 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataEntry.h @@ -0,0 +1,117 @@ +#ifndef METADATAENTRY_H_ +#define METADATAENTRY_H_ + +#include +#include "jni.h" +#include "MetadataTreeNode.h" +#include "MetadataMethodInfo.h" +#include "MetadataFieldInfo.h" + +namespace tns { + enum class NodeType { + Package, + Class, + Interface, + Method, + Field, + StaticField + }; + + enum class MethodReturnType { + Unknown, + Void, + Byte, + Short, + Int, + Long, + Float, + Double, + Char, + Boolean, + String, + Object + }; + + class MetadataEntry { + public: + + MetadataEntry(MetadataTreeNode *m_treeNode, NodeType nodeType); + + MetadataEntry(const MetadataEntry &other) = default; + + MetadataEntry &operator=(const MetadataEntry &other) { + if (this != &other) { + treeNode = other.treeNode; + type = other.type; + isExtensionFunction = other.isExtensionFunction; + isStatic = other.isStatic; + isTypeMember = other.isTypeMember; + memberId = other.memberId; + clazz = other.clazz; + parsedSig = other.parsedSig; + mi = other.mi; + fi = other.fi; + sfi = other.sfi; + name = other.name; + sig = other.sig; + returnType = other.returnType; + retType = other.retType; + retTypeParsed = other.retTypeParsed; + declaringType = other.declaringType; + paramCount = other.paramCount; + isFinal = other.isFinal; + isResolved = other.isResolved; + isResolvedSet = other.isResolvedSet; + isFinalSet = other.isFinalSet; + } + return *this; + } + + std::string &getName(); + + std::string &getSig(); + + std::string &getReturnType(); + + MethodReturnType getRetType(); + + std::string &getDeclaringType(); + + int getParamCount(); + + bool getIsFinal(); + + bool getIsResolved(); + + MetadataTreeNode *treeNode; + NodeType type; + bool isExtensionFunction; + bool isStatic; + bool isTypeMember; + void *memberId; + jclass clazz; + std::vector parsedSig; + + MethodInfo mi; + FieldInfo *fi; + StaticFieldInfo *sfi; + + std::string name; + std::string sig; + std::string returnType; + MethodReturnType retType; + std::string declaringType; + int paramCount; + bool isFinal; + bool isResolved; + + private: + + bool retTypeParsed; + bool isFinalSet; + bool isResolvedSet; + + }; +} + +#endif /* METADATAENTRY_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataFieldInfo.h b/NativeScript/ffi/jni/jsi/metadata/MetadataFieldInfo.h new file mode 100644 index 000000000..6c428960d --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataFieldInfo.h @@ -0,0 +1,28 @@ +#ifndef METADATAFIELDINFO_H_ +#define METADATAFIELDINFO_H_ + +#include + +namespace tns { +struct __attribute__ ((__packed__)) FieldInfo { + FieldInfo() + : +nameOffset(0), nodeId(0), finalModifier(0) { +} + +uint32_t nameOffset; +uint16_t nodeId; +uint8_t finalModifier; +}; + +struct __attribute__ ((__packed__)) StaticFieldInfo: FieldInfo { + StaticFieldInfo() + : +FieldInfo(), declaringType(0) { +} + +uint16_t declaringType; +}; +} + +#endif /* METADATAFIELDINFO_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataMethodInfo.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataMethodInfo.cpp new file mode 100644 index 000000000..42289ab89 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataMethodInfo.cpp @@ -0,0 +1,99 @@ +#include "MetadataMethodInfo.h" +#include "MetadataNode.h" + + +using namespace tns; + +std::string MethodInfo::GetName() { + string methodName = MetadataNode::getMetadataReader()->ReadName(nameOffset); + return methodName; +} + +uint8_t MethodInfo::CheckIsResolved() { + return resolvedData; +} + +uint16_t MethodInfo::GetSignatureLength() { + return m_signatureLength; +} + +std::string MethodInfo::GetSignature() { //use nodeId's to read the whole signature + auto m_reader = MetadataNode::getMetadataReader(); + string signature = "("; + string ret; + for (int i = 0; i < m_signatureLength; i++) { + uint16_t nodeId = nodeIds[i]; + string curArgTypeName = m_reader->ReadTypeName(nodeId); + MetadataTreeNode* node = m_reader->GetNodeById(nodeId); + + uint8_t nodeType = m_reader->GetNodeType(node); + bool isRefType = m_reader->IsNodeTypeClass(nodeType) || m_reader->IsNodeTypeInterface(nodeType); + if (i == 0) { + if ((curArgTypeName[0] != '[') && isRefType) { + ret.append("L"); + } + ret.append(curArgTypeName); + if ((curArgTypeName[0] != '[') && isRefType) { + ret.append(";"); + } + } else { + if ((curArgTypeName[0] != '[') && isRefType) { + signature.append("L"); + } + signature.append(curArgTypeName); + if ((curArgTypeName[0] != '[') && isRefType) { + signature.append(";"); + } + } + } + if (ret.empty()) { + ret = "V"; + } + signature += ")" + ret; + + return signature; +} + +std::string MethodInfo::GetDeclaringType() { + auto m_reader = MetadataNode::getMetadataReader(); + + return m_reader->ReadTypeName(declaringNodeId); +} + +int MethodInfo::GetSizeOfReadMethodInfo() { + + if (!sizeMeasured) { + sizeMeasured = true; + // name + nameOffset = *reinterpret_cast(m_pData); + m_pData += sizeof(uint32_t); + // resolved data + resolvedData = *reinterpret_cast(m_pData); + m_pData += sizeof(uint8_t); + // sig length + m_signatureLength = *reinterpret_cast(m_pData); + m_pData += sizeof(uint16_t); + + // signature + if (m_signatureLength > 0) { + uint16_t* nodeIdPtr = reinterpret_cast(m_pData); + nodeIds.resize(m_signatureLength); + for (int i = 0; i < m_signatureLength; i++) { + nodeIds[i] = *nodeIdPtr++; + } + m_pData += m_signatureLength * sizeof(uint16_t); + } + + // declaring type + if (isStatic) { + auto declaringTypePtr = reinterpret_cast(m_pData); + declaringNodeId = *declaringTypePtr; + m_pData += sizeof(uint16_t); + } + + + + } + + return m_pData - m_pStartData; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataMethodInfo.h b/NativeScript/ffi/jni/jsi/metadata/MetadataMethodInfo.h new file mode 100644 index 000000000..aadda536e --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataMethodInfo.h @@ -0,0 +1,66 @@ +#ifndef METHODINFOSMARTPOINTER_H_ +#define METHODINFOSMARTPOINTER_H_ + +#include +#include +#include + +using namespace std; + +namespace tns { + class MethodInfo { + public: + + MethodInfo(uint8_t *pValue) + : isStatic(false), m_pData(pValue), m_pStartData(pValue), m_signatureLength(0), + sizeMeasured(false), nameOffset(0), resolvedData(0), + declaringNodeId(0){ + } + + MethodInfo(const MethodInfo& other) = default; + + MethodInfo& operator=(const MethodInfo& other) { + if (this != &other) { + isStatic = other.isStatic; + m_pData = other.m_pData; + m_pStartData = other.m_pStartData; + m_signatureLength = other.m_signatureLength; + sizeMeasured = other.sizeMeasured; + nameOffset = other.nameOffset; + resolvedData = other.resolvedData; + declaringNodeId = other.declaringNodeId; + nodeIds = other.nodeIds; + } + return *this; + } + + std::string GetName(); + + uint8_t CheckIsResolved(); + + uint16_t GetSignatureLength(); + + std::string GetSignature(); + + std::string GetDeclaringType(); //used only for static methods + + int GetSizeOfReadMethodInfo(); + + bool isStatic; + + private: + uint8_t *m_pData; //where we currently read + uint8_t *m_pStartData; // pointer to the beginning + uint16_t m_signatureLength; + bool sizeMeasured; + + uint32_t nameOffset; + uint8_t resolvedData; + uint16_t declaringNodeId; + std::vector nodeIds; + + + }; +} + +#endif /* METHODINFOSMARTPOINTER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp new file mode 100644 index 000000000..357692760 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.cpp @@ -0,0 +1,2655 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "NativeScriptException.h" +#include "MetadataNode.h" +#include "CallbackHandlers.h" +#include "NativeScriptAssert.h" +#include "File.h" +#include "Runtime.h" +#include "ArgConverter.h" +#include "FieldCallbackData.h" +#include "MetadataBuilder.h" +#include "ArgsWrapper.h" +// Only referenced from the module-prologue column adjustment below, which is +// itself standalone-only -- a guest has no module wrapper to undo. +#if !defined(NS_JSI_HOST_RUNTIME) +#include "ModuleInternal.h" +#endif +#include "Util.h" +#include "GlobalHelpers.h" +#include "JSONObjectHelper.h" + +using namespace std; + +namespace { +// Carries a MetadataNode* in an object's native-state slot; see +// MetadataNode::GetNullNode. It overrides none of the HostObject traps and is +// never handed to JS as an object of its own. +struct NullNodeState : public engine::HostObject { + explicit NullNodeState(MetadataNode *node) : node(node) {} + + MetadataNode *node; +}; + +// Wraps a callback in the NativeScriptException -> JS translation every +// MetadataNode callback repeats. A JSError is already the engine's own throw and +// is left to propagate. +template +JsValue Guarded(JsRuntime &rt, Fn &&fn) { + try { + return fn(); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (JsError &) { + throw; + } catch (std::exception &e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJs(rt); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJs(rt); + } +} + +// Defines `name` on `object` as an accessor backed by the given host functions. +void DefineAccessor(JsRuntime &rt, const JsObject &object, const char *name, + engine::HostFunctionType getter, engine::HostFunctionType setter) { + JsFunction getterFn; + JsFunction setterFn; + if (getter) { + getterFn = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, name), 0, std::move(getter)); + } + if (setter) { + setterFn = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, name), 1, std::move(setter)); + } + js_util::define_property_get_set(rt, object, name, getter ? &getterFn : nullptr, + setter ? &setterFn : nullptr); +} +} + +void MetadataNode::Init(JsRuntime &rt) { + auto cache = GetMetadataNodeCache(rt); +} + +JsValue MetadataNode::CreateArrayObjectConstructor(JsRuntime &rt) { + auto it = s_arrayObjects.find(rt.identity()); + if (it != s_arrayObjects.end()) { + if (!js_util::is_null_or_undefined(it->second)) return JsValue(rt, it->second); + } + + auto node = GetOrCreate("java/lang/Object"); + auto objectConstructor = node->GetConstructorFunction(rt); + + // The engine layer synthesises the receiver for a host constructor (with the + // right prototype) on every backend, so the napi tree's EnsureConstructorThis + // -- which existed because some engines handed a null `this` -- has no + // counterpart here and this body is empty. + auto arrayConstructor = JsFunction::createFromHostConstructor( + rt, engine::PropNameID::forAscii(rt, "ArrayObjectWrapper"), 0, + [](JsRuntime &rt, const JsValue &thisVal, const JsValue *, size_t) -> JsValue { + return JsValue(rt, thisVal); + }); + + auto proto = arrayConstructor.getPropertyAsObject(rt, "prototype"); + ObjectManager::MarkObject(rt, JsValue(rt, proto)); + + js_util::set_function(rt, proto, "setValueAtIndex", ArraySetterCallback); + js_util::set_function(rt, proto, "getValueAtIndex", ArrayGetterCallback); + js_util::set_function(rt, proto, "getAllValues", ArrayGetAllValuesCallback); + DefineAccessor(rt, proto, "length", ArrayLengthCallback, nullptr); + + // Native helpers (previously synthesized by the JS getNativeArrayProp). + js_util::set_function(rt, proto, "map", ArrayMapCallback); + js_util::set_function(rt, proto, "forEach", ArrayForEachCallback); + js_util::set_function(rt, proto, "toString", ArrayToStringCallback); + { + auto symbolCtor = rt.global().getPropertyAsObject(rt, "Symbol"); + auto symbolIterator = symbolCtor.getProperty(rt, "iterator"); + auto iteratorFn = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, "[Symbol.iterator]"), 0, + ArraySymbolIteratorCallback); + proto.setProperty(rt, symbolIterator, JsValue(rt, iteratorFn)); + } + + js_util::inherits(rt, arrayConstructor, objectConstructor.asObject(rt)); + + JsValue result(rt, arrayConstructor); + s_arrayObjects.emplace(rt.identity(), JsValue(rt, result)); + + return result; +} + +JsValue MetadataNode::CreateExtendedJSWrapper(JsRuntime &rt, ObjectManager *objectManager, + const std::string &proxyClassName, + int javaObjectID, MetadataNode **outNode) { + JsValue extInstance; + + auto cacheData = GetCachedExtendedClassData(rt, proxyClassName); + + if (cacheData.node != nullptr) { + extInstance = objectManager->GetEmptyObject(); + if (js_util::is_null_or_undefined(extInstance)) { + return js_util::undefined(); + } + ObjectManager::MarkSuperCall(rt, extInstance); + auto extendedCtorFunc = cacheData.extendedCtorFunction.asObjectBorrowed(rt); + auto extendedPrototype = extendedCtorFunc.getProperty(rt, "prototype"); + js_util::setPrototypeOf(rt, extInstance, extendedPrototype); + + extInstance.asObjectBorrowed(rt).setProperty(rt, "constructor", + cacheData.extendedCtorFunction); + + SetInstanceMetadata(rt, extInstance, cacheData.node); + *outNode = cacheData.node; + } + + return extInstance; +} + +string MetadataNode::GetTypeMetadataName(JsRuntime &rt, const JsValue &value) { + if (!value.isObject()) return ""; + auto typeMetadataName = value.asObjectBorrowed(rt).getProperty(rt, PRIVATE_TYPE_NAME); + if (!typeMetadataName.isString()) return ""; + return typeMetadataName.asString(rt).utf8(rt); +} + + +bool MetadataNode::isArray() { + return m_isArray; +} + +JsValue MetadataNode::CreateJSWrapper(JsRuntime &rt, ObjectManager *objectManager) { + if (m_isArray) { + return CreateArrayWrapper(rt); + } + + JsValue obj = objectManager->GetEmptyObject(); + JsValue ctorFunc = GetConstructorFunction(rt); + auto object = obj.asObjectBorrowed(rt); + object.setProperty(rt, "constructor", ctorFunc); + js_util::setPrototypeOf(rt, obj, js_util::get_prototype(rt, ctorFunc)); + SetInstanceMetadata(rt, obj, this); + + return obj; +} + +JsValue MetadataNode::ArrayGetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + int32_t indexValue = argc > 0 ? js_util::get_int32(args[0]) : 0; + auto node = GetInstanceMetadata(rt, thisVal); + + return CallbackHandlers::GetArrayElement(rt, thisVal, indexValue, node->m_name); + }); +} + +JsValue MetadataNode::ArrayGetAllValuesCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + auto node = GetInstanceMetadata(rt, thisVal); + auto length = CallbackHandlers::GetArrayLength(rt, thisVal); + engine::Array arr(rt, (size_t) length); + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(thisVal); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + JsValue element = CallbackHandlers::GetArrayElement(rt, thisVal, i, node->m_name, + objectManager, javaArrObj); + arr.setValueAtIndex(rt, (size_t) i, element); + } + + return JsValue(rt, arr); + }); +} + +JsValue MetadataNode::ArraySetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + if (argc < 2) return js_util::undefined(); + + int32_t indexValue = js_util::get_int32(args[0]); + auto node = GetInstanceMetadata(rt, thisVal); + + CallbackHandlers::SetArrayElement(rt, thisVal, indexValue, node->m_name, args[1]); + return JsValue(rt, args[1]); + }); +} + +JsValue MetadataNode::ArrayLengthCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + return JsValue(CallbackHandlers::GetArrayLength(rt, thisVal)); + }); +} + +JsValue MetadataNode::ArrayMapCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + if (argc < 1 || !args[0].isObject()) return js_util::undefined(); + auto callback = args[0].asObjectBorrowed(rt).asFunction(rt); + auto node = GetInstanceMetadata(rt, thisVal); + int length = CallbackHandlers::GetArrayLength(rt, thisVal); + + engine::Array result(rt, (size_t) length); + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(thisVal); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + JsValue element = + CallbackHandlers::GetArrayElement(rt, thisVal, i, node->m_name, + objectManager, javaArrObj); + const JsValue cbArgs[] = {element, JsValue(i), JsValue(rt, thisVal)}; + JsValue mapped = callback.call(rt, cbArgs, (size_t) 3); + result.setValueAtIndex(rt, (size_t) i, mapped); + } + + return JsValue(rt, result); + }); +} + +JsValue MetadataNode::ArrayForEachCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + if (argc < 1 || !args[0].isObject()) return js_util::undefined(); + auto callback = args[0].asObjectBorrowed(rt).asFunction(rt); + auto node = GetInstanceMetadata(rt, thisVal); + int length = CallbackHandlers::GetArrayLength(rt, thisVal); + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(thisVal); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + JsValue element = + CallbackHandlers::GetArrayElement(rt, thisVal, i, node->m_name, + objectManager, javaArrObj); + const JsValue cbArgs[] = {element, JsValue(i), JsValue(rt, thisVal)}; + callback.call(rt, cbArgs, (size_t) 3); + } + + return js_util::undefined(); + }); +} + +namespace { +// Builds a real JS array snapshot of all elements (native get loop). +engine::Array BuildArraySnapshot(JsRuntime &rt, const JsValue &thisVal, + const std::string &signature) { + int length = CallbackHandlers::GetArrayLength(rt, thisVal); + engine::Array values(rt, (size_t) length); + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = tns::Runtime::GetRuntime(rt)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(thisVal); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + JsValue element = + CallbackHandlers::GetArrayElement(rt, thisVal, i, signature, + objectManager, javaArrObj); + values.setValueAtIndex(rt, (size_t) i, element); + } + return values; +} +} + +JsValue MetadataNode::ArrayToStringCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + auto node = GetInstanceMetadata(rt, thisVal); + auto values = BuildArraySnapshot(rt, thisVal, node->m_name); + + // values.join(",") + auto joinFn = values.getPropertyAsFunction(rt, "join"); + const JsValue joinArgs[] = {js_util::to_js_string(rt, ",")}; + return joinFn.callWithThis(rt, values, joinArgs, (size_t) 1); + }); +} + +JsValue +MetadataNode::ArraySymbolIteratorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return Guarded(rt, [&]() -> JsValue { + auto node = GetInstanceMetadata(rt, thisVal); + auto values = BuildArraySnapshot(rt, thisVal, node->m_name); + + // return values[Symbol.iterator]() -> delegate to the real array iterator + auto symbolCtor = rt.global().getPropertyAsObject(rt, "Symbol"); + auto symbolIterator = symbolCtor.getProperty(rt, "iterator"); + auto iterMethod = values.getProperty(rt, symbolIterator); + return iterMethod.asObject(rt).asFunction(rt).callWithThis(rt, values); + }); +} + +JsValue MetadataNode::CreateArrayWrapper(JsRuntime &rt) { + JsValue constructor = CreateArrayObjectConstructor(rt); + auto instance = constructor.asObjectBorrowed(rt).asFunction(rt) + .callAsConstructor(rt, static_cast(nullptr), (size_t) 0); + SetInstanceMetadata(rt, instance, this); + return instance; +} + +JsValue MetadataNode::GetImplementationObject(JsRuntime &rt, const JsValue &object) { + if (!object.isObject()) return js_util::undefined(); + + JsValue currentPrototype = JsValue(rt, object); + + JsValue implementationObject = + object.asObjectBorrowed(rt).getProperty(rt, CLASS_IMPLEMENTATION_OBJECT); + + if (!implementationObject.isUndefined()) { + return implementationObject; + } + + auto objectAsObject = object.asObjectBorrowed(rt); + + if (js_util::has_own_property(rt, objectAsObject, + PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT)) { + if (!js_util::has_own_property(rt, objectAsObject, "prototype")) { + return js_util::undefined(); + } + + return js_util::get_prototype(rt, object); + } + + auto activityImplementationObject = + objectAsObject.getProperty(rt, "t::ActivityImplementationObject"); + + if (!activityImplementationObject.isUndefined()) { + return activityImplementationObject; + } + + JsValue lastPrototype; + + bool prototypeCycleDetected = false; + + bool foundImplementationObject = false; + + while (!foundImplementationObject) { + currentPrototype = js_util::getPrototypeOf(rt, currentPrototype); + + if (currentPrototype.isNull() || currentPrototype.isUndefined()) { + break; + } + + if (js_util::strict_equal(rt, lastPrototype, currentPrototype)) { + auto abovePrototype = js_util::getPrototypeOf(rt, currentPrototype); + prototypeCycleDetected = js_util::strict_equal(rt, abovePrototype, currentPrototype); + break; + } + + if (currentPrototype.isNull() || prototypeCycleDetected) { + return js_util::undefined(); + } + + auto implObject = + currentPrototype.asObjectBorrowed(rt).getProperty(rt, CLASS_IMPLEMENTATION_OBJECT); + + if (!implObject.isUndefined()) { + foundImplementationObject = true; + return currentPrototype; + } + + lastPrototype = JsValue(rt, currentPrototype); + } + + return implementationObject; +} + +void MetadataNode::SetInstanceMetadata(JsRuntime &rt, const JsValue &object, MetadataNode *node) { + // node lives on the per-instance JSInstanceInfo (set in ObjectManager::Link / + // GetOrCreateProxy); the napi tree's non-host "#instance_metadata" external + // has no counterpart here. + (void) rt; + (void) object; + (void) node; +} + +MetadataNode *MetadataNode::GetNullNode(JsRuntime &rt, const JsValue &value) { + if (!value.isObject()) return nullptr; + auto state = value.asObjectBorrowed(rt).getNativeState(rt); + return state != nullptr ? state->node : nullptr; +} + + +JsValue MetadataNode::ExtendedClassConstructorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + ExtendedClassCallbackData *extData) { + return Guarded(rt, [&]() -> JsValue { + // The engine layer builds the receiver for a host constructor on every + // backend, so the napi tree's new.target probe and EnsureConstructorThis + // fallback are both unnecessary here. + JsValue receiver(rt, thisVal); + + SetInstanceMetadata(rt, receiver, extData->node); + + ObjectManager::MarkSuperCall(rt, receiver); + + string fullClassName = extData->fullClassName; + + ArgsWrapper argWrapper(args, argc, ArgType::Class); + JsValue jsThisProxy; + bool success = CallbackHandlers::RegisterInstance(rt, receiver, fullClassName, argWrapper, + extData->implementationObject, false, + &jsThisProxy, extData->node->m_name, + extData->node); + + return jsThisProxy; + }); +} + +JsValue MetadataNode::InterfaceConstructorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + MetadataNode *node) { + return Guarded(rt, [&]() -> JsValue { + JsValue implementationObject; + JsValue interfaceName; + + if (argc == 1) { + if (!args[0].isObject()) { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be an object if only one argument is provided")); + } + implementationObject = JsValue(rt, args[0]); + } else if (argc == 2) { + if (!args[0].isString()) { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be a string if only two argument is provided")); + } + + if (!args[1].isObject()) { + throw NativeScriptException( + string("Invalid arguments provided, second argument must be an object if only one argument is provided")); + } + + interfaceName = JsValue(rt, args[0]); + implementationObject = JsValue(rt, args[1]); + } else { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be a string and second argument must be an object")); + } + + auto className = node->m_implType; + JsValue receiver(rt, thisVal); + + SetInstanceMetadata(rt, receiver, node); + + ObjectManager::MarkSuperCall(rt, receiver); + + + js_util::setPrototypeOf(rt, implementationObject, + js_util::getPrototypeOf(rt, receiver)); + + js_util::setPrototypeOf(rt, receiver, implementationObject); + + receiver.asObjectBorrowed(rt).setProperty(rt, CLASS_IMPLEMENTATION_OBJECT, + implementationObject); + + ArgsWrapper argsWrapper(args, argc, ArgType::Interface); + + JsValue jsThisProxy; + auto success = CallbackHandlers::RegisterInstance(rt, receiver, className, argsWrapper, + implementationObject, true, &jsThisProxy, + std::string(), node); + return jsThisProxy; + }); +} + +JsValue MetadataNode::ClassConstructorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + MetadataNode *node) { + return Guarded(rt, [&]() -> JsValue { + JsValue receiver(rt, thisVal); + + SetInstanceMetadata(rt, receiver, node); + + // Plain construction has no extend name, so the full class name equals the + // base class name; skip CreateFullClassName (a string copy) and use the + // node's name directly for both. + const string &className = node->m_name; + + ArgsWrapper argsWrapper(args, argc, ArgType::Class); + JsValue jsThisProxy; + bool success = CallbackHandlers::RegisterInstance(rt, receiver, className, argsWrapper, + js_util::undefined(), false, &jsThisProxy, + className, node); + + return jsThisProxy; + }); +} + +string MetadataNode::CreateFullClassName(const std::string &className, + const std::string &extendNameAndLocation = "") { + string fullClassName = className; + + // create a class name consisting only of the base class name + last file name part + line + column + variable identifier + if (!extendNameAndLocation.empty()) { + string tempClassName = className; + fullClassName = Util::ReplaceAll(tempClassName, "$", "_"); + fullClassName += "_" + extendNameAndLocation; + } + + return fullClassName; +} + +// The compile-time policy, which is also the floor: a build that hard-wires +// content keying cannot be talked out of it by an app's config. +static bool ContentKeyedBindingsForcedOn() { +#if defined(NS_CONTENT_KEYED_BINDINGS) + return true; +#elif defined(NS_JSI_HOST_RUNTIME) + // A guest runs inside the embedder's bundle, so no location key computed here + // can match one the generator computed from a source tree it never saw. + return true; +#else + return false; +#endif +} + +// Resolved once, before any JS runs. The naming scheme has to match what the +// static binding generator used for this app, so the value comes from the build +// that ran the generator (app/package.json -> AppConfig -> Runtime::Init) rather +// than from anything decided here. +static bool s_contentKeyedBindings = ContentKeyedBindingsForcedOn(); + +void MetadataNode::SetContentKeyedBindings(bool enabled) { + // Only ever widens: an app may turn content keying on, never off under a + // runtime that was built to require it. + s_contentKeyedBindings = s_contentKeyedBindings || enabled; +} + +bool MetadataNode::ContentKeyedBindingsEnabled() { + return s_contentKeyedBindings; +} + +// A class-name token derived from what the generated binding is actually made +// of, rather than from where the extend() call happens to sit. +// +// The static binding generator writes a class out of exactly three inputs: the +// base class, the implemented interfaces, and the overridden method names. Two +// extend() sites that agree on all three produce byte-identical Java, so hashing +// them is enough to name the class -- and two sites that collide are meant to +// share, which is why the generator already treats a duplicate file with equal +// content as a warning rather than an error. +// +// Location keys cannot do this once an embedder owns the bundler: the generator +// reads source coordinates and the runtime reads bundle coordinates. A content +// key needs no shared coordinate system at all. +// +// Sorted, because the runtime's order is JS own-property order and the +// generator's is source order; the binding treats both sets as unordered. +// FNV-1a rather than std::hash, which is not stable across implementations and +// so could not be reproduced by the generator. +// Package separators and nested-type separators both become '.' -- see the +// comment in CreateContentKey. +static string Canonicalize(const string &name) { + string canonical = name; + canonical = Util::ReplaceAll(canonical, std::string("/"), std::string(".")); + return Util::ReplaceAll(canonical, std::string("$"), std::string(".")); +} + +string MetadataNode::CreateContentKey(const string &baseClassName, + vector interfaceNames, + vector methodNames) { + // Canonicalized before sorting, not after: '$' and '.' are different bytes, + // so sorting the binary form and sorting the dotted form can disagree on + // order even once the names themselves match. + for (auto &interfaceName: interfaceNames) { + interfaceName = Canonicalize(interfaceName); + } + + sort(interfaceNames.begin(), interfaceNames.end()); + sort(methodNames.begin(), methodNames.end()); + + // Nested types reach here in their binary form -- `Pack200$Unpacker`, and + // `/` for package separators on the interface side. The generator reads the + // same names out of JS source, where both are written with a dot and there + // is no way to tell a nested type from a package. So the dotted form is the + // canonical one for hashing; the JNI arrays that actually load the classes + // keep the binary form, which is what Class.forName needs. + string canonicalBase = Canonicalize(baseClassName); + + string payload = canonicalBase; + payload += "|"; + for (size_t i = 0; i < interfaceNames.size(); i++) { + if (i > 0) payload += ","; + payload += interfaceNames[i]; + } + payload += "|"; + for (size_t i = 0; i < methodNames.size(); i++) { + if (i > 0) payload += ","; + payload += methodNames[i]; + } + + uint64_t hash = 14695981039346656037ULL; + for (unsigned char c: payload) { + hash ^= static_cast(c); + hash *= 1099511628211ULL; + } + + char buffer[19]; + snprintf(buffer, sizeof(buffer), "h%016llx", static_cast(hash)); + + DEBUG_WRITE("CreateContentKey: %s -> %s", payload.c_str(), buffer); + + return string(buffer); +} + +bool MetadataNode::IsValidExtendName(JsRuntime &rt, const JsValue &name) { + string extendName = ArgConverter::ConvertToString(rt, name); + + for (char currentSymbol: extendName) { + bool isValidExtendNameSymbol = isalpha(currentSymbol) || + isdigit(currentSymbol) || + currentSymbol == '_'; + if (!isValidExtendNameSymbol) { + return false; + } + } + + return true; +} + + +bool +MetadataNode::GetExtendLocation(JsRuntime &rt, string &extendLocation, bool isTypeScriptExtend) { + stringstream extendLocationStream; + + auto frames = tns::BuildStacktraceFrames(rt, nullptr, 4); + if (frames.empty()) { + DEBUG_WRITE("%s", "FRAME IS NULL!"); + // Reported as a failure, not a success with an empty location. An empty + // location makes every unnamed extend() of the same base class produce + // the same generated class name, and the second one's overrides then + // dispatch to themselves -- a StackOverflowError far from the cause. + // The caller turns this into "No name specified for extend at location". + return false; + } + + tns::JsStacktraceFrame *frame; + if (isTypeScriptExtend) { + if (frames.size() > 3 && Util::Contains(frames[2].text, "call_super")) { + frame = &frames[3]; + } else if (frames.size() > 2) { + frame = &frames[2]; // the _super.apply call to ts_helpers will always be the third call frame + } else { + frame = &frames[0]; + } + } else { + frame = &frames[0]; + } + + string srcFileName = Util::ReplaceAll(frame->filename, "file://", ""); + + string fullPathToFile; + if (srcFileName == "" || srcFileName == "" || srcFileName == "JavaScript") { + fullPathToFile = "script"; + } else { + // The app-root-relative substring this used to compute was assigned and + // then immediately overwritten by the full name below, so it was already + // dead -- and it throws for any path that does not start with the app + // root, which is every path a guest sees. + fullPathToFile = srcFileName; + replace(fullPathToFile.begin(), fullPathToFile.end(), '/', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), '.', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), '-', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), ' ', '_'); + + vector pathParts; + Util::SplitString(fullPathToFile, "_", pathParts); + fullPathToFile = + pathParts.back() == "js" ? pathParts[pathParts.size() - 2] : pathParts.back(); + } + + if (frame->line < 0) { + extendLocationStream << fullPathToFile << " unknown line number"; + extendLocation = extendLocationStream.str(); + return false; + } + + if (frame->col < 0) { + extendLocationStream << fullPathToFile << " line:" << frame->line + << " unknown column number"; + extendLocation = extendLocationStream.str(); + return false; + } + int column = frame->col; + + // Both adjustments below exist to undo the runtime's own module wrapper, so + // that a call site's key matches what the static binding generator computed + // from the unwrapped source. A guest has no such wrapper -- the embedder's + // bundler (Metro) owns module wrapping and the static binding generator + // never saw the source -- so applying them here only corrupts the key, and + // two different extend() sites can collide onto one generated class. +#if !defined(NS_JSI_HOST_RUNTIME) + if (frame->line == 1) { + column -= ModuleInternal::MODULE_PROLOGUE_LENGTH; + } + +#ifdef TARGET_ENGINE_HERMES + column = column - 6; +#endif +#endif // !NS_JSI_HOST_RUNTIME + + extendLocationStream << fullPathToFile << "_" << frame->line << "_" << column << "_"; + extendLocation = extendLocationStream.str(); + return true; +} + + +bool MetadataNode::ValidateExtendArguments(JsRuntime &rt, size_t argc, const JsValue *argv, + bool extendLocationFound, string &extendLocation, + JsValue *extendName, JsValue *implementationObject, + bool isTypeScriptExtend) { + + if (argc == 1) { + if (!extendLocationFound) { + stringstream ss; + ss << "Invalid extend() call. No name specified for extend at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!argv[0].isObject()) { + stringstream ss; + ss << "Invalid extend() call. No implementation object specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + *implementationObject = JsValue(rt, argv[0]); + } else if (argc == 2 || isTypeScriptExtend) { + if (!argv[0].isString()) { + stringstream ss; + ss << "Invalid extend() call. No name for extend specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!argv[1].isObject()) { + stringstream ss; + ss + << "Invalid extend() call. Named extend should be called with second object parameter containing overridden methods at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + DEBUG_WRITE("ExtendsCallMethodHandler: getting extend name"); + + *extendName = JsValue(rt, argv[0]); + bool isValidExtendName = IsValidExtendName(rt, *extendName); + if (!isValidExtendName) { + stringstream ss; + ss << "The extend name \"" << ArgConverter::ConvertToString(rt, *extendName) + << "\" you provided contains invalid symbols. Try using the symbols [a-z, A-Z, 0-9, _]." + << endl; + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + *implementationObject = JsValue(rt, argv[1]); + } else { + stringstream ss; + ss << "Invalid extend() call at location: " << extendLocation.c_str(); + string exceptionMessage = ss.str(); + throw NativeScriptException(exceptionMessage); + } + + return true; +} + +MetadataNode::ExtendedClassCacheData +MetadataNode::GetCachedExtendedClassData(JsRuntime &rt, const string &proxyClassName) { + auto cache = GetMetadataNodeCache(rt); + ExtendedClassCacheData cacheData; + auto itFound = cache->ExtendedCtorFuncCache.find(proxyClassName); + if (itFound != cache->ExtendedCtorFuncCache.end()) { + cacheData.extendedCtorFunction = JsValue(rt, itFound->second.extendedCtorFunction); + cacheData.extendedName = itFound->second.extendedName; + cacheData.node = itFound->second.node; + } + + return cacheData; +} + +MetadataNode::MetadataNodeCache *MetadataNode::GetMetadataNodeCache(JsRuntime &rt) { + const void *key = rt.identity(); + auto cache = s_metadata_node_cache.Get(key); + if (cache) return cache; + cache = new MetadataNodeCache; + s_metadata_node_cache.Insert(key, cache); + return cache; +} + +MetadataNode::MetadataNode(MetadataTreeNode *treeNode) : m_treeNode(treeNode) { + uint8_t nodeType = s_metadataReader.GetNodeType(treeNode); + + m_name = s_metadataReader.ReadTypeName(m_treeNode); + + uint8_t parentNodeType = s_metadataReader.GetNodeType(treeNode->parent); + + m_isArray = s_metadataReader.IsNodeTypeArray(parentNodeType); + + bool isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + + if (!m_isArray && isInterface) { + bool isPrefix; + auto impTypeName = s_metadataReader.ReadInterfaceImplementationTypeName(m_treeNode, + isPrefix); + m_implType = isPrefix + ? (impTypeName + m_name) + : impTypeName; + } +} + +void MetadataNode::CreateTopLevelNamespaces(JsRuntime &rt) { + auto global = rt.global(); + + auto root = s_metadataReader.GetRoot(); + + const auto &children = *root->children; + + for (auto treeNode: children) { + uint8_t nodeType = s_metadataReader.GetNodeType(treeNode); + + if (nodeType == MetadataTreeNode::PACKAGE) { + auto node = GetOrCreateInternal(treeNode); + + JsValue packageObj = node->CreateWrapper(rt); + + string nameSpace = node->m_treeNode->name; + // if the namespaces matches a javascript keyword, prefix it with $ to avoid TypeScript and JavaScript errors + if (IsJavascriptKeyword(nameSpace)) { + nameSpace = "$" + nameSpace; + } + if (global.hasProperty(rt, nameSpace.c_str())) { + string fallback = "$" + nameSpace; + __android_log_print(ANDROID_LOG_WARN, "TNS.Runtime", + "Java package '%s' is exposed as '%s': the global '%s' already exists.", + nameSpace.c_str(), fallback.c_str(), nameSpace.c_str()); + nameSpace = fallback; + } + global.setProperty(rt, nameSpace.c_str(), packageObj); + } + } +} + +MetadataTreeNode *MetadataNode::GetOrCreateTreeNodeByName(const string &className) { + MetadataTreeNode *result = nullptr; + + auto itFound = s_name2TreeNodeCache.find(className); + + if (itFound != s_name2TreeNodeCache.end()) { + result = itFound->second; + } else { + result = s_metadataReader.GetOrCreateTreeNodeByName(className); + + s_name2TreeNodeCache.emplace(className, result); + } + + return result; +} + +string MetadataNode::GetName() { + return m_name; +} + +MetadataNode *MetadataNode::GetOrCreate(const string &className) { + MetadataNode *node = nullptr; + + auto it = s_name2NodeCache.find(className); + + if (it == s_name2NodeCache.end()) { + MetadataTreeNode *treeNode = GetOrCreateTreeNodeByName(className); + + node = GetOrCreateInternal(treeNode); + + s_name2NodeCache.emplace(className, node); + } else { + node = it->second; + } + + return node; +} + +MetadataNode *MetadataNode::GetOrCreateInternal(MetadataTreeNode *treeNode) { + MetadataNode *result = nullptr; + + auto it = s_treeNode2NodeCache.find(treeNode); + + if (it != s_treeNode2NodeCache.end()) { + result = it->second; + } else { + auto name = GetJniClassName(treeNode); + if (!name.empty()) { + auto it2 = s_name2NodeCache.find(name); + if ( it2 != s_name2NodeCache.end()) { + result = it2->second; + } + } + + if (!result) { + result = new MetadataNode(treeNode); + s_treeNode2NodeCache.emplace(treeNode, result); + if (!result->m_name.empty()) { + s_name2NodeCache.emplace(result->m_name, result); + } + } + } + + auto found = s_treeNode2NodeCache.find(treeNode); + if (found == s_treeNode2NodeCache.end()) { + s_treeNode2NodeCache.emplace(treeNode, result); + } + + return result; +} + +MetadataEntry MetadataNode::GetChildMetadataForPackage(MetadataNode *node, const char *propName) { + assert(node->m_treeNode->children != nullptr); + + MetadataEntry child(nullptr, NodeType::Class); + + const auto &children = *node->m_treeNode->children; + + for (auto treeNodeChild: children) { + if (strcmp(treeNodeChild->name.c_str(), propName) == 0) { + child.name = propName; + child.treeNode = treeNodeChild; + child.type = static_cast(s_metadataReader.GetNodeType(treeNodeChild)); + + if (s_metadataReader.IsNodeTypeInterface((uint8_t) child.type)) { + bool isPrefix; + string declaringType = s_metadataReader.ReadInterfaceImplementationTypeName( + treeNodeChild, isPrefix); + child.declaringType = isPrefix + ? (declaringType + + s_metadataReader.ReadTypeName(child.treeNode)) + : declaringType; + } + } + } + + return child; +} + +bool MetadataNode::IsJavascriptKeyword(const std::string &word) { + static set keywords; + + if (keywords.empty()) { + string kw[]{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", + "class", "const", "continue", "debugger", "default", "delete", "do", + "double", "else", "enum", "eval", "export", "extends", "false", "final", + "finally", "float", "for", "function", "goto", "if", "implements", + "import", "in", "instanceof", "int", "interface", "let", "long", "native", + "new", "null", "package", "private", "protected", "public", "return", + "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", + "transient", "true", "try", "typeof", "var", "void", "volatile", "while", + "with", "yield"}; + + keywords = set(kw, kw + sizeof(kw) / sizeof(kw[0])); + } + + return keywords.find(word) != keywords.end(); +} + +JsValue MetadataNode::CreateWrapper(JsRuntime &rt) { + uint8_t nodeType = s_metadataReader.GetNodeType(m_treeNode); + bool isClass = s_metadataReader.IsNodeTypeClass(nodeType), + isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + + if (isClass || isInterface) { + return GetConstructorFunction(rt); + } + + if (s_metadataReader.IsNodeTypePackage(nodeType)) { + return CreatePackageObject(rt); + } + + std::stringstream ss; + ss << "(InternalError): Can't create proxy for this type=" << static_cast(nodeType); + throw NativeScriptException(ss.str()); +} + +JsValue MetadataNode::PackageGetterCallback(JsRuntime &rt, const JsValue &thisVal, + MetadataTreeNode *childTreeNode) { + return Guarded(rt, [&]() -> JsValue { + DEBUG_WRITE("Get package item: %s", childTreeNode->name.c_str()); + + auto childNode = MetadataNode::GetOrCreateInternal(childTreeNode); + JsValue value = childNode->CreateWrapper(rt); + + uint8_t childNodeType = s_metadataReader.GetNodeType(childTreeNode); + if (s_metadataReader.IsNodeTypeInterface(childNodeType)) { + // For all java interfaces we register the special Symbol.hasInstance property + // which is invoked by the instanceof operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance). + // For example: + // + // Object.defineProperty(android.view.animation.Interpolator, Symbol.hasInstance, { + // value: function(obj) { + // return true; + // } + // }); + RegisterSymbolHasInstanceCallback(rt, childTreeNode, value); + } + + // org.json.JSONObject special-case. Cheap name check first so the parent + // lookup only happens for the one class that needs it. + if (childTreeNode->name == "JSONObject") { + auto parentNode = GetOrCreateInternal(childTreeNode->parent); + if (parentNode->m_name == "org/json") { + JSONObjectHelper::RegisterFromFunction(rt, value); + } + } + + // Replace this accessor on the receiver with the resolved value as a plain + // (configurable) data property, so every subsequent `pkg.Child` access is a + // direct, inline-cacheable property load instead of re-invoking this getter. + if (thisVal.isObject()) { + js_util::define_property_value(rt, thisVal.asObjectBorrowed(rt), + childTreeNode->name.c_str(), value); + } + + return value; + }); +} + +void MetadataNode::RegisterSymbolHasInstanceCallback(JsRuntime &rt, + const MetadataTreeNode *treeNode, + const JsValue &interface) { + if (!interface.isObject()) { + return; + } + + JEnv jEnv; + + auto className = GetJniClassName(treeNode); + auto clazz = jEnv.FindClass(className); + if (clazz == nullptr) { + return; + } + + auto symbol = rt.global().getPropertyAsObject(rt, "Symbol"); + auto hasInstance = symbol.getProperty(rt, "hasInstance"); + + // The class ref is captured by the callback itself. The napi tree had to box + // it in a heap holder because PrimJS packed the napi `data` pointer into 48 + // bits and corrupted a JNI global ref; there is no `data` pointer here. + auto method = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, "hasInstance"), 1, + [clazz](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + if (argc != 1) { + throw JsError(rt, "Symbol.hasInstance must take exactly 1 argument"); + } + + const JsValue &object = args[0]; + + if (!object.isObject()) { + return JsValue(false); + } + + auto runtime = Runtime::GetRuntime(rt); + auto objectManager = runtime->GetObjectManager(); + auto obj = objectManager->GetJavaObjectByJsObject(object); + + if (obj.IsNull()) { + // Couldn't find a corresponding java instance counterpart. This could happen + // if the "instanceof" operator is invoked on a pure javascript instance + return JsValue(false); + } + + JEnv jEnv; + return JsValue((bool) jEnv.IsInstanceOf(obj, clazz)); + }); + + // Defined (not assigned) so the well-known symbol lands as a non-enumerable + // own property, matching napi_define_properties with napi_default. + JsObject descriptor(rt); + descriptor.setProperty(rt, "value", JsValue(rt, method)); + descriptor.setProperty(rt, "enumerable", false); + descriptor.setProperty(rt, "configurable", false); + descriptor.setProperty(rt, "writable", false); + const JsValue defineArgs[] = {JsValue(rt, interface), hasInstance, JsValue(rt, descriptor)}; + js_util::Builtins::of(rt).defineProperty.call(rt, defineArgs, (size_t) 3); +} + + +std::string MetadataNode::GetJniClassName(const MetadataTreeNode *node) { + std::stack s; + + while (node != nullptr && !node->name.empty()) { + s.push(node->name); + node = node->parent; + } + + string fullClassName; + while (!s.empty()) { + auto top = s.top(); + fullClassName = (fullClassName.empty()) ? top : fullClassName + "/" + top; + s.pop(); + } + + return fullClassName; +} + +JsValue MetadataNode::CreatePackageObject(JsRuntime &rt) { + JsObject packageObj(rt); + + auto ptrChildren = this->m_treeNode->children; + + if (ptrChildren != nullptr) { + const auto &children = *ptrChildren; + auto lastChildName = ""; + for (auto childNode: children) { + if (strcmp(childNode->name.c_str(), lastChildName) == 0) { + continue; + } + lastChildName = childNode->name.c_str(); + DefineAccessor(rt, packageObj, childNode->name.c_str(), + [childNode](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return PackageGetterCallback(rt, thisVal, childNode); + }, + nullptr); + } + } + + return JsValue(rt, packageObj); +} + +std::vector MetadataNode::SetClassMembers( + JsRuntime &rt, JsObject constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + + if (treeNode->metadata != nullptr) { + return SetInstanceMembersFromRuntimeMetadata( + rt, constructor, instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); + } + + return SetClassMembersFromStaticMetadata( + rt, constructor, instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); +} + +std::vector MetadataNode::SetClassMembersFromStaticMetadata( + JsRuntime &rt, JsObject constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + + std::vector instanceMethodData; + + uint8_t *curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1; + + auto nodeType = s_metadataReader.GetNodeType(treeNode); + auto curType = s_metadataReader.ReadTypeName(treeNode); + curPtr += sizeof(uint16_t /* baseClassId */); + + if (s_metadataReader.IsNodeTypeInterface(nodeType)) { + curPtr += sizeof(uint8_t) + sizeof(uint32_t); + } + + std::string lastMethodName; + MethodCallbackData *callbackData = nullptr; + + robin_hood::unordered_map collectedExtensionMethods; + + auto prototype = constructor.getPropertyAsObject(rt, "prototype"); + + // The napi tree also took a strong reference to the prototype here, for the + // non-host receiver check in IsInstanceReceiver. Host objects are the only + // path, so nothing needs it. + + auto objectManager = Runtime::GetObjectManager(rt); + auto cache = GetMetadataNodeCache(rt); + auto extensionFunctionsCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + collectedExtensionMethods.reserve(extensionFunctionsCount); + + for (auto i = 0; i < extensionFunctionsCount; i++) { + auto entry = MetadataReader::ReadExtensionFunctionEntry(&curPtr); + + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = tryGetExtensionMethodCallbackData(collectedExtensionMethods, + methodName); + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + + auto method = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, methodName), 0, + [callbackData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return MethodCallback(rt, thisVal, args, argc, callbackData); + }); + + js_util::define_property_value(rt, prototype, methodName.c_str(), + JsValue(rt, method), false, true, true); + lastMethodName = methodName; + collectedExtensionMethods.emplace(methodName, callbackData); + + } + } + + callbackData->candidates.push_back(std::move(entry)); + callbackData->objectManager = objectManager; + } + + auto instanceMethodCount = *reinterpret_cast(curPtr); + collectedExtensionMethods.reserve(instanceMethodCount); + curPtr += sizeof(uint16_t); + + for (auto i = 0; i < instanceMethodCount; i++) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = tryGetExtensionMethodCallbackData(collectedExtensionMethods, + methodName); + + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + auto method = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, methodName), 0, + [callbackData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return MethodCallback(rt, thisVal, args, argc, callbackData); + }); + js_util::define_property_value(rt, prototype, methodName.c_str(), + JsValue(rt, method), false, true, true); + collectedExtensionMethods.emplace(methodName, callbackData); + } + + instanceMethodData.push_back(callbackData); + instanceMethodsCallbackData.push_back(callbackData); + + auto itFound = std::find_if(baseInstanceMethodsCallbackData.begin(), + baseInstanceMethodsCallbackData.end(), + [&methodName](MethodCallbackData *x) { + return x->candidates.front().name == methodName; + }); + if (itFound != baseInstanceMethodsCallbackData.end()) { + callbackData->parent = *itFound; + } + + lastMethodName = methodName; + } + + callbackData->candidates.push_back(std::move(entry)); + callbackData->objectManager = objectManager; + } + for (const auto &extension: collectedExtensionMethods) { + if (std::find(instanceMethodData.begin(), instanceMethodData.end(), extension.second) == + instanceMethodData.end()) { + cache->methodCallbackData.push_back(extension.second); + } + } + auto instanceFieldCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < instanceFieldCount; i++) { + auto entry = MetadataReader::ReadInstanceFieldEntry(&curPtr); + auto &fieldName = entry.getName(); + auto fieldInfo = new FieldCallbackData(entry); + fieldInfo->metadata.declaringType = curType; + fieldInfo->objectManager = objectManager; + DefineAccessor(rt, prototype, fieldName.c_str(), + [fieldInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return FieldAccessorGetterCallback(rt, thisVal, fieldInfo); + }, + [fieldInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return FieldAccessorSetterCallback(rt, thisVal, args, argc, fieldInfo); + }); + + cache->fieldCallbackData.push_back(fieldInfo); + + } + + auto kotlinPropertiesCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (int i = 0; i < kotlinPropertiesCount; ++i) { + uint32_t nameOffset = *reinterpret_cast(curPtr); + auto propertyName = s_metadataReader.ReadName(nameOffset); + curPtr += sizeof(uint32_t); + + auto hasGetter = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + + // Keep the full method entry (not just its name) so the accessor can call + // CallJavaMethod directly instead of looking up + invoking the JS method. + MetadataEntry *getterEntry = nullptr; + std::string getterMethodName; + if (hasGetter >= 1) { + getterEntry = new MetadataEntry(MetadataReader::ReadInstanceMethodEntry(&curPtr)); + getterMethodName = getterEntry->getName(); + } + + auto hasSetter = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + + MetadataEntry *setterEntry = nullptr; + std::string setterMethodName; + if (hasSetter >= 1) { + setterEntry = new MetadataEntry(MetadataReader::ReadInstanceMethodEntry(&curPtr)); + setterMethodName = setterEntry->getName(); + } + + auto propertyInfo = new PropertyCallbackData(propertyName, getterMethodName, + setterMethodName); + propertyInfo->getterEntry = getterEntry; + propertyInfo->setterEntry = setterEntry; + propertyInfo->node = this; + propertyInfo->objectManager = objectManager; + cache->propertyCallbackData.push_back(propertyInfo); + DefineAccessor(rt, prototype, propertyName.c_str(), + [propertyInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return PropertyAccessorGetterCallback(rt, thisVal, propertyInfo); + }, + [propertyInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return PropertyAccessorSetterCallback(rt, thisVal, args, argc, + propertyInfo); + }); + } + + // Set static class members on constructor + lastMethodName.clear(); + callbackData = nullptr; + + auto origin = Constants::APP_ROOT_FOLDER_PATH + this->m_name; + + // get candidates from static methods metadata + auto staticMethodCout = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < staticMethodCout; i++) { + auto entry = MetadataReader::ReadStaticMethodEntry(&curPtr); + // In java there can be multiple methods of same name with different parameters. + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = new MethodCallbackData(this); + cache->methodCallbackData.push_back(callbackData); + auto method = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, methodName), 0, + [callbackData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return MethodCallback(rt, thisVal, args, argc, callbackData); + }); + + js_util::define_property_value(rt, constructor, methodName.c_str(), + JsValue(rt, method), false, true, true); + lastMethodName = methodName; + } + callbackData->candidates.push_back(std::move(entry)); + callbackData->objectManager = objectManager; + } + + MetadataNode *self = this; + auto extendMethod = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, PROP_KEY_EXTEND), 0, + [self](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return ExtendMethodCallback(rt, thisVal, args, argc, self); + }); + constructor.setProperty(rt, PROP_KEY_EXTEND, JsValue(rt, extendMethod)); + + // Brand the runtime's native extend() so ts_helpers can reliably tell a native class's + // extend from a user/JS extend. It must NOT rely on Function.prototype.toString() sniffing + // "[native code]": in release builds JS is compiled to bytecode and every function + // (native or JS) stringifies to "[native code]", so a plain JS class with a static method + // named "extend" would be misdetected as native. This brand is a real, non-enumerable + // property set by the runtime, so it works identically for source and bytecode on all engines. + js_util::define_property_value(rt, extendMethod, "__isNativeExtend__", JsValue(true), + false, false, false); + + // get candidates from static fields metadata + auto staticFieldCout = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < staticFieldCout; i++) { + auto entry = MetadataReader::ReadStaticFieldEntry(&curPtr); + auto &fieldName = entry.getName(); + auto fieldInfo = new FieldCallbackData(entry); + fieldInfo->objectManager = objectManager; + DefineAccessor(rt, constructor, fieldName.c_str(), + [fieldInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return FieldAccessorGetterCallback(rt, thisVal, fieldInfo); + }, + [fieldInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return FieldAccessorSetterCallback(rt, thisVal, args, argc, fieldInfo); + }); + cache->fieldCallbackData.push_back(fieldInfo); + } + + + DefineAccessor(rt, constructor, PROP_KEY_NULLOBJECT, + [self](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return NullObjectAccessorGetterCallback(rt, thisVal, self); + }, + nullptr); + + + std::string tname = s_metadataReader.ReadTypeName(treeNode); + constructor.setProperty(rt, PRIVATE_TYPE_NAME, ArgConverter::convertToJsString(rt, tname)); + + SetClassAccessor(rt, constructor); + + return instanceMethodData; +} + +MetadataNode::MethodCallbackData *MetadataNode::tryGetExtensionMethodCallbackData( + const robin_hood::unordered_map &collectedMethodCallbackData, + const std::string &lookupName) { + + if (collectedMethodCallbackData.empty()) { + return nullptr; + } + + auto itFound = collectedMethodCallbackData.find(lookupName); + if (itFound != collectedMethodCallbackData.end()) { + return itFound->second; + } + + return nullptr; +} + +bool MetadataNode::IsNodeTypeInterface() { + uint8_t nodeType = s_metadataReader.GetNodeType(m_treeNode); + return s_metadataReader.IsNodeTypeInterface(nodeType); +} + +std::vector MetadataNode::SetInstanceMembersFromRuntimeMetadata( + JsRuntime &rt, JsObject constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + assert(treeNode->metadata != nullptr); + + std::vector instanceMethodData; + + std::string line; + const std::string &metadata = *treeNode->metadata; + std::stringstream s(metadata); + + std::string kind; + std::string name; + std::string signature; + int paramCount; + + std::getline(s, line); // type line + std::getline(s, line); // base class line + + std::string lastMethodName; + MethodCallbackData *callbackData = nullptr; + + auto cache = GetMetadataNodeCache(rt); + auto proto = constructor.getPropertyAsObject(rt, "prototype"); + while (std::getline(s, line)) { + std::stringstream tmp(line); + tmp >> kind >> name >> signature >> paramCount; + + char chKind = kind[0]; + + assert((chKind == 'M') || (chKind == 'F')); + + MetadataEntry entry(nullptr, NodeType::Field); + + entry.name = name; + entry.sig = signature; + entry.paramCount = paramCount; + entry.isStatic = false; + if (chKind == 'M') { + entry.type = NodeType::Method; + if (entry.name != lastMethodName) { + callbackData = new MethodCallbackData(this); + instanceMethodData.push_back(callbackData); + instanceMethodsCallbackData.push_back(callbackData); + + auto itFound = std::find_if(baseInstanceMethodsCallbackData.begin(), + baseInstanceMethodsCallbackData.end(), + [&entry](MethodCallbackData *x) { + return x->candidates.front().name == entry.name; + }); + if (itFound != baseInstanceMethodsCallbackData.end()) { + callbackData->parent = *itFound; + } + + auto method = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, entry.name), 0, + [callbackData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return MethodCallback(rt, thisVal, args, argc, callbackData); + }); + proto.setProperty(rt, entry.name.c_str(), JsValue(rt, method)); + + lastMethodName = entry.name; + } + callbackData->candidates.push_back(std::move(entry)); + } else if (chKind == 'F') { + entry.type = NodeType::Field; + auto *fieldInfo = new FieldCallbackData(entry); + DefineAccessor(rt, proto, entry.name.c_str(), + [fieldInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return FieldAccessorGetterCallback(rt, thisVal, fieldInfo); + }, + [fieldInfo](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return FieldAccessorSetterCallback(rt, thisVal, args, argc, + fieldInfo); + }); + + cache->fieldCallbackData.push_back(fieldInfo); + } + } + + return instanceMethodData; +} + +void MetadataNode::SetClassAccessor(JsRuntime &rt, JsObject constructor) { + DefineAccessor(rt, constructor, PROP_KEY_CLASS, + [](JsRuntime &rt, const JsValue &thisVal, const JsValue *, size_t) -> JsValue { + return ClassAccessorGetterCallback(rt, thisVal); + }, + nullptr); +} + +JsValue MetadataNode::ClassAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal) { + return Guarded(rt, [&]() -> JsValue { + if (!thisVal.isObject()) return js_util::undefined(); + auto name = thisVal.asObjectBorrowed(rt).getProperty(rt, PRIVATE_TYPE_NAME); + if (!name.isString()) return js_util::undefined(); + auto nameValue = name.asString(rt).utf8(rt); + return CallbackHandlers::FindClass(rt, nameValue.c_str()); + }); +} + +JsValue MetadataNode::GetConstructorFunction(JsRuntime &rt) { + std::vector instanceMethodsCallbackData; + return GetConstructorFunctionInternal(rt, m_treeNode, instanceMethodsCallbackData); +} + +JsValue MetadataNode::GetConstructorFunctionInternal(JsRuntime &rt, MetadataTreeNode *treeNode, + std::vector instanceMethodsCallbackData) { + + auto cache = GetMetadataNodeCache(rt); + auto itFound = cache->CtorFuncCache.find(treeNode); + if (itFound != cache->CtorFuncCache.end()) { + if (!js_util::is_null_or_undefined(itFound->second.constructorFunction)) { + instanceMethodsCallbackData = itFound->second.instanceMethodCallbacks; + return JsValue(rt, itFound->second.constructorFunction); + } + } + + if (itFound != cache->CtorFuncCache.end()) { + // The napi tree guarded this delete with #ifndef __JSC__. The callback + // data is owned here and never reachable from JS, so the guard has no + // engine dependency to express and is dropped. + for (auto data: itFound->second.instanceMethodCallbacks) { + delete data; + } + itFound->second.instanceMethodCallbacks.clear(); + cache->CtorFuncCache.erase(itFound); + } + + auto node = GetOrCreateInternal(treeNode); + + JEnv jEnv; + // if we already have an exception (which will be rethrown later) + // then we don't want to ignore the next exception + bool ignoreFindClassException = jEnv.ExceptionCheck() == JNI_FALSE; + auto currentClass = jEnv.FindClass(node->m_name); + if (ignoreFindClassException && jEnv.ExceptionCheck()) { + jEnv.ExceptionClear(); + // JNI found an exception looking up this class + // but we don't care, because this means this class doesn't exist + // like when you try to get a class that only exists in a higher API level + CtorCacheData ctorCacheItem(js_util::undefined(), instanceMethodsCallbackData); + cache->CtorFuncCache.emplace(treeNode, std::move(ctorCacheItem)); + return js_util::undefined(); + }; + + auto currentNode = treeNode; + std::string finalName(currentNode->name); + while (currentNode->parent) { + if (!currentNode->parent->name.empty()) { + finalName = currentNode->parent->name + "." + finalName; + } + currentNode = currentNode->parent; + } + + // 1. Create the class and get the constructor + + auto isInterface = s_metadataReader.IsNodeTypeInterface(treeNode->type); + auto constructor = JsFunction::createFromHostConstructor( + rt, engine::PropNameID::forAscii(rt, finalName), 0, + isInterface + ? engine::HostFunctionType( + [node](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return InterfaceConstructorCallback(rt, thisVal, args, argc, node); + }) + : engine::HostFunctionType( + [node](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return ClassConstructorCallback(rt, thisVal, args, argc, node); + })); + + // Mark this constructor's prototype as a runtime object. + ObjectManager::MarkObject(rt, constructor.getProperty(rt, "prototype")); + + // 2. Create the base constructor if it doesn't exist and inherit from it. + JsValue baseConstructor; + std::vector baseInstanceMethodsCallbackData; + auto tmpTreeNode = treeNode; + std::vector skippedBaseTypes; + + while (true) { + auto baseTreeNode = s_metadataReader.GetBaseClassNode(tmpTreeNode); + if (CheckClassHierarchy(jEnv, currentClass, treeNode, baseTreeNode, skippedBaseTypes)) { + tmpTreeNode = baseTreeNode; + continue; + } + + if ((baseTreeNode != treeNode) && (baseTreeNode != nullptr) && + (baseTreeNode->offsetValue > 0)) { + baseConstructor = GetConstructorFunctionInternal(rt, baseTreeNode, + baseInstanceMethodsCallbackData); + + + if (baseConstructor.isObject()) { + js_util::inherits(rt, constructor, baseConstructor.asObjectBorrowed(rt)); + } + } else { + baseConstructor = js_util::undefined(); + } + break; + } + + // 3. Define the class members now. + auto instanceMethodData = node->SetClassMembers(rt, constructor, + instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); + + if (!skippedBaseTypes.empty()) { + // If there is a mismatch between base type of this class in metadata compared to the class + // at runtime, we will add methods of base class to this class's prototype. + node->SetMissingBaseMethods(rt, skippedBaseTypes, instanceMethodData, constructor); + } + + + SetInnerTypes(rt, constructor, treeNode); + + JsValue constructorValue(rt, constructor); + + if (baseConstructor.isObject()) { + js_util::setPrototypeOf(rt, constructorValue, baseConstructor); + } + + CtorCacheData ctorCacheItem(JsValue(rt, constructorValue), instanceMethodsCallbackData); + cache->CtorFuncCache.emplace(treeNode, std::move(ctorCacheItem)); + + return constructorValue; +} + +void MetadataNode::SetInnerTypes(JsRuntime &rt, JsObject constructor, + MetadataTreeNode *treeNode) { + if (treeNode->children != nullptr) { + const auto &children = *treeNode->children; + + for (auto curChild: children) { + if (!js_util::has_own_property(rt, constructor, curChild->name.c_str())) { + DefineAccessor(rt, constructor, curChild->name.c_str(), + [curChild](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return InnerTypeGetterCallback(rt, thisVal, curChild); + }, + nullptr); + } + } + } +} + +JsValue MetadataNode::InnerTypeGetterCallback(JsRuntime &rt, const JsValue &thisVal, + MetadataTreeNode *curChild) { + return Guarded(rt, [&]() -> JsValue { + auto childNode = GetOrCreateInternal(curChild); + // GetConstructorFunction caches per node (CtorFuncCache); inner types are + // always class/interface, both resolved here. + JsValue constructor = childNode->GetConstructorFunction(rt); + + // Java interfaces need Symbol.hasInstance for `instanceof` support, just + // like package-level interfaces in PackageGetterCallback. + uint8_t childNodeType = s_metadataReader.GetNodeType(curChild); + if (s_metadataReader.IsNodeTypeInterface(childNodeType)) { + RegisterSymbolHasInstanceCallback(rt, curChild, constructor); + } + + // Replace this accessor on the receiver (the outer type) with the resolved + // inner class/interface as a plain (configurable) data property, so every + // subsequent Outer.Inner access is a direct, inline-cacheable property load + // instead of re-invoking this getter. + if (thisVal.isObject()) { + js_util::define_property_value(rt, thisVal.asObjectBorrowed(rt), + curChild->name.c_str(), constructor); + } + + return constructor; + }); +} + +MetadataReader *MetadataNode::getMetadataReader() { + return &MetadataNode::s_metadataReader; +} + +JsValue MetadataNode::NullObjectAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal, + MetadataNode *node) { + return Guarded(rt, [&]() -> JsValue { + if (!thisVal.isObject()) return js_util::undefined(); + + auto object = thisVal.asObjectBorrowed(rt); + + if (GetNullNode(rt, thisVal) == nullptr) { + object.setNativeState(rt, std::make_shared(node)); + js_util::set_function(rt, object, "valueOf", MetadataNode::NullValueOfCallback); + } + + return JsValue(rt, thisVal); + }); +} + +JsValue MetadataNode::NullValueOfCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc) { + return js_util::null(); +} + +bool MetadataNode::IsInstanceReceiver(JsRuntime &rt, const JsValue &jsThis) { + // Real instances are host-object proxies; the class prototype is not. A + // non-host receiver means someone touched Class.prototype.. + return Runtime::GetRuntime(rt)->GetObjectManager()->IsHostObject(jsThis); +} + +JsValue MetadataNode::FieldAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal, + FieldCallbackData *fieldData) { + return Guarded(rt, [&]() -> JsValue { + auto &fieldMetadata = fieldData->metadata; + + if (fieldMetadata.getDeclaringType().empty()) { + return js_util::undefined(); + } + + if (fieldData->objectManager == nullptr) { + fieldData->objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + if (fieldMetadata.isStatic) { + return CallbackHandlers::GetJavaField(rt, thisVal, fieldData, + fieldData->objectManager); + } + + // A single probe both validates the receiver and resolves the java + // object; null + non-host means Class.prototype. access. + JniLocalRef target = fieldData->objectManager->GetJavaObjectByJsObjectFast(thisVal); + if (target.IsNull() && !IsInstanceReceiver(rt, thisVal)) { + return js_util::undefined(); + } + return CallbackHandlers::GetJavaField(rt, thisVal, fieldData, + fieldData->objectManager, std::move(target)); + }); +} + +JsValue MetadataNode::FieldAccessorSetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + FieldCallbackData *fieldData) { + return Guarded(rt, [&]() -> JsValue { + if (argc < 1) return js_util::undefined(); + + auto &fieldMetadata = fieldData->metadata; + + if (fieldData->objectManager == nullptr) { + fieldData->objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + } + + // A single probe both validates the receiver and resolves the java + // object; null + non-host means Class.prototype. access. + JniLocalRef target; + if (!fieldMetadata.isStatic) { + target = fieldData->objectManager->GetJavaObjectByJsObjectFast(thisVal); + if (target.IsNull() && !IsInstanceReceiver(rt, thisVal)) { + return js_util::undefined(); + } + } + + if (fieldMetadata.getIsFinal()) { + stringstream ss; + ss << "You are trying to set \"" << fieldMetadata.getName() + << "\" which is a final field! Final fields can only be read."; + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + CallbackHandlers::SetJavaField(rt, thisVal, args[0], fieldData, + fieldData->objectManager, std::move(target)); + return JsValue(rt, args[0]); + }); +} + +JsValue MetadataNode::PropertyAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal, + PropertyCallbackData *propertyCallbackData) { + return Guarded(rt, [&]() -> JsValue { + if (propertyCallbackData->getterEntry == nullptr) { + return js_util::undefined(); + } + + if (!IsInstanceReceiver(rt, thisVal)) { + return js_util::undefined(); + } + + // Call the Java getter directly — no JS method lookup, no nested + // MethodCallback. Invariants are resolved once and cached. + if (propertyCallbackData->cachedIsFromInterface < 0) { + propertyCallbackData->cachedIsFromInterface = + propertyCallbackData->node->IsNodeTypeInterface() ? 1 : 0; + } + if (propertyCallbackData->objectManager == nullptr) { + propertyCallbackData->objectManager = + Runtime::GetRuntime(rt)->GetObjectManager(); + } + return CallbackHandlers::CallJavaMethod( + rt, thisVal, propertyCallbackData->node->m_name, + propertyCallbackData->getterMethodName, propertyCallbackData->getterEntry, + propertyCallbackData->cachedIsFromInterface == 1, + propertyCallbackData->getterEntry->isStatic, false, nullptr, 0, + propertyCallbackData->objectManager); + }); +} + +JsValue MetadataNode::PropertyAccessorSetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + PropertyCallbackData *propertyCallbackData) { + return Guarded(rt, [&]() -> JsValue { + if (propertyCallbackData->setterEntry == nullptr) { + return js_util::undefined(); + } + + if (!IsInstanceReceiver(rt, thisVal)) { + return js_util::undefined(); + } + + // Call the Java setter directly — no JS method lookup, no nested + // MethodCallback. Invariants are resolved once and cached. + if (propertyCallbackData->cachedIsFromInterface < 0) { + propertyCallbackData->cachedIsFromInterface = + propertyCallbackData->node->IsNodeTypeInterface() ? 1 : 0; + } + if (propertyCallbackData->objectManager == nullptr) { + propertyCallbackData->objectManager = + Runtime::GetRuntime(rt)->GetObjectManager(); + } + return CallbackHandlers::CallJavaMethod( + rt, thisVal, propertyCallbackData->node->m_name, + propertyCallbackData->setterMethodName, propertyCallbackData->setterEntry, + propertyCallbackData->cachedIsFromInterface == 1, + propertyCallbackData->setterEntry->isStatic, false, args, argc, + propertyCallbackData->objectManager); + }); +} + +JsValue MetadataNode::ExtendMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, MetadataNode *node) { + return Guarded(rt, [&]() -> JsValue { + JsValue extendName; + JsValue implementationObject; + string extendLocation; + + auto hasDot = false; + auto isTypeScriptExtend = false; + + if (argc == 2) { + if (!args[0].isString()) { + stringstream ss; + ss << "Invalid extend() call. No name for extend specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!args[1].isObject()) { + stringstream ss; + ss << "Invalid extend() call. No implementation object specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + string strName = args[0].asString(rt).utf8(rt); + hasDot = strName.find('.') != string::npos; + } else if (argc == 3) { + if (args[2].isBool()) { + isTypeScriptExtend = args[2].getBool(); + } + } + + auto useContentKey = false; + + if (hasDot) { + extendName = JsValue(rt, args[0]); + implementationObject = JsValue(rt, args[1]); + } else { + useContentKey = ContentKeyedBindingsEnabled(); + + // Skipped outright when content keying is on: this walks the stack + // purely to build a name, on every class creation. + bool validExtend = + useContentKey || GetExtendLocation(rt, extendLocation, isTypeScriptExtend); + + if (!validExtend) { + // No usable call site -- an anonymous frame, or a bundle the + // generator never indexed. Naming by content still works here, + // where before this was fatal. + useContentKey = true; + validExtend = true; + extendLocation.clear(); + } + + extendName = js_util::to_js_string(rt, ""); + auto validArgs = ValidateExtendArguments(rt, argc, args, validExtend, + extendLocation, + &extendName, &implementationObject, + isTypeScriptExtend); + if (!validArgs) { + return js_util::undefined(); + } + } + + string baseClassName = node->m_name; + + // An explicitly named extend keeps its name in front of the hash. The + // generator reads the same name out of the bundle, so the two still + // agree, and it buys back two things the bare hash costs: a class name + // that says what it is in a stack trace, and uniqueness for the classes + // that need it. Anything Java instantiates by name -- an Activity, a + // Fragment, a Worker, anything reached by reflection -- cannot share a + // generated class, because the runtime has only the class name to find + // the JS implementation with. Naming such a class is what makes it + // unique; unnamed extends stay shared, which is the common case and the + // point of the scheme. + string extendNameString = ArgConverter::ConvertToString(rt, extendName); + string extendNameAndLocation; + + if (useContentKey) { + string contentKey = + CreateContentKey(baseClassName, + CallbackHandlers::CollectImplementedInterfaceNames( + rt, implementationObject), + CallbackHandlers::CollectMethodOverrideNames( + rt, implementationObject, /* functionsOnly */ false)); + extendNameAndLocation = + extendNameString.empty() ? contentKey : extendNameString + "_" + contentKey; + } else { + extendNameAndLocation = extendLocation + extendNameString; + } + + string fullClassName; + if (!hasDot) { + fullClassName = TNS_PREFIX + CreateFullClassName(baseClassName, extendNameAndLocation); + } else { + fullClassName = ArgConverter::ConvertToString(rt, args[0]); + } + + uint8_t nodeType = s_metadataReader.GetNodeType(node->m_treeNode); + bool isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + auto clazz = CallbackHandlers::ResolveClass(rt, baseClassName, fullClassName, + implementationObject, isInterface); + auto fullExtendedName = CallbackHandlers::ResolveClassName(rt, clazz); + + auto implObject = implementationObject.asObjectBorrowed(rt); + + // Which JS constructor to reuse is a different question from which Java + // class to load, and only the second one has to agree with the static + // binding generator. + // + // Two extend() calls with the same base, interfaces and overridden names + // share one generated Java class -- the Java is identical, and sharing it + // is the point. Their JS bodies are not identical, though, so caching the + // constructor under the class name would hand the second call the first + // call's implementation. Under content keying that is not a rare + // collision but the common case, so the constructor is cached per + // implementation object instead. + // The stamp identifies the object, not the class it was used to build: + // combining it with fullExtendedName keeps a second extend() of the same + // object against a *different* base a cache miss, so it still reaches the + // "used to extend another class" check below instead of silently + // returning the first class. + string ctorCacheKey = fullExtendedName; + if (useContentKey) { + auto stamped = implObject.getProperty(rt, EXTEND_CTOR_CACHE_KEY); + string implObjectId; + if (stamped.isString()) { + implObjectId = stamped.asString(rt).utf8(rt); + } else { + static std::atomic s_implObjectCounter{0}; + implObjectId = std::to_string(s_implObjectCounter.fetch_add(1)); + implObject.setProperty(rt, EXTEND_CTOR_CACHE_KEY, + ArgConverter::convertToJsString(rt, implObjectId)); + } + ctorCacheKey = fullExtendedName + "#" + implObjectId; + } + + auto cachedData = GetCachedExtendedClassData(rt, ctorCacheKey); + if (!js_util::is_null_or_undefined(cachedData.extendedCtorFunction)) { + return JsValue(rt, cachedData.extendedCtorFunction); + } + auto implementationObjectName = implObject.getProperty(rt, CLASS_IMPLEMENTATION_OBJECT); + + if (js_util::is_null_or_undefined(implementationObjectName)) { + implObject.setProperty(rt, CLASS_IMPLEMENTATION_OBJECT, + ArgConverter::convertToJsString(rt, fullExtendedName)); + } else { + string usedClassName = ArgConverter::ConvertToString(rt, implementationObjectName); + stringstream s; + s << "This object is used to extend another class '" << usedClassName << "'"; + throw NativeScriptException(s.str()); + } + + auto baseClassCtorFunction = node->GetConstructorFunction(rt); + + auto *extData = new ExtendedClassCallbackData(node, extendNameAndLocation, + JsValue(rt, implementationObject), + fullClassName); + GetMetadataNodeCache(rt)->extendedClassCallbackData.push_back(extData); + + auto extendFuncCtor = JsFunction::createFromHostConstructor( + rt, engine::PropNameID::forAscii(rt, fullExtendedName), 0, + [extData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return ExtendedClassConstructorCallback(rt, thisVal, args, argc, extData); + }); + + auto extendFuncPrototype = extendFuncCtor.getPropertyAsObject(rt, "prototype"); + ObjectManager::MarkObject(rt, JsValue(rt, extendFuncPrototype)); + + js_util::setPrototypeOf(rt, implementationObject, + js_util::get_prototype(rt, baseClassCtorFunction)); + + DefineAccessor(rt, implObject, PROP_KEY_SUPER, + [](JsRuntime &rt, const JsValue &thisVal, const JsValue *, + size_t) -> JsValue { + return SuperAccessorGetterCallback(rt, thisVal); + }, + nullptr); + + js_util::setPrototypeOf(rt, JsValue(rt, extendFuncPrototype), implementationObject); + + JsValue extendFuncCtorValue(rt, extendFuncCtor); + js_util::setPrototypeOf(rt, extendFuncCtorValue, baseClassCtorFunction); + + SetClassAccessor(rt, extendFuncCtor); + + extendFuncCtor.setProperty(rt, PRIVATE_TYPE_NAME, + ArgConverter::convertToJsString(rt, fullExtendedName)); + + s_name2NodeCache.emplace(fullExtendedName, node); + + auto cache = GetMetadataNodeCache(rt); + + // Two entries, because there are two ways in. + // + // From JS, extend() asks "have I already built a constructor for this + // implementation object?" -- keyed per object, so two identical-looking + // extends keep their own bodies. + // + // From Java, createJSInstanceNative has only the class name of the + // instance it is wrapping (CreateExtendedJSWrapper), so the class name + // has to resolve too. emplace leaves an existing entry alone, so for a + // shared generated class the first implementation registered is the one + // Java-side instantiation finds. That is unambiguous for a named extend + // -- naming it makes the class unique -- and inherently ambiguous for an + // unnamed one, which is why anything Java constructs by name should be + // named. + ExtendedClassCacheData byImplObject(JsValue(rt, extendFuncCtorValue), fullExtendedName, + node); + cache->ExtendedCtorFuncCache.emplace(ctorCacheKey, std::move(byImplObject)); + + if (ctorCacheKey != fullExtendedName) { + ExtendedClassCacheData byClassName(JsValue(rt, extendFuncCtorValue), fullExtendedName, + node); + cache->ExtendedCtorFuncCache.emplace(fullExtendedName, std::move(byClassName)); + } + + return extendFuncCtorValue; + }); +} + + +JsValue MetadataNode::SuperAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal) { + return Guarded(rt, [&]() -> JsValue { + if (!thisVal.isObject()) return js_util::undefined(); + + auto jsThis = thisVal.asObjectBorrowed(rt); + + JsValue superValue = jsThis.getProperty(rt, PROP_KEY_SUPERVALUE); + + if (js_util::is_null_or_undefined(superValue)) { + auto objectManager = Runtime::GetRuntime(rt)->GetObjectManager(); + superValue = objectManager->GetEmptyObject(); + + js_util::delete_property(rt, superValue, js_util::to_js_string(rt, PROP_KEY_TOSTRING)); + js_util::delete_property(rt, superValue, js_util::to_js_string(rt, PROP_KEY_VALUEOF)); + ObjectManager::MarkSuperCall(rt, superValue); + + JsValue superProto = js_util::getPrototypeOf( + rt, js_util::getPrototypeOf(rt, js_util::getPrototypeOf(rt, thisVal))); + + js_util::setPrototypeOf(rt, superValue, superProto); + objectManager->CloneLink(thisVal, superValue); + auto node = GetInstanceMetadata(rt, thisVal); + SetInstanceMetadata(rt, superValue, node); + + int javaObjectID = -1; + objectManager->GetJavaObjectByJsObject(thisVal, &javaObjectID); + if (javaObjectID != -1) { + superValue = objectManager->GetOrCreateProxyWeak(javaObjectID, superValue); + } + jsThis.setProperty(rt, PROP_KEY_SUPERVALUE, superValue); + } + + return superValue; + }); +} + +JsValue MetadataNode::MethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + MethodCallbackData *initialCallbackData) { + return Guarded(rt, [&]() -> JsValue { + MetadataEntry *entry = nullptr; + + auto callbackData = initialCallbackData; + + MethodCallbackData *selectedCallbackData = nullptr; + std::vector arityCandidates; + string *className; + auto &first = callbackData->candidates.front(); + auto &methodName = first.getName(); + + // Fast path for the overwhelmingly common single-overload, non-extension + // method with no parent chain: skip the candidate-search loop entirely. + if (callbackData->parent == nullptr && + callbackData->candidates.size() == 1 && + !first.isExtensionFunction && + first.getParamCount() == argc) { + className = &callbackData->node->m_name; + selectedCallbackData = callbackData; + arityCandidates.push_back(&first); + entry = &first; + } + + while ((callbackData != nullptr) && (entry == nullptr)) { + auto &candidates = callbackData->candidates; + + className = &callbackData->node->m_name; + + arityCandidates.clear(); + for (auto &c: candidates) { + if ((!c.isExtensionFunction && c.getParamCount() == argc) || + (c.isExtensionFunction && c.getParamCount() == argc + 1)) { + arityCandidates.push_back(&c); + } + } + + if (!arityCandidates.empty()) { + selectedCallbackData = callbackData; + if (arityCandidates.front()->isExtensionFunction) { + className = &arityCandidates.front()->getDeclaringType(); + } + break; + } else { + callbackData = callbackData->parent; + } + } + + if (arityCandidates.size() == 1) { + entry = arityCandidates.front(); + } else if (arityCandidates.size() > 1) { + entry = arityCandidates.front(); + } + + + if (initialCallbackData->cachedIsValueOf < 0) { + initialCallbackData->cachedIsValueOf = + (methodName == PROP_KEY_VALUEOF) ? 1 : 0; + } + if (argc == 0 && initialCallbackData->cachedIsValueOf == 1) { + return JsValue(rt, thisVal); + } + + if (initialCallbackData->cachedIsFromInterface < 0) { + initialCallbackData->cachedIsFromInterface = + initialCallbackData->node->IsNodeTypeInterface() ? 1 : 0; + } + bool isFromInterface = initialCallbackData->cachedIsFromInterface == 1; + if (initialCallbackData->objectManager == nullptr) { + initialCallbackData->objectManager = + Runtime::GetRuntime(rt)->GetObjectManager(); + } + + size_t metadataMatches = 0; + MetadataEntry *metadataMatch = nullptr; + bool metadataSignatureIsUnambiguous = false; + bool forceReflectionFallback = false; + bool preserveGenericObject = false; + bool hasObjectCandidate = false; + bool hasSpecificInterfaceCandidate = false; + bool hasExactStringCandidate = false; + for (auto *candidate : arityCandidates) { + const auto signature = candidate->getSig(); + hasObjectCandidate |= signature.find("Ljava/lang/Object;") != std::string::npos; + hasSpecificInterfaceCandidate |= signature.find("Ljava/io/Serializable;") != std::string::npos || + signature.find("Ljava/lang/CharSequence;") != std::string::npos; + hasExactStringCandidate |= signature.find("(Ljava/lang/String;") == 0; + } + preserveGenericObject = hasObjectCandidate && hasSpecificInterfaceCandidate && !hasExactStringCandidate; + struct MetadataCandidateMatch { + MetadataEntry *entry; + std::vector parameterClasses; + }; + std::vector metadataCandidateMatches; + JEnv metadataEnv; + bool hasNullArgument = false; + for (size_t i = 0; i < argc; i++) { + if (args[i].isNull() || args[i].isUndefined() || + (args[i].isObject() && MetadataNode::GetNullNode(rt, args[i]) != nullptr)) { + hasNullArgument = true; + break; + } + } + constexpr int kMetadataMismatch = -1; + constexpr int kMetadataUnknown = 0; + constexpr int kMetadataMatch = 1; + auto metadataTypesMatch = [&](MetadataEntry &candidate, + std::vector ¶meterClasses) -> int { + const auto signature = candidate.getSig(); + if (signature.empty() || signature[0] != '(') { + return kMetadataMismatch; + } + + std::vector parameterTypes; + size_t index = 1; + while (index < signature.size() && signature[index] != ')') { + const size_t start = index; + if (signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else if (signature[index] == '[') { + do { + index++; + } while (index < signature.size() && signature[index] == '['); + if (index < signature.size() && signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else if (index >= signature.size()) { + return kMetadataMismatch; + } else { + index++; + } + } else { + index++; + } + parameterTypes.emplace_back(signature.substr(start, index - start)); + } + + if (candidate.isExtensionFunction) { + if (parameterTypes.empty()) return kMetadataMismatch; + parameterTypes.erase(parameterTypes.begin()); + } + if (index >= signature.size() || parameterTypes.size() != argc) return kMetadataMismatch; + + parameterClasses.assign(argc, nullptr); + bool hasUnknownArgument = false; + for (size_t i = 0; i < argc; i++) { + const auto ¶meterType = parameterTypes[i]; + if (args[i].isNull() || args[i].isUndefined()) { + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == 'L' || parameterType[0] == '[') { + auto expectedName = parameterType; + if (expectedName[0] == 'L') { + expectedName = expectedName.substr(1, expectedName.size() - 2); + } + auto expectedClass = metadataEnv.FindClass(expectedName); + if (expectedClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + parameterClasses[i] = expectedClass; + if (args[i].isString()) { + if (expectedName != "java/lang/String") return kMetadataMismatch; + auto stringClass = metadataEnv.FindClass("java/lang/String"); + if (stringClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + if (metadataEnv.IsAssignableFrom(expectedClass, stringClass) != JNI_TRUE) return kMetadataMismatch; + } else { + if (!args[i].isObject()) return kMetadataMismatch; + if (MetadataNode::GetNullNode(rt, args[i]) != nullptr) return kMetadataMismatch; + auto actualObject = initialCallbackData->objectManager->GetJavaObjectByJsObject(args[i]); + if (actualObject.IsNull()) { + // JS array for an array parameter, or a JS value + // without a Java peer: only the lenient + // JsArgConverter can marshal these. + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == '[' && expectedName == "[Ljava/lang/Object;" && + ObjectManager::GetClassName((jobject) actualObject) != expectedName) return kMetadataMismatch; + bool hasSerializableCandidate = false; + for (auto *arityCandidate : arityCandidates) { + if (arityCandidate->getSig().find("Ljava/io/Serializable;") != std::string::npos) { + hasSerializableCandidate = true; + break; + } + } + if (expectedName == "java/lang/Object" && !hasSerializableCandidate) return kMetadataMismatch; + if (metadataEnv.IsInstanceOf(actualObject, expectedClass) != JNI_TRUE) return kMetadataMismatch; + } + } else if (parameterType[0] == 'Z') { + if (!args[i].isBool()) return kMetadataMismatch; + } else if (!args[i].isNumber() || + (parameterType[0] != 'B' && parameterType[0] != 'C' && + parameterType[0] != 'S' && parameterType[0] != 'I' && + parameterType[0] != 'J' && parameterType[0] != 'F' && + parameterType[0] != 'D')) { + return kMetadataMismatch; + } + } + return hasUnknownArgument ? kMetadataUnknown : kMetadataMatch; + }; + if (!hasNullArgument && selectedCallbackData != nullptr && arityCandidates.size() > 1 && !preserveGenericObject) { + for (auto *candidate : arityCandidates) { + if (candidate->isStatic != first.isStatic) continue; + std::vector parameterClasses; + if (metadataTypesMatch(*candidate, parameterClasses) == kMetadataMatch) { + metadataCandidateMatches.push_back({candidate, std::move(parameterClasses)}); + } + } + for (size_t i = 0; i < metadataCandidateMatches.size(); i++) { + bool dominated = false; + for (size_t j = 0; j < metadataCandidateMatches.size(); j++) { + if (i == j) continue; + const auto &candidate = metadataCandidateMatches[i]; + const auto &other = metadataCandidateMatches[j]; + for (size_t parameter = 0; parameter < argc; parameter++) { + auto candidateClass = candidate.parameterClasses[parameter]; + auto otherClass = other.parameterClasses[parameter]; + if (candidateClass == nullptr || otherClass == nullptr) { + if (candidate.entry->getSig() != other.entry->getSig()) break; + continue; + } + if (metadataEnv.IsAssignableFrom(candidateClass, otherClass) == JNI_TRUE && + metadataEnv.IsAssignableFrom(otherClass, candidateClass) != JNI_TRUE) { + dominated = true; + break; + } + } + if (dominated) break; + } + if (!dominated) { + metadataMatches++; + metadataMatch = metadataCandidateMatches[i].entry; + } + } + metadataSignatureIsUnambiguous = metadataMatches == 1 && metadataMatch != nullptr; + for (const auto &candidate : metadataCandidateMatches) { + hasExactStringCandidate |= candidate.entry->getSig().find("(Ljava/lang/String;") == 0; + } + if (metadataCandidateMatches.size() > 1 && !hasExactStringCandidate) { + metadataSignatureIsUnambiguous = false; + metadataMatch = nullptr; + forceReflectionFallback = false; + } + if (!metadataSignatureIsUnambiguous && !metadataCandidateMatches.empty()) { + for (const auto &candidate : metadataCandidateMatches) { + const auto signature = candidate.entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen != std::string::npos && closeParen > 1 && signature[1] == '[') { + forceReflectionFallback = true; + break; + } + if (closeParen != std::string::npos && closeParen > 1 && + signature[1] != 'L' && signature[1] != 'Z') { + forceReflectionFallback = true; + break; + } + } + } + } + if (!hasNullArgument && entry != nullptr && !entry->isExtensionFunction && arityCandidates.size() == 1) { + std::vector parameterClasses; + const int compatibility = metadataTypesMatch(*entry, parameterClasses); + if (compatibility == kMetadataMatch) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } else if (compatibility == kMetadataMismatch) { + // Provable mismatch against the only metadata candidate: the + // metadata may be stale, so let reflection resolve it. + const auto signature = entry->getSig(); + if (entry->isStatic || signature.find("Ljava/lang/Object;") == std::string::npos) { + entry = nullptr; + } + } + // kMetadataUnknown: keep the entry (direct call via JsArgConverter + // when generator-resolved, reflection otherwise). Reflection can't + // marshal JS arrays, so demoting here breaks valid calls. + } + if (!hasNullArgument && entry != nullptr && entry->isExtensionFunction) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } + if (metadataSignatureIsUnambiguous) { + entry = metadataMatch; + } else if (forceReflectionFallback) { + entry = nullptr; + } + const bool selectedIsStatic = entry != nullptr + ? entry->isStatic + : (arityCandidates.size() == 1 + ? arityCandidates.front()->isStatic + : first.isStatic); + return CallbackHandlers::CallJavaMethod(rt, thisVal, *className, methodName, entry, + isFromInterface, selectedIsStatic, false, + args, argc, initialCallbackData->objectManager, + metadataSignatureIsUnambiguous); + }); +} + +/** + * Compare class hierarchy in metadata with that at runtime. If a base class is missing + * at runtime, we must add all it's methods to the current class. + */ +bool +MetadataNode::CheckClassHierarchy(JEnv &env, jclass currentClass, MetadataTreeNode *currentTreeNode, + MetadataTreeNode *baseTreeNode, + std::vector &skippedBaseTypes) { + auto shouldSkipBaseClass = false; + if ((currentClass != nullptr) && (baseTreeNode != currentTreeNode) && + (baseTreeNode != nullptr) && + (baseTreeNode->offsetValue > 0)) { + auto baseNode = GetOrCreateInternal(baseTreeNode); + auto baseClass = env.FindClass(baseNode->m_name); + if (baseClass != nullptr) { + auto isBaseClass = env.IsAssignableFrom(currentClass, baseClass) == JNI_TRUE; + if (!isBaseClass) { + skippedBaseTypes.push_back(baseTreeNode); + shouldSkipBaseClass = true; + } + } + } + return shouldSkipBaseClass; +} + +void MetadataNode::SetMissingBaseMethods( + JsRuntime &rt, const std::vector &skippedBaseTypes, + const std::vector &instanceMethodData, + JsObject constructor) { + for (auto treeNode: skippedBaseTypes) { + uint8_t *curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1; + + auto nodeType = s_metadataReader.GetNodeType(treeNode); + auto curType = s_metadataReader.ReadTypeName(treeNode); + curPtr += sizeof(uint16_t /* baseClassId */); + + if (s_metadataReader.IsNodeTypeInterface(nodeType)) { + curPtr += sizeof(uint8_t) + sizeof(uint32_t); + } + + // Get candidates from instance methods metadata + auto instanceMethodCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + MethodCallbackData *callbackData = nullptr; + + for (auto i = 0; i < instanceMethodCount; i++) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + auto &methodName = entry.getName(); + auto isConstructor = methodName == ""; + if (isConstructor) { + continue; + } + + for (auto data: instanceMethodData) { + if (data->candidates.front().name == methodName) { + callbackData = data; + break; + } + } + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + GetMetadataNodeCache(rt)->methodCallbackData.push_back(callbackData); + auto proto = constructor.getPropertyAsObject(rt, "prototype"); + auto method = JsFunction::createFromHostFunction( + rt, engine::PropNameID::forAscii(rt, methodName), 0, + [callbackData](JsRuntime &rt, const JsValue &thisVal, const JsValue *args, + size_t argc) -> JsValue { + return MethodCallback(rt, thisVal, args, argc, callbackData); + }); + proto.setProperty(rt, methodName.c_str(), JsValue(rt, method)); + } + + bool foundSameSig = false; + for (auto &m: callbackData->candidates) { + foundSameSig = m.getSig() == entry.getSig(); + if (foundSameSig) { + break; + } + } + + if (!foundSameSig) { + callbackData->candidates.push_back(std::move(entry)); + } + } + } +} + +void MetadataNode::BuildMetadata(const std::string &filesPath) { + // Once per process: the static node caches point into this tree, and it + // outlives any one main runtime. + if (s_metadataReader.GetRoot() != nullptr) { + return; + } + s_metadataReader = MetadataBuilder::BuildMetadata(filesPath); +} + +void MetadataNode::onDisposeRuntime(JsRuntime &rt) { + const void *key = rt.identity(); + { + auto it = s_metadata_node_cache.Get(key); + if (it != nullptr) { + // Erasing the maps releases every owned constructor handle; the napi + // tree had to napi_delete_reference each one (and its check was + // inverted, so it only ever deleted null refs). + for (const auto &entry: it->CtorFuncCache) { + for (const auto data: entry.second.instanceMethodCallbacks) { + delete data; + } + } + it->CtorFuncCache.clear(); + + it->ExtendedCtorFuncCache.clear(); + + for (const auto &entry: it->fieldCallbackData) { + delete entry; + } + for (const auto &entry: it->propertyCallbackData) { + delete entry->getterEntry; + delete entry->setterEntry; + delete entry; + } + for (const auto &entry: it->extendedClassCallbackData) { + delete entry; + } + for (const auto &entry: it->methodCallbackData) { + delete entry; + } + } + s_metadata_node_cache.Remove(key); + delete it; + } + { + auto it = s_arrayObjects.find(key); + if (it != s_arrayObjects.end()) { + s_arrayObjects.erase(it); + } + } +} + + +string MetadataNode::TNS_PREFIX = "com/tns/gen/"; +MetadataReader MetadataNode::s_metadataReader; +robin_hood::unordered_map MetadataNode::s_name2NodeCache; +robin_hood::unordered_map MetadataNode::s_name2TreeNodeCache; +robin_hood::unordered_map MetadataNode::s_treeNode2NodeCache; +tns::ConcurrentMap MetadataNode::s_metadata_node_cache; +robin_hood::unordered_map MetadataNode::s_arrayObjects; + +bool MetadataNode::s_profilerEnabled = false; diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h new file mode 100644 index 000000000..265783b4a --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataNode.h @@ -0,0 +1,385 @@ +#ifndef METADATA_NODE_H +#define METADATA_NODE_H + +#include +#include "MetadataTreeNode.h" +#include "MetadataEntry.h" +#include "robin_hood.h" +#include "MetadataReader.h" +#include "Runtime.h" +#include "ObjectManager.h" + +#include "FieldCallbackData.h" +using namespace tns; + +class MetadataNode { +public: + // Set once from Runtime::Init, out of the app's config: the static binding + // generator picked the naming scheme at build time and the runtime has to + // look names up the same way. Only ever widens -- a runtime compiled to + // require content keys cannot be turned back off by an app. + static void SetContentKeyedBindings(bool enabled); + + static void Init(JsRuntime &rt); + + static void BuildMetadata(const std::string &filesPath); + + static void CreateTopLevelNamespaces(JsRuntime &rt); + + JsValue CreateWrapper(JsRuntime &rt); + + JsValue CreateJSWrapper(JsRuntime &rt, tns::ObjectManager *objectManager); + + JsValue CreateArrayWrapper(JsRuntime &rt); + + static MetadataNode *GetOrCreate(const std::string &className); + + static MetadataReader *getMetadataReader(); + + static JsValue GetImplementationObject(JsRuntime &rt, const JsValue &object); + + inline static MetadataNode* GetInstanceMetadata(JsRuntime &rt, const JsValue &object) { + // Metadata lives on the per-instance JSInstanceInfo (set in + // ObjectManager::Link / GetOrCreateProxy). The napi tree had a second, + // non-host branch reading a "#instance_metadata" external; a host object + // is the only path here, so there is one lookup. + return tns::Runtime::GetRuntime(rt)->GetObjectManager()->GetInstanceNode(object); + } + + inline static MetadataNode* GetNodeFromHandle(JsRuntime &rt, const JsValue &value) { + auto node = GetInstanceMetadata(rt, value); + return node; + } + + // The napi tree marked a "null object" by hanging a napi_external carrying + // this MetadataNode* off a `nullNode` property. engine:: has no external, and + // the native-state slot is both faster (a field load, not a prototype-chain + // walk) and free on these objects -- they are constructor-level singletons, + // never Java-backed wrappers, so they never carry a JSInstanceInfo. + // Returns nullptr when `value` is not a null object. + static MetadataNode *GetNullNode(JsRuntime &rt, const JsValue &value); + + static string GetTypeMetadataName(JsRuntime &rt, const JsValue &value); + + static JsValue CreateExtendedJSWrapper(JsRuntime &rt, ObjectManager *objectManager, + const std::string &proxyClassName, int javaObjectID, + MetadataNode **outNode = nullptr); + + std::string GetName(); + + static void onDisposeRuntime(JsRuntime &rt); + + bool isArray(); + +private: + struct CtorCacheData; + struct MethodCallbackData; + struct PropertyCallbackData; + struct ExtendedClassCallbackData; + struct ExtendedClassCacheData; + struct MetadataNodeCache; + + static string CreateFullClassName(const std::string& className, const std::string& extendNameAndLocation); + + static string CreateContentKey(const std::string& baseClassName, + std::vector interfaceNames, + std::vector methodNames); + + // Whether an unnamed extend() names its class by content rather than by call + // site. Always available as a fallback when no usable location exists; the + // default is on wherever the bundler is the embedder's, since the generator + // and the runtime cannot agree on coordinates there. + static bool ContentKeyedBindingsEnabled(); + + static JsValue CreateArrayObjectConstructor(JsRuntime &rt); + + static void SetInstanceMetadata(JsRuntime &rt, const JsValue &object, MetadataNode* node); + + + static bool + CheckClassHierarchy(JEnv &env, jclass currentClass, MetadataTreeNode *currentTreeNode, + MetadataTreeNode *baseTreeNode, + std::vector &skippedBaseTypes); + + static MetadataNode *GetOrCreateInternal(MetadataTreeNode *treeNode); + + static MetadataNodeCache *GetMetadataNodeCache(JsRuntime &rt); + + explicit MetadataNode(MetadataTreeNode *treeNode); + + void SetMissingBaseMethods( + JsRuntime &rt, const std::vector &skippedBaseTypes, + const std::vector &instanceMethodData, + JsObject constructor); + + + + + JsValue GetConstructorFunction(JsRuntime &rt); + + JsValue GetConstructorFunctionInternal(JsRuntime &rt, MetadataTreeNode *treeNode, + std::vector instanceMethodsCallbackData); + + JsValue CreatePackageObject(JsRuntime &rt); + + + static bool IsValidExtendName(JsRuntime &rt, const JsValue &name); + static bool GetExtendLocation(JsRuntime &rt, std::string& extendLocation, bool isTypeScriptExtend); + static ExtendedClassCacheData GetCachedExtendedClassData(JsRuntime &rt, const std::string& proxyClassName); + static std::string GetJniClassName(const MetadataTreeNode* node); + + static void SetClassAccessor(JsRuntime &rt, JsObject constructor); + + static MetadataEntry GetChildMetadataForPackage(MetadataNode *node, const char *propName); + + static MetadataTreeNode *GetOrCreateTreeNodeByName(const std::string &className); + + bool IsNodeTypeInterface(); + + std::vector SetClassMembersFromStaticMetadata( + JsRuntime &rt, JsObject constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + std::vector SetInstanceMembersFromRuntimeMetadata( + JsRuntime &rt, JsObject constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + + inline static MethodCallbackData *tryGetExtensionMethodCallbackData( + const robin_hood::unordered_map &collectedMethodCallbackData, + const std::string &lookupName); + + std::vector SetClassMembers( + JsRuntime &rt, JsObject constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + + static JsValue NullObjectAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal, + MetadataNode *node); + + // Returns true if `jsThis` is a real backed instance rather than the class + // prototype (i.e. someone did Class.prototype.). Every real instance + // is a host-object proxy and the prototype is not; the napi tree's + // non-host fallback (an identity compare against a cached prototype ref) has + // no counterpart here because host objects are the only path. + static bool IsInstanceReceiver(JsRuntime &rt, const JsValue &jsThis); + + static JsValue FieldAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal, + FieldCallbackData *fieldData); + + static JsValue FieldAccessorSetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + FieldCallbackData *fieldData); + + static JsValue ArraySetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ArrayGetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ArrayGetAllValuesCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ArrayLengthCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + // Native equivalents of the helpers that used to live in getNativeArrayProp. + static JsValue ArrayMapCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ArrayForEachCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ArrayToStringCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue ArraySymbolIteratorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + static JsValue PropertyAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal, + PropertyCallbackData *data); + + static JsValue PropertyAccessorSetterCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + PropertyCallbackData *data); + + static JsValue ExtendMethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, MetadataNode *node); + + static JsValue MethodCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, MethodCallbackData *data); + + static JsValue ClassAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal); + + static JsValue PackageGetterCallback(JsRuntime &rt, const JsValue &thisVal, + MetadataTreeNode *childTreeNode); + + static JsValue ExtendedClassConstructorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + ExtendedClassCallbackData *extData); + + static JsValue InterfaceConstructorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, + MetadataNode *node); + + static JsValue ClassConstructorCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc, MetadataNode *node); + + static void SetInnerTypes(JsRuntime &rt, JsObject constructor, MetadataTreeNode *treeNode); + + static JsValue InnerTypeGetterCallback(JsRuntime &rt, const JsValue &thisVal, + MetadataTreeNode *curChild); + + static JsValue NullValueOfCallback(JsRuntime &rt, const JsValue &thisVal, + const JsValue *args, size_t argc); + + // The napi tree needed a SymbolHasInstanceData heap holder because PrimJS + // boxed the napi `data` pointer into 48 bits and corrupted a raw JNI global + // ref. engine:: host functions carry their state in the callback's own + // capture, so there is no `data` pointer to box and no holder to allocate. + static void RegisterSymbolHasInstanceCallback(JsRuntime &rt, const MetadataTreeNode *treeNode, + const JsValue &interface); + + static JsValue SuperAccessorGetterCallback(JsRuntime &rt, const JsValue &thisVal); + + static bool ValidateExtendArguments(JsRuntime &rt, size_t argc, const JsValue *argv, + bool extendLocationFound, string &extendLocation, + JsValue *extendName, JsValue *implementationObject, + bool isTypeScriptExtend); + + MetadataTreeNode *m_treeNode; + + std::string m_name; + std::string m_implType; + bool m_isArray; + + static bool IsJavascriptKeyword(const std::string &word); + + static std::string TNS_PREFIX; + static MetadataReader s_metadataReader; + + static robin_hood::unordered_map s_name2NodeCache; + static robin_hood::unordered_map s_name2TreeNodeCache; + static robin_hood::unordered_map s_treeNode2NodeCache; + // Both keyed by JsRuntime::identity(); &rt is not stable across callbacks. + static tns::ConcurrentMap s_metadata_node_cache; + static robin_hood::unordered_map s_arrayObjects; + + // An owned engine handle replaces every napi_ref below: it survives handle + // scopes and is released by destroying/erasing the owner, so there are no + // napi_delete_reference calls left in the teardown paths. + struct CtorCacheData { + CtorCacheData(JsValue _constructorFunction, + std::vector _instanceMethodCallbacks) + : + constructorFunction(std::move(_constructorFunction)), + instanceMethodCallbacks(std::move(_instanceMethodCallbacks)) { + } + + JsValue constructorFunction; + std::vector instanceMethodCallbacks; + }; + + struct MethodCallbackData { + MethodCallbackData() + : + node(nullptr), parent(nullptr), isSuper(false) { + } + + MethodCallbackData(MetadataNode *_node) + : + node(_node), parent(nullptr), isSuper(false) { + } + + std::vector candidates; + MetadataNode *node; + MethodCallbackData *parent; + bool isSuper; + // Lazily-cached, per-class invariants resolved on first dispatch + // (-1 = not yet computed, 0 = false, 1 = true). + int8_t cachedIsFromInterface = -1; + int8_t cachedIsValueOf = -1; + // Cached per-runtime ObjectManager (this data is created per runtime, so + // the pointer's lifetime matches it — no staleness across runtimes). + tns::ObjectManager *objectManager = nullptr; + }; + + struct ExtendedClassCacheData { + ExtendedClassCacheData() + : + node(nullptr) { + } + + ExtendedClassCacheData(JsValue extCtorFunc, const std::string &_extendedName, + MetadataNode *_node) + : + extendedCtorFunction(std::move(extCtorFunc)), extendedName(_extendedName), + node(_node) { + } + + JsValue extendedCtorFunction; + std::string extendedName; + MetadataNode *node; + }; + + struct PropertyCallbackData { + PropertyCallbackData(std::string _propertyName, std::string _getterMethodName, + std::string _setterMethodName) + : + propertyName(std::move(_propertyName)), + getterMethodName(std::move(_getterMethodName)), + setterMethodName(std::move(_setterMethodName)) { + + } + + std::string propertyName; + std::string getterMethodName; + std::string setterMethodName; + // Direct-dispatch support: the resolved getter/setter method entries plus + // cached invariants let the accessor call CallJavaMethod directly, with no + // JS method lookup or nested MethodCallback. nullptr => no getter/setter. + MetadataEntry *getterEntry = nullptr; + MetadataEntry *setterEntry = nullptr; + MetadataNode *node = nullptr; + int8_t cachedIsFromInterface = -1; + tns::ObjectManager *objectManager = nullptr; + }; + + struct ExtendedClassCallbackData { + ExtendedClassCallbackData(MetadataNode *_node, const std::string &_extendedName, + JsValue _implementationObject, std::string _fullClassName) + : + node(_node), extendedName(_extendedName), + implementationObject(std::move(_implementationObject)), + fullClassName(std::move(_fullClassName)) { + } + + MetadataNode *node; + std::string extendedName; + JsValue implementationObject; + + std::string fullClassName; + }; + + struct MetadataNodeCache { + robin_hood::unordered_map CtorFuncCache; + robin_hood::unordered_map ExtendedCtorFuncCache; + std::vector fieldCallbackData; + std::vector propertyCallbackData; + std::vector extendedClassCallbackData; + // Method callback data no CtorFuncCache entry owns: static methods, + // extension-only methods and methods copied from a skipped base class. + std::vector methodCallbackData; + }; + + static bool s_profilerEnabled; + +}; + +#endif //METADATA_NODE_H diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp new file mode 100644 index 000000000..daacbe8a9 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataReader.cpp @@ -0,0 +1,367 @@ +#include "MetadataReader.h" +#include "MetadataMethodInfo.h" +#include +#include "Util.h" +#include + +using namespace std; +using namespace tns; + +MetadataReader::MetadataReader() : m_root(nullptr), m_nodesLength(0), m_nameLength(0), + m_valueLength(0), + m_nodeData(nullptr), m_nameData(nullptr), m_valueData(nullptr), + m_getTypeMetadataCallback(nullptr) {} + +MetadataReader::MetadataReader(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData, + GetTypeMetadataCallback getTypeMetadataCallback) + : + m_nodesLength(nodesLength), m_nameLength(nameLength), + m_valueLength(valueLength), m_nodeData(nodeData), m_nameData(nameData), + m_valueData(valueData), + m_getTypeMetadataCallback(getTypeMetadataCallback) { + m_root = BuildTree(); +} + + + +// helper debug function when need to convert a metadata node to its full name +//std::string toFullName(MetadataTreeNode* p) { +// std::string final = p->name; +// while((p = p->parent) && !p->name.empty()) { +// final.insert(0,p->name + "."); +// }; +// return final; +//} + +MetadataTreeNode *MetadataReader::BuildTree() { + MetadataTreeNodeRawData *rootNodeData = reinterpret_cast(m_nodeData); + + MetadataTreeNodeRawData *curNodeData = rootNodeData; + + int len = m_nodesLength / sizeof(MetadataTreeNodeRawData); + + m_v.resize(len + 1000); + MetadataTreeNode *emptyNode = nullptr; + fill(m_v.begin(), m_v.end(), emptyNode); + + for (int i = 0; i < len; i++) { + MetadataTreeNode *node = GetNodeById(i); + if (nullptr == node) { + node = new MetadataTreeNode; + node->name = ReadName(curNodeData->offsetName); + node->offsetValue = curNodeData->offsetValue; + m_v[i] = node; + } + + uint16_t curNodeDataId = curNodeData - rootNodeData; + + if (curNodeDataId != curNodeData->firstChildId) { + node->children = new vector; + MetadataTreeNodeRawData *childNodeData = rootNodeData + curNodeData->firstChildId; + while (true) { + + uint16_t childNodeDataId = childNodeData - rootNodeData; + + MetadataTreeNode *childNode; + // node (and its next siblings) already visited, so we don't need to visit it again + if (m_v[childNodeDataId] != emptyNode) { + childNode = m_v[childNodeDataId]; + __android_log_print(ANDROID_LOG_ERROR, "TNS.error", + "Consistency error in metadata. A child should never have been visited before its parent. Parent: %s Child: %s. Child metadata id: %u", + node->name.c_str(), childNode->name.c_str(), + childNodeDataId); + break; + } else { + childNode = new MetadataTreeNode; + childNode->name = ReadName(childNodeData->offsetName); + childNode->offsetValue = childNodeData->offsetValue; + } + childNode->parent = node; + + node->children->push_back(childNode); + + m_v[childNodeDataId] = childNode; + + if (childNodeDataId == childNodeData->nextSiblingId) { + break; + } + + childNodeData = rootNodeData + childNodeData->nextSiblingId; + } + } + + curNodeData++; + } + + return GetNodeById(0); +} + +MetadataTreeNode *MetadataReader::GetNodeById(uint16_t nodeId) { + return m_v[nodeId]; +} + + +string MetadataReader::ReadTypeName(MetadataTreeNode *treeNode) { + string name; + + auto itFound = m_typeNameCache.find(treeNode); + + if (itFound != m_typeNameCache.end()) { + name = itFound->second; + } else { + name = ReadTypeNameInternal(treeNode); + + m_typeNameCache.emplace(treeNode, name); + } + + return name; +} + +string MetadataReader::ReadTypeNameInternal(MetadataTreeNode *treeNode) { + string name; + + uint8_t prevNodeType; + + while (treeNode->parent != nullptr) { + int curNodeType = GetNodeType(treeNode); + + bool isArrayElement = treeNode->offsetValue > ARRAY_OFFSET; + + if (isArrayElement) { + uint16_t forwardNodeId = treeNode->offsetValue - ARRAY_OFFSET; + MetadataTreeNode *forwardNode = GetNodeById(forwardNodeId); + name = ReadTypeName(forwardNode); + uint8_t forwardNodeType = GetNodeType(forwardNode); + if (IsNodeTypeInterface(forwardNodeType) || IsNodeTypeClass(forwardNodeType)) { + name = "L" + name + ";"; + } + } else { + if (!name.empty()) { + if (!IsNodeTypeArray(curNodeType)) { + if ((IsNodeTypeClass(prevNodeType) || IsNodeTypeInterface(prevNodeType)) + && (IsNodeTypeClass(curNodeType) || IsNodeTypeInterface(curNodeType))) { + name = "$" + name; + } else { + name = "/" + name; + } + } + } + + name = treeNode->name + name; + + prevNodeType = curNodeType; + } + + treeNode = treeNode->parent; + } + + return name; +} + +uint8_t *MetadataReader::GetValueData() const { + return m_valueData; +} + +uint16_t MetadataReader::GetNodeId(MetadataTreeNode *treeNode) { + auto itFound = find(m_v.begin(), m_v.end(), treeNode); + assert(itFound != m_v.end()); + uint16_t nodeId = itFound - m_v.begin(); + + return nodeId; +} + +MetadataTreeNode *MetadataReader::GetRoot() const { + return m_root; +} + +uint8_t MetadataReader::GetNodeType(MetadataTreeNode *treeNode) { + if (treeNode->type == MetadataTreeNode::INVALID_TYPE) { + uint8_t nodeType; + + uint32_t offsetValue = treeNode->offsetValue; + + if (offsetValue == 0) { + nodeType = MetadataTreeNode::PACKAGE; + } else if ((0 < offsetValue) && (offsetValue < ARRAY_OFFSET)) { + nodeType = *(m_valueData + offsetValue); + } else if (offsetValue == ARRAY_OFFSET) { + nodeType = MetadataTreeNode::ARRAY; + } else { + uint16_t nodeId = offsetValue - ARRAY_OFFSET; + MetadataTreeNode *arrElemNode = GetNodeById(nodeId); + nodeType = *(m_valueData + arrElemNode->offsetValue); + } + + treeNode->type = nodeType; + } + + return treeNode->type; +} + +MetadataTreeNode *MetadataReader::GetOrCreateTreeNodeByName(const string &className) { + MetadataTreeNode *treeNode = GetRoot(); + + int arrayIdx = -1; + string arrayName = "["; + + while (className[++arrayIdx] == '[') { + MetadataTreeNode *child = treeNode->GetChild(arrayName); + + if (child == nullptr) { + vector *children = treeNode->children; + if (children == nullptr) { + children = treeNode->children = new vector; + } + + child = new MetadataTreeNode; + child->name = "["; + child->parent = treeNode; + child->offsetValue = ARRAY_OFFSET; + + children->push_back(child); + m_v.push_back(child); + } + + treeNode = child; + } + + string cn = className.substr(arrayIdx); + + if (arrayIdx > 0) { + char last = *cn.rbegin(); + if (last == ';') { + cn = cn.substr(1, cn.length() - 2); + } + } + + vector names; + Util::SplitString(cn, "/$", names); + + if (arrayIdx > 0) { + bool found = false; + MetadataTreeNode *forwardedNode = GetOrCreateTreeNodeByName(cn); + + uint16_t forwardedNodeId = GetNodeId(forwardedNode); + if (treeNode->children == nullptr) { + treeNode->children = new vector(); + } + vector &children = *treeNode->children; + for (auto childNode: children) { + uint32_t childNodeId = (childNode->offsetValue >= ARRAY_OFFSET) + ? (childNode->offsetValue - ARRAY_OFFSET) + : + GetNodeId(childNode); + + if (childNodeId == forwardedNodeId) { + treeNode = childNode; + found = true; + break; + } + } + + if (!found) { + MetadataTreeNode *forwardNode = new MetadataTreeNode; + forwardNode->offsetValue = forwardedNodeId + ARRAY_OFFSET; + forwardNode->parent = treeNode; + + m_v.push_back(forwardNode); + children.push_back(forwardNode); + + treeNode = forwardNode; + } + + return treeNode; + } + + int curIdx = 0; + for (auto it = names.begin(); it != names.end(); ++it) { + MetadataTreeNode *child = treeNode->GetChild(*it); + + if (child == nullptr) { + vector api = m_getTypeMetadataCallback(cn, curIdx); + + for (const auto &part: api) { + vector *children = treeNode->children; + if (children == nullptr) { + children = treeNode->children = new vector; + } + + child = new MetadataTreeNode; + child->name = *it++; + child->parent = treeNode; + // Rebuilt by reflection because the metadata files did not + // describe it -- see the field's comment. + child->synthesizedAtRuntime = true; + + string line; + string kind; + string name; + stringstream s(part); + + getline(s, line); + stringstream typeLine(line); + typeLine >> kind >> name; + auto cKind = kind[0]; + + // package, class, interface + assert((cKind == 'P') || (cKind == 'C') || (cKind == 'I')); + + if ((cKind == 'C') || (cKind == 'I')) { + child->metadata = new string(part); + child->type = (cKind == 'C') ? MetadataTreeNode::CLASS + : MetadataTreeNode::INTERFACE; + if (name == "S") { + child->type |= MetadataTreeNode::STATIC; + } + + getline(s, line); + stringstream baseClassLine(line); + baseClassLine >> kind >> name; + cKind = kind[0]; + + assert(cKind == 'B'); + auto baseClassTreeNode = GetOrCreateTreeNodeByName(name); + auto baseClassNodeId = GetNodeId(baseClassTreeNode); + + child->offsetValue = m_valueLength; + m_valueData[m_valueLength++] = child->type; + *reinterpret_cast(m_valueData + m_valueLength) = baseClassNodeId; + m_valueLength += sizeof(uint16_t); + } else { + child->type = MetadataTreeNode::PACKAGE; + } + + m_v.push_back(child); + children->push_back(child); + + treeNode = child; + } + + return treeNode; + } else { + treeNode = child; + } + ++curIdx; + } + + return treeNode; +} + +MetadataTreeNode *MetadataReader::GetBaseClassNode(MetadataTreeNode *treeNode) { + MetadataTreeNode *baseClassNode = nullptr; + + if (treeNode != nullptr) { + uint16_t baseClassNodeId = *reinterpret_cast(m_valueData + + treeNode->offsetValue + 1); + + size_t nodeCount = m_v.size(); + + assert(baseClassNodeId < nodeCount); + + baseClassNode = GetNodeById(baseClassNodeId); + } + + return baseClassNode; +} + diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataReader.h b/NativeScript/ffi/jni/jsi/metadata/MetadataReader.h new file mode 100644 index 000000000..8d19683d7 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataReader.h @@ -0,0 +1,238 @@ +#ifndef METADATAREADER_H_ +#define METADATAREADER_H_ + +#include "MetadataEntry.h" +#include "MetadataFieldInfo.h" +#include +#include +#include +#include "robin_hood.h" + +namespace tns { + typedef std::vector (*GetTypeMetadataCallback)(const std::string &classname, + int index); + + class MetadataReader { + public: + MetadataReader(); + + MetadataReader(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData, + GetTypeMetadataCallback getTypeMetadataCallack); + + inline static MetadataEntry ReadInstanceFieldEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Field); + entry.fi = *reinterpret_cast(data); + entry.isStatic = false; + entry.isTypeMember = false; + + *data += sizeof(FieldInfo); + + return entry; + } + + inline static MetadataEntry ReadStaticFieldEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::StaticField); + entry.sfi = *reinterpret_cast(data); + entry.isStatic = true; + entry.isTypeMember = false; + + *data += sizeof(StaticFieldInfo); + + return entry; + } + + inline static MetadataEntry ReadInstanceMethodEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + entry.isTypeMember = true; + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline static MetadataEntry ReadStaticMethodEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + entry.isTypeMember = true; + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + entry.mi.isStatic = true; + entry.isStatic = true; + + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline static MetadataEntry ReadExtensionFunctionEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + entry.mi.isStatic = true; + entry.isExtensionFunction = true; + entry.isStatic = true; + + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline std::string ReadTypeName(uint16_t nodeId) { + MetadataTreeNode *treeNode = GetNodeById(nodeId); + + return ReadTypeName(treeNode); + } + + std::string ReadTypeName(MetadataTreeNode *treeNode); + + inline std::string ReadName(uint32_t offset) { + uint16_t length = *reinterpret_cast(m_nameData + offset); + + std::string name(reinterpret_cast(m_nameData + offset + sizeof(uint16_t)), + length); + + return name; + } + + inline std::string + ReadInterfaceImplementationTypeName(MetadataTreeNode *treeNode, bool &isPrefix) { + uint8_t *data = + m_valueData + treeNode->offsetValue + sizeof(uint8_t) + sizeof(uint16_t); + + isPrefix = *data == 1; + + uint32_t pos = *reinterpret_cast(data + sizeof(uint8_t)); + + uint16_t len = *reinterpret_cast(m_nameData + pos); + + char *ptr = reinterpret_cast(m_nameData + pos + sizeof(uint16_t)); + + std::string name(ptr, len); + + assert(name.length() == len); + + return name; + } + + uint8_t *GetValueData() const; + + uint8_t GetNodeType(MetadataTreeNode *treeNode); + + uint16_t GetNodeId(MetadataTreeNode *treeNode); + + MetadataTreeNode *GetRoot() const; + + MetadataTreeNode *GetOrCreateTreeNodeByName(const std::string &className); + + MetadataTreeNode *GetBaseClassNode(MetadataTreeNode *treeNode); + + MetadataTreeNode *GetNodeById(uint16_t nodeId); + + inline bool IsNodeTypeArray(uint8_t type) { + bool isArray = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::ARRAY) == MetadataTreeNode::ARRAY)); + + return isArray; + } + + inline bool IsNodeTypeStatic(uint8_t type) { + bool isStatic = (type & MetadataTreeNode::STATIC) == MetadataTreeNode::STATIC; + + return isStatic; + } + + inline bool IsNodeTypeClass(uint8_t type) { + bool isClass = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::CLASS) == MetadataTreeNode::CLASS)); + + return isClass; + } + + inline bool IsNodeTypeInterface(uint8_t type) { + bool isInterface = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::INTERFACE) == + MetadataTreeNode::INTERFACE)); + + return isInterface; + } + + inline bool IsNodeTypePackage(uint8_t type) { + bool isPackage = type == MetadataTreeNode::PACKAGE; + + return isPackage; + } + + inline static std::string ParseReturnType(const std::string &signature) { + int idx = signature.find(')'); + auto returnType = signature.substr(idx + 1); + return returnType; + } + + inline static MethodReturnType GetReturnType(const std::string &returnType) { + MethodReturnType retType; + char retTypePrefix = returnType[0]; + switch (retTypePrefix) { + case 'V': + retType = MethodReturnType::Void; + break; + case 'B': + retType = MethodReturnType::Byte; + break; + case 'S': + retType = MethodReturnType::Short; + break; + case 'I': + retType = MethodReturnType::Int; + break; + case 'J': + retType = MethodReturnType::Long; + break; + case 'F': + retType = MethodReturnType::Float; + break; + case 'D': + retType = MethodReturnType::Double; + break; + case 'C': + retType = MethodReturnType::Char; + break; + case 'Z': + retType = MethodReturnType::Boolean; + break; + case '[': + case 'L': + retType = (returnType == "Ljava/lang/String;") + ? MethodReturnType::String + : MethodReturnType::Object; + break; + default: + assert(false); + break; + } + return retType; + } + + private: +// static const uint32_t ARRAY_OFFSET = 1000000000; + static const uint32_t ARRAY_OFFSET = INT32_MAX; // 2147483647 + + MetadataTreeNode *BuildTree(); + + std::string ReadTypeNameInternal(MetadataTreeNode *treeNode); + + MetadataTreeNode *m_root; + uint32_t m_nodesLength; + uint32_t m_nameLength; + uint32_t m_valueLength; + uint8_t *m_nodeData; + uint8_t *m_nameData; + uint8_t *m_valueData; + std::vector m_v; + GetTypeMetadataCallback m_getTypeMetadataCallback; + + robin_hood::unordered_map m_typeNameCache; + }; +} + +#endif /* METADATAREADER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.cpp b/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.cpp new file mode 100644 index 000000000..d8f909084 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.cpp @@ -0,0 +1,26 @@ +#include "MetadataTreeNode.h" + +using namespace std; +using namespace tns; + +MetadataTreeNode::MetadataTreeNode() + : + children(nullptr), parent(nullptr), metadata(nullptr), offsetValue(0), type(INVALID_TYPE) { +} + +MetadataTreeNode* MetadataTreeNode::GetChild(const string& childName) { + MetadataTreeNode* child = nullptr; + + if (children != nullptr) { + auto itEnd = children->end(); + auto itFound = find_if(children->begin(), itEnd, [&childName] (MetadataTreeNode *x) { + return x->name == childName; + }); + if (itFound != itEnd) { + child = *itFound; + } + } + + return child; +} + diff --git a/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h b/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h new file mode 100644 index 000000000..ed07707ec --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MetadataTreeNode.h @@ -0,0 +1,56 @@ +#ifndef TREENODE_H_ +#define TREENODE_H_ + +#include +#include + +namespace tns { +struct MetadataTreeNode { + MetadataTreeNode(); + + MetadataTreeNode* GetChild(const std::string& name); + + std::string name; + MetadataTreeNode* parent; + uint32_t offsetValue; + std::vector* children; + // + std::string* metadata; + uint8_t type; + + // True when this node was not read from the metadata files but rebuilt at + // runtime by MetadataReader::GetOrCreateTreeNodeByName, via reflection. + // That path is the reason a class missing from filtered metadata can still + // be marshalled: it is slower, not broken. Only the usage tracer reads + // this, to tell "recovered" apart from "would have thrown". + bool synthesizedAtRuntime = false; + + static const uint8_t PACKAGE = 0; + static const uint8_t CLASS = 1 << 0; + static const uint8_t INTERFACE = 1 << 1; + static const uint8_t STATIC = 1 << 2; + static const uint8_t ARRAY = 1 << 3; + static const uint8_t PRIMITIVE = 1 << 4; + + static const uint8_t FINAL = 1; + + static const uint8_t PRIMITIVE_BYTE = 1 + PRIMITIVE; + static const uint8_t PRIMITIVE_SHORT = 2 + PRIMITIVE; + static const uint8_t PRIMITIVE_INT = 3 + PRIMITIVE; + static const uint8_t PRIMITIVE_LONG = 4 + PRIMITIVE; + static const uint8_t PRIMITIVE_FLOAT = 5 + PRIMITIVE; + static const uint8_t PRIMITIVE_DOUBLE = 6 + PRIMITIVE; + static const uint8_t PRIMITIVE_BOOL = 7 + PRIMITIVE; + static const uint8_t PRIMITIVE_CHAR = 8 + PRIMITIVE; + static const uint8_t INVALID_TYPE = 0xFF; +}; + +struct MetadataTreeNodeRawData { + uint16_t firstChildId; + uint16_t nextSiblingId; + uint32_t offsetName; + uint32_t offsetValue; +}; +} + +#endif /* TREENODE_H_ */ diff --git a/NativeScript/ffi/jni/jsi/metadata/MethodCache.cpp b/NativeScript/ffi/jni/jsi/metadata/MethodCache.cpp new file mode 100644 index 000000000..24a1f0f4b --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MethodCache.cpp @@ -0,0 +1,34 @@ +#include "MethodCache.h" +#include "JniLocalRef.h" +#include "JsArgToArrayConverter.h" +#include "MetadataNode.h" +#include "NativeScriptAssert.h" +#include "Util.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +void MethodCache::Init() +{ + JEnv jEnv; + + RUNTIME_CLASS = jEnv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + RESOLVE_METHOD_OVERLOAD_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveMethodOverload", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;"); + assert(RESOLVE_METHOD_OVERLOAD_METHOD_ID != nullptr); + + RESOLVE_CONSTRUCTOR_SIGNATURE_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveConstructorSignature", "(Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/String;"); + assert(RESOLVE_CONSTRUCTOR_SIGNATURE_ID != nullptr); +} + + +robin_hood::unordered_map MethodCache::s_method_ctor_signature_cache; +jclass MethodCache::RUNTIME_CLASS = nullptr; +jmethodID MethodCache::RESOLVE_METHOD_OVERLOAD_METHOD_ID = nullptr; +jmethodID MethodCache::RESOLVE_CONSTRUCTOR_SIGNATURE_ID = nullptr; diff --git a/NativeScript/ffi/jni/jsi/metadata/MethodCache.h b/NativeScript/ffi/jni/jsi/metadata/MethodCache.h new file mode 100644 index 000000000..b0eddc66d --- /dev/null +++ b/NativeScript/ffi/jni/jsi/metadata/MethodCache.h @@ -0,0 +1,351 @@ +#ifndef METHODCACHE_H_ +#define METHODCACHE_H_ + +#include +#include +#include "JEnv.h" +#include "MetadataEntry.h" +#include "ArgsWrapper.h" +#include "NativeScriptAssert.h" +#include "MetadataReader.h" +#include "Runtime.h" +#include "MetadataNode.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "JsArgToArrayConverter.h" +#include "Util.h" + +namespace tns { +/* + * MethodCache: class dealing with method/constructor resolution. + */ +class MethodCache { + public: + /* + * CacheMethodInfo: struct holding resolved methods/constructor resolution + */ + struct CacheMethodInfo { + CacheMethodInfo() + : + retType(MethodReturnType::Unknown), mid(nullptr), clazz(nullptr), isStatic(false) { + } + std::string signature; + std::string returnType; + MethodReturnType retType; + jmethodID mid; + jclass clazz; + bool isStatic; + }; + + static void Init(); + + inline static MethodCache::CacheMethodInfo ResolveMethodSignature(JsRuntime &rt, const string &className, const string &methodName, size_t argc, const JsValue* argv, bool isStatic) + { + CacheMethodInfo method_info; + + auto encoded_method_signature = EncodeSignature(rt, className, methodName, argc, argv, isStatic); + auto it = s_method_ctor_signature_cache.find(encoded_method_signature); + + if (it == s_method_ctor_signature_cache.end()) + { + auto signature = ResolveJavaMethod(rt, argc, argv, className, methodName); + + DEBUG_WRITE("ResolveMethodSignature %s='%s'", encoded_method_signature.c_str(), signature.c_str()); + + if (!signature.empty()) + { + JEnv jEnv; + auto clazz = jEnv.FindClass(className); + assert(clazz != nullptr); + method_info.clazz = clazz; + method_info.signature = signature; + method_info.returnType = MetadataReader::ParseReturnType(method_info.signature); + method_info.retType = MetadataReader::GetReturnType(method_info.returnType); + method_info.isStatic = isStatic; + method_info.mid = isStatic + ? jEnv.GetStaticMethodID(clazz, methodName, signature) + : jEnv.GetMethodID(clazz, methodName, signature); + + s_method_ctor_signature_cache.emplace(encoded_method_signature, method_info); + } + } + else + { + method_info = (*it).second; + } + + return method_info; + } + + inline static MethodCache::CacheMethodInfo ResolveConstructorSignature(JsRuntime &rt, const ArgsWrapper &argWrapper, const string &fullClassName, jclass javaClass, bool isInterface) + { + CacheMethodInfo constructor_info; + + auto encoded_ctor_signature = EncodeSignature(rt, fullClassName, "", argWrapper.argc, argWrapper.argv, false); + auto it = s_method_ctor_signature_cache.find(encoded_ctor_signature); + + if (it == s_method_ctor_signature_cache.end()) + { + auto signature = ResolveConstructor(rt, argWrapper.argc, argWrapper.argv, javaClass, isInterface); + + DEBUG_WRITE("ResolveConstructorSignature %s='%s'", encoded_ctor_signature.c_str(), signature.c_str()); + + if (!signature.empty()) + { + JEnv jEnv; + constructor_info.clazz = javaClass; + constructor_info.signature = signature; + constructor_info.mid = jEnv.GetMethodID(javaClass, "", signature); + + s_method_ctor_signature_cache.emplace(encoded_ctor_signature, constructor_info); + } + } + else + { + constructor_info = (*it).second; + } + + return constructor_info; + } + +private: + MethodCache() { + } + + // Encoded signature .S/I....<...> + inline static string EncodeSignature(JsRuntime &rt, const string &className, const string &methodName, size_t argc, const JsValue* argv, bool isStatic) + { + string sig(className); + sig.append("."); + if (isStatic) + { + sig.append("S."); + } + else + { + sig.append("I."); + } + sig.append(methodName); + sig.append("."); + + stringstream s; + s << argc; + sig.append(s.str()); + + for (size_t i = 0; i < argc; i++) + { + sig.append("."); + sig.append(GetType(rt, argv[i])); + } + + return sig; + } + + inline static string GetType(JsRuntime &rt, const JsValue &value) + { + string type = ""; + + if (value.isObject()) + { + MetadataNode *nullNode = MetadataNode::GetNullNode(rt, value); + if (nullNode != nullptr) + { + type = nullNode->GetName(); + + DEBUG_WRITE("Parameter of type %s with NULL value is passed to the method.", type.c_str()); + return type; + } + } + + if (value.isString()) { + type = "string"; + } else if (value.isNull()) { + type = "null"; + } else if (value.isUndefined()) { + type = "undefined"; + } else if (value.isNumber()) { + type = "number"; + } else if (value.isBool()) { + type = "bool"; + } else if (value.isObject()) { + // engine:: has one object kind, so the napi typeof ladder (which + // separated napi_function from napi_object before reaching the + // is_array/is_typedarray probes) collapses into this ordering. + if (js_util::is_array(rt, value)) { + type = "array"; + } else if (js_util::is_typedarray(rt, value)) { + type = "typedarray"; + } else if (js_util::is_dataview(rt, value)) { + type = "view"; + } else if (js_util::is_date(rt, value)) { + type = "date"; + } else { + type = "object"; + } + } + + // Handle special cases for typed arrays + if (type == "typedarray") + { + auto ctor = value.asObjectBorrowed(rt).getProperty(rt, "constructor"); + string name; + if (ctor.isObject()) { + auto ctorName = ctor.asObjectBorrowed(rt).getProperty(rt, "name"); + if (ctorName.isString()) name = ctorName.asString(rt).utf8(rt); + } + + if (name == "Int8Array" || name == "Uint8Array" || name == "Uint8ClampedArray") { + type = "bytebuffer"; + } else if (name == "Int16Array" || name == "Uint16Array") { + type = "shortbuffer"; + } else if (name == "Int32Array" || name == "Uint32Array") { + type = "intbuffer"; + } else if (name == "BigInt64Array" || name == "BigUint64Array") { + type = "longbuffer"; + } else if (name == "Float32Array") { + type = "floatbuffer"; + } else if (name == "Float64Array") { + type = "doublebuffer"; + } else { + type = ""; + } + } + + // Handle special cases for numbers + if (type == "number") + { + double d = js_util::get_number(value); + int64_t i = (int64_t)d; + bool isInteger = d == i; + type = isInteger ? "intnumber" : "doublenumber"; + } + + // Handle special cases for objects + if (type == "object") + { + auto castType = NumericCasts::GetCastType(rt, value); + MetadataNode *node; + + switch (castType) + { + case CastType::Char: + type = "char"; + break; + case CastType::Byte: + type = "byte"; + break; + case CastType::Short: + type = "short"; + break; + case CastType::Long: + type = "long"; + break; + case CastType::Float: + type = "float"; + break; + case CastType::Double: + type = "double"; + break; + case CastType::None: + node = MetadataNode::GetNodeFromHandle(rt, value); + type = (node != nullptr) ? node->GetName() : ""; + + if (type == "") { + if (js_util::is_number_object(rt, value)) { + JsValue numValue = js_util::valueOf(rt, value); + if (js_util::is_float(rt, numValue)) { + type = "float"; + } else { + type = "int"; + } + } else if (js_util::is_string_object(rt, value)) { + type = "string"; + } else if (js_util::is_boolean_object(rt, value)) { + type = "bool"; + } + } + + break; + default: + throw NativeScriptException("Unsupported cast type"); + } + } + + if (type == "undefined") { + type = "null"; + } + + return type; + } + + inline static string ResolveJavaMethod(JsRuntime &rt, size_t argc, const JsValue* argv, const string &className, const string &methodName) + { + JEnv jEnv; + + JsArgToArrayConverter argConverter(rt, argc, argv, false); + + auto canonicalClassName = Util::ConvertFromJniToCanonicalName(className); + JniLocalRef jsClassName(jEnv.NewStringUTF(canonicalClassName.c_str())); + JniLocalRef jsMethodName(jEnv.NewStringUTF(methodName.c_str())); + + jobjectArray arrArgs = argConverter.ToJavaArray(); + + auto runtime = Runtime::GetRuntime(rt); + + jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_METHOD_OVERLOAD_METHOD_ID, (jstring)jsClassName, (jstring)jsMethodName, arrArgs); + + string resolvedSignature; + + const char *str = jEnv.GetStringUTFChars(signature, nullptr); + resolvedSignature = string(str); + jEnv.ReleaseStringUTFChars(signature, str); + + jEnv.DeleteLocalRef(signature); + + return resolvedSignature; + } + + inline static string ResolveConstructor(JsRuntime &rt, size_t argc, const JsValue* argv, jclass javaClass, bool isInterface) + { + JEnv jEnv; + string resolvedSignature; + + JsArgToArrayConverter argConverter(rt, argc, argv, isInterface); + if (argConverter.IsValid()) + { + jobjectArray javaArgs = argConverter.ToJavaArray(); + + auto runtime = Runtime::GetRuntime(rt); + + jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_CONSTRUCTOR_SIGNATURE_ID, javaClass, javaArgs); + + const char *str = jEnv.GetStringUTFChars(signature, nullptr); + resolvedSignature = string(str); + jEnv.ReleaseStringUTFChars(signature, str); + jEnv.DeleteLocalRef(signature); + } + else + { + JsArgToArrayConverter::Error err = argConverter.GetError(); + throw NativeScriptException(err.msg); + } + + return resolvedSignature; + } + + static jclass RUNTIME_CLASS; + + static jmethodID RESOLVE_METHOD_OVERLOAD_METHOD_ID; + + static jmethodID RESOLVE_CONSTRUCTOR_SIGNATURE_ID; + + /* + * "s_method_ctor_signature_cache" holding all resolved CacheMethodInfo against an encoded_signature string. + * Used for caching the resolved constructor or method signature. + * The encoded signature has template: .S/I....<...> + */ + static robin_hood::unordered_map s_method_ctor_signature_cache; +}; +} + +#endif /* METHODCACHE_H_ */ diff --git a/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp b/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp new file mode 100644 index 000000000..08e000964 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.cpp @@ -0,0 +1,806 @@ +#include "ObjectManager.h" +#include "NativeScriptAssert.h" +#include "MetadataNode.h" +#include "ArgConverter.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "CallbackHandlers.h" +#include +#include + +using namespace std; +using namespace tns; + +// GetClassName is static so exception handling can resolve a Java class name +// without retrieving the runtime/ObjectManager (which may be unavailable +// mid-exception). These JNI ids are process-global once looked up. +jclass ObjectManager::JAVA_LANG_CLASS = nullptr; +jmethodID ObjectManager::GET_NAME_METHOD_ID = nullptr; + +ObjectManager::ObjectManager(jobject javaRuntimeObject) : + m_javaRuntimeObject(javaRuntimeObject), + m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, ValidateWeakGlobalRefCallback, 1000, this), + m_currentObjectId(0), + m_rt(nullptr), + m_proxyRegistry(std::make_shared()) { + + JEnv env; + auto runtimeClass = env.FindClass("com/tns/Runtime"); + assert(runtimeClass != nullptr); + + GET_JAVAOBJECT_BY_ID_METHOD_ID = env.GetMethodID(runtimeClass, "getJavaObjectByID", + "(I)Ljava/lang/Object;"); + assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr); + + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = env.GetMethodID(runtimeClass, + "getOrCreateJavaObjectID", + "(Ljava/lang/Object;)I"); + assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(I)V"); + assert(MAKE_INSTANCE_WEAK_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(Ljava/nio/ByteBuffer;IZ)V"); + assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); + + MAKE_INSTANCE_STRONG_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceStrong", + "(I)V"); + assert(MAKE_INSTANCE_STRONG_METHOD_ID != nullptr); + + JAVA_LANG_CLASS = env.FindClass("java/lang/Class"); + assert(JAVA_LANG_CLASS != nullptr); + + GET_NAME_METHOD_ID = env.GetMethodID(JAVA_LANG_CLASS, "getName", "()Ljava/lang/String;"); + assert(GET_NAME_METHOD_ID != nullptr); +} + + +void ObjectManager::Init(JsRuntime &rt) { + m_rt = &rt; + + JsFunction jsObjectCtor = JsFunction::createFromHostConstructor( + rt, JsPropNameID::forAscii(rt, "JSObject"), 0, + [](JsRuntime &rt, const JsValue &jsThis, const JsValue *argv, size_t argc) { + return jsThis; + }); + + JsValue prototype = js_util::get_prototype(rt, JsValue(rt, jsObjectCtor)); + if (prototype.isObject()) { + prototype.asObject(rt).setProperty(rt, PRIVATE_IS_NAPI, true); + } + + m_jsObjectCtor = jsObjectCtor; +} + + +void ObjectManager::OnDisposeRuntime() { + JEnv jEnv; + // Every entry owns an engine handle; clearing the maps releases them while + // the runtime is still alive, which QuickJS requires (JS_FreeRuntime asserts + // an empty gc object list). + m_idToProxy.clear(); + m_idToObject.clear(); + m_jsObjectCtor = JsFunction(); + + // Its destructor leaves the weak global refs alive, and the process-wide + // weak reference table is finite. + m_cache.clear(); + + // The host-object proxies are owned by the *engine*, and each holds an owned + // handle to the instance it wraps. Nothing above reaches them: they are only + // destroyed when the engine tears its heap down, at which point + // ~HostObjectProxy cannot legally release anything (a JS_FreeValue inside + // QuickJS' sweep corrupts the collector), so it leaks the handle instead -- + // and a leaked handle is exactly what JS_FreeRuntime's + // list_empty(&rt->gc_obj_list) assertion catches. V8 and JSC only leak. + // + // So release those handles here, while the runtime is still healthy, and + // clear objectManager to tell the destructor there is nothing left to do. + { + std::lock_guard lock(m_proxyRegistry->mutex); + for (auto *proxy: m_proxyRegistry->proxies) { + proxy->target.reset(); + proxy->objectManager = nullptr; + } + m_proxyRegistry->proxies.clear(); + } +} + +JsValue ObjectManager::GetOrCreateProxyWeak(jint javaObjectID, const JsValue &instance) { + // A miss is expected here (the instance may carry no native state); the + // proxy handles a null info. + auto info = GetJSInstanceInfoShared(instance); + // Transient (weak) proxy: borrows the instance's existing JSInstanceInfo. + return CreateHostObjectProxy(instance, info.get(), /*isPrimary=*/false); +} + +JsValue ObjectManager::GetOrCreateProxy(jint javaObjectID, const JsValue &instance) { + auto it = m_idToProxy.find(javaObjectID); + if (it != m_idToProxy.end() && !it->second.empty()) { + JsValue proxy = it->second.lock(*m_rt); + if (!js_util::is_null_or_undefined(proxy)) { + return proxy; + } else { + m_idToProxy.erase(javaObjectID); + } + } + + DEBUG_WRITE("%s %d", "Creating a new proxy for java object with id:", javaObjectID); + + // Primary (cached) proxy: owns a fresh JSInstanceInfo and marks the java + // instance weak when collected. + auto info = new JSInstanceInfo(javaObjectID, nullptr); + // Carry the class metadata from the raw instance's JSInstanceInfo (set in + // Link) so GetInstanceMetadata resolves it from the proxy. + auto rawInfo = GetJSInstanceInfoShared(instance); + if (rawInfo != nullptr) { + info->node = rawInfo->node; + } + JsValue proxy = CreateHostObjectProxy(instance, info, /*isPrimary=*/true); + + auto javaObjectIdFound = m_weakObjectIds.find(javaObjectID); + if (javaObjectIdFound != m_weakObjectIds.end()) { + m_weakObjectIds.erase(javaObjectID); + JEnv jenv; + jenv.CallVoidMethod(m_javaRuntimeObject, + MAKE_INSTANCE_STRONG_METHOD_ID, + javaObjectID); + DEBUG_WRITE("Making instance strong: %d", javaObjectID); + } + + m_idToProxy.emplace(javaObjectID, engine::WeakObject(*m_rt, proxy)); + + return proxy; +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObject(const JsValue &object, int *objectId, bool *isSuper) { + int32_t javaObjectId = (objectId) ? *objectId : -1; + // Cache slot for the super-call flag on whichever per-object info we resolve; + // resolved once from PRIVATE_CALLSUPER, then read from the cached field. + int8_t *superSlot = nullptr; + + if (object.isObject()) { + auto proxy = object.asObjectBorrowed(*m_rt).getHostObject(*m_rt); + if (proxy != nullptr) { + if (proxy->instanceInfo) javaObjectId = proxy->instanceInfo->JavaObjectID; + superSlot = &proxy->isSuper; + } else { + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + if (jsInstanceInfo != nullptr) { + javaObjectId = jsInstanceInfo->JavaObjectID; + superSlot = &jsInstanceInfo->isSuper; + } + } + } + + if (isSuper) { + if (superSlot != nullptr) { + if (*superSlot < 0) { + JsValue superValue = js_util::get_property(*m_rt, object, PRIVATE_CALLSUPER); + *superSlot = js_util::get_bool(superValue) ? 1 : 0; + } + *isSuper = (*superSlot == 1); + } else { + *isSuper = false; + } + } + + if (objectId) { + *objectId = javaObjectId; + } + + if (javaObjectId != -1) { + try { + return {GetJavaObjectByID(javaObjectId), true}; + } catch (NativeScriptException &e) { + // Surface which object failed instead of a bare error — this usually + // means the id belongs to a different runtime/thread. + throw NativeScriptException("Failed to get Java object by ID. id=" + + std::to_string(javaObjectId) + ". " + e.what()); + } + } + + return {}; +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObjectFast(const JsValue &object) { + if (!object.isObject()) { + return {}; + } + + JsObject borrowed = object.asObjectBorrowed(*m_rt); + + auto proxy = borrowed.getHostObject(*m_rt); + if (proxy != nullptr) { + if (proxy->instanceInfo) { + return {GetJavaObjectByID(proxy->instanceInfo->JavaObjectID), true}; + } + } + + auto info = borrowed.getNativeState(*m_rt); + if (info != nullptr) { + return {GetJavaObjectByID(info->JavaObjectID), true}; + } + + return GetJavaObjectByJsObject(object); +} + +std::shared_ptr +ObjectManager::GetJSInstanceInfoShared(const JsValue &object) { + if (!object.isObject()) return nullptr; + auto info = object.asObjectBorrowed(*m_rt).getNativeState(*m_rt); + if (info != nullptr) return info; + + // A host proxy carries no native state of its own; the instance it wraps + // does. Which of the two an accessor receives is engine-dependent -- reading + // `super` off an extended instance lands on the target under V8 and on the + // proxy under QuickJS -- so resolve through the proxy rather than making + // every caller know. One hop only: a target is never itself a proxy. + auto proxy = object.asObjectBorrowed(*m_rt).getHostObject(*m_rt); + if (proxy != nullptr && proxy->target != nullptr) { + return proxy->target->asObjectBorrowed(*m_rt).getNativeState(*m_rt); + } + return nullptr; +} + +ObjectManager::JSInstanceInfo *ObjectManager::GetJSInstanceInfo(const JsValue &object) { + if (!object.isObject()) return nullptr; + + auto proxy = object.asObjectBorrowed(*m_rt).getHostObject(*m_rt); + if (proxy != nullptr) { + if (proxy->instanceInfo) { + return proxy->instanceInfo; + } + } + + if (!IsRuntimeJsObject(object)) return nullptr; + return GetJSInstanceInfoFromRuntimeObject(object); +} + +MetadataNode *ObjectManager::GetInstanceNode(const JsValue &object) { + JSInstanceInfo *info = GetJSInstanceInfo(object); + return info != nullptr ? info->node : nullptr; +} + +bool ObjectManager::IsHostObject(const JsValue &object) { + if (!object.isObject()) return false; + return object.asObjectBorrowed(*m_rt).isHostObject(*m_rt); +} + +// ---------------------------------------------------------------------------- +// Host object proxy: callbacks + lifecycle +// +// The proxy forwards to the wrapped `instance` (kept in HostObjectProxy::target) +// via these callbacks. Array-like instances route numeric-index get/set straight +// into the native element accessor. +// ---------------------------------------------------------------------------- + +// Recognise a canonical array index in a host-object trap key. Every key reaches +// get()/set() as a PropNameID, so an index arrives as its canonical decimal +// string ("0", "1", ...) on every engine -- the napi tree also had to accept a +// napi_number here because V8 routed indices through a separate interceptor. +static bool TryGetArrayIndex(const std::string &name, uint32_t &outIndex) { + size_t len = name.size(); + if (len == 0 || len > 10) return false; // a uint32 has at most 10 digits + if (name[0] == '0') { // canonical form has no leading zeros; only "0" itself + if (len != 1) return false; + outIndex = 0; + return true; + } + uint64_t v = 0; + for (size_t i = 0; i < len; i++) { + if (name[i] < '0' || name[i] > '9') return false; + v = v * 10 + (uint64_t) (name[i] - '0'); + } + if (v > 4294967294ULL) return false; // max array index is 2^32 - 2 + outIndex = (uint32_t) v; + return true; +} + +ObjectManager::HostObjectProxy::HostObjectProxy(ObjectManager *objectManager, + JSInstanceInfo *instanceInfo, bool isPrimary, + JsRuntime &rt, const JsValue &target) + : objectManager(objectManager), + instanceInfo(instanceInfo), + isPrimary(isPrimary), + rt(&rt), + target(std::make_unique(rt, target)), + isArray(false) { + registry = objectManager->m_proxyRegistry; + std::lock_guard lock(registry->mutex); + registry->proxies.insert(this); +} + +ObjectManager::HostObjectProxy::~HostObjectProxy() { + // Deregister first, and through the registry rather than the ObjectManager: + // this destructor runs whenever the engine collects the proxy, which for a + // worker is after its Runtime (and so its ObjectManager) has been deleted. + { + std::lock_guard lock(registry->mutex); + registry->proxies.erase(this); + } + + // Neutralised by OnDisposeRuntime: the handle is already gone and the + // runtime is on its way out, so there is nothing to defer. + if (objectManager == nullptr) { + return; + } + + // Runs inside the engine's GC sweep. Releasing the owned target handle here + // is illegal on every engine (V8's InvokeFinalizerFromGC; a reentrant + // JS_FreeValue during a QuickJS sweep corrupts the collector), so the handle + // and the remaining C++ cleanup are handed to the runtime's post-GC drain. + auto *pending = new HostObjectProxy::PendingCleanup{target.release(), instanceInfo, + isPrimary, objectManager}; + Runtime::PostFinalizer(*rt, ObjectManager::HostObjectProxyPostFinalizer, pending, nullptr); +} + +JsValue ObjectManager::HostObjectProxy::ArrayElementAt(JsRuntime &rt, uint32_t index) { + jobject arr = instanceInfo + ? (jobject) objectManager->GetJavaObjectByID(instanceInfo->JavaObjectID) + : nullptr; + const JsValue &host = receiver() != nullptr ? *receiver() : *target; + return CallbackHandlers::GetArrayElement(rt, host, index, arraySignature, objectManager, arr); +} + +void ObjectManager::HostObjectProxy::SetArrayElementAt(JsRuntime &rt, uint32_t index, + const JsValue &value) { + jobject arr = instanceInfo + ? (jobject) objectManager->GetJavaObjectByID(instanceInfo->JavaObjectID) + : nullptr; + const JsValue &host = receiver() != nullptr ? *receiver() : *target; + CallbackHandlers::SetArrayElement(rt, host, index, arraySignature, value, objectManager, arr); +} + +// Reached from an engine that delivers an index as an integer. The engine layer +// has already established that this proxy opted in, so no isArray re-check. +JsValue ObjectManager::HostObjectProxy::getValueAtIndex(JsRuntime &rt, uint32_t index) { + try { + return ArrayElementAt(rt, index); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectGet").ReThrowToJs(rt); + } +} + +bool ObjectManager::HostObjectProxy::setValueAtIndex(JsRuntime &rt, uint32_t index, + const JsValue &value) { + try { + SetArrayElementAt(rt, index, value); + return true; + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectSet").ReThrowToJs(rt); + } +} + +JsValue ObjectManager::HostObjectProxy::get(JsRuntime &rt, const JsPropNameID &name) { + try { + std::string key = name.utf8(rt); + + // Numeric keys on arrays: straight into the native element accessor. + // Only an engine with no way to hand over an index as an integer gets + // here (Hermes); the rest arrive at getValueAtIndex instead. + uint32_t index = 0; + if (isArray && !arraySignature.empty() && TryGetArrayIndex(key, index)) { + return ArrayElementAt(rt, index); + } + + // Mirrors the old "super" accessor: `proxy.super` resolves to + // `target.super`. The napi tree installed it with napi_define_properties; + // here there is no separate accessor slot, so it is one more name the get + // trap answers -- and the forwarding below would answer it identically + // anyway. + // Everything else (incl. map/forEach/toString/Symbol.iterator/length, which + // are native methods on the array prototype) forwards to the instance. + return target->asObjectBorrowed(rt).getProperty(rt, key); + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectGet").ReThrowToJs(rt); + } +} + +bool ObjectManager::HostObjectProxy::set(JsRuntime &rt, const JsPropNameID &name, + const JsValue &value) { + try { + std::string key = name.utf8(rt); + + uint32_t index = 0; + if (isArray && !arraySignature.empty() && TryGetArrayIndex(key, index)) { + SetArrayElementAt(rt, index, value); + return true; + } + + target->asObjectBorrowed(rt).setProperty(rt, key, value); + return true; + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectSet").ReThrowToJs(rt); + } +} + +std::vector ObjectManager::HostObjectProxy::getPropertyNames(JsRuntime &rt) { + std::vector names; + try { + JsArray keys = target->asObjectBorrowed(rt).getPropertyNames(rt); + size_t size = keys.size(rt); + names.reserve(size); + for (size_t i = 0; i < size; i++) { + JsValue key = keys.getValueAtIndexBorrowed(rt, i); + if (key.isString()) { + names.push_back(JsPropNameID::forAscii(rt, key.asString(rt).utf8(rt))); + } + } + } catch (NativeScriptException &e) { + e.ReThrowToJs(rt); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToJs(rt); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectOwnKeys").ReThrowToJs(rt); + } + return names; +} + +void ObjectManager::HostObjectProxyPostFinalizer(JsRuntime &rt, void *data, void *hint) { + auto *pending = reinterpret_cast(data); + if (pending == nullptr) return; + + auto rtOwner = Runtime::GetRuntimeUnchecked(rt); + // Once the runtime is tearing down, the dispose path owns every outstanding + // handle: OnDisposeRuntime clears the id maps. Releasing the target here in + // that window would touch a runtime that is already going away. + bool destroying = (rtOwner == nullptr) || rtOwner->is_destroying; + + if (pending->target != nullptr) { + if (destroying) { + // Leak the handle rather than release it against a dying runtime; + // the engine frees the whole heap immediately afterwards. + (void) pending->target; + } else { + delete pending->target; + } + } + + // Primary (cached) proxies own their JSInstanceInfo and mark the java + // instance weak on collection. + if (pending->isPrimary && pending->instanceInfo) { + if (!destroying) { + auto objManager = rtOwner->GetObjectManager(); + auto javaObjectID = pending->instanceInfo->JavaObjectID; + if (objManager->m_weakObjectIds.find(javaObjectID) == + objManager->m_weakObjectIds.end()) { + objManager->m_weakObjectIds.emplace(javaObjectID); + JEnv jEnv; + jEnv.CallVoidMethod(objManager->m_javaRuntimeObject, + objManager->MAKE_INSTANCE_WEAK_METHOD_ID, + javaObjectID); + } + } + delete pending->instanceInfo; + } + + delete pending; +} + +JsValue ObjectManager::CreateHostObjectProxy(const JsValue &instance, + JSInstanceInfo *instanceInfo, + bool isPrimary) { + auto proxy = std::make_shared(this, instanceInfo, isPrimary, *m_rt, instance); + + proxy->isArray = js_util::has_property(*m_rt, instance, "__is__javaArray"); + if (proxy->isArray) { + // Cache the jni array signature so numeric index access goes straight + // into the native element accessor (no JS getValueAtIndex dispatch). + // node is already on the raw instance's JSInstanceInfo (set in Link). + MetadataNode *node = GetInstanceNode(instance); + if (node != nullptr) { + proxy->arraySignature = node->GetName(); + } + // Opt this proxy into the engine's indexed path, which is what lets + // `a[0]` skip being spelled out as the property name "0" and parsed + // back. Only meaningful with a signature: without one there is nothing + // to marshal through and the named path would have declined too. + proxy->setIndexedAccess(!proxy->arraySignature.empty()); + } + + // A native instance, not an opaque host object: the proxy is given the Java + // class prototype just below, and that is where the field accessors and + // methods live. On V8 this selects the non-masking named interceptor, so a + // field read resolves on the prototype (and can form a load IC) instead of + // entering the trap, crossing into C++ and re-reading the same property off + // the wrapped target. Other backends do not distinguish the two. + JsObject proxyObject = + JsObject::createNativeInstanceHostObject(*m_rt, proxy); + + // The engine layer does not touch the prototype chain for host objects, so + // do it here to preserve behaviour (instanceof checks, super dispatch). + js_util::setPrototypeOf(*m_rt, JsValue(*m_rt, proxyObject), + js_util::getPrototypeOf(*m_rt, instance)); + + return JsValue(*m_rt, proxyObject); +} + +ObjectManager::JSInstanceInfo * +ObjectManager::GetJSInstanceInfoFromRuntimeObject(const JsValue &object) { + auto info = GetJSInstanceInfoShared(object); + + if (info == nullptr) { + JsValue proto = js_util::get__proto__(*m_rt, object); + //Typescript object layout has an object instance as child of the actual registered instance. checking for that + if (!js_util::is_null_or_undefined(proto)) { + if (IsRuntimeJsObject(proto)) { + info = GetJSInstanceInfoShared(proto); + } + } + } + + return info.get(); +} + +bool ObjectManager::IsRuntimeJsObject(const JsValue &object) { + if (!object.isObject()) return false; + + return js_util::has_property(*m_rt, object, PRIVATE_IS_NAPI); +} + +jweak ObjectManager::GetJavaObjectByID(uint32_t javaObjectID) { + return m_cache(javaObjectID); +} + +jobject ObjectManager::GetJavaObjectByIDImpl(uint32_t javaObjectID) { + JEnv env; + jobject object = env.CallObjectMethod(m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, + javaObjectID); + return object; +} + +void ObjectManager::UpdateCache(int objectID, jobject obj) { + m_cache.update(objectID, obj); +} + +jclass ObjectManager::GetJavaClass(const JsValue &value) { + JSInstanceInfo *jsInfo = GetJSInstanceInfo(value); + jclass clazz = jsInfo->ObjectClazz; + + return clazz; +} + +void ObjectManager::SetJavaClass(const JsValue &value, jclass clazz) { + JSInstanceInfo *jsInfo = GetJSInstanceInfo(value); + jsInfo->ObjectClazz = clazz; +} + +int ObjectManager::GetOrCreateObjectId(jobject object) { + JEnv env; + jint javaObjectID = env.CallIntMethod(m_javaRuntimeObject, + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); + return javaObjectID; +} + +JsValue ObjectManager::GetJsObjectByJavaObject(int javaObjectID) { + auto it = m_idToObject.find(javaObjectID); + if (it == m_idToObject.end()) { + return js_util::undefined(); + } + + JsValue instance = it->second; + if (js_util::is_null_or_undefined(instance)) return js_util::undefined(); + return GetOrCreateProxy(javaObjectID, instance); +} + + +JsValue ObjectManager::CreateJSWrapper(jint javaObjectID, const std::string &typeName) { + return CreateJSWrapperHelper(javaObjectID, typeName, nullptr); +} + +JsValue ObjectManager::CreateJSWrapper(jint javaObjectID, const std::string &typeName, + jobject instance) { + JEnv jenv; + JniLocalRef clazz(jenv.GetObjectClass(instance)); + + return CreateJSWrapperHelper(javaObjectID, typeName, clazz); +} + +JsValue ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const std::string &typeName, + jclass clazz) { + auto className = (clazz != nullptr) ? GetClassName(clazz) : typeName; + + auto node = MetadataNode::GetOrCreate(className); + JsValue proxy = js_util::undefined(); + JsValue jsWrapper = node->CreateJSWrapper(*m_rt, this); + if (jsWrapper.isObject()) { + // Reuse the class we already resolved via GetObjectClass on the instance + // path instead of re-resolving it with a JNI FindClass. The class is only + // stored on JSInstanceInfo::ObjectClazz, which nothing on this path reads, + // so a fresh FindClass is pure overhead; only fall back to it for the + // typeName-only overload where no instance class was available. + jclass linkClazz = clazz; + if (linkClazz == nullptr) { + JEnv jenv; + linkClazz = jenv.FindClass(className); + } + Link(jsWrapper, javaObjectID, linkClazz, node); + if (node->isArray()) { + jsWrapper.asObject(*m_rt).setProperty(*m_rt, "__is__javaArray", true); + } + proxy = GetOrCreateProxy(javaObjectID, jsWrapper); + } + + return proxy; +} + +void ObjectManager::Link(const JsValue &object, uint32_t javaObjectID, jclass clazz, + MetadataNode *node) { + if (!IsRuntimeJsObject(object)) { + std::string errMsg("Trying to link invalid 'this' to a Java object"); + throw NativeScriptException(errMsg); + } + + DEBUG_WRITE("Linking js object and java instance id: %d", javaObjectID); + + auto jsInstanceInfo = std::make_shared(javaObjectID, clazz); + jsInstanceInfo->node = node; + + // One slot, one owner: the native state both carries the record and keeps it + // alive, replacing the napi tree's external-plus-wrap pair. + object.asObjectBorrowed(*m_rt).setNativeState(*m_rt, jsInstanceInfo); + + m_idToObject.emplace(javaObjectID, JsValue(*m_rt, object)); +} + +bool ObjectManager::CloneLink(const JsValue &src, const JsValue &dest) { + auto jsInfo = GetJSInstanceInfoShared(src); + + auto success = jsInfo != nullptr; + + if (success) { + dest.asObjectBorrowed(*m_rt).setNativeState(*m_rt, jsInfo); + } + + return success; +} + +string ObjectManager::GetClassName(jobject javaObject) { + JEnv env; + JniLocalRef objectClass(env.GetObjectClass(javaObject)); + + return GetClassName((jclass) objectClass); +} + +string ObjectManager::GetClassName(jclass clazz) { + JEnv env; + JniLocalRef javaCanonicalName(env.CallObjectMethod(clazz, GET_NAME_METHOD_ID)); + + string className = ArgConverter::jstringToString(javaCanonicalName); + + std::replace(className.begin(), className.end(), '.', '/'); + + return className; +} + +int ObjectManager::GenerateNewObjectID() { + const int one = 1; + int oldValue = __sync_fetch_and_add(&m_currentObjectId, one); + return oldValue; +} + +jweak ObjectManager::NewWeakGlobalRefCallback(const int &javaObjectID, void *state) { + auto objManager = reinterpret_cast(state); + JniLocalRef obj(objManager->GetJavaObjectByIDImpl(javaObjectID)); + JEnv jEnv; + jweak weakRef = jEnv.NewWeakGlobalRef(obj); + + return weakRef; +} + +void ObjectManager::DeleteWeakGlobalRefCallback(const jweak &object, void *state) { + JEnv jEnv; + jEnv.DeleteWeakGlobalRef(object); +} + +bool ObjectManager::ValidateWeakGlobalRefCallback(const int &javaObjectID, const jweak &object, + void *state) { + JEnv jEnv; + // A weak ref that is now IsSameObject(NULL) points to a collected object and + // must not be reused; report it as invalid so the cache evicts it. + return !jEnv.isSameObject(object, NULL); +} + +JsValue ObjectManager::GetEmptyObject() { + JsValue jsWrapper = m_jsObjectCtor.callAsConstructor( + *m_rt, static_cast(nullptr), (size_t) 0); + if (jsWrapper.isObject()) { + return jsWrapper; + } + + JsObject plain(*m_rt); + MarkObject(*m_rt, JsValue(*m_rt, plain)); + auto prototype = js_util::get_prototype(*m_rt, JsValue(*m_rt, m_jsObjectCtor)); + if (!js_util::is_null_or_undefined(prototype)) { + js_util::setPrototypeOf(*m_rt, JsValue(*m_rt, plain), prototype); + } + + return JsValue(*m_rt, plain); +} + +void ObjectManager::ReleaseObjectNow(JsRuntime &rt, int javaObjectId) { + auto rtOwner = Runtime::GetRuntimeUnchecked(rt); + if (!rtOwner || rtOwner->is_destroying) return; + ObjectManager *objMgr = rtOwner->GetObjectManager(); + + auto itFound = objMgr->m_weakObjectIds.find(javaObjectId); + if (itFound == objMgr->m_weakObjectIds.end()) { + JEnv jEnv; + jEnv.CallVoidMethod(objMgr->m_javaRuntimeObject, objMgr->MAKE_INSTANCE_WEAK_METHOD_ID, + javaObjectId); + objMgr->m_weakObjectIds.emplace(javaObjectId); + } + + objMgr->m_idToProxy.erase(javaObjectId); + objMgr->m_idToObject.erase(javaObjectId); + + Runtime::GetRuntime(rt)->js_method_cache->cleanupObject(javaObjectId); +} + +void ObjectManager::ReleaseNativeObject(JsRuntime &rt, const JsValue &object) { + int32_t javaObjectId = -1; + + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + + if (jsInstanceInfo) { + javaObjectId = jsInstanceInfo->JavaObjectID; + } + + if (javaObjectId == -1) { + throw NativeScriptException("Trying to release a non native object!"); + } + + ReleaseObjectNow(rt, javaObjectId); +} + +void ObjectManager::OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids) { + JEnv jenv(jEnv); + jsize length = jenv.GetArrayLength(object_ids); + int *cppArray = jenv.GetIntArrayElements(object_ids, nullptr); + for (jsize i = 0; i < length; i++) { + auto rt = Runtime::GetRuntimeUnchecked(*m_rt); + if (rt && rt->is_destroying) return; + int javaObjectId = cppArray[i]; + auto itFound = this->m_idToObject.find(javaObjectId); + if (itFound != this->m_idToObject.end()) { + this->m_idToObject.erase(javaObjectId); + + if (rt && !rt->is_destroying) { + rt->js_method_cache->cleanupObject(javaObjectId); + } + + DEBUG_WRITE("JS Object released for object id: %d", javaObjectId); + } + + } +} diff --git a/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.h b/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.h new file mode 100644 index 000000000..c59720750 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/objectmanager/ObjectManager.h @@ -0,0 +1,260 @@ +#ifndef OBJECTMANAGER_H_ +#define OBJECTMANAGER_H_ + +#include "Engine.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "JniLocalRef.h" +#include "DirectBuffer.h" +#include "LRUCache.h" +#include +#include +#include +#include +#include +#include +#include "Constants.h" + +class MetadataNode; + +namespace tns { + class ObjectManager { + public: + ObjectManager(jobject javaRuntimeObject); + + void OnDisposeRuntime(); + + void Init(JsRuntime &rt); + + JniLocalRef GetJavaObjectByJsObject(const JsValue &object, int *objectId = nullptr, + bool *isSuper = nullptr); + + + JniLocalRef GetJavaObjectByJsObjectFast(const JsValue &object); + + void UpdateCache(int objectID, jobject obj); + + jclass GetJavaClass(const JsValue &value); + + void SetJavaClass(const JsValue &instance, jclass clazz); + + int GetOrCreateObjectId(jobject object); + + JsValue GetJsObjectByJavaObject(int javaObjectID); + + JsValue CreateJSWrapper(jint javaObjectID, const std::string &typeName); + + JsValue CreateJSWrapper(jint javaObjectID, const std::string &typeName, jobject instance); + + JsValue GetOrCreateProxy(jint javaObjectID, const JsValue &instance); + + JsValue GetOrCreateProxyWeak(jint javaObjectID, const JsValue &instance); + + void Link(const JsValue &object, uint32_t javaObjectID, jclass clazz, + MetadataNode *node = nullptr); + + // Returns the class metadata stored on the per-instance JSInstanceInfo + // (host proxy's, or the raw instance's native state). Used by + // MetadataNode::GetInstanceMetadata. + MetadataNode *GetInstanceNode(const JsValue &object); + + bool CloneLink(const JsValue &src, const JsValue &dest); + + bool IsRuntimeJsObject(const JsValue &object); + + static std::string GetClassName(jobject javaObject); + + static std::string GetClassName(jclass clazz); + + int GenerateNewObjectID(); + + JsValue GetEmptyObject(); + + inline static void MarkObject(JsRuntime &rt, const JsValue &object) { + if (!object.isObject()) return; + object.asObjectBorrowed(rt).setProperty(rt, PRIVATE_IS_NAPI, true); + } + + inline static void MarkSuperCall(JsRuntime &rt, const JsValue &object) { + if (!object.isObject()) return; + object.asObjectBorrowed(rt).setProperty(rt, PRIVATE_CALLSUPER, true); + } + + void OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids); + + void ReleaseNativeObject(JsRuntime &rt, const JsValue &object); + + inline static void ReleaseObjectNow(JsRuntime &rt, int javaObjectId); + + bool IsHostObject(const JsValue &object); + + // The JS<->Java identity record. In the napi tree this pointer was owned + // twice -- once by a napi_external carrying a finalizer and once by an + // ownership-free napi_wrap used for fast access. engine::Object's native + // state slot is itself a fast, non-property lookup, so one shared_ptr in + // one slot serves both roles and there is a single owner. + // + // It derives from engine::HostObject because that is what the native + // state slot stores; it overrides none of the traps and is never exposed + // to JS as an object of its own. + struct JSInstanceInfo : public engine::HostObject { + public: + JSInstanceInfo(uint32_t javaObjectID, jclass claz) + : JavaObjectID(javaObjectID), ObjectClazz(claz) { + } + + uint32_t JavaObjectID; + jclass ObjectClazz; + // Cached super-call flag (-1 = unresolved, 0 = false, 1 = true). + int8_t isSuper = -1; + // Per-instance class metadata; reachable via GetInstanceNode. + MetadataNode *node = nullptr; + }; + + private: + struct ProxyRegistry; + + // Backing host object for a wrapper. The napi tree gates this against its + // JS-Proxy alternative with USE_HOST_OBJECT; there is no conditional here + // because a host object is the only path, and the engine owning the + // shared_ptr means this destructor *is* the finalizer. + // + // engine::HostObject exposes get/set/getPropertyNames only -- there are no + // has/delete/ownKeys/indexed traps -- so `in`, `delete` and numeric index + // access all arrive through get/set and are dispatched on the property + // name here. + class HostObjectProxy : public engine::HostObject { + public: + HostObjectProxy(ObjectManager *objectManager, JSInstanceInfo *instanceInfo, + bool isPrimary, JsRuntime &rt, const JsValue &target); + + ~HostObjectProxy() override; + + // What the destructor hands to the post-GC drain: the owned target + // handle plus the C++ state that outlives the host object. + struct PendingCleanup { + JsValue *target; + JSInstanceInfo *instanceInfo; + bool isPrimary; + ObjectManager *objectManager; + }; + + JsValue get(JsRuntime &rt, const JsPropNameID &name) override; + + bool set(JsRuntime &rt, const JsPropNameID &name, const JsValue &value) override; + + std::vector getPropertyNames(JsRuntime &rt) override; + + // Java array element access. An engine that can hand over an index + // without stringifying it reaches these directly; the get/set traps + // above route to the same element accessors for an engine that + // cannot (see the note there). + JsValue getValueAtIndex(JsRuntime &rt, uint32_t index) override; + + bool setValueAtIndex(JsRuntime &rt, uint32_t index, const JsValue &value) override; + + // Cleared by ObjectManager::OnDisposeRuntime to mark this proxy + // neutralised; the destructor then does nothing. See there for why. + ObjectManager *objectManager; + // Outlives the ObjectManager; see m_proxyRegistry. + std::shared_ptr registry; + JSInstanceInfo *instanceInfo; // java object id holder + bool isPrimary; // owns instanceInfo + marks weak on GC + JsRuntime *rt; + // Heap-held so the destructor can hand the owned handle to the + // post-GC drain instead of releasing it inside the sweep. + std::unique_ptr target; + bool isArray; + std::string arraySignature; // jni array signature (arrays only) + int8_t isSuper = -1; // cached super-call flag (-1=unresolved) + int64_t arrayLength = -1; // cached fixed length (arrays only; -1=unresolved) + + private: + // The element accessors without exception translation, so the + // get/set traps can reach them from inside their own try blocks. + JsValue ArrayElementAt(JsRuntime &rt, uint32_t index); + + void SetArrayElementAt(JsRuntime &rt, uint32_t index, const JsValue &value); + }; + + struct ProxyRegistry { + std::mutex mutex; + std::set proxies; + }; + + JsValue CreateHostObjectProxy(const JsValue &instance, JSInstanceInfo *instanceInfo, + bool isPrimary); + + // Actual cleanup, deferred to the runtime's safe post-GC finalizer drain + // (Runtime::PostFinalizer) so its handle-releasing work is legal. + static void HostObjectProxyPostFinalizer(JsRuntime &rt, void *data, void *hint); + + std::shared_ptr GetJSInstanceInfoShared(const JsValue &object); + + JSInstanceInfo *GetJSInstanceInfo(const JsValue &object); + + JSInstanceInfo *GetJSInstanceInfoFromRuntimeObject(const JsValue &object); + + JsValue CreateJSWrapperHelper(jint javaObjectID, const std::string &typeName, jclass clazz); + + jweak GetJavaObjectByID(uint32_t javaObjectID); + + jobject GetJavaObjectByIDImpl(uint32_t javaObjectID); + + static jweak NewWeakGlobalRefCallback(const int &javaObjectID, void *state); + + static void DeleteWeakGlobalRefCallback(const jweak &object, void *state); + + static bool ValidateWeakGlobalRefCallback(const int &javaObjectID, const jweak &object, void *state); + + jobject m_javaRuntimeObject; + + JsRuntime *m_rt; + + // The napi tree stored a weak napi_ref for proxies and a strong one for + // instances; those map onto engine::WeakObject and an owned Value. + robin_hood::unordered_map m_idToProxy; + robin_hood::unordered_map m_idToObject; + robin_hood::unordered_set m_weakObjectIds; + robin_hood::unordered_set m_markedAsWeakIds; + + // Every live host-object proxy. The engine owns the proxies, so without + // this the only thing that ever destroys them is the engine's own + // teardown -- far too late to release the handles they hold. See + // OnDisposeRuntime. + // + // Held by shared_ptr, and every proxy holds one too, because a proxy can + // outlive this ObjectManager: a worker's runtime is deleted before its + // VM is, so the engine destroys the remaining proxies afterwards and + // ~HostObjectProxy would otherwise erase itself through a dangling + // pointer. The mutex is needed for the same reason -- those destructors + // run on the engine's own thread. + std::shared_ptr m_proxyRegistry; + + LRUCache m_cache; + + volatile int m_currentObjectId; + + DirectBuffer m_buff; + + DirectBuffer m_outBuff; + + static jclass JAVA_LANG_CLASS; + + static jmethodID GET_NAME_METHOD_ID; + + jmethodID GET_JAVAOBJECT_BY_ID_METHOD_ID; + + jmethodID GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID; + + jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; + + jmethodID MAKE_INSTANCE_WEAK_METHOD_ID; + + jmethodID MAKE_INSTANCE_STRONG_METHOD_ID; + + JsFunction m_jsObjectCtor; + }; +} + +#endif /* OBJECTMANAGER_H_ */ diff --git a/NativeScript/ffi/jni/jsi/weakref/WeakRef.cpp b/NativeScript/ffi/jni/jsi/weakref/WeakRef.cpp new file mode 100644 index 000000000..ddddb3722 --- /dev/null +++ b/NativeScript/ffi/jni/jsi/weakref/WeakRef.cpp @@ -0,0 +1,63 @@ +// +// Created by Ammar Ahmed on 03/12/2024. +// + +#include "WeakRef.h" + +using namespace tns; + +WeakRef::WeakRef(JsRuntime &rt, const JsValue &value) + : ref_(std::make_unique(rt, value)) { +} + +WeakRef::~WeakRef() = default; + +void WeakRef::Init(JsRuntime &rt) { + JsObject global = rt.global(); + + JsValue wr = global.getProperty(rt, "WeakRef"); + if (js_util::is_null_or_undefined(wr)) { + JsFunction cons = JsFunction::createFromHostConstructor( + rt, JsPropNameID::forAscii(rt, "WeakRef"), 1, WeakRef::New); + + JsValue prototype = js_util::get_prototype(rt, JsValue(rt, cons)); + if (prototype.isObject()) { + JsObject prototypeObject = prototype.asObject(rt); + js_util::set_function(rt, prototypeObject, "get", WeakRef::Deref); + js_util::set_function(rt, prototypeObject, "deref", WeakRef::Deref); + } + + global.setProperty(rt, "WeakRef", cons); + } +} + +JsValue WeakRef::New(JsRuntime &rt, const JsValue &jsThis, const JsValue *argv, size_t argc) { + // The napi version rejects a plain call by checking new.target. engine:: has + // no new.target, and a host constructor that is called without `new` is + // handed an undefined receiver, so that is the test here. + if (js_util::is_null_or_undefined(jsThis)) { + throw JsError(rt, "WeakRef must be called as a constructor"); + } + + if (argc != 1) { + throw JsError(rt, "WeakRef constructor must be called with one argument"); + } + + jsThis.asObjectBorrowed(rt).setNativeState( + rt, std::make_shared(rt, argv[0])); + + return jsThis; +} + +JsValue WeakRef::Deref(JsRuntime &rt, const JsValue &jsThis, const JsValue *argv, size_t argc) { + if (!jsThis.isObject()) { + return js_util::undefined(); + } + + auto obj = jsThis.asObjectBorrowed(rt).getNativeState(rt); + if (obj == nullptr || obj->ref_ == nullptr) { + return js_util::undefined(); + } + + return *obj->ref_; +} diff --git a/NativeScript/ffi/jni/jsi/weakref/WeakRef.h b/NativeScript/ffi/jni/jsi/weakref/WeakRef.h new file mode 100644 index 000000000..df1cfdc9e --- /dev/null +++ b/NativeScript/ffi/jni/jsi/weakref/WeakRef.h @@ -0,0 +1,27 @@ +// +// Created by Ammar Ahmed on 03/12/2024. +// + +#ifndef TEST_APP_WEAKREF_H +#define TEST_APP_WEAKREF_H + +#include "Engine.h" + +namespace tns { + class WeakRef : public engine::HostObject { + public: + static void Init(JsRuntime &rt); + static JsValue New(JsRuntime &rt, const JsValue &jsThis, const JsValue *argv, size_t argc); + + explicit WeakRef(JsRuntime &rt, const JsValue &value); + ~WeakRef() override; + + private: + std::unique_ptr ref_; + + static JsValue Deref(JsRuntime &rt, const JsValue &jsThis, const JsValue *argv, + size_t argc); + }; + +} +#endif //TEST_APP_WEAKREF_H diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp new file mode 100644 index 000000000..daa5e6b78 --- /dev/null +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp @@ -0,0 +1,1894 @@ +// +// Created by Ammar Ahmed on 20/09/2024. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "JEnv.h" +#include "CallbackHandlers.h" +#include "Util.h" +#include "JniLocalRef.h" +#include "MetadataNode.h" +#include "MethodCache.h" +#include "ArgConverter.h" +#include "JsArgConverter.h" +#include "GlobalHelpers.h" +#include "WorkerWrapper.h" +#include + +#ifdef USE_MIMALLOC + +#include "mimalloc.h" + +#endif + +using namespace std; +using namespace tns; + +namespace { +bool EnsureMetadataReturnType(MetadataEntry *entry) { + if (entry == nullptr) { + return false; + } + + auto &returnType = entry->getReturnType(); + if (!returnType.empty()) { + return true; + } + + const auto &signature = entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen == std::string::npos || closeParen + 1 >= signature.size()) { + return false; + } + + const auto candidate = signature.substr(closeParen + 1); + switch (candidate[0]) { + case 'V': + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + if (candidate.size() != 1) { + return false; + } + break; + case 'L': + if (candidate.size() < 3 || candidate.back() != ';') { + return false; + } + break; + case '[': + if (candidate.size() < 2) { + return false; + } + break; + default: + return false; + } + + returnType = candidate; + return true; +} +} + +void CallbackHandlers::Init(napi_env env) { + JEnv jEnv; + + JAVA_LANG_STRING = jEnv.FindClass("java/lang/String"); + assert(JAVA_LANG_STRING != nullptr); + + RUNTIME_CLASS = jEnv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + RESOLVE_CLASS_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveClass", + "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Z)Ljava/lang/Class;"); + assert(RESOLVE_CLASS_METHOD_ID != nullptr); + + CURRENT_OBJECTID_FIELD_ID = jEnv.GetFieldID(RUNTIME_CLASS, "currentObjectId", "I"); + assert(CURRENT_OBJECTID_FIELD_ID != nullptr); + + MAKE_INSTANCE_STRONG_ID = jEnv.GetMethodID(RUNTIME_CLASS, "makeInstanceStrong", + "(Ljava/lang/Object;I)V"); + assert(MAKE_INSTANCE_STRONG_ID != nullptr); + + GET_TYPE_METADATA = jEnv.GetStaticMethodID(RUNTIME_CLASS, "getTypeMetadata", + "(Ljava/lang/String;I)[Ljava/lang/String;"); + assert(GET_TYPE_METADATA != nullptr); + + ENABLE_VERBOSE_LOGGING_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "enableVerboseLogging", + "()V"); + assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); + + DISABLE_VERBOSE_LOGGING_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "disableVerboseLogging", + "()V"); + assert(ENABLE_VERBOSE_LOGGING_METHOD_ID != nullptr); + + MetadataNode::Init(env); + + MethodCache::Init(); +} + +napi_value CallbackHandlers::CallJavaMethod(napi_env env, napi_value caller, const string &className, + const string &methodName, MetadataEntry *entry, + bool isFromInterface, bool isStatic, napi_callback_info info, size_t argc, napi_value* argv, + ObjectManager *objectManager, + bool metadataSignatureIsUnambiguous) { + + JEnv jEnv; + jclass clazz; + jmethodID mid; + string *sig = nullptr; + string *returnType = nullptr; + auto retType = MethodReturnType::Unknown; + MethodCache::CacheMethodInfo mi; + bool isSuper = false; + napi_status status; + + if (metadataSignatureIsUnambiguous && entry != nullptr && + !EnsureMetadataReturnType(entry)) { + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + + if (metadataSignatureIsUnambiguous && entry != nullptr && entry->memberId == nullptr) { + JEnv metadataEnv; + auto metadataClass = metadataEnv.FindClass(className); + if (metadataClass != nullptr) { + jmethodID metadataMethod; + jclass resolvedMetadataClass = metadataClass; + if (isStatic && isFromInterface) { + auto methodAndClassPair = metadataEnv.GetInterfaceStaticMethodIDAndJClass( + className, methodName, entry->getSig()); + metadataMethod = methodAndClassPair.first; + if (methodAndClassPair.second != nullptr) { + resolvedMetadataClass = methodAndClassPair.second; + } + } else { + metadataMethod = isStatic + ? metadataEnv.GetStaticMethodID(metadataClass, methodName, + entry->getSig()) + : metadataEnv.GetMethodID(metadataClass, methodName, + entry->getSig()); + } + if (metadataMethod != nullptr) { + entry->memberId = reinterpret_cast(metadataMethod); + entry->clazz = resolvedMetadataClass; + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } else { + metadataEnv.ExceptionClear(); + entry = nullptr; + metadataSignatureIsUnambiguous = false; + } + } + + if ((entry != nullptr) && (entry->getIsResolved() || metadataSignatureIsUnambiguous)) { + auto &entrySignature = entry->getSig(); + isStatic = entry->isStatic; + + + if (entry->memberId == nullptr) { + clazz = jEnv.FindClass(className); + + if (clazz == nullptr) { + MetadataNode *callerNode = MetadataNode::GetNodeFromHandle(env, caller); + const string callerClassName = callerNode->GetName(); + + DEBUG_WRITE("Cannot resolve class: %s while calling method: %s callerClassName: %s", + className.c_str(), methodName.c_str(), callerClassName.c_str()); + clazz = jEnv.FindClass(callerClassName); + if (clazz == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve caller's class name: %s", callerClassName.c_str()); + return nullptr; + } + + if (isStatic) { + if (isFromInterface) { + auto methodAndClassPair = jEnv.GetInterfaceStaticMethodIDAndJClass( + className, + methodName, + entrySignature); + entry->memberId = methodAndClassPair.first; + clazz = methodAndClassPair.second; + } else { + entry->memberId = jEnv.GetStaticMethodID(clazz, methodName, entrySignature); + } + } else { + entry->memberId = jEnv.GetMethodID(clazz, methodName, entrySignature); + } + + if (entry->memberId == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve a method %s on caller class: %s", + methodName.c_str(), callerClassName.c_str()); + return nullptr; + } + } else { + if (isStatic) { + if (isFromInterface) { + auto methodAndClassPair = jEnv.GetInterfaceStaticMethodIDAndJClass( + className, + methodName, entrySignature); + entry->memberId = methodAndClassPair.first; + clazz = methodAndClassPair.second; + } else { + entry->memberId = jEnv.GetStaticMethodID(clazz, methodName, entrySignature); + } + } else { + entry->memberId = jEnv.GetMethodID(clazz, methodName, entrySignature); + } + + if (entry->memberId == nullptr) { + //todo: plamen5kov: throw exception here + DEBUG_WRITE("Cannot resolve a method %s on class: %s", methodName.c_str(), + className.c_str()); + return nullptr; + } + } + entry->clazz = clazz; + } + + mid = reinterpret_cast(entry->memberId); + clazz = entry->clazz; + sig = &entry->getSig(); + returnType = &entry->getReturnType(); + retType = entry->getRetType(); + } else { + DEBUG_WRITE("Resolving method: %s on className %s", methodName.c_str(), className.c_str()); + + clazz = jEnv.FindClass(className); + if (clazz != nullptr) { + mi = MethodCache::ResolveMethodSignature(env, className, methodName, argc, argv, isStatic); + if (mi.mid == nullptr) { + DEBUG_WRITE("Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d", + className.c_str(), methodName.c_str(), isStatic, isSuper); + return nullptr; + } + } else { + MetadataNode *callerNode = MetadataNode::GetNodeFromHandle(env, caller); + const string callerClassName = callerNode->GetName(); + DEBUG_WRITE("Resolving method on caller class: %s.%s on className %s", + callerClassName.c_str(), methodName.c_str(), className.c_str()); + mi = MethodCache::ResolveMethodSignature(env, callerClassName, methodName, argc, argv, + isStatic); + if (mi.mid == nullptr) { + DEBUG_WRITE( + "Cannot resolve class=%s, method=%s, isStatic=%d, isSuper=%d, callerClass=%s", + className.c_str(), methodName.c_str(), isStatic, isSuper, + callerClassName.c_str()); + return nullptr; + } + } + + clazz = mi.clazz; + mid = mi.mid; + sig = &mi.signature; + returnType = &mi.returnType; + retType = mi.retType; + } + + + if (!isStatic) { + DEBUG_WRITE("CallJavaMethod on instance %s", methodName.c_str()); + } else { + DEBUG_WRITE("CallJavaMethod on class %s", methodName.c_str()); + } + + // The caller (MethodCallback) passes a cached ObjectManager*; only fall back + // to the locked env->runtime map lookup when invoked without one. Resolved + // before the converter so object-arg conversion can reuse it too. + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + JsArgConverter argConverter = (entry != nullptr && entry->isExtensionFunction) + ? JsArgConverter(env, caller, argv, argc, *sig, entry, (JNIEnv *) jEnv, objectManager) + : JsArgConverter(env, argv, argc, false, *sig, entry, (JNIEnv *) jEnv, objectManager); + + + + if (!argConverter.IsValid()) { + JsArgConverter::Error err = argConverter.GetError(); + throw NativeScriptException(err.msg); + } + + JniLocalRef callerJavaObject; + + jvalue *javaArgs = argConverter.ToArgs(); + + + if (!isStatic) { + int objectId = -1; + + callerJavaObject = objectManager->GetJavaObjectByJsObject(caller, &objectId, &isSuper); + + if (callerJavaObject.IsNull()) { + stringstream ss; + + napi_value new_target; + NAPI_GUARD(napi_get_new_target(env, info, &new_target)) {} + if (!napi_util::is_null_or_undefined(env, new_target)) { + ss << "No java object found on which to call \"" << methodName + << "\" method. It is possible your Javascript object is not linked with the corresponding Java class. Try passing context(this) to the constructor function."; + } else { + ss << "Failed calling " << methodName << " on a " << className + << " instance. The JavaScript instance no longer has available Java instance counterpart."; + } + throw NativeScriptException(ss.str()); + } + } + + + napi_value returnValue; + + switch (retType) { + case MethodReturnType::Void: { + if (isStatic) { + jEnv.CallStaticVoidMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + jEnv.CallNonvirtualVoidMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + jEnv.CallVoidMethodA(callerJavaObject, mid, javaArgs); + } + returnValue = nullptr; + break; + } + case MethodReturnType::Boolean: { + jboolean result; + if (isStatic) { + result = jEnv.CallStaticBooleanMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualBooleanMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallBooleanMethodA(callerJavaObject, mid, javaArgs); + } + + NAPI_GUARD(napi_get_boolean(env, result != 0, &returnValue)) { return nullptr; } + break; + } + case MethodReturnType::Byte: { + jbyte result; + if (isStatic) { + result = jEnv.CallStaticByteMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualByteMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallByteMethodA(callerJavaObject, mid, javaArgs); + } + + NAPI_GUARD(napi_create_int32(env, result, &returnValue)) { return nullptr; } + break; + } + case MethodReturnType::Char: { + jchar result; + if (isStatic) { + result = jEnv.CallStaticCharMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualCharMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallCharMethodA(callerJavaObject, mid, javaArgs); + } + + JniLocalRef str(jEnv.NewString(&result, 1)); + jboolean bol = true; + const char *resP = jEnv.GetStringUTFChars(str, &bol); + returnValue = ArgConverter::convertToJsString(env, resP, 1); + jEnv.ReleaseStringUTFChars(str, resP); + break; + } + case MethodReturnType::Short: { + jshort result; + if (isStatic) { + result = jEnv.CallStaticShortMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualShortMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallShortMethodA(callerJavaObject, mid, javaArgs); + } + + NAPI_GUARD(napi_create_int32(env, result, &returnValue)) { return nullptr; } + + break; + } + case MethodReturnType::Int: { + jint result; + if (isStatic) { + result = jEnv.CallStaticIntMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualIntMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallIntMethodA(callerJavaObject, mid, javaArgs); + } + NAPI_GUARD(napi_create_int32(env, result, &returnValue)) { return nullptr; } + break; + + } + case MethodReturnType::Long: { + jlong result; + if (isStatic) { + result = jEnv.CallStaticLongMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualLongMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallLongMethodA(callerJavaObject, mid, javaArgs); + } + returnValue = ArgConverter::ConvertFromJavaLong(env, result); + break; + } + case MethodReturnType::Float: { + jfloat result; + if (isStatic) { + result = jEnv.CallStaticFloatMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualFloatMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallFloatMethodA(callerJavaObject, mid, javaArgs); + } + NAPI_GUARD(napi_create_double(env, (double) result, &returnValue)) { return nullptr; } + break; + } + case MethodReturnType::Double: { + jdouble result; + if (isStatic) { + result = jEnv.CallStaticDoubleMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualDoubleMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallDoubleMethodA(callerJavaObject, mid, javaArgs); + } + NAPI_GUARD(napi_create_double(env, (double) result, &returnValue)) { return nullptr; } + break; + } + case MethodReturnType::String: { + jobject result = nullptr; + bool exceptionOccurred; + + if (isStatic) { + result = jEnv.CallStaticObjectMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallObjectMethodA(callerJavaObject, mid, javaArgs); + } + + if (result != nullptr) { + returnValue = ArgConverter::jstringToJsString(env, static_cast(result)); + jEnv.DeleteLocalRef(result); + } else { + NAPI_GUARD(napi_get_null(env, &returnValue)) { return nullptr; } + } + + break; + } + case MethodReturnType::Object: { + jobject result = nullptr; + bool exceptionOccurred; + + if (isStatic) { + result = jEnv.CallStaticObjectMethodA(clazz, mid, javaArgs); + } else if (isSuper) { + result = jEnv.CallNonvirtualObjectMethodA(callerJavaObject, clazz, mid, javaArgs); + } else { + result = jEnv.CallObjectMethodA(callerJavaObject, mid, javaArgs); + } + + + if (result != nullptr) { + // A declared array return can never be a java.lang.String, so skip + // the per-return IsInstanceOf JNI probe on the array-return hot path. + // Non-array Object/CharSequence returns can be polymorphic Strings, + // so those still need the check. + bool isArrayReturn = returnType != nullptr && !returnType->empty() && + (*returnType)[0] == '['; + auto isString = !isArrayReturn && jEnv.IsInstanceOf(result, JAVA_LANG_STRING); + + if (isString) { + returnValue = ArgConverter::jstringToJsString(env, (jstring) result); + } else { + jint javaObjectID = objectManager->GetOrCreateObjectId(result); + returnValue = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, returnValue)) { + returnValue = objectManager->CreateJSWrapper(javaObjectID, *returnType, + result); + } + } + + jEnv.DeleteLocalRef(result); + } else { + NAPI_GUARD(napi_get_null(env, &returnValue)) { return nullptr; } + } + + break; + } + default: { + returnValue = napi_util::undefined(env); + assert(false); + break; + } + } + + + return returnValue; +} + + +bool CallbackHandlers::RegisterInstance(napi_env env, napi_value jsObject, + const std::string &fullClassName, + const ArgsWrapper &argWrapper, + napi_value implementationObject, + bool isInterface, + napi_value *jsThisProxy, + const std::string &baseClassName, + MetadataNode *node) { + bool success; + + DEBUG_WRITE("RegisterInstance called for '%s'", fullClassName.c_str()); + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + jclass generatedJavaClass = ResolveClass(env, baseClassName, fullClassName, + implementationObject, + isInterface); + + int javaObjectID = objectManager->GenerateNewObjectID(); + + objectManager->Link(jsObject, javaObjectID, nullptr, node); + + // resolve constructor + auto mi = MethodCache::ResolveConstructorSignature(env, argWrapper, fullClassName, + generatedJavaClass, isInterface); + + // while the "instance" is being created, if an exception is thrown during the construction + // this scope will guarantee the "javaObjectID" will be set to -1 and won't have an invalid value + jobject instance; + { + JavaObjectIdScope objIdScope(jEnv, CURRENT_OBJECTID_FIELD_ID, runtime->GetJavaRuntime(), + javaObjectID); + + if (argWrapper.type == ArgType::Interface) { + instance = jEnv.NewObject(generatedJavaClass, mi.mid); + } else { + // resolve arguments before passing them on to the constructor + // JSToJavaConverter argConverter(isolate, argWrapper.args, mi.signature); + + + + JsArgConverter argConverter(env, argWrapper.argv, argWrapper.argc, mi.signature); + auto ctorArgs = argConverter.ToArgs(); + + instance = jEnv.NewObjectA(generatedJavaClass, mi.mid, ctorArgs); + } + } + + // Set runtimeId field on interface and extended classes + if (runtime->GetId() != 0 && (isInterface || implementationObject != nullptr)) { + jfieldID runtimeIdField; + auto itFound = jclass_to_runtimeId_cache.find(generatedJavaClass); + if (itFound != jclass_to_runtimeId_cache.end()) { + runtimeIdField = itFound->second; + } else { + runtimeIdField = jEnv.GetFieldID(generatedJavaClass, "runtimeId", "I"); + jclass_to_runtimeId_cache.emplace(generatedJavaClass, runtimeIdField); + } + if (runtimeIdField != nullptr) { + jint runtimeId = runtime->GetId(); // Assuming GetId() returns the current runtime's id + DEBUG_WRITE("Setting runtimeId %d on instance of %s", runtimeId, fullClassName.c_str()); + jEnv.SetIntField(instance, runtimeIdField, runtimeId); + } + } + + jEnv.CallVoidMethod(runtime->GetJavaRuntime(), MAKE_INSTANCE_STRONG_ID, instance, javaObjectID); + + // Reuse the runtime we already resolved instead of re-querying via env. + runtime->AdjustAmountOfExternalAllocatedMemory(); + runtime->TryCallGC(); + + JniLocalRef localInstance(instance); + success = !localInstance.IsNull(); + + if (success) { + // ResolveClass already cached this exact (global) jclass under + // fullClassName, so reuse it instead of a redundant FindClass lookup. + objectManager->SetJavaClass(jsObject, generatedJavaClass); + *jsThisProxy = objectManager->GetOrCreateProxy(javaObjectID, jsObject); + } else { + DEBUG_WRITE_FORCE("RegisterInstance failed with null new instance class: %s", + fullClassName.c_str()); + } + + + return success; +} + +jclass CallbackHandlers::ResolveClass(napi_env env, const string &baseClassName, + const string &fullClassName, + napi_value implementationObject, bool isInterface) { + JEnv jEnv; + jclass globalRefToGeneratedClass = jEnv.CheckForClassInCache(fullClassName); + + if (globalRefToGeneratedClass == nullptr) { + + // get needed arguments in order to load binding + JniLocalRef javaBaseClassName(jEnv.NewStringUTF(baseClassName.c_str())); + JniLocalRef javaFullClassName(jEnv.NewStringUTF(fullClassName.c_str())); + + jobjectArray methodOverrides = GetMethodOverrides(env, jEnv, implementationObject); + + jobjectArray implementedInterfaces = GetImplementedInterfaces(env, jEnv, + implementationObject); + + auto runtime = Runtime::GetRuntime(env); + + // create or load generated binding (java class) + jclass generatedClass = (jclass) jEnv.CallObjectMethod(runtime->GetJavaRuntime(), + RESOLVE_CLASS_METHOD_ID, + (jstring) javaBaseClassName, + (jstring) javaFullClassName, + methodOverrides, + implementedInterfaces, + isInterface); + + globalRefToGeneratedClass = jEnv.InsertClassIntoCache(fullClassName, generatedClass); + + jEnv.DeleteGlobalRef(methodOverrides); + jEnv.DeleteGlobalRef(implementedInterfaces); + } + + return globalRefToGeneratedClass; +} + +// Called by ExtendMethodCallback when extending a class +string CallbackHandlers::ResolveClassName(napi_env env, jclass &clazz) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + auto className = objectManager->GetClassName(clazz); + return className; +} + +napi_value CallbackHandlers::GetArrayElement(napi_env env, napi_value array, + uint32_t index, const string &arraySignature, + ObjectManager *objectManager, jobject arrayObject) { + return arrayElementAccessor.GetArrayElement(env, array, index, arraySignature, + objectManager, arrayObject); +} + +void CallbackHandlers::SetArrayElement(napi_env env, napi_value array, + uint32_t index, + const string &arraySignature, napi_value value, + ObjectManager *objectManager, jobject arrayObject) { + + arrayElementAccessor.SetArrayElement(env, array, index, arraySignature, value, + objectManager, arrayObject); +} + +napi_value CallbackHandlers::GetJavaField(napi_env env, napi_value caller, + FieldCallbackData *fieldData, + ObjectManager *objectManager, + JniLocalRef targetJavaObject) { + return fieldAccessor.GetJavaField(env, caller, fieldData, objectManager, + std::move(targetJavaObject)); +} + +void CallbackHandlers::SetJavaField(napi_env env, napi_value target, + napi_value value, FieldCallbackData *fieldData, + ObjectManager *objectManager, + JniLocalRef targetJavaObject) { + fieldAccessor.SetJavaField(env, target, value, fieldData, objectManager, + std::move(targetJavaObject)); +} + +void CallbackHandlers::AdjustAmountOfExternalAllocatedMemory(napi_env env) { + auto runtime = Runtime::GetRuntime(env); + runtime->AdjustAmountOfExternalAllocatedMemory(); + runtime->TryCallGC(); +} + +napi_value CallbackHandlers::CreateJSWrapper(napi_env env, jint javaObjectID, + const string &typeName) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + return objectManager->CreateJSWrapper(javaObjectID, typeName); +} + +// The two Collect* helpers below define which names take part in a binding. +// Content-keyed class names hash exactly these sets, so the static binding +// generator has to reproduce them from the bundle byte for byte -- a name that +// is collected here but not there (or the reverse) yields two different class +// names for one extend() site, which fails at runtime as LookedUpClassNotFound. +// Keeping the JNI arrays and the hash on one implementation is what makes that +// contract checkable in a single place. +vector +CallbackHandlers::CollectImplementedInterfaceNames(napi_env env, + napi_value implementationObject) { + vector interfaceNames; + + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { + return interfaceNames; + } + + napi_status status; + napi_value prop; + NAPI_GUARD(napi_get_named_property(env, implementationObject, "interfaces", &prop)) {} + bool isArray; + NAPI_GUARD(napi_is_array(env, prop, &isArray)) {} + + if (isArray) { + uint32_t length; + NAPI_GUARD(napi_get_array_length(env, prop, &length)) {} + + for (uint32_t j = 0; j < length; j++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, prop, j, &element)) {} + + if (napi_util::is_object(env, element)) { + auto node = MetadataNode::GetTypeMetadataName(env, element); + + node = Util::ReplaceAll(node, std::string("/"), std::string(".")); + + interfaceNames.push_back(node); + } + } + } + + return interfaceNames; +} + +vector +CallbackHandlers::CollectMethodOverrideNames(napi_env env, napi_value implementationObject, + bool functionsOnly) { + vector methodNames; + + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { + return methodNames; + } + + napi_status status; + napi_value propNames; + + NAPI_GUARD(napi_get_all_property_names(env, implementationObject, napi_key_own_only, + napi_key_all_properties, napi_key_numbers_to_strings, &propNames)) {} + + uint32_t length; + NAPI_GUARD(napi_get_array_length(env, propNames, &length)) {} + + for (uint32_t i = 0; i < length; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, propNames, i, &element)) {} + auto name = ArgConverter::ConvertToString(env, element); + + if (name == "super") { + continue; + } + + // Runtime bookkeeping stamped onto the implementation object, not + // something the user wrote. napi_key_all_properties returns + // non-enumerable properties too, so these have to be filtered by name. + if (name == EXTEND_CTOR_CACHE_KEY || name == CLASS_IMPLEMENTATION_OBJECT) { + continue; + } + + // Written onto the prototype by ts_helpers' __extends before it defers + // to extend(), so every TypeScript-transpiled subclass carries them. + // They are invisible to the static binding generator, which reads the + // class body from source -- hashing them put the two sides permanently + // out of step and sent every such class down the on-device dex path. + if (name == PROP_KEY_TS_PARENT || name == PROP_KEY_TS_CHILD) { + continue; + } + + if (!functionsOnly) { + // Hashing, not binding. The generator reads the implementation object + // out of the bundle, where it cannot tell a function from any other + // value -- `{ foo: someIdentifier }` is a method it must assume, and + // a statically-typed guess either way would disagree with what this + // enumeration sees. Taking every name instead makes the two sides + // agree by construction on everything the generator can parse. + // + // The three exclusions below are the names the generator provably + // never puts in the method field: `super` and `constructor` are + // artifacts of how the implementation object is built (ts_helpers + // extends with the class prototype, which always carries a + // constructor), and an array-valued `interfaces` is routed to its own + // field instead. + if (name == "constructor") { + continue; + } + + string lowered = name; + std::transform(lowered.begin(), lowered.end(), lowered.begin(), + [](unsigned char c) { return std::tolower(c); }); + + napi_value value; + NAPI_GUARD(napi_get_property(env, implementationObject, element, &value)) {} + bool valueIsArray = false; + NAPI_GUARD(napi_is_array(env, value, &valueIsArray)) {} + if (lowered == "interfaces" && valueIsArray) { + continue; + } + + methodNames.push_back(name); + continue; + } + + napi_value method; + NAPI_GUARD(napi_get_property(env, implementationObject, element, &method)) {} + + if (napi_util::is_of_type(env, method, napi_function)) { + methodNames.push_back(name); + } + } + + return methodNames; +} + +jobjectArray +CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, + napi_value implementationObject) { + auto names = CollectImplementedInterfaceNames(env, implementationObject); + + vector interfacesToImplement; + interfacesToImplement.reserve(names.size()); + for (const auto &name: names) { + interfacesToImplement.push_back(jEnv.NewStringUTF(name.c_str())); + } + + int interfacesCount = interfacesToImplement.size(); + + jobjectArray implementedInterfaces = CallbackHandlers::GetJavaStringArray(jEnv, + interfacesCount); + for (int i = 0; i < interfacesCount; i++) { + jEnv.SetObjectArrayElement(implementedInterfaces, i, interfacesToImplement[i]); + } + + for (int i = 0; i < interfacesCount; i++) { + jEnv.DeleteLocalRef(interfacesToImplement[i]); + } + + return implementedInterfaces; +} + +jobjectArray +CallbackHandlers::GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implementationObject) { + auto names = CollectMethodOverrideNames(env, implementationObject, /* functionsOnly */ true); + + vector methodNames; + methodNames.reserve(names.size()); + for (const auto &name: names) { + methodNames.push_back(jEnv.NewStringUTF(name.c_str())); + } + + int methodCount = methodNames.size(); + + jobjectArray methodOverrides = CallbackHandlers::GetJavaStringArray(jEnv, methodCount); + for (int i = 0; i < methodCount; i++) { + jEnv.SetObjectArrayElement(methodOverrides, i, methodNames[i]); + } + + for (int i = 0; i < methodCount; i++) { + jEnv.DeleteLocalRef(methodNames[i]); + } + + return methodOverrides; +} + +napi_value CallbackHandlers::RunOnMainThreadCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { return nullptr; } + + assert(argc == 1); + assert(napi_util::is_of_type(env, args[0], napi_function)); + + uint64_t key = ++count_; + bool inserted; + + std::tie(std::ignore, inserted) = cache_.try_emplace(key, env, args[0]); + assert(inserted && "Main thread callback ID should not be duplicated"); + + auto value = Callback(key); + auto size = sizeof(Callback); + auto wrote = write(Runtime::GetWriter(), &value, size); + + + + return nullptr; +} + +int CallbackHandlers::RunOnMainThreadFdCallback(int fd, int events, void *data) { + struct Callback value; + auto size = sizeof(Callback); + ssize_t nr = read(fd, &value, sizeof(value)); + + auto key = value.id_; + + auto it = cache_.find(key); + if (it == cache_.end()) { + return 1; + } + + napi_env env = it->second.env_; + napi_ref callback_ref = it->second.callback_; + + NapiScope scope(env); + + napi_value cb = napi_util::get_ref_value(env, callback_ref); + + napi_status status; + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + + cache_.erase(it); + + napi_value result; + NAPI_GUARD(napi_call_function(env, global, cb, 0, nullptr, &result)) {} + + if (status != napi_ok) { + napi_throw_error(env, nullptr, "Error calling JavaScript callback"); + } + + + return 1; +} + +napi_value CallbackHandlers::LogMethodCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { return nullptr; } + + try { + if (argc > 0) { + napi_valuetype valuetype; + NAPI_GUARD(napi_typeof(env, args[0], &valuetype)) { return nullptr; } + if (valuetype == napi_string) { + size_t str_size; + NAPI_GUARD(napi_get_value_string_utf8(env, args[0], nullptr, 0, &str_size)) { return nullptr; } + std::string message(str_size + 1, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, args[0], &message[0], str_size + 1, &str_size)) { return nullptr; } + DEBUG_WRITE("%s", message.c_str()); + } + } + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value CallbackHandlers::DrainMicrotaskCallback(napi_env env, napi_callback_info info) { + js_execute_pending_jobs(env); + return nullptr; +} + +napi_value CallbackHandlers::TimeCallback(napi_env env, napi_callback_info info) { + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double duration = nano.time_since_epoch().count(); + napi_value result; + napi_status status; + NAPI_GUARD(napi_create_double(env, duration, &result)) { return nullptr; } + return result; +} + +napi_value +CallbackHandlers::ReleaseNativeCounterpartCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + if (argc != 1) { + napi_throw_error(env, "0", "Unexpected arguments count!"); + return napi_util::undefined(env); + } + + if (!napi_util::is_of_type(env, argv[0], napi_object)) { + napi_throw_error(env, "0", "Argument is not an object!"); + return napi_util::undefined(env); + } + + + Runtime::GetRuntime(env)->GetObjectManager()->ReleaseNativeObject(env, argv[0]); + return napi_util::undefined(env); +} + +void CallbackHandlers::validateProvidedArgumentsLength(napi_env env, napi_callback_info info, + int expectedSize) { + size_t argc = 0; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr)) {} + if ((int) argc != expectedSize) { + throw NativeScriptException("Unexpected arguments count!"); + } +} + +napi_value +CallbackHandlers::DumpReferenceTablesMethodCallback(napi_env env, napi_callback_info info) { + DumpReferenceTablesMethod(); + return nullptr; +} + +void CallbackHandlers::DumpReferenceTablesMethod() { + try { + JEnv jEnv; + jclass vmDbgClass = jEnv.FindClass("dalvik/system/VMDebug"); + if (vmDbgClass != nullptr) { + jmethodID mid = jEnv.GetStaticMethodID(vmDbgClass, "dumpReferenceTables", "()V"); + if (mid != 0) { + jEnv.CallStaticVoidMethod(vmDbgClass, mid); + } + } + } + catch (NativeScriptException &e) { + // e.ReThrowToNapi(env); + } + 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(); + } +} + +napi_value +CallbackHandlers::EnableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info) { + try { + tns::LogEnabled = true; + JEnv jEnv; + jEnv.CallVoidMethod(Runtime::GetRuntime(env)->GetJavaRuntime(), + ENABLE_VERBOSE_LOGGING_METHOD_ID); + } + catch (NativeScriptException &e) { + // e.ReThrowToNapi(env); + } + 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(); + } + return nullptr; +} + +napi_value +CallbackHandlers::DisableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info) { + try { + tns::LogEnabled = false; + JEnv jEnv; + jEnv.CallVoidMethod(Runtime::GetRuntime(env)->GetJavaRuntime(), + DISABLE_VERBOSE_LOGGING_METHOD_ID); + } + catch (NativeScriptException &e) { + // e.ReThrowToNapi(env); + } + 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(); + } + return nullptr; +} + +napi_value CallbackHandlers::ExitMethodCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + auto msg = ArgConverter::ConvertToString(env, argv[0]); + DEBUG_WRITE_FATAL("FORCE EXIT: %s", msg.c_str()); + exit(-1); + return nullptr; +} + +void CallbackHandlers::CreateGlobalCastFunctions(napi_env env) { + napi_value global; + napi_status status; + NAPI_GUARD(napi_get_global(env, &global)) { return; } + castFunctions.CreateGlobalCastFunctions(env, global); +} + +vector CallbackHandlers::GetTypeMetadata(const string &name, int index) { + JEnv env; + + string canonicalName = Util::ConvertFromJniToCanonicalName(name); + + JniLocalRef className(env.NewStringUTF(canonicalName.c_str())); + jint idx = index; + + JniLocalRef pubApi( + env.CallStaticObjectMethod(RUNTIME_CLASS, GET_TYPE_METADATA, (jstring) className, idx)); + + jsize length = env.GetArrayLength(pubApi); + + assert(length > 0); + + vector result; + + for (jsize i = 0; i < length; i++) { + JniLocalRef s(env.GetObjectArrayElement(pubApi, i)); + const char *pc = env.GetStringUTFChars(s, nullptr); + result.push_back(string(pc)); + env.ReleaseStringUTFChars(s, pc); + } + + return result; +} + +napi_value CallbackHandlers::CallJSMethod(napi_env env, JNIEnv *_jEnv, + napi_value jsObject, jclass claz,const string &methodName,int javaObjectId, + jobjectArray args) { + JEnv jEnv(_jEnv); + napi_status status; + napi_value result; + napi_value method; + +#ifndef __HERMES__ + auto runtime = Runtime::GetRuntime(env); + method = runtime->js_method_cache->getCachedMethod(javaObjectId, methodName); + if (!method) { +#endif + NAPI_GUARD(napi_get_named_property(env, jsObject, methodName.c_str(), &method)) {} +#ifndef __HERMES__ + if (napi_util::is_of_type(env, method, napi_function)) { + runtime->js_method_cache->cacheMethod(javaObjectId, methodName, method); + } + } +#endif + + if (method == nullptr || !napi_util::is_of_type(env, method, napi_function)) { + stringstream ss; + ss << "Cannot find method '" << methodName << "' implementation"; + throw NativeScriptException(ss.str()); + } else { + DEBUG_WRITE("Calling JS Method %s", methodName.c_str()); + + bool exceptionPending; + NAPI_GUARD(napi_is_exception_pending(env, &exceptionPending)) {} + + int argc = jEnv.GetArrayLength(args) / 3; + if (argc > 0) { + napi_value* jsArgs = nullptr; + napi_value stack_args[8]; + if (argc <= 8) { + jsArgs = stack_args; + } else { +#ifdef USE_MIMALLOC + jsArgs = (napi_value *) mi_malloc(sizeof(napi_value) * argc); +#else + jsArgs = (napi_value *) malloc(sizeof(napi_value) * argc); +#endif + } + ArgConverter::ConvertJavaArgsToJsArgs(env, args, argc, jsArgs); + NAPI_GUARD(napi_call_function(env, jsObject, method, argc, jsArgs, &result)) {} + + if (argc > 8) { +#ifdef USE_MIMALLOC + mi_free(jsArgs); +#else + free(jsArgs); +#endif + } + } else { + NAPI_GUARD(napi_call_function(env, jsObject, method, 0, nullptr, &result)) {} + } + + if (!exceptionPending) { + NAPI_GUARD(napi_is_exception_pending(env, &exceptionPending)) {} + if (exceptionPending) { + napi_value error; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &error)) {} + throw NativeScriptException(env, error, "Error calling js method: " + methodName); + } + } + } + + + return result; +} + +napi_value CallbackHandlers::FindClass(napi_env env, const char *name) { + napi_value clazz = nullptr; + JEnv jEnv; + jclass javaClass = jEnv.FindClass(name); + if (jEnv.ExceptionCheck() == JNI_FALSE) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + jint javaObjectID = objectManager->GetOrCreateObjectId(javaClass); + clazz = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (clazz == nullptr) { + clazz = objectManager->CreateJSWrapper(javaObjectID, "Ljava/lang/Class;", javaClass); + } + } + return clazz; +} + +int CallbackHandlers::GetArrayLength(napi_env env, napi_value arr) { + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + JEnv jEnv; + + auto javaArr = objectManager->GetJavaObjectByJsObjectFast(arr); + + auto length = jEnv.GetArrayLength(javaArr); + + return length; +} + +jobjectArray CallbackHandlers::GetJavaStringArray(JEnv &jEnv, int length) { + if (length > CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH) { + stringstream ss; + ss << "You are trying to override more methods than the limit of " + << CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH; + throw NativeScriptException(ss.str()); + } + + JniLocalRef tmpArr(jEnv.NewObjectArray(length, JAVA_LANG_STRING, nullptr)); + return (jobjectArray) jEnv.NewGlobalRef(tmpArr); +} + +CallbackHandlers::func_AChoreographer_getInstance AChoreographer_getInstance_; + +CallbackHandlers::func_AChoreographer_postFrameCallback AChoreographer_postFrameCallback_; +CallbackHandlers::func_AChoreographer_postFrameCallbackDelayed AChoreographer_postFrameCallbackDelayed_; + +CallbackHandlers::func_AChoreographer_postFrameCallback64 AChoreographer_postFrameCallback64_; +CallbackHandlers::func_AChoreographer_postFrameCallbackDelayed64 AChoreographer_postFrameCallbackDelayed64_; + +void CallbackHandlers::PostCallback(napi_env env, napi_callback_info info, + CallbackHandlers::FrameCallbackCacheEntry *entry) { + size_t argc = 2; + napi_value args[2]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { return; } + + ALooper_prepare(0); + auto instance = AChoreographer_getInstance_(); + napi_value delay = args[1]; + napi_valuetype delayType; + NAPI_GUARD(napi_typeof(env, delay, &delayType)) { return; } + + if (android_get_device_api_level() >= 29) { + if (delayType == napi_number) { + uint32_t delayValue; + NAPI_GUARD(napi_get_value_uint32(env, delay, &delayValue)) { return; } + AChoreographer_postFrameCallbackDelayed64_(instance, entry->frameCallback64_, entry, + delayValue); + } else { + AChoreographer_postFrameCallback64_(instance, entry->frameCallback64_, entry); + } + } else { + if (delayType == napi_number) { + int64_t delayValue; + NAPI_GUARD(napi_get_value_int64(env, delay, &delayValue)) { return; } + AChoreographer_postFrameCallbackDelayed_(instance, entry->frameCallback_, entry, + static_cast(delayValue)); + } else { + AChoreographer_postFrameCallback_(instance, entry->frameCallback_, entry); + } + } +} + +napi_value CallbackHandlers::PostFrameCallback(napi_env env, napi_callback_info info) { + if (android_get_device_api_level() >= 24) { + InitChoreographer(); + + napi_status status; + size_t argc = 2; + napi_value args[2]; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { return nullptr; } + + if (argc < 1) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_valuetype argType; + NAPI_GUARD(napi_typeof(env, args[0], &argType)) { return nullptr; } + if (argType != napi_function) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_value func = args[0]; + + napi_value idKey; + NAPI_GUARD(napi_create_string_utf8(env, "_postFrameCallbackId", NAPI_AUTO_LENGTH, &idKey)) { return nullptr; } + + napi_value pId; + NAPI_GUARD(napi_get_property(env, func, idKey, &pId)) { return nullptr; } + + napi_valuetype pIdType; + NAPI_GUARD(napi_typeof(env, pId, &pIdType)) { return nullptr; } + if (pIdType == napi_number) { + int32_t id; + NAPI_GUARD(napi_get_value_int32(env, pId, &id)) { return nullptr; } + auto cb = frameCallbackCache_.find(id); + if (cb != frameCallbackCache_.end()) { + bool shouldReschedule = !cb->second.isScheduled(); + cb->second.markScheduled(); + if (shouldReschedule) { + PostCallback(env, info, &cb->second); + } + return nullptr; + } + } + + uint64_t key = ++frameCallbackCount_; + + napi_value keyValue; + NAPI_GUARD(napi_create_int64(env, key, &keyValue)) { return nullptr; } + NAPI_GUARD(napi_set_property(env, func, idKey, keyValue)) {} + + auto [val, inserted] = frameCallbackCache_.try_emplace(key, env, func, key); + assert(inserted && "Frame callback ID should not be duplicated"); + + val->second.markScheduled(); + PostCallback(env, info, &val->second); + + } + return nullptr; +} + +napi_value CallbackHandlers::RemoveFrameCallback(napi_env env, napi_callback_info info) { + if (android_get_device_api_level() >= 24) { + InitChoreographer(); + + napi_status status; + size_t argc = 1; + napi_value args[1]; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { return nullptr; } + + if (argc < 1) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_valuetype argType; + NAPI_GUARD(napi_typeof(env, args[0], &argType)) { return nullptr; } + if (argType != napi_function) { + napi_throw_type_error(env, nullptr, "Frame callback argument is not a function"); + return nullptr; + } + + napi_value func = args[0]; + + napi_value idKey; + NAPI_GUARD(napi_create_string_utf8(env, "_postFrameCallbackId", NAPI_AUTO_LENGTH, &idKey)) { return nullptr; } + + napi_value pId; + NAPI_GUARD(napi_get_property(env, func, idKey, &pId)) { return nullptr; } + + if (pId != nullptr && napi_util::is_of_type(env, pId, napi_number)) { + int32_t id; + NAPI_GUARD(napi_get_value_int32(env, pId, &id)) { return nullptr; } + auto cb = frameCallbackCache_.find(id); + if (cb != frameCallbackCache_.end()) { + cb->second.markRemoved(); + } + } + } + return nullptr; +} + +void CallbackHandlers::InitChoreographer() { + if (AChoreographer_getInstance_ == nullptr) { + void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (lib != nullptr) { + AChoreographer_getInstance_ = reinterpret_cast( + dlsym(lib, "AChoreographer_getInstance")); + AChoreographer_postFrameCallback_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallback")); + AChoreographer_postFrameCallbackDelayed_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallbackDelayed")); + + assert(AChoreographer_getInstance_); + assert(AChoreographer_postFrameCallback_); + assert(AChoreographer_postFrameCallbackDelayed_); + + if (android_get_device_api_level() >= 29) { + AChoreographer_postFrameCallback64_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallback64")); + AChoreographer_postFrameCallbackDelayed64_ = reinterpret_cast( + dlsym(lib, "AChoreographer_postFrameCallbackDelayed64")); + + assert(AChoreographer_postFrameCallback64_); + assert(AChoreographer_postFrameCallbackDelayed64_); + } + } + } +} + +void CallbackHandlers::RemoveEnvEntries(napi_env env) { + for (auto &item: cache_) { + if (item.second.env_ == env) { + cache_.erase(item.first); + } + } + + for (auto &item: frameCallbackCache_) { + if (item.second.env == env) { + frameCallbackCache_.erase(item.first); + } + } + +} + +// Worker + +napi_value CallbackHandlers::NewThreadCallback(napi_env env, napi_callback_info info) { + try { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + napi_value newTarget; + NAPI_GUARD(napi_get_new_target(env, info, &newTarget)) { return nullptr; } + if (napi_util::is_null_or_undefined(env, newTarget)) { + throw NativeScriptException("Worker should be called as a constructor!"); + } + + if (argc != 1) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL/URL OBJECT to the file"); + } + + napi_valuetype value_type; + NAPI_GUARD(napi_typeof(env, argv[0], &value_type)) { return nullptr; } + + if (value_type != napi_string && value_type != napi_object) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL/URL OBJECT to the file"); + } + + napi_value workerFilePath; + std::string baseurl_str; + if (value_type == napi_object) { + NAPI_GUARD(napi_get_named_property(env, argv[0], "href", &workerFilePath)) { return nullptr; } + if (napi_util::is_null_or_undefined(env, workerFilePath)) { + throw NativeScriptException( + "Worker should be called with one parameter (name of file to run) or a URL to the file"); + } + } else { + workerFilePath = argv[0]; + } + + + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + + auto frames = tns::BuildStacktraceFrames(env, nullptr, 1); + string currentExecutingScriptNameStr = + frames.size() < 3 ? frames[0].filename : frames[2].filename; + + auto lastForwardSlash = currentExecutingScriptNameStr.find_last_of("/"); + auto currentDir = currentExecutingScriptNameStr.substr(0, lastForwardSlash + 1); + std::string fileSchema("file://"); + if (currentDir.compare(0, fileSchema.length(), fileSchema) == 0) { + currentDir = currentDir.substr(fileSchema.length()); + } + + std::string workerPath = ArgConverter::ConvertToString(env, workerFilePath); + + if (workerPath.compare(0, fileSchema.length(), fileSchema) == 0) { + workerPath = workerPath.substr(fileSchema.length()); + auto workerPathPrefix = workerPath.substr(0, 1) == "/" ? "~" : "~/"; + workerPath = workerPathPrefix + workerPath; + } + + DEBUG_WRITE("Worker Path: %s, Current Dir: %s", workerPath.c_str(), currentDir.c_str()); + + + + // Will throw if path is invalid or doesn't exist + ModuleInternal::CheckFileExists(env, workerPath, currentDir); + + // Resolve the JNI handles used by the worker thread bootstrap while we + // are still on the parent (main, for the first worker) thread. + WorkerWrapper::EnsureJniCached(); + + auto workerId = WorkerWrapper::NextWorkerId(); + napi_value workerIdValue; + NAPI_GUARD(napi_create_int32(env, workerId, &workerIdValue)) { return nullptr; } + NAPI_GUARD(napi_set_named_property(env, jsThis, "workerId", workerIdValue)) { return nullptr; } + + DEBUG_WRITE("Called Worker constructor id=%d", workerId); + + // THREAD_PRIORITY_BACKGROUND (android.os.Process) == 10 + const int kThreadPriorityBackground = 10; + auto wrapper = std::make_shared(env, workerId, workerPath, currentDir, + kThreadPriorityBackground, jsThis); + WorkerWrapper::Insert(workerId, wrapper); + wrapper->Start(); + + napi_value stack; + napi_value error; + napi_value empty; + NAPI_GUARD(napi_create_string_utf8(env, "",0, &empty)) {} + NAPI_GUARD(napi_create_error(env, empty, empty, &error)) {} + NAPI_GUARD(napi_get_named_property(env, error, "stack", &stack)) {} + NAPI_GUARD(napi_set_named_property(env, jsThis, "__stack__", stack)) {} + + return jsThis; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value +CallbackHandlers::WorkerObjectPostMessageCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(2) + + try { + if (argc != 1) { + NativeScriptException exception( + "Failed to execute 'postMessage' on 'Worker': 1 argument required."); + throw exception; + } + + napi_value isTerminated; + NAPI_GUARD(napi_get_named_property(env, jsThis, "isTerminated", &isTerminated)) {} + if (!napi_util::is_null_or_undefined(env, isTerminated)) { + bool terminated; + NAPI_GUARD(napi_get_value_bool(env, isTerminated, &terminated)) {} + if (terminated) { + return nullptr; + } + } + + std::string msg = tns::JsonStringifyObject(env, argv[0], false); + + // get worker's ID that is associated with this Worker object + napi_value jsId; + NAPI_GUARD(napi_get_named_property(env, jsThis, "workerId", &jsId)) {} + auto id = napi_util::get_int32(env, jsId); + + auto wrapper = WorkerWrapper::GetById(id); + if (wrapper != nullptr) { + wrapper->PostMessage(std::make_shared( + worker::Message::MakeData(std::move(msg)))); + } + + DEBUG_WRITE( + "MAIN: WorkerObjectPostMessageCallback called postMessage on Worker object(id=%d)", + id); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value +CallbackHandlers::WorkerGlobalPostMessageCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + try { + if (argc != 1) { + napi_throw_error(env, nullptr, + "Failed to execute 'postMessage' on WorkerGlobalScope: 1 argument required."); + return nullptr; + } + + bool pendingException; + NAPI_GUARD(napi_is_exception_pending(env, &pendingException)) {} + if (pendingException) { + napi_value err; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &err)) {} + CallWorkerScopeOnErrorHandle(env, err); + } + + napi_value objToStringify = argv[0]; + std::string msg = tns::JsonStringifyObject(env, objToStringify, false); + + auto wrapper = WorkerWrapper::FromEnv(env); + if (wrapper != nullptr) { + wrapper->PostMessageToParent(std::make_shared( + worker::Message::MakeData(std::move(msg)))); + } + + DEBUG_WRITE("WORKER: WorkerGlobalPostMessageCallback called."); + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value CallbackHandlers::WorkerObjectTerminateCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value thiz; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, nullptr, &thiz, nullptr)) { return nullptr; } + + DEBUG_WRITE("WORKER: WorkerObjectTerminateCallback called."); + + try { + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + + napi_value jsId; + NAPI_GUARD(napi_get_named_property(env, thiz, "workerId", &jsId)) {} + + int32_t id; + NAPI_GUARD(napi_get_value_int32(env, jsId, &id)) {} + + napi_value isTerminated; + NAPI_GUARD(napi_get_named_property(env, thiz, "isTerminated", &isTerminated)) {} + if (!napi_util::is_null_or_undefined(env, isTerminated)) { + bool terminated; + NAPI_GUARD(napi_get_value_bool(env, isTerminated, &terminated)) {} + if (terminated) { + return nullptr; + } + } + + napi_value trueValue; + NAPI_GUARD(napi_get_boolean(env, true, &trueValue)) {} + NAPI_GUARD(napi_set_named_property(env, thiz, "isTerminated", trueValue)) {} + + auto wrapper = WorkerWrapper::GetById(id); + if (wrapper != nullptr) { + wrapper->Terminate(); + } + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value CallbackHandlers::WorkerGlobalCloseCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value thiz; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, nullptr, &thiz, nullptr)) { return nullptr; } + + DEBUG_WRITE("WORKER: WorkerThreadCloseCallback called."); + + try { + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + + napi_value isTerminated; + NAPI_GUARD(napi_get_named_property(env, global, "isTerminating", &isTerminated)) {} + if (!napi_util::is_null_or_undefined(env, isTerminated)) { + bool terminated; + NAPI_GUARD(napi_get_value_bool(env, isTerminated, &terminated)) {} + if (terminated) { + return nullptr; + } + } + + napi_value trueValue; + NAPI_GUARD(napi_get_boolean(env, true, &trueValue)) {} + NAPI_GUARD(napi_set_named_property(env, global, "isTerminating", trueValue)) {} + + napi_value callback; + NAPI_GUARD(napi_get_named_property(env, global, "onclose", &callback)) {} + if (napi_util::is_of_type(env, callback, napi_function)) { + napi_value result; + NAPI_GUARD(napi_call_function(env, global, callback, 0, nullptr, &result)) {} + } + + bool pendingException; + NAPI_GUARD(napi_is_exception_pending(env, &pendingException)) {} + if (pendingException) { + napi_value err; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &err)) {} + CallWorkerScopeOnErrorHandle(env, err); + } + + auto wrapper = WorkerWrapper::FromEnv(env); + if (wrapper != nullptr) { + wrapper->Close(); + } + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return napi_util::undefined(env); +} + +void CallbackHandlers::CallWorkerScopeOnErrorHandle(napi_env env, napi_value error) { + try { + napi_status status; + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + + napi_value callback; + NAPI_GUARD(napi_get_named_property(env, global, "onerror", &callback)) {} + + napi_value message = nullptr; + napi_value stack = nullptr; + std::vector frames; + if (napi_util::is_of_type(env, error, napi_object)) { + frames = tns::BuildStacktraceFrames(env, error, 1); + NAPI_GUARD(napi_get_named_property(env, error, "message", &message)) {} + NAPI_GUARD(napi_get_named_property(env, error, "stack", &stack)) {} + } else { + NAPI_GUARD(napi_coerce_to_string(env, error, &message)) {} + NAPI_GUARD(napi_create_string_utf8(env, "", 0, &stack)) {} + } + + if (napi_util::is_of_type(env, callback, napi_function)) { + napi_value args[1] = {error}; + napi_value result; + NAPI_GUARD(napi_call_function(env, global, callback, 1, args, &result)) {} + + bool pendingException; + NAPI_GUARD(napi_is_exception_pending(env, &pendingException)) {} + if (pendingException) { + napi_value perror = nullptr; + napi_value pmessage = nullptr; + napi_value pstack = nullptr; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &perror)) {} + + std::vector pframes; + if (napi_util::is_of_type(env, perror, napi_object)) { + pframes = tns::BuildStacktraceFrames(env, perror, 1); + NAPI_GUARD(napi_get_named_property(env, perror, "message", &pmessage)) {} + NAPI_GUARD(napi_get_named_property(env, perror, "stack", &pstack)) {} + } else { + NAPI_GUARD(napi_coerce_to_string(env, perror, &pmessage)) {} + NAPI_GUARD(napi_create_string_utf8(env, "", 0, &pstack)) {} + } + + auto line = 0; + std::string filename; + if (!pframes.empty()) { + line = pframes[0].line; + filename = pframes[0].filename; + } + auto wrapper = WorkerWrapper::FromEnv(env); + if (wrapper != nullptr) { + wrapper->PassUncaughtExceptionFromWorkerToParent( + ArgConverter::ConvertToString(env, pmessage), + filename, + ArgConverter::ConvertToString(env, pstack), + line); + } + } else if (!napi_util::is_null_or_undefined(env, result)) { + bool handled; + NAPI_GUARD(napi_get_value_bool(env, result, &handled)) {} + if (handled) { + return; + } + } + } + + auto line = 0; + std::string filename; + if (!frames.empty()) { + line = frames[0].line; + filename = frames[0].filename; + } + auto wrapper = WorkerWrapper::FromEnv(env); + if (wrapper != nullptr) { + wrapper->PassUncaughtExceptionFromWorkerToParent( + ArgConverter::ConvertToString(env, message), + filename, + ArgConverter::ConvertToString(env, stack), + line); + } + + + } catch (NativeScriptException &ex) { + ex.ReThrowToNapi(env); + } catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } +} + +robin_hood::unordered_map CallbackHandlers::cache_; +robin_hood::unordered_map CallbackHandlers::jclass_to_runtimeId_cache; + +robin_hood::unordered_map CallbackHandlers::frameCallbackCache_; + +std::atomic_int64_t CallbackHandlers::count_ = {0}; +std::atomic_uint64_t CallbackHandlers::frameCallbackCount_ = {0}; + +int CallbackHandlers::lastCallId = -1; +napi_value CallbackHandlers::lastCallValue = nullptr; + +short CallbackHandlers::MAX_JAVA_STRING_ARRAY_LENGTH = 100; +jclass CallbackHandlers::RUNTIME_CLASS = nullptr; +jclass CallbackHandlers::JAVA_LANG_STRING = nullptr; +jfieldID CallbackHandlers::CURRENT_OBJECTID_FIELD_ID = nullptr; +jmethodID CallbackHandlers::RESOLVE_CLASS_METHOD_ID = nullptr; +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; + +NumericCasts CallbackHandlers::castFunctions; + +ArrayElementAccessor CallbackHandlers::arrayElementAccessor; +FieldAccessor CallbackHandlers::fieldAccessor; diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h new file mode 100644 index 000000000..274bba27e --- /dev/null +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h @@ -0,0 +1,394 @@ +#ifndef CALLBACKHANDLERS_H_ +#define CALLBACKHANDLERS_H_ + +#include +#include +#include +#include "JEnv.h" +#include "ArgsWrapper.h" +#include "MetadataEntry.h" +#include "FieldCallbackData.h" +#include "MetadataTreeNode.h" +#include "NumericCasts.h" +#include "FieldAccessor.h" +#include "ArrayElementAccessor.h" +#include "ObjectManager.h" +#include "robin_hood.h" +#include +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +namespace tns { + class CallbackHandlers { + public: + static void Init(napi_env env); + + static napi_value + CreateJSWrapper(napi_env env, jint javaObjectID, const std::string &typeName); + + static bool RegisterInstance(napi_env env, napi_value jsObject, + const std::string &fullClassName, + const ArgsWrapper &argWrapper, + napi_value implementationObject, + bool isInterface, + napi_value *jsThisProxy, + const std::string &baseClassName = std::string(), + MetadataNode *node = nullptr); + + static jclass ResolveClass(napi_env env, const std::string &baseClassName, + const std::string &fullClassName, + napi_value implementationObject, + bool isInterface); + + static std::string ResolveClassName(napi_env env, jclass &clazz); + + static napi_value + GetArrayElement(napi_env env, napi_value array, uint32_t index, + const std::string &arraySignature, + ObjectManager *objectManager = nullptr, jobject arrayObject = nullptr); + + static void + SetArrayElement(napi_env env, napi_value array, uint32_t index, + const std::string &arraySignature, napi_value value, + ObjectManager *objectManager = nullptr, jobject arrayObject = nullptr); + + static int GetArrayLength(napi_env env, napi_value arr); + + static napi_value + CallJavaMethod(napi_env env, napi_value caller, const std::string &className, + const std::string &methodName, MetadataEntry *entry, bool isFromInterface, + bool isStatic, napi_callback_info info, size_t argc, napi_value* argv, + ObjectManager *objectManager = nullptr, + bool metadataSignatureIsUnambiguous = false); + + static napi_value + CallJSMethod(napi_env env, JNIEnv *jEnv, napi_value jsObject,jclass claz, + const std::string &methodName,int javaObjectId, jobjectArray args); + static napi_value + GetJavaField(napi_env env, napi_value caller, + FieldCallbackData *fieldData, ObjectManager *objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); + + static void SetJavaField(napi_env env, napi_value target, + napi_value value, FieldCallbackData *fieldData, + ObjectManager *objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); + + static napi_value RunOnMainThreadCallback(napi_env env, napi_callback_info info); + + static int RunOnMainThreadFdCallback(int fd, int events, void *data); + + static napi_value LogMethodCallback(napi_env env, napi_callback_info info); + + static napi_value TimeCallback(napi_env env, napi_callback_info info); + + static napi_value + DumpReferenceTablesMethodCallback(napi_env env, napi_callback_info info); + + static napi_value DrainMicrotaskCallback(napi_env env, napi_callback_info info); + + static void DumpReferenceTablesMethod(); + + static napi_value ExitMethodCallback(napi_env env, napi_callback_info info); + + static void CreateGlobalCastFunctions(napi_env env); + + static std::vector GetTypeMetadata(const std::string &name, int index); + + /* + * The two Collect* helpers below define which names take part in a binding. + * Content-keyed class names hash exactly these sets, so the static binding + * generator has to reproduce them from the bundle byte for byte -- a name + * that is collected here but not there (or the reverse) yields two + * different class names for one extend() site, which fails at runtime as + * LookedUpClassNotFound. Keeping the JNI arrays and the hash on one + * implementation is what makes that contract checkable in a single place. + */ + static std::vector + CollectImplementedInterfaceNames(napi_env env, napi_value implementationObject); + + static std::vector + CollectMethodOverrideNames(napi_env env, napi_value implementationObject, + bool functionsOnly); + + /* + * Gets all methods in the implementation object, and packs them in a jobjectArray + * to pass them to Java Land, so that their corresponding Java callbacks are written when + * the dexFactory generates the class + */ + static jobjectArray + GetMethodOverrides(napi_env env, JEnv &jEnv, napi_value implementationObject); + + /* + * Gets all interfaces declared in the 'interfaces' array inside the implementation object, + * and packs them in a jobjectArray to pass them to Java Land, so that they may be + * implemented when the dexFactory generates the corresponding class + */ + static jobjectArray + GetImplementedInterfaces(napi_env env, JEnv &jEnv, napi_value implementationObject); + + static napi_value + EnableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info); + + static napi_value + DisableVerboseLoggingMethodCallback(napi_env env, napi_callback_info info); + + static napi_value ReleaseNativeCounterpartCallback(napi_env env, napi_callback_info info); + + static napi_value FindClass(napi_env env, const char *name); + + static napi_value NewThreadCallback(napi_env env, napi_callback_info info); + + /* + * main -> worker messaging + * Fired when a Worker instance's postMessage is called + */ + static napi_value WorkerObjectPostMessageCallback(napi_env env, napi_callback_info info); + + /* + * worker -> main thread messaging + * Fired when a Worker script's "postMessage" is called + */ + static napi_value WorkerGlobalPostMessageCallback(napi_env env, napi_callback_info info); + + /* + * Fired when a Worker instance's terminate is called (cooperatively + * stops the worker thread's looper) + */ + static napi_value WorkerObjectTerminateCallback(napi_env env, napi_callback_info info); + + /* + * Fired when a Worker script's close is called + */ + static napi_value WorkerGlobalCloseCallback(napi_env env, napi_callback_info info); + + /* + * Is called when an unhandled exception is thrown inside the worker + * Will execute 'onerror' if one is provided inside the Worker Scope + * Will make the exception "bubble up" through to the parent, to be handled by the Worker Object + * if 'onerror' isn't implemented or returns false + */ + static void CallWorkerScopeOnErrorHandle(napi_env env, napi_value tc); + + static napi_value PostFrameCallback(napi_env env, napi_callback_info info); + + static napi_value RemoveFrameCallback(napi_env env, napi_callback_info info); + + static void RemoveEnvEntries(napi_env env); + + struct AChoreographer; + + typedef void (*AChoreographer_frameCallback)(long frameTimeNanos, void *data); + + typedef void (*AChoreographer_frameCallback64)(int64_t frameTimeNanos, void *data); + + typedef AChoreographer *(*func_AChoreographer_getInstance)(); + + typedef void (*func_AChoreographer_postFrameCallback)( + AChoreographer *choreographer, AChoreographer_frameCallback callback, + void *data); + + typedef void (*func_AChoreographer_postFrameCallback64)( + AChoreographer *choreographer, AChoreographer_frameCallback64 callback, + void *data); + + typedef void (*func_AChoreographer_postFrameCallbackDelayed)( + AChoreographer *choreographer, AChoreographer_frameCallback callback, + void *data, long delayMillis); + + typedef void (*func_AChoreographer_postFrameCallbackDelayed64)( + AChoreographer *choreographer, AChoreographer_frameCallback64 callback, + void *data, uint32_t delayMillis); + + + static jint lastCallId; + static napi_value lastCallValue; + + private: + CallbackHandlers() { + } + + static void AdjustAmountOfExternalAllocatedMemory(napi_env napiEnv); + + /* + * Helper method that creates a java string array for sending strings over JNI + */ + static jobjectArray GetJavaStringArray(JEnv &jEnv, int length); + + static void + validateProvidedArgumentsLength(napi_env env, napi_callback_info info, int expectedSize); + + static short MAX_JAVA_STRING_ARRAY_LENGTH; + + static jclass RUNTIME_CLASS; + + static jclass JAVA_LANG_STRING; + + static jmethodID RESOLVE_CLASS_METHOD_ID; + + static jfieldID CURRENT_OBJECTID_FIELD_ID; + + static jmethodID MAKE_INSTANCE_STRONG_ID; + + static jmethodID GET_TYPE_METADATA; + + static jmethodID ENABLE_VERBOSE_LOGGING_METHOD_ID; + + static jmethodID DISABLE_VERBOSE_LOGGING_METHOD_ID; + + static NumericCasts castFunctions; + + static ArrayElementAccessor arrayElementAccessor; + + static FieldAccessor fieldAccessor; + + struct JavaObjectIdScope { + JavaObjectIdScope(JEnv &_jEnv, jfieldID fieldId, jobject runtime, int javaObjectId) + : jEnv(_jEnv), _fieldID(fieldId), _runtime(runtime) { + jEnv.SetIntField(_runtime, _fieldID, javaObjectId); + } + + ~JavaObjectIdScope() { + jEnv.SetIntField(_runtime, _fieldID, -1); + } + + private: + JEnv jEnv; + jfieldID _fieldID; + jobject _runtime; + }; + + static std::atomic_int64_t count_; + + struct Callback { + Callback() {} + + Callback(uint64_t id) + : id_(id) { + } + + uint64_t id_; + }; + + struct CacheEntry { + CacheEntry(napi_env env, napi_value callback) + : env_(env) { + napi_create_reference(env, callback, 1, &callback_); + } + + ~CacheEntry() { + napi_delete_reference(env_, callback_); + } + + napi_env env_; + napi_ref callback_; + }; + + static robin_hood::unordered_map cache_; + + static robin_hood::unordered_map jclass_to_runtimeId_cache; + + static std::atomic_uint64_t frameCallbackCount_; + + struct FrameCallbackCacheEntry { + FrameCallbackCacheEntry(napi_env _env, napi_value callback_, uint64_t aId) + : env(_env), + id(aId) { + napi_create_reference(env, callback_, 1, &callback); + } + + ~FrameCallbackCacheEntry() { + napi_delete_reference(env, callback); + } + + napi_env env; + napi_ref callback; + uint64_t id; + + bool isScheduled() { + return scheduled; + } + + void markScheduled() { + scheduled = true; + removed = false; + } + + void markRemoved() { + // we can never unschedule a callback, so we just mark it as removed + removed = true; + uint32_t result; + } + + AChoreographer_frameCallback frameCallback_ = [](long ts, void *data) { + execute((double) ts, data); + }; + + AChoreographer_frameCallback64 frameCallback64_ = [](int64_t ts, void *data) { + execute((double) ts, data); + }; + + static void execute(double ts, void *data) { + if (data != nullptr) { + + auto entry = static_cast(data); + if (entry->shouldRemoveBeforeCall()) { + frameCallbackCache_.erase(entry->id); // invalidates *entry + return; + } + napi_env env = entry->env; + NapiScope scope(env); + napi_value cb = napi_util::get_ref_value(env, entry->callback); + + napi_value global; + napi_get_global(env, &global); + + entry->markUnscheduled(); + + napi_value args[1]; + napi_create_double(env, ts, &args[0]); + + napi_valuetype type; + napi_typeof(env, cb, &type); + + napi_value result; + napi_call_function(env, global, cb, 1, args, &result); + + + // check if we should remove it (it should be both unscheduled and removed) + if (entry->shouldRemoveAfterCall()) { + frameCallbackCache_.erase(entry->id); // invalidates *entry + } + } + } + + private: + bool removed = false; + bool scheduled = false; + + void markUnscheduled() { + scheduled = false; + removed = true; + } + + bool shouldRemoveBeforeCall() { + return removed; + } + + bool shouldRemoveAfterCall() { + return !scheduled && removed; + } + }; + + static robin_hood::unordered_map frameCallbackCache_; + + static void InitChoreographer(); + + static void PostCallback(napi_env env, napi_callback_info info, + FrameCallbackCacheEntry *entry); + + }; +} + +#endif /* CALLBACKHANDLERS_H_ */ diff --git a/NativeScript/ffi/jni/napi/constants/Constants.cpp b/NativeScript/ffi/jni/napi/constants/Constants.cpp new file mode 100644 index 000000000..b957a7eea --- /dev/null +++ b/NativeScript/ffi/jni/napi/constants/Constants.cpp @@ -0,0 +1,12 @@ +/* + * Constants.cpp + * + * Created on: Nov 6, 2015 + * Author: gatanasov + */ + +#include "Constants.h" + +std::string Constants::APP_ROOT_FOLDER_PATH = ""; +bool Constants::CACHE_COMPILED_CODE = false; + diff --git a/NativeScript/ffi/jni/napi/constants/Constants.h b/NativeScript/ffi/jni/napi/constants/Constants.h new file mode 100644 index 000000000..15aea1c77 --- /dev/null +++ b/NativeScript/ffi/jni/napi/constants/Constants.h @@ -0,0 +1,39 @@ +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#include + +#define PROP_KEY_EXTEND "extend" +#define PROP_KEY_NULLOBJECT "null" +#define PROP_KEY_NULL_NODE_NAME "nullNode" +#define PROP_KEY_VALUEOF "valueOf" +#define PROP_KEY_CLASS "class" +#define PRIVATE_TYPE_NAME "#typename" +#define CLASS_IMPLEMENTATION_OBJECT "t::ClassImplementationObject" +#define EXTEND_CTOR_CACHE_KEY "t::ExtendCtorCacheKey" +#define PROP_KEY_SUPER "super" +#define PROP_KEY_SUPERVALUE "supervalue" +#define PRIVATE_JSINFO "#js_info" +#define PRIVATE_CALLSUPER "#supercall" +#define PRIVATE_IS_NAPI "#is_napi" +#define PROP_KEY_TOSTRING "toString" +#define PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT "__isPrototypeImplementationObject" +// Stamped onto the implementation object by ts_helpers' __extends, not written +// by the user. They exist only after that helper has run, so the static binding +// generator cannot see them -- see CollectMethodOverrideNames. +#define PROP_KEY_TS_PARENT "__parent" +#define PROP_KEY_TS_CHILD "__child" + +class Constants { + public: + const static char CLASS_NAME_LOCATION_SEPARATOR = '_'; + + static std::string APP_ROOT_FOLDER_PATH; + static bool CACHE_COMPILED_CODE; + + private: + Constants() { + } +}; + +#endif /* CONSTANTS_H_ */ diff --git a/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp new file mode 100644 index 000000000..7f3cccf6e --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp @@ -0,0 +1,342 @@ +#include "ArgConverter.h" +#include "ObjectManager.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "NumericCasts.h" +#include "NativeScriptAssert.h" +#include +#ifdef USE_MIMALLOC +#include "mimalloc.h" +#endif + + +using namespace std; +using namespace tns; + +namespace { +napi_value EnsurePlainConstructorThis(napi_env env, napi_value jsThis, napi_value prototype) { + if (!napi_util::is_null_or_undefined(env, jsThis)) { + return jsThis; + } + + napi_value receiver = nullptr; + if (napi_create_object(env, &receiver) != napi_ok || receiver == nullptr) { + return nullptr; + } + + if (!napi_util::is_null_or_undefined(env, prototype)) { + napi_util::setPrototypeOf(env, receiver, prototype); + } + + return receiver; +} +} + +void ArgConverter::Init(napi_env env) { + napi_status status; + auto cache = GetTypeLongCache(env); + + napi_value longNumberCtorFunc; + napi_value valueOfFunc; + napi_value toStringFunc; + + NAPI_GUARD(napi_define_class(env, "NativeScriptLongNumber", NAPI_AUTO_LENGTH, ArgConverter::NativeScriptLongFunctionCallback,nullptr, 0, nullptr, &longNumberCtorFunc)) { + return; + } + + napi_value longNumberPrototype = napi_util::get_prototype(env, longNumberCtorFunc); + + NAPI_GUARD(napi_create_function(env, "valueOf", strlen("valueOf"), + ArgConverter::NativeScriptLongValueOfFunctionCallback, nullptr, + &valueOfFunc)) { + return; + } + + NAPI_GUARD(napi_create_function(env, "toString", strlen("toString"), + ArgConverter::NativeScriptLongToStringFunctionCallback, nullptr, + &toStringFunc)) { + return; + } + + + NAPI_GUARD(napi_set_named_property(env, longNumberPrototype, "valueOf", valueOfFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, longNumberPrototype, "toString", toStringFunc)) { + return; + } + + cache->LongNumberCtorFunc = napi_util::make_ref(env, longNumberCtorFunc, 1); + napi_value nanValue; + NAPI_GUARD(napi_create_double(env, numeric_limits::quiet_NaN(), &nanValue)) { + return; + } + + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + + napi_value numCtor; + NAPI_GUARD(napi_get_named_property(env, global, "Number", &numCtor)) { + return; + } + + napi_value nanObject; + NAPI_GUARD(napi_new_instance(env, numCtor, 1, &nanValue, &nanObject)) { + return; + } + + cache->NanNumberObject = napi_util::make_ref(env, nanObject, 1); +} + +napi_value ArgConverter::NativeScriptLongValueOfFunctionCallback(napi_env env, napi_callback_info info) { + try { + napi_status status; + napi_value result; + NAPI_GUARD(napi_create_double(env, numeric_limits::quiet_NaN(), &result)) { + return nullptr; + } + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value ArgConverter::NativeScriptLongToStringFunctionCallback(napi_env env, napi_callback_info info) { + try { + napi_status status; + napi_value thisArg; + NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr)) { + return nullptr; + } + + napi_value value; + NAPI_GUARD(napi_get_named_property(env, thisArg, "value", &value)) { + return nullptr; + } + + return value; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value ArgConverter::NativeScriptLongFunctionCallback(napi_env env, napi_callback_info info) { + try { + NAPI_CALLBACK_BEGIN(1); + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + napi_value receiver = EnsurePlainConstructorThis(env, jsThis, receiverPrototype); + if (receiver == nullptr) { + return nullptr; + } + auto cache = GetTypeLongCache(env); + napi_value javaLong; + NAPI_GUARD(napi_get_boolean(env, true, &javaLong)) { + return nullptr; + } + NAPI_GUARD(napi_set_named_property(env, receiver, "javaLong", javaLong)) { + return nullptr; + } + + NumericCasts::MarkAsLong(env, receiver, argv[0]); + + NAPI_GUARD(napi_set_named_property(env, receiver, "prototype", napi_util::get_ref_value(env, cache->NanNumberObject))) { + return nullptr; + } + return receiver; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +void ArgConverter::ConvertJavaArgsToJsArgs(napi_env env, jobjectArray args, size_t argc, napi_value* arr) { + napi_status status; + JEnv jenv; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + int jArrayIndex = 0; + for (int i = 0; i < argc; i++) { + JniLocalRef argTypeIDObj(jenv.GetObjectArrayElement(args, jArrayIndex++)); + JniLocalRef arg(jenv.GetObjectArrayElement(args, jArrayIndex++)); + JniLocalRef argJavaClassPath(jenv.GetObjectArrayElement(args, jArrayIndex++)); + + Type argTypeID = (Type) JType::IntValue(jenv, argTypeIDObj); + + napi_value jsArg; + switch (argTypeID) { + case Type::Boolean: + NAPI_GUARD(napi_get_boolean(env, JType::BooleanValue(jenv, arg), &jsArg)) {} + break; + case Type::Char: + jsArg = jcharToJsString(env, JType::CharValue(jenv, arg)); + break; + case Type::Byte: + NAPI_GUARD(napi_create_int32(env, JType::ByteValue(jenv, arg), &jsArg)) {} + break; + case Type::Short: + NAPI_GUARD(napi_create_int32(env, JType::ShortValue(jenv, arg), &jsArg)) {} + break; + case Type::Int: + NAPI_GUARD(napi_create_int32(env, JType::IntValue(jenv, arg), &jsArg)) {} + break; + case Type::Long: + NAPI_GUARD(napi_create_int64(env, JType::LongValue(jenv, arg), &jsArg)) {} + break; + case Type::Float: + NAPI_GUARD(napi_create_double(env, JType::FloatValue(jenv, arg), &jsArg)) {} + break; + case Type::Double: + NAPI_GUARD(napi_create_double(env, JType::DoubleValue(jenv, arg), &jsArg)) {} + break; + case Type::String: + jsArg = jstringToJsString(env, (jstring) arg); + break; + case Type::JsObject: { + jint javaObjectID = JType::IntValue(jenv, arg); + jsArg = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, jsArg)) { + string argClassName = jstringToString(ObjectToString(argJavaClassPath)); + argClassName = Util::ConvertFromCanonicalToJniName(argClassName); + jsArg = objectManager->CreateJSWrapper(javaObjectID, argClassName); + } + break; + } + case Type::Null: + NAPI_GUARD(napi_get_null(env, &jsArg)) {} + break; + } + + arr[i] = jsArg; + } + +} + +napi_value ArgConverter::ConvertFromJavaLong(napi_env env, jlong value) { + napi_status status; + napi_value convertedValue; + long long longValue = value; + + if ((-JS_LONG_LIMIT < longValue) && (longValue < JS_LONG_LIMIT)) { + NAPI_GUARD(napi_create_double(env, longValue, &convertedValue)) { + return nullptr; + } + } else { + auto cache = GetTypeLongCache(env); + char strNumber[24]; + sprintf(strNumber, "%lld", longValue); + napi_value strValue; + NAPI_GUARD(napi_create_string_utf8(env, strNumber, NAPI_AUTO_LENGTH, &strValue)) { + return nullptr; + } + napi_value args[1] = {strValue}; + + NAPI_GUARD(napi_new_instance(env, napi_util::get_ref_value(env, cache->LongNumberCtorFunc), 1, args, + &convertedValue)) { + return nullptr; + } + } + + return convertedValue; +} + +int64_t ArgConverter::ConvertToJavaLong(napi_env env, napi_value value) { + napi_status status; + napi_value valueProp; + NAPI_GUARD(napi_get_named_property(env, value, "value", &valueProp)) { + return 0; + } + + size_t str_len; + NAPI_GUARD(napi_get_value_string_utf8(env, valueProp, nullptr, 0, &str_len)) { + return 0; + } + string num(str_len, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, valueProp, &num[0], str_len + 1, &str_len)) { + return 0; + } + + int64_t longValue = atoll(num.c_str()); + + return longValue; +} + +ArgConverter::TypeLongOperationsCache *ArgConverter::GetTypeLongCache(napi_env env) { + auto cache = s_type_long_operations_cache.Get(env); + if (cache) return cache; + cache = new TypeLongOperationsCache; + s_type_long_operations_cache.Insert(env, cache); + return cache; +} + +u16string ArgConverter::ConvertToUtf16String(napi_env env, napi_value s) { + if (s == nullptr) { + return {}; + } else { + napi_status status; + size_t str_len; + NAPI_GUARD(napi_get_value_string_utf8(env, s, nullptr, 0, &str_len)) { + return {}; + } + string str(str_len, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, s, &str[0], str_len + 1, &str_len)) { + return {}; + } + auto utf16str = Util::ConvertFromUtf8ToUtf16(str); + + return utf16str; + } +} + +void ArgConverter::onDisposeEnv(napi_env env) { + napi_status status; + auto cache = s_type_long_operations_cache.Get(env); + if (cache) { + if (cache->LongNumberCtorFunc) { + NAPI_GUARD(napi_delete_reference(env, cache->LongNumberCtorFunc)) {} + } + if (cache->NanNumberObject) { + NAPI_GUARD(napi_delete_reference(env, cache->NanNumberObject)) {} + } + s_type_long_operations_cache.Remove(env); + delete cache; + } +} + +tns::ConcurrentMap ArgConverter::s_type_long_operations_cache; diff --git a/NativeScript/ffi/jni/napi/conversion/ArgConverter.h b/NativeScript/ffi/jni/napi/conversion/ArgConverter.h new file mode 100644 index 000000000..ea6eb9e81 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.h @@ -0,0 +1,137 @@ +/* + * ArgConverter.h + * + * Created on: Jan 29, 2014 + * Author: slavchev + */ + +#ifndef ARGCONVERTER_H_ +#define ARGCONVERTER_H_ + +#include "Runtime.h" +#include "NativeScriptAssert.h" +#include "JEnv.h" +#include +#include + +namespace tns { + + class ArgConverter { + public: + static void Init(napi_env env); + + static void ConvertJavaArgsToJsArgs(napi_env env, jobjectArray args, size_t length, napi_value* arr); + + static napi_value ConvertFromJavaLong(napi_env env, jlong value); + + static int64_t ConvertToJavaLong(napi_env env, napi_value value); + + static napi_value jstringToJsString(napi_env env, jstring value) { + if (value == nullptr) return napi_util::null(env); + + JEnv jenv; + auto chars = jenv.GetStringUTFChars(value,JNI_FALSE); + auto length = jenv.GetStringUTFLength(value); + auto jsString = convertToJsString(env, chars, length); + jenv.ReleaseStringUTFChars(value, chars); + + return jsString; + } + + static std::string jstringToString(jstring value) { + if (value == nullptr) { + return {}; + } + + JEnv jenv; + + jboolean f = JNI_FALSE; + auto chars = jenv.GetStringUTFChars(value, &f); + std::string s(chars); + jenv.ReleaseStringUTFChars(value, chars); + + return s; + } + + inline static std::string ConvertToString(napi_env env, napi_value s) { + if (s == nullptr) { + return {}; + } else { + return napi_util::get_string_value(env, s); + } + } + + static std::u16string ConvertToUtf16String(napi_env env, napi_value s); + + inline static jstring ConvertToJavaString(napi_env env, napi_value jsValue) { + JEnv jenv; + return jenv.NewStringUTF(napi_util::get_string_value(env, jsValue, 0)); + } + + inline static napi_value convertToJsString(napi_env env, const jchar *data, int length) { + napi_value result; + napi_create_string_utf16(env, reinterpret_cast(data), length, + &result); + return result; + } + + inline static napi_value convertToJsString(napi_env env, const std::string &s) { + napi_value result; + napi_create_string_utf8(env, s.c_str(), s.length(), &result); + return result; + } + + inline static napi_value convertToJsString(napi_env env, const char *data, int length) { + napi_value result; + napi_create_string_utf8(env, data, length, &result); + return result; + } + + inline static napi_value + ConvertToJsUTF16String(napi_env env, const std::u16string &utf16string) { + napi_value result; + napi_create_string_utf16(env, reinterpret_cast(utf16string.data()), + utf16string.length(), &result); + return result; + } + + static void onDisposeEnv(napi_env env); + + private: + + // TODO: plamen5kov: rewrite logic for java long number operations in javascript (java long -> javascript number operations check) + static const long long JS_LONG_LIMIT = ((long long) 1) << 53; + + struct TypeLongOperationsCache { + napi_ref LongNumberCtorFunc; + napi_ref NanNumberObject; + }; + + static TypeLongOperationsCache *GetTypeLongCache(napi_env env); + + inline static jstring ObjectToString(jobject object) { + return (jstring) object; + } + + inline static napi_value jcharToJsString(napi_env env, jchar value) { + auto v8String = convertToJsString(env, &value, 1); + return v8String; + } + + static napi_value NativeScriptLongFunctionCallback(napi_env env, napi_callback_info info); + + static napi_value + NativeScriptLongValueOfFunctionCallback(napi_env env, napi_callback_info info); + + static napi_value + NativeScriptLongToStringFunctionCallback(napi_env env, napi_callback_info info); + + /* + * "s_type_long_operations_cache" used to keep function + * dealing with operations concerning java long -> javascript number. + */ + static tns::ConcurrentMap s_type_long_operations_cache; + }; +} + +#endif /* ARGCONVERTER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArgsWrapper.h b/NativeScript/ffi/jni/napi/conversion/ArgsWrapper.h new file mode 100644 index 000000000..70b96d8c8 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgsWrapper.h @@ -0,0 +1,30 @@ +/* + * ArgsWrapper.h + * + * Created on: Dec 20, 2013 + * Author: slavchev + */ + +#ifndef ARGSWRAPPER_H_ +#define ARGSWRAPPER_H_ +#include "js_native_api.h" + +namespace tns { +enum class ArgType { + Class, + Interface +}; + +struct ArgsWrapper { + public: + ArgsWrapper(napi_value* argv_, size_t argc_, ArgType t) + : + argv(argv_), argc(argc_), type(t) { + } + napi_value* argv; + size_t argc; + ArgType type; +}; +} + +#endif /* ARGSWRAPPER_H_ */ diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.cpp b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.cpp new file mode 100644 index 000000000..ac5a0a7fe --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.cpp @@ -0,0 +1,135 @@ +#include "ArrayBufferHelper.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include + +using namespace tns; + +ArrayBufferHelper::ArrayBufferHelper() + : m_objectManager(nullptr), m_ByteBufferClass(nullptr), m_isDirectMethodID(nullptr), + m_remainingMethodID(nullptr), m_getMethodID(nullptr) { +} + +void ArrayBufferHelper::CreateConvertFunctions(napi_env env, napi_value global, ObjectManager* objectManager) { + napi_status status; + m_objectManager = objectManager; + napi_value fromFunc; + NAPI_GUARD(napi_create_function(env, "from", NAPI_AUTO_LENGTH, CreateFromCallbackStatic, this, &fromFunc)) { + return; + } + + napi_value arrBufferCtorFunc; + NAPI_GUARD(napi_get_named_property(env, global, "ArrayBuffer", &arrBufferCtorFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, arrBufferCtorFunc, "from", fromFunc)) {} +} + +napi_value ArrayBufferHelper::CreateFromCallbackStatic(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + try { + auto thiz = reinterpret_cast(data); + return thiz->CreateFromCallbackImpl(env, argc, argv); + } catch (NativeScriptException& e) { + e.ReThrowToNapi(env); + } catch (std::exception& e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value ArrayBufferHelper::CreateFromCallbackImpl(napi_env env, size_t argc, napi_value* args) { + napi_status status; + if (argc != 1) { + throw NativeScriptException("Wrong number of arguments (1 expected)"); + } + + napi_value arg = args[0]; + + bool isObject = napi_util::is_object(env, arg); + + if (!isObject) { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + auto argObj = arg; + + auto obj = m_objectManager->GetJavaObjectByJsObject(argObj); + + if (obj.IsNull()) { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + JEnv jEnv; + + if (m_ByteBufferClass == nullptr) { + m_ByteBufferClass = jEnv.FindClass("java/nio/ByteBuffer"); + assert(m_ByteBufferClass != nullptr); + } + + auto isByteBuffer = jEnv.IsInstanceOf(obj, m_ByteBufferClass); + + if (!isByteBuffer) { + throw NativeScriptException("Wrong type of argument (ByteBuffer expected)"); + } + + if (m_isDirectMethodID == nullptr) { + m_isDirectMethodID = jEnv.GetMethodID(m_ByteBufferClass, "isDirect", "()Z"); + assert(m_isDirectMethodID != nullptr); + } + + auto ret = jEnv.CallBooleanMethod(obj, m_isDirectMethodID); + + auto isDirectBuffer = ret == JNI_TRUE; + + napi_value arrayBuffer; + + if (isDirectBuffer) { + auto data = jEnv.GetDirectBufferAddress(obj); + auto size = jEnv.GetDirectBufferCapacity(obj); + + void* externalData = data; + NAPI_GUARD(napi_create_external_arraybuffer(env, externalData, size, nullptr, nullptr, &arrayBuffer)) { + return nullptr; + } + } else { + if (m_remainingMethodID == nullptr) { + m_remainingMethodID = jEnv.GetMethodID(m_ByteBufferClass, "remaining", "()I"); + assert(m_remainingMethodID != nullptr); + } + + int bufferRemainingSize = jEnv.CallIntMethod(obj, m_remainingMethodID); + + if (m_getMethodID == nullptr) { + m_getMethodID = jEnv.GetMethodID(m_ByteBufferClass, "get", "([BII)Ljava/nio/ByteBuffer;"); + assert(m_getMethodID != nullptr); + } + + jbyteArray byteArray = jEnv.NewByteArray(bufferRemainingSize); + jEnv.CallObjectMethod(obj, m_getMethodID, byteArray, 0, bufferRemainingSize); + + auto byteArrayElements = jEnv.GetByteArrayElements(byteArray, 0); + + jbyte* data = new jbyte[bufferRemainingSize]; + memcpy(data, byteArrayElements, bufferRemainingSize); + + NAPI_GUARD(napi_create_external_arraybuffer(env, data, bufferRemainingSize, [](napi_env env, void* finalize_data, void* finalize_hint) { + delete[] static_cast(finalize_data); + }, nullptr, &arrayBuffer)) {} + + jEnv.ReleaseByteArrayElements(byteArray, byteArrayElements, 0); + } + + napi_value nativeObjectKey; + NAPI_GUARD(napi_create_string_utf8(env, "nativeObject", NAPI_AUTO_LENGTH, &nativeObjectKey)) {} + NAPI_GUARD(napi_set_property(env, arrayBuffer, nativeObjectKey, argObj)) {} + + return arrayBuffer; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.h b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.h new file mode 100644 index 000000000..cccd172cd --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayBufferHelper.h @@ -0,0 +1,29 @@ +#ifndef ARRAYBUFFERHELPER_H_ +#define ARRAYBUFFERHELPER_H_ + +#include "ObjectManager.h" + +namespace tns { + class ArrayBufferHelper { + public: + ArrayBufferHelper(); + + void CreateConvertFunctions(napi_env env, napi_value global, ObjectManager* objectManager); + + private: + + static napi_value CreateFromCallbackStatic(napi_env env, napi_callback_info info); + + napi_value CreateFromCallbackImpl(napi_env env, size_t argc, napi_value* args); + + ObjectManager* m_objectManager; + + jclass m_ByteBufferClass; + jmethodID m_isDirectMethodID; + jmethodID m_remainingMethodID; + jmethodID m_getMethodID; + }; +} + + +#endif /* ARRAYBUFFERHELPER_H_ */ diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.cpp b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.cpp new file mode 100644 index 000000000..6d5781966 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.cpp @@ -0,0 +1,328 @@ +#include "ArrayElementAccessor.h" +#include "JsArgToArrayConverter.h" +#include "ArgConverter.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +using namespace std; +using namespace tns; + +napi_value ArrayElementAccessor::GetArrayElement(napi_env env, napi_value array, uint32_t index, + const string& arraySignature, + ObjectManager* objectManager, jobject arrayObject) { + JEnv jenv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + // The caller may hand us the already-resolved Java array (single probe per + // loop instead of per element); otherwise resolve it here. + JniLocalRef localArr; + jobject arr; + if (arrayObject != nullptr) { + arr = arrayObject; + } else { + localArr = objectManager->GetJavaObjectByJsObject(array); + assertNonNullNativeArray(localArr); + arr = localArr; + } + + napi_status status; + napi_value value; + const jsize startIndex = index; + const jsize length = 1; + + // Dispatch on the element-type char (no substr allocation, no string-compare + // chain). Primitive element values are created inline. + switch (arraySignature[1]) { + case 'Z': { + jboolean v; + jenv.GetBooleanArrayRegion((jbooleanArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_get_boolean(env, v, &value)) { + return nullptr; + } + break; + } + case 'B': { + jbyte v; + jenv.GetByteArrayRegion((jbyteArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_create_int32(env, v, &value)) { + return nullptr; + } + break; + } + case 'C': { + jchar v; + jenv.GetCharArrayRegion((jcharArray) arr, startIndex, length, &v); + JniLocalRef s(jenv.NewString(&v, 1)); + jboolean isCopy = false; + const char* singleChar = jenv.GetStringUTFChars(s, &isCopy); + NAPI_GUARD(napi_create_string_utf8(env, singleChar, 1, &value)) {} + jenv.ReleaseStringUTFChars(s, singleChar); + break; + } + case 'S': { + jshort v; + jenv.GetShortArrayRegion((jshortArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_create_int32(env, v, &value)) { + return nullptr; + } + break; + } + case 'I': { + jint v; + jenv.GetIntArrayRegion((jintArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_create_int32(env, v, &value)) { + return nullptr; + } + break; + } + case 'J': { + jlong v; + jenv.GetLongArrayRegion((jlongArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_create_int64(env, v, &value)) { + return nullptr; + } + break; + } + case 'F': { + jfloat v; + jenv.GetFloatArrayRegion((jfloatArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_create_double(env, v, &value)) { + return nullptr; + } + break; + } + case 'D': { + jdouble v; + jenv.GetDoubleArrayRegion((jdoubleArray) arr, startIndex, length, &v); + NAPI_GUARD(napi_create_double(env, v, &value)) { + return nullptr; + } + break; + } + default: { // 'L' object or '[' nested array + jobject result = jenv.GetObjectArrayElement((jobjectArray) arr, index); + // Pass the element signature as a string_view into arraySignature (drop + // the leading '[') instead of allocating a fresh substring per element. + value = ConvertToJsValue(env, objectManager, jenv, + std::string_view(arraySignature).substr(1), &result); + jenv.DeleteLocalRef(result); + break; + } + } + + return value; +} + +void ArrayElementAccessor::SetArrayElement(napi_env env, napi_value array, uint32_t index, + const string& arraySignature, napi_value value, + ObjectManager* objectManager, jobject arrayObject) { + JEnv jenv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + napi_status status; + JniLocalRef localArr; + jobject arr; + if (arrayObject != nullptr) { + arr = arrayObject; + } else { + localArr = objectManager->GetJavaObjectByJsObject(array); + assertNonNullNativeArray(localArr); + arr = localArr; + } + + // Dispatch on the element-type char (no substr allocation, no string-compare + // chain). + switch (arraySignature[1]) { + case 'Z': { //bool + bool b; + NAPI_GUARD(napi_get_value_bool(env, value, &b)) { + return; + } + jboolean v = static_cast(b); + jenv.SetBooleanArrayRegion((jbooleanArray) arr, index, 1, &v); + break; + } + case 'B': { //byte + int32_t i; + NAPI_GUARD(napi_get_value_int32(env, value, &i)) { + return; + } + jbyte v = static_cast(i); + jenv.SetByteArrayRegion((jbyteArray) arr, index, 1, &v); + break; + } + case 'C': { //char + size_t str_len; + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, 0, &str_len)) { + return; + } + string str(str_len, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, value, &str[0], str_len + 1, &str_len)) { + return; + } + JniLocalRef s(jenv.NewString(reinterpret_cast(str.c_str()), 1)); + jboolean isCopy = false; + const char* singleChar = jenv.GetStringUTFChars(s, &isCopy); + jchar v = *singleChar; + jenv.ReleaseStringUTFChars(s, singleChar); + jenv.SetCharArrayRegion((jcharArray) arr, index, 1, &v); + break; + } + case 'S': { //short + int32_t i; + NAPI_GUARD(napi_get_value_int32(env, value, &i)) { + return; + } + jshort v = static_cast(i); + jenv.SetShortArrayRegion((jshortArray) arr, index, 1, &v); + break; + } + case 'I': { //int + int32_t i; + NAPI_GUARD(napi_get_value_int32(env, value, &i)) { + return; + } + jint v = static_cast(i); + jenv.SetIntArrayRegion((jintArray) arr, index, 1, &v); + break; + } + case 'J': { //long + int64_t l; + NAPI_GUARD(napi_get_value_int64(env, value, &l)) { + return; + } + jlong v = static_cast(l); + jenv.SetLongArrayRegion((jlongArray) arr, index, 1, &v); + break; + } + case 'F': { //float + double d; + NAPI_GUARD(napi_get_value_double(env, value, &d)) { + return; + } + jfloat v = static_cast(d); + jenv.SetFloatArrayRegion((jfloatArray) arr, index, 1, &v); + break; + } + case 'D': { //double + double d; + NAPI_GUARD(napi_get_value_double(env, value, &d)) { + return; + } + jdouble v = static_cast(d); + jenv.SetDoubleArrayRegion((jdoubleArray) arr, index, 1, &v); + break; + } + default: { //string or object + napi_valuetype ref_type; + NAPI_GUARD(napi_typeof(env, value, &ref_type)) { + return; + } + + if (ref_type == napi_object || ref_type == napi_function || ref_type == napi_string) { + JsArgToArrayConverter argConverter(env, value, false, (int) Type::Null, objectManager); + if (argConverter.IsValid()) { + jobject objectElementValue = argConverter.GetConvertedArg(); + jenv.SetObjectArrayElement((jobjectArray) arr, index, objectElementValue); + } else { + JsArgToArrayConverter::Error err = argConverter.GetError(); + throw NativeScriptException(string(err.msg)); + } + } else { + throw NativeScriptException(string("Cannot assign primitive value to array of objects.")); + } + break; + } + } +} + +napi_value ArrayElementAccessor::ConvertToJsValue(napi_env env, ObjectManager* objectManager, JEnv& jenv, std::string_view elementSignature, const void* value) { + napi_status status; + napi_value jsValue; + + switch (elementSignature[0]) { + case 'Z': + NAPI_GUARD(napi_get_boolean(env, *(jboolean*) value, &jsValue)) { + return nullptr; + } + break; + case 'B': + NAPI_GUARD(napi_create_int32(env, *(jbyte*) value, &jsValue)) { + return nullptr; + } + break; + case 'C': + NAPI_GUARD(napi_create_string_utf8(env, (const char*) value, 1, &jsValue)) { + return nullptr; + } + break; + case 'S': + NAPI_GUARD(napi_create_int32(env, *(jshort*) value, &jsValue)) { + return nullptr; + } + break; + case 'I': + NAPI_GUARD(napi_create_int32(env, *(jint*) value, &jsValue)) { + return nullptr; + } + break; + case 'J': + NAPI_GUARD(napi_create_int64(env, *(jlong*) value, &jsValue)) { + return nullptr; + } + break; + case 'F': + NAPI_GUARD(napi_create_double(env, *(jfloat*) value, &jsValue)) { + return nullptr; + } + break; + case 'D': + NAPI_GUARD(napi_create_double(env, *(jdouble*) value, &jsValue)) { + return nullptr; + } + break; + default: { + if (nullptr != (*(jobject*) value)) { + bool isString = elementSignature == "Ljava/lang/String;"; + + if (isString) { + jsValue = ArgConverter::jstringToJsString(env, *(jstring *) value); + } else { + jint javaObjectID = objectManager->GetOrCreateObjectId(*(jobject*) value); + jsValue = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, jsValue)) { + string className; + if (elementSignature[0] == '[') { + className = Util::JniClassPathToCanonicalName(string(elementSignature)); + } else { + className = objectManager->GetClassName(*(jobject*) value); + } + + jsValue = objectManager->CreateJSWrapper(javaObjectID, className); + } + } + } else { + NAPI_GUARD(napi_get_null(env, &jsValue)) { + return nullptr; + } + } + break; + } + } + + return jsValue; +} + +void ArrayElementAccessor::assertNonNullNativeArray(tns::JniLocalRef& arrayReference) { + if(arrayReference.IsNull()){ + throw NativeScriptException("Failed calling indexer operator on native array. The JavaScript instance no longer has available Java instance counterpart."); + } +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.h b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.h new file mode 100644 index 000000000..99032a244 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayElementAccessor.h @@ -0,0 +1,35 @@ +#ifndef ARRAYELEMENTACCESSOR_H_ +#define ARRAYELEMENTACCESSOR_H_ + +#include "JEnv.h" +#include "JniLocalRef.h" +#include "js_native_api.h" +#include +#include +#include "ObjectManager.h" + + +namespace tns { + class ArrayElementAccessor { + public: + // `objectManager` and `arrayObject` may be supplied pre-resolved by the + // caller (e.g. the host indexed interceptor or an array-loop helper) to + // avoid a locked env->runtime lookup and re-resolving the Java array on + // every element. Both fall back to resolving internally when omitted. + napi_value GetArrayElement(napi_env env, napi_value array, uint32_t index, + const std::string& arraySignature, + ObjectManager* objectManager = nullptr, + jobject arrayObject = nullptr); + + void SetArrayElement(napi_env env, napi_value array, uint32_t index, + const std::string& arraySignature, napi_value value, + ObjectManager* objectManager = nullptr, + jobject arrayObject = nullptr); + + private: + napi_value ConvertToJsValue(napi_env env, ObjectManager* objectManager, JEnv& jEnv, std::string_view elementSignature, const void* value); + void assertNonNullNativeArray(tns::JniLocalRef& arrayReference); + }; +} + +#endif /* ARRAYELEMENTACCESSOR_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayHelper.cpp b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.cpp new file mode 100644 index 000000000..f00b4c57e --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.cpp @@ -0,0 +1,199 @@ +#include "ArrayHelper.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +ArrayHelper::ArrayHelper() { +} + +void ArrayHelper::Init(napi_env env) { + napi_status status; + JEnv jenv; + + RUNTIME_CLASS = jenv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + CREATE_ARRAY_HELPER = jenv.GetStaticMethodID(RUNTIME_CLASS, "createArrayHelper", "(Ljava/lang/String;I)Ljava/lang/Object;"); + assert(CREATE_ARRAY_HELPER != nullptr); + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + + napi_value arrayConstructor; + NAPI_GUARD(napi_get_named_property(env, global, "Array", &arrayConstructor)) { + return; + } + + napi_util::napi_set_function(env, arrayConstructor, "create", CreateJavaArrayCallback, nullptr); + +} + +napi_value ArrayHelper::CreateJavaArrayCallback(napi_env env, napi_callback_info info) { + try { + napi_value array = CreateJavaArray(env, info); + return array; + } catch (NativeScriptException& e) { + e.ReThrowToNapi(env); + } catch (std::exception& e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value ArrayHelper::CreateJavaArray(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + if (argc != 2) { + Throw(env, "Expect two parameters."); + return nullptr; + } + + napi_value type = argv[0]; + napi_value length = argv[1]; + + JniLocalRef array; + + auto runtime = Runtime::GetRuntime(env); + auto objectManager = runtime->GetObjectManager(); + + napi_valuetype typeType; + NAPI_GUARD(napi_typeof(env, type, &typeType)) { + return nullptr; + } + + napi_valuetype lengthType; + NAPI_GUARD(napi_typeof(env, length, &lengthType)) { + return nullptr; + } + + if (typeType == napi_string) { + if (lengthType != napi_number) { + Throw(env, "Expect integer value as a second argument."); + return nullptr; + } + + bool isFloat = napi_util::is_float(env, length); + + if (isFloat) { + Throw(env, "Expect integer value as a second argument. It is a float"); + return nullptr; + } + + int32_t len; + NAPI_GUARD(napi_get_value_int32(env, length, &len)) { + return nullptr; + } + if (len < 0) { + Throw(env, "Expect non-negative integer value as a second argument."); + return nullptr; + } + + string typeName = ArgConverter::ConvertToString(env, type); + array = JniLocalRef(CreateArrayByClassName(typeName, len)); + } else if (typeType == napi_function || typeType == napi_object) { + if (lengthType != napi_number) { + Throw(env, "Expect integer value as a second argument."); + return nullptr; + } + + bool isFloat = napi_util::is_float(env, length); + + if (isFloat) { + Throw(env, "Expect integer value as a second argument."); + return nullptr; + } + + int32_t len; + NAPI_GUARD(napi_get_value_int32(env, length, &len)) { + return nullptr; + } + if (len < 0) { + Throw(env, "Expect non-negative integer value as a second argument."); + return nullptr; + } + + napi_value classVal; + NAPI_GUARD(napi_get_named_property(env, type, "class", &classVal)) { + return nullptr; + } + + napi_valuetype classValType; + NAPI_GUARD(napi_typeof(env, classVal, &classValType)) { + return nullptr; + } + + if (classValType == napi_undefined) { + Throw(env, "Expect known class as a second argument."); + return nullptr; + } + + auto c = objectManager->GetJavaObjectByJsObject(classVal); + + JEnv jenv; + array = jenv.NewObjectArray(len, static_cast(c), nullptr); + } else { + Throw(env, "Expect primitive type name or class function as a first argument"); + return nullptr; + } + + jint javaObjectID = objectManager->GetOrCreateObjectId(array); + return objectManager->CreateJSWrapper(javaObjectID, "" /* ignored */, array); +} + +void ArrayHelper::Throw(napi_env env, const std::string& errorMessage) { + napi_status status; + napi_value errMsg; + NAPI_GUARD(napi_create_string_utf8(env, errorMessage.c_str(), NAPI_AUTO_LENGTH, &errMsg)) { + return; + } + + napi_value err; + NAPI_GUARD(napi_create_error(env, nullptr, errMsg, &err)) { + return; + } + + NAPI_GUARD(napi_throw(env, err)) {} +} + +jobject ArrayHelper::CreateArrayByClassName(const string& typeName, int length) { + JEnv jEnv; + jobject array; + + if (typeName == "char") { + array = jEnv.NewCharArray(length); + } else if (typeName == "boolean") { + array = jEnv.NewBooleanArray(length); + } else if (typeName == "byte") { + array = jEnv.NewByteArray(length); + } else if (typeName == "short") { + array = jEnv.NewShortArray(length); + } else if (typeName == "int") { + array = jEnv.NewIntArray(length); + } else if (typeName == "long") { + array = jEnv.NewLongArray(length); + } else if (typeName == "float") { + array = jEnv.NewFloatArray(length); + } else if (typeName == "double") { + array = jEnv.NewDoubleArray(length); + } else { + JniLocalRef s(jEnv.NewStringUTF(typeName.c_str())); + array = jEnv.CallStaticObjectMethod(RUNTIME_CLASS, CREATE_ARRAY_HELPER, (jstring)s, length); + } + + return array; +} + +jclass ArrayHelper::RUNTIME_CLASS = nullptr; +jmethodID ArrayHelper::CREATE_ARRAY_HELPER = nullptr; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/ArrayHelper.h b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.h new file mode 100644 index 000000000..bb234df26 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArrayHelper.h @@ -0,0 +1,30 @@ +#ifndef ARRAYHELPER_H_ +#define ARRAYHELPER_H_ + +#include "js_native_api.h" +#include "ObjectManager.h" +#include + +namespace tns { +class ArrayHelper { + public: + static void Init(napi_env env); + + private: + ArrayHelper(); + + static napi_value CreateJavaArrayCallback(napi_env env, napi_callback_info info); + + static napi_value CreateJavaArray(napi_env env, napi_callback_info info); + + static void Throw(napi_env env, const std::string& errorMessage); + + static jobject CreateArrayByClassName(const std::string& typeName, int length); + + static jclass RUNTIME_CLASS; + + static jmethodID CREATE_ARRAY_HELPER; +}; +} + +#endif /* ARRAYHELPER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgConverter.cpp b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.cpp new file mode 100644 index 000000000..1e9730c23 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.cpp @@ -0,0 +1,926 @@ +#include "JsArgConverter.h" +#include "ObjectManager.h" +#include "JniSignatureParser.h" +#include "JsArgToArrayConverter.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +JsArgConverter::JsArgConverter(napi_env env, napi_value caller, napi_value *args, size_t argc, + const std::string &methodSignature, MetadataEntry *entry, JNIEnv *jniEnv, + ObjectManager *objectManager) + : m_env(env), m_jniEnv(jniEnv), m_objectManager(objectManager), m_isValid(true), + m_error(Error()) { + int napiProvidedArgumentsLength = argc; + m_argsLen = 1 + napiProvidedArgumentsLength; + + if (m_argsLen > 0) { + if ((entry != nullptr) && (entry->getIsResolved())) { + if (entry->parsedSig.empty()) { + JniSignatureParser parser(methodSignature); + entry->parsedSig = parser.Parse(); + } + m_tokens = &entry->parsedSig; + } else { + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + m_tokens = &m_ownedTokens; + } + + m_isValid = ConvertArg(env, caller, 0); + + if (!m_isValid) { + throw NativeScriptException("Error while converting argument!"); + } + + for (size_t i = 0; i < napiProvidedArgumentsLength; i++) { + m_isValid = ConvertArg(env, args[i], i + 1); + + if (!m_isValid) { + break; + } + } + } +} + +JsArgConverter::JsArgConverter(napi_env env, napi_value *args, size_t argc, + bool hasImplementationObject, const std::string &methodSignature, + MetadataEntry *entry, JNIEnv *jniEnv, ObjectManager *objectManager) + : m_env(env), m_jniEnv(jniEnv), m_objectManager(objectManager), m_isValid(true), + m_error(Error()) { + m_argsLen = !hasImplementationObject ? argc : argc - 1; + + if (m_argsLen > 0) { + if ((entry != nullptr) && (entry->getIsResolved())) { + if (entry->parsedSig.empty()) { + JniSignatureParser parser(methodSignature); + entry->parsedSig = parser.Parse(); + } + m_tokens = &entry->parsedSig; + } else { + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + m_tokens = &m_ownedTokens; + } + + for (size_t i = 0; i < m_argsLen; i++) { + m_isValid = ConvertArg(env, args[i], i); + + if (!m_isValid) { + break; + } + } + } +} + +JsArgConverter::JsArgConverter(napi_env env, napi_value *args, size_t argc, + const std::string &methodSignature) + : m_env(env), m_isValid(true), m_error(Error()) { + m_argsLen = argc; + + JniSignatureParser parser(methodSignature); + m_ownedTokens = parser.Parse(); + m_tokens = &m_ownedTokens; + + for (size_t i = 0; i < m_argsLen; i++) { + m_isValid = ConvertArg(env, args[i], i); + + if (!m_isValid) { + break; + } + } +} + +tns::BufferCastType JsArgConverter::GetCastType(napi_typedarray_type type) { + switch (type) { + case napi_uint16_array: + case napi_int16_array: + return tns::BufferCastType::Short; + case napi_uint32_array: + case napi_int32_array: + return tns::BufferCastType::Int; + case napi_float32_array: + return tns::BufferCastType::Float; + case napi_float64_array: + return tns::BufferCastType::Double; + case napi_bigint64_array: + case napi_biguint64_array: + return tns::BufferCastType::Long; + default: + return tns::BufferCastType::Byte; + } +} + +bool JsArgConverter::ConvertArg(napi_env env, napi_value arg, int index) { + napi_status status; + bool success = false; + + char buff[1024]; + buff[0] = '\0'; + + const auto &typeSignature = (*m_tokens)[index]; + + // Record only the failing index up front (cheap). The default diagnostic + // string is built lazily in GetError() from m_tokens[index], so the common + // success path pays no per-argument string allocation. A NAPI_GUARD early + // `return false` below still leaves m_error.index set for GetError(). + m_error.index = index; + + if (arg == nullptr) { + SetConvertedObject(index, nullptr); + success = false; + } else { + napi_valuetype argType; + NAPI_GUARD(napi_typeof(m_env, arg, &argType)) { + return false; + } + + if (argType == napi_object || argType == napi_function) { + bool isArray; + NAPI_GUARD(napi_is_array(m_env, arg, &isArray)) { + return false; + } + + if (isArray) { + success = typeSignature[0] == '['; + + if (success) { + success = ConvertJavaScriptArray(env, arg, index); + } + + if (!success) { + sprintf(buff, "Cannot convert array to %s at index %d", typeSignature.c_str(), + index); + } + } else { + + CastType castType = CastType::None; + +#ifdef USE_HOST_OBJECT + // A non-ok status here just means "not a host object" (some + // engines, e.g. PrimJS, return an error rather than data=NULL for + // plain objects); treat it as no host data and continue. + void *data = nullptr; + napi_get_host_object_data(env, arg, &data); + if (data) { + castType = CastType::None; + } else { + castType = NumericCasts::GetCastType(env, arg); + } +#else + castType = NumericCasts::GetCastType(m_env, arg); +#endif + + CastType castTypeCheck = NumericCasts::GetCastType(env, arg); + if (castTypeCheck != CastType::None) { + castType = castTypeCheck; + } + napi_value castValue; + napi_valuetype valueType = napi_undefined; + if (castType != CastType::None) { + castValue = NumericCasts::GetCastValue(m_env, arg); + if (castValue != nullptr) { + NAPI_GUARD(napi_typeof(env, castValue, &valueType)) { + return false; + } + } + } + + JniLocalRef obj; + + auto objectManager = m_objectManager != nullptr + ? m_objectManager + : Runtime::GetRuntime(m_env)->GetObjectManager(); + + JEnv jEnv = GetJEnv(); + + switch (castType) { + case CastType::Char: + if (valueType == napi_string) { + string value = ArgConverter::ConvertToString(m_env, castValue); + m_args[index].c = (jchar) value[0]; + success = true; + } + break; + + case CastType::Byte: + if (valueType == napi_string) { + string strValue = ArgConverter::ConvertToString(m_env, castValue); + int byteArg = atoi(strValue.c_str()); + jbyte value = (jbyte) byteArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (valueType == napi_number) { + int byteArg = napi_util::get_int32(env, castValue); + jbyte value = (jbyte) byteArg; + success = ConvertFromCastFunctionObject(value, index); + } + + break; + + case CastType::Short: + if (valueType == napi_string) { + string strValue = ArgConverter::ConvertToString(m_env, castValue); + int shortArg = atoi(strValue.c_str()); + jshort value = (jshort) shortArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (valueType == napi_number) { + int shortArg; + NAPI_GUARD(napi_get_value_int32(m_env, castValue, &shortArg)) { + return false; + } + jshort value = (jshort) shortArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Long: + if (valueType == napi_string) { + string strValue = ArgConverter::ConvertToString(m_env, castValue); + int64_t longArg = atoll(strValue.c_str()); + jlong value = (jlong) longArg; + success = ConvertFromCastFunctionObject(value, index); + } else if (valueType == napi_number) { + int64_t longArg; + NAPI_GUARD(napi_get_value_int64(m_env, castValue, &longArg)) { + return false; + } + jlong value = (jlong) longArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Float: + if (valueType == napi_number) { + double floatArg; + NAPI_GUARD(napi_get_value_double(m_env, castValue, &floatArg)) { + return false; + } + jfloat value = (jfloat) floatArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::Double: + if (valueType == napi_number) { + double doubleArg; + NAPI_GUARD(napi_get_value_double(m_env, castValue, &doubleArg)) { + return false; + } + jdouble value = (jdouble) doubleArg; + success = ConvertFromCastFunctionObject(value, index); + } + break; + + case CastType::None: + obj = objectManager->GetJavaObjectByJsObject(arg); + + if (obj.IsNull()) { + bool isArrayBuffer = false; + bool isDataView = false; + bool isTypedArray = false; + + NAPI_GUARD(napi_is_arraybuffer(env, arg, &isArrayBuffer)) { + return false; + } + if (!isArrayBuffer) { + NAPI_GUARD(napi_is_typedarray(env, arg, &isTypedArray)) { + return false; + } + if (!isTypedArray) { + NAPI_GUARD(napi_is_dataview(env, arg, &isDataView)) { + return false; + } + } + } + + if (isArrayBuffer || isDataView || isTypedArray) { + obj = JsArgConverter::GetByteBuffer(env, arg, isArrayBuffer, + isTypedArray, isDataView); + } + } + +#ifdef USE_HOST_OBJECT + if (!data) { +#endif + napi_value nullNode; + NAPI_GUARD(napi_get_named_property(env, arg, PROP_KEY_NULL_NODE_NAME, &nullNode)) { + return false; + } + if (!napi_util::is_null_or_undefined(env, nullNode)) { + SetConvertedObject(index, nullptr); + success = true; + break; + } +#ifdef USE_HOST_OBJECT + } +#endif + + success = !obj.IsNull(); + + if (success) { + SetConvertedObject(index, obj.Move(), obj.IsGlobal()); + } else { + if (napi_util::is_number_object(env, arg)) { + success = ConvertJavaScriptNumber(env, arg, index, true); + break; + } else if (napi_util::is_string_object(env, arg)) { + napi_value stringValue = napi_util::valueOf(env, arg); + success = ConvertJavaScriptString(env, stringValue, index); + break; + } else if (napi_util::is_boolean_object(env, arg)) { + napi_value boolValue = napi_util::valueOf(env, arg); + success = ConvertJavaScriptBoolean(env, boolValue, index); + break; + } + + if (!success) { + sprintf(buff, "Cannot convert object to %s at index %d", + typeSignature.c_str(), index); + } + } + break; + + default: + throw NativeScriptException("Unsupported cast type"); + } + } + } else if (argType == napi_number) { + success = ConvertJavaScriptNumber(env, arg, index, false); + + if (!success) { + sprintf(buff, "Cannot convert number to %s at index %d", typeSignature.c_str(), + index); + } + } else if (argType == napi_boolean) { + success = ConvertJavaScriptBoolean(env, arg, index); + + if (!success) { + sprintf(buff, "Cannot convert boolean to %s at index %d", typeSignature.c_str(), + index); + } + } else if (argType == napi_string) { + success = ConvertJavaScriptString(env, arg, index); + + if (!success) { + sprintf(buff, "Cannot convert string to %s at index %d", typeSignature.c_str(), + index); + } + } else if (argType == napi_undefined || argType == napi_null) { + SetConvertedObject(index, nullptr); + success = true; + } else { + SetConvertedObject(index, nullptr); + success = false; + } + } + + if (!success) { + m_error.index = index; + // Keep the seeded default when no specific message was formatted (buff + // untouched), avoiding a garbage/empty message. + if (buff[0] != '\0') { + m_error.msg = string(buff); + } + } + + return success; +} + + +void JsArgConverter::SetConvertedObject(int index, jobject obj, bool isGlobal) { + m_args[index].l = obj; + if ((obj != nullptr) && !isGlobal) { + m_args_refs[m_args_refs_size++] = index; + } +} + +bool JsArgConverter::ConvertJavaScriptNumber(napi_env env, napi_value jsValue, int index, + bool isNumberObject = false) { + napi_status status; + bool success = true; + + jvalue value = {0}; + + const auto &typeSignature = (*m_tokens)[index]; + + const char typePrefix = typeSignature[0]; + + switch (typePrefix) { + case 'B': { // byte + int32_t intValue; + if (isNumberObject) { + NAPI_GUARD(napi_get_value_int32(env, napi_util::valueOf(env, jsValue), &intValue)) { + return false; + } + } else { + NAPI_GUARD(napi_get_value_int32(env, jsValue, &intValue)) { + return false; + } + } + value.b = (jbyte) intValue; + break; + } + case 'S': { // short + int intValue; + if (isNumberObject) { + NAPI_GUARD(napi_get_value_int32(env, napi_util::valueOf(env, jsValue), &intValue)) { + return false; + } + } else { + NAPI_GUARD(napi_get_value_int32(env, jsValue, &intValue)) { + return false; + } + } + value.s = (jshort) intValue; + break; + } + case 'I': { // int + int intValue; + if (isNumberObject) { + NAPI_GUARD(napi_get_value_int32(env, napi_util::valueOf(env, jsValue), &intValue)) { + return false; + } + } else { + NAPI_GUARD(napi_get_value_int32(env, jsValue, &intValue)) { + return false; + } + } + value.i = (jint) intValue; + break; + } + case 'J': { // long + int64_t intValue; + if (isNumberObject) { + NAPI_GUARD(napi_get_value_int64(env, napi_util::valueOf(env, jsValue), &intValue)) { + return false; + } + } else { + NAPI_GUARD(napi_get_value_int64(env, jsValue, &intValue)) { + return false; + } + } + value.j = (jlong) intValue; + break; + } + case 'F': { // float + double doubleValue; + if (isNumberObject) { + NAPI_GUARD(napi_get_value_double(env, napi_util::valueOf(env, jsValue), &doubleValue)) { + return false; + } + } else { + NAPI_GUARD(napi_get_value_double(env, jsValue, &doubleValue)) { + return false; + } + } + value.f = (jfloat) doubleValue; + break; + } + case 'D': { // double + double doubleValue; + if (isNumberObject) { + NAPI_GUARD(napi_get_value_double(env, napi_util::valueOf(env, jsValue), &doubleValue)) { + return false; + } + } else { + NAPI_GUARD(napi_get_value_double(env, jsValue, &doubleValue)) { + return false; + } + } + value.d = (jdouble) doubleValue; + break; + } + default: + success = false; + break; + } + + if (success) { + m_args[index] = value; + } + + return success; +} + +bool JsArgConverter::ConvertJavaScriptBoolean(napi_env env, napi_value jsValue, int index) { + napi_status status; + bool success; + + const auto &typeSignature = (*m_tokens)[index]; + + if (typeSignature == "Z") { + bool argValue; + NAPI_GUARD(napi_get_value_bool(env, jsValue, &argValue)) { + return false; + } + + jboolean value = argValue ? JNI_TRUE : JNI_FALSE; + m_args[index].z = value; + success = true; + } else { + success = false; + } + + return success; +} + +bool JsArgConverter::ConvertJavaScriptString(napi_env env, napi_value jsValue, int index) { + jstring stringObject = ArgConverter::ConvertToJavaString(env, jsValue); + SetConvertedObject(index, stringObject); + return true; +} + +bool JsArgConverter::ConvertJavaScriptArray(napi_env env, napi_value jsArr, int index) { + napi_status status; + bool success = true; + + jarray arr = nullptr; + + uint32_t jsLen; + NAPI_GUARD(napi_get_array_length(env, jsArr, &jsLen)) { + return false; + } + + const jsize arrLength = jsLen; + + const auto &arraySignature = (*m_tokens)[index]; + + std::string elementType = arraySignature.substr(1); + + const char elementTypePrefix = elementType[0]; + + jclass elementClass; + std::string strippedClassName; + + JEnv jenv = GetJEnv(); + switch (elementTypePrefix) { + case 'Z': { + arr = jenv.NewBooleanArray(arrLength); + std::vector bools(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + + bool boolValue; + NAPI_GUARD(napi_get_value_bool(env, element, &boolValue)) {} + bools[i] = (jboolean) boolValue; + } + jenv.SetBooleanArrayRegion((jbooleanArray) arr, 0, arrLength, bools.data()); + break; + } + case 'B': { + arr = jenv.NewByteArray(arrLength); + std::vector bytes(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + int32_t intValue; + NAPI_GUARD(napi_get_value_int32(env, element, &intValue)) {} + bytes[i] = (jbyte) intValue; + } + jenv.SetByteArrayRegion((jbyteArray) arr, 0, arrLength, bytes.data()); + break; + } + case 'C': { + arr = jenv.NewCharArray(arrLength); + std::vector chars(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + size_t str_len; + NAPI_GUARD(napi_get_value_string_utf8(env, element, nullptr, 0, &str_len)) {} + std::string str(str_len, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, element, &str[0], str_len + 1, &str_len)) {} + chars[i] = (jchar) str[0]; + } + jenv.SetCharArrayRegion((jcharArray) arr, 0, arrLength, chars.data()); + break; + } + case 'S': { + arr = jenv.NewShortArray(arrLength); + std::vector shorts(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + int32_t intValue; + NAPI_GUARD(napi_get_value_int32(env, element, &intValue)) {} + shorts[i] = (jshort) intValue; + } + jenv.SetShortArrayRegion((jshortArray) arr, 0, arrLength, shorts.data()); + break; + } + case 'I': { + arr = jenv.NewIntArray(arrLength); + std::vector ints(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + int32_t intValue; + NAPI_GUARD(napi_get_value_int32(env, element, &intValue)) {} + ints[i] = (jint) intValue; + } + jenv.SetIntArrayRegion((jintArray) arr, 0, arrLength, ints.data()); + break; + } + case 'J': { + arr = jenv.NewLongArray(arrLength); + std::vector longs(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + int64_t intValue; + NAPI_GUARD(napi_get_value_int64(env, element, &intValue)) {} + longs[i] = (jlong) intValue; + } + jenv.SetLongArrayRegion((jlongArray) arr, 0, arrLength, longs.data()); + break; + } + case 'F': { + arr = jenv.NewFloatArray(arrLength); + std::vector floats(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + double doubleValue; + NAPI_GUARD(napi_get_value_double(env, element, &doubleValue)) {} + floats[i] = (jfloat) doubleValue; + } + jenv.SetFloatArrayRegion((jfloatArray) arr, 0, arrLength, floats.data()); + break; + } + case 'D': { + arr = jenv.NewDoubleArray(arrLength); + std::vector doubles(arrLength); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + double doubleValue; + NAPI_GUARD(napi_get_value_double(env, element, &doubleValue)) {} + doubles[i] = (jdouble) doubleValue; + } + jenv.SetDoubleArrayRegion((jdoubleArray) arr, 0, arrLength, doubles.data()); + break; + } + case 'L': + strippedClassName = elementType.substr(1, elementType.length() - 2); + elementClass = jenv.FindClass(strippedClassName); + arr = jenv.NewObjectArray(arrLength, elementClass, nullptr); + for (uint32_t i = 0; i < arrLength; i++) { + napi_value element; + NAPI_GUARD(napi_get_element(env, jsArr, i, &element)) {} + JsArgToArrayConverter c(env, element, false, (int) Type::Null, + m_objectManager != nullptr + ? m_objectManager + : Runtime::GetRuntime(env)->GetObjectManager()); + jobject o = c.GetConvertedArg(); + jenv.SetObjectArrayElement((jobjectArray) arr, (int) i, o); + } + break; + default: + success = false; + break; + } + + if (success) { + SetConvertedObject(index, arr); + } + + return success; +} + + +template +bool JsArgConverter::ConvertFromCastFunctionObject(T value, int index) { + bool success = false; + + const auto &typeSignature = (*m_tokens)[index]; + + const char typeSignaturePrefix = typeSignature[0]; + + switch (typeSignaturePrefix) { + case 'B': + m_args[index].b = (jbyte) value; + success = true; + break; + + case 'S': + m_args[index].s = (jshort) value; + success = true; + break; + + case 'I': + m_args[index].i = (jint) value; + success = true; + break; + + case 'J': + m_args[index].j = (jlong) value; + success = true; + break; + + case 'F': + m_args[index].f = (jfloat) value; + success = true; + break; + + case 'D': + m_args[index].d = (jdouble) value; + success = true; + break; + + default: + success = false; + break; + } + + return success; +} + +int JsArgConverter::Length() const { + return m_argsLen; +} + +bool JsArgConverter::IsValid() const { + return m_isValid; +} + +jvalue *JsArgConverter::ToArgs() { + return m_args; +} + +JsArgConverter::Error JsArgConverter::GetError() const { + Error e = m_error; + // Build the default diagnostic lazily (only when an error is actually + // queried and no specific message was already formatted on the failure path). + if (e.index >= 0 && e.msg.empty() && m_tokens != nullptr && + e.index < (int) m_tokens->size()) { + e.msg = "Cannot convert argument at index " + std::to_string(e.index) + + " to " + (*m_tokens)[e.index]; + } + return e; +} + +JsArgConverter::~JsArgConverter() { + if (m_argsLen > 0) { + JEnv env = GetJEnv(); + for (int i = 0; i < m_args_refs_size; i++) { + int index = m_args_refs[i]; + if (index != -1) { + env.DeleteLocalRef(m_args[index].l); + } + } + } +} + +JniLocalRef JsArgConverter::GetByteBuffer(napi_env env, napi_value object, bool isArrayBuffer, + bool isTypedArray, bool isDataView) { + napi_status status; + JEnv jEnv; + + BufferCastType bufferCastType = tns::BufferCastType::Byte; + size_t offset = 0; + size_t length = 0; + void *data = nullptr; + + if (isTypedArray) { + napi_typedarray_type type; + napi_value arrayBuffer = nullptr; + size_t byteOffset = 0; + // Bail on failure: continuing would feed uninitialized arrayBuffer/offset + // (and a possibly-null data pointer) into NewDirectByteBuffer below. + NAPI_GUARD(napi_get_typedarray_info(env, object, &type, nullptr, &data, + &arrayBuffer, &byteOffset)) { + return JniLocalRef(); + } + NAPI_GUARD(napi_get_arraybuffer_info(env, arrayBuffer, nullptr, &length)) { + return JniLocalRef(); + } + + offset = byteOffset; + bufferCastType = JsArgConverter::GetCastType(type); + } else if (isArrayBuffer) { + NAPI_GUARD(napi_get_arraybuffer_info(env, object, &data, &length)) { + return JniLocalRef(); + } + } else if (isDataView) { + NAPI_GUARD(napi_get_dataview_info(env, object, &length, &data, nullptr, + &offset)) { + return JniLocalRef(); + } + } + + jobject directBuffer; + + if (isDataView || isTypedArray) { + directBuffer =jEnv.NewDirectByteBuffer(static_cast(data) + offset, length); + } else { + directBuffer = jEnv.NewDirectByteBuffer(static_cast(data), length); + } + + + auto directBufferClazz = jEnv.GetObjectClass(directBuffer); + + auto byteOrderId = BYTE_ORDER_METHOD_ID; + + if (!BYTE_ORDER_METHOD_ID) { + byteOrderId = jEnv.GetMethodID(directBufferClazz, "order", + "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); + BYTE_ORDER_METHOD_ID = byteOrderId; + } + + auto byteOrderClazz = jEnv.FindClass("java/nio/ByteOrder"); + + auto byteOrderEnumId = BYTE_ORDER_ENUM_ID; + + if (!byteOrderEnumId) { + byteOrderEnumId = jEnv.GetStaticMethodID(byteOrderClazz, + "nativeOrder", + "()Ljava/nio/ByteOrder;"); + BYTE_ORDER_ENUM_ID = byteOrderEnumId; + } + + auto nativeByteOrder = jEnv.CallStaticObjectMethodA(byteOrderClazz, + byteOrderEnumId, + nullptr); + + directBuffer = jEnv.CallObjectMethod(directBuffer, byteOrderId, + nativeByteOrder); + + jobject buffer; + + if (bufferCastType == BufferCastType::Short) { + auto id = AS_SHORT_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asShortBuffer", + "()Ljava/nio/ShortBuffer;"); + AS_SHORT_BUFFER = id; + } + + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Int) { + auto id = AS_INT_BUFFER; + + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asIntBuffer", + "()Ljava/nio/IntBuffer;"); + AS_INT_BUFFER = id; + } + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Long) { + auto id = AS_LONG_BUFFER; + + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asLongBuffer", + "()Ljava/nio/LongBuffer;"); + AS_LONG_BUFFER = id; + } + + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Float) { + + auto id = AS_FLOAT_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asFloatBuffer", + "()Ljava/nio/FloatBuffer;"); + AS_FLOAT_BUFFER = id; + } + + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else if (bufferCastType == BufferCastType::Double) { + + auto id = AS_DOUBLE_BUFFER; + if (!id) { + id = jEnv.GetMethodID(directBufferClazz, "asDoubleBuffer", + "()Ljava/nio/DoubleBuffer;"); + AS_DOUBLE_BUFFER = id; + } + buffer = jEnv.CallObjectMethodA(directBuffer, id, nullptr); + } else { + buffer = directBuffer; + } + + buffer = jEnv.NewGlobalRef(buffer); + + ObjectManager *objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + + int id = objectManager->GetOrCreateObjectId(buffer); + auto clazz = jEnv.GetObjectClass(buffer); + + ObjectManager::MarkObject(env, object); + + objectManager->Link(object, id, clazz); + + return objectManager->GetJavaObjectByJsObject(object); +} + +jmethodID JsArgConverter::BYTE_ORDER_METHOD_ID = nullptr; +jmethodID JsArgConverter::BYTE_ORDER_ENUM_ID = nullptr; +jmethodID JsArgConverter::AS_SHORT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_LONG_BUFFER = nullptr; +jmethodID JsArgConverter::AS_FLOAT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_INT_BUFFER = nullptr; +jmethodID JsArgConverter::AS_DOUBLE_BUFFER = nullptr; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgConverter.h b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.h new file mode 100644 index 000000000..451efcd66 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgConverter.h @@ -0,0 +1,115 @@ +#ifndef JSARGCONVERTER_H_ +#define JSARGCONVERTER_H_ + +#include +#include +#include "JEnv.h" +#include "Runtime.h" +#include "MetadataEntry.h" + +namespace tns { + + enum class BufferCastType { + Byte, + Short, + Int, + Long, + Float, + Double + }; + + class JsArgConverter { + public: + + JsArgConverter(napi_env env, napi_value caller, napi_value* args, size_t argc, const std::string& methodSignature, MetadataEntry* entry, JNIEnv* jniEnv = nullptr, ObjectManager* objectManager = nullptr); + + JsArgConverter(napi_env env, napi_value* args, size_t argc, bool hasImplementationObject, const std::string& methodSignature, MetadataEntry* entry, JNIEnv* jniEnv = nullptr, ObjectManager* objectManager = nullptr); + + JsArgConverter(napi_env env, napi_value* args, size_t argc, const std::string& methodSignature); + + ~JsArgConverter(); + + jvalue* ToArgs(); + + int Length() const; + + bool IsValid() const; + + struct Error; + + Error GetError() const; + + struct Error { + Error() : + index(-1), msg(std::string()) { + } + + int index; + std::string msg; + }; + + static BufferCastType GetCastType(napi_typedarray_type type); + + static JniLocalRef GetByteBuffer(napi_env env, napi_value object, bool isArrayBuffer, bool isTypedArray, bool isDataView); + + + + static jmethodID BYTE_ORDER_METHOD_ID; + static jmethodID BYTE_ORDER_ENUM_ID; + static jmethodID AS_SHORT_BUFFER; + static jmethodID AS_INT_BUFFER; + static jmethodID AS_LONG_BUFFER; + static jmethodID AS_FLOAT_BUFFER; + static jmethodID AS_DOUBLE_BUFFER; + private: + + bool ConvertArg(napi_env env, napi_value arg, int index); + + bool ConvertJavaScriptArray(napi_env env, napi_value jsArr, int index); + + bool ConvertJavaScriptNumber(napi_env env, napi_value jsValue, int index, bool isNumberObject); + + bool ConvertJavaScriptBoolean(napi_env env, napi_value jsValue, int index); + + bool ConvertJavaScriptString(napi_env env, napi_value jsValue, int index); + + void SetConvertedObject(int index, jobject obj, bool isGlobal = false); + + + template + bool ConvertFromCastFunctionObject(T value, int index); + + napi_env m_env; + + // Current thread's JNIEnv* threaded down from the caller (avoids + // re-querying the JavaVM via GetEnv); nullptr => construct locally. + JNIEnv* m_jniEnv = nullptr; + + // Returns a JEnv reusing the threaded JNIEnv* when available. + inline JEnv GetJEnv() const { + return m_jniEnv != nullptr ? JEnv(m_jniEnv, JEnv::Adopt::Trusted) : JEnv(); + } + + // Cached ObjectManager threaded from the caller (avoids a locked + // env->runtime lookup per object-typed argument). + ObjectManager* m_objectManager = nullptr; + + int m_argsLen; + + bool m_isValid; + + jvalue m_args[255]; + int m_args_refs[255]; + int m_args_refs_size = 0; + + // Parsed argument-type tokens. On the common path this points directly at + // the MetadataEntry's cached `parsedSig` (no copy); only the entry-less / + // unresolved fallback owns its tokens in m_ownedTokens. + const std::vector* m_tokens = nullptr; + std::vector m_ownedTokens; + + Error m_error; + }; +} + +#endif /* JSARGCONVERTER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.cpp b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.cpp new file mode 100644 index 000000000..824e42622 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.cpp @@ -0,0 +1,482 @@ +#include "JsArgToArrayConverter.h" +#include +#include "ObjectManager.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "MetadataNode.h" +#include "JsArgConverter.h" + +using namespace std; +using namespace tns; + +JsArgToArrayConverter::JsArgToArrayConverter(napi_env env, napi_value arg, + bool isImplementationObject, int classReturnType, + ObjectManager* objectManager) + : m_arr(nullptr), m_argsAsObject(nullptr), m_argsLen(0), m_isValid(false), m_error(Error()), + m_return_type(classReturnType) { + m_objectManager = objectManager; + if (!isImplementationObject) { + m_argsLen = 1; + m_argsAsObject = (m_argsLen <= INLINE_CAPACITY) ? m_inlineArgs : new jobject[m_argsLen]; + memset(m_argsAsObject, 0, m_argsLen * sizeof(jobject)); + + m_isValid = ConvertArg(env, arg, 0); + } +} + +JsArgToArrayConverter::JsArgToArrayConverter(napi_env env, size_t argc, napi_value *argv, + bool hasImplementationObject) + : m_arr(nullptr), m_argsAsObject(nullptr), m_argsLen(0), m_isValid(false), m_error(Error()), + m_return_type(static_cast(Type::Null)) { + m_argsLen = !hasImplementationObject ? argc : argc - 2; + + bool success = true; + + if (m_argsLen > 0) { + m_argsAsObject = (m_argsLen <= INLINE_CAPACITY) ? m_inlineArgs : new jobject[m_argsLen]; + memset(m_argsAsObject, 0, m_argsLen * sizeof(jobject)); + + for (int i = 0; i < m_argsLen; i++) { + success = ConvertArg(env, argv[i], i); + + if (!success) { + break; + } + } + } + + m_isValid = success; +} + +bool JsArgToArrayConverter::ConvertArg(napi_env env, napi_value arg, int index) { + bool success = false; + napi_status status; + // Error text is built only on failure (avoids a per-call stringstream). + std::string errMsg; + + // Seed a default diagnostic: the NAPI_GUARD bails below `return false` + // without reaching the error-population tail, and the caller loop stops at + // the first failing argument, so GetError() always carries a non-empty, + // indexed message even on those early-exit paths. + m_error.index = index; + m_error.msg = "Cannot marshal JavaScript argument at index " + + std::to_string(index) + " to Java type."; + + JEnv jEnv; + + Type returnType = JType::getClassType(m_return_type); + + napi_valuetype argType; + NAPI_GUARD(napi_typeof(env, arg, &argType)) { + return false; + } + + if (argType == napi_undefined || argType == napi_null) { + SetConvertedObject(jEnv, index, nullptr); + success = true; + } else if (argType == napi_number) { + double d; + NAPI_GUARD(napi_get_value_double(env, arg, &d)) { + return false; + } + int64_t i = (int64_t) d; + + bool isWholeNumber = d == i; + + if (isWholeNumber) { + jobject obj; + + if ((INT_MIN <= i) && (i <= INT_MAX) && + (returnType == Type::Int || returnType == Type::Null)) { + obj = JType::NewInt(jEnv, (jint) i); + } else { + obj = JType::NewLong(jEnv, (jlong) d); + } + + SetConvertedObject(jEnv, index, obj); + success = true; + } else { + jobject obj; + + if ((FLT_MIN <= d) && (d <= FLT_MAX) && + (returnType == Type::Float || returnType == Type::Null)) { + obj = JType::NewFloat(jEnv, (jfloat) d); + } else { + obj = JType::NewDouble(jEnv, (jdouble) d); + } + + SetConvertedObject(jEnv, index, obj); + success = true; + } + } else if (argType == napi_boolean) { + bool value; + NAPI_GUARD(napi_get_value_bool(env, arg, &value)) { + return false; + } + auto javaObject = JType::NewBoolean(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } else if (argType == napi_string) { + auto stringObject = ArgConverter::ConvertToJavaString(env, arg); + SetConvertedObject(jEnv, index, stringObject); + success = true; + } else if (argType == napi_object || argType == napi_function) { + napi_value jsObj = arg; + + + CastType castType = CastType::None; +#ifdef USE_HOST_OBJECT + // A non-ok status here just means "not a host object" (some engines, + // e.g. PrimJS, return an error rather than data=NULL for plain objects); + // treat it as no host data and continue. + void *data = nullptr; + napi_get_host_object_data(env, jsObj, &data); + if (data) { + castType = CastType::None; + } else { + castType = NumericCasts::GetCastType(env, jsObj); + } +#else + castType = NumericCasts::GetCastType(env, jsObj); +#endif + + napi_value castValue; + jchar charValue; + jbyte byteValue; + jshort shortValue; + jlong longValue; + jfloat floatValue; + jdouble doubleValue; + jobject javaObject; + JniLocalRef obj; + + auto objectManager = m_objectManager != nullptr + ? m_objectManager + : Runtime::GetRuntime(env)->GetObjectManager(); + + switch (castType) { + case CastType::Char: + castValue = NumericCasts::GetCastValue(env, jsObj); + charValue = '\0'; + if (castValue != nullptr) { + string str = ArgConverter::ConvertToString(env, castValue); + charValue = (jchar) str[0]; + } + javaObject = JType::NewChar(jEnv, charValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Byte: + castValue = NumericCasts::GetCastValue(env, jsObj); + byteValue = 0; + + if (castValue != nullptr) { + if (napi_util::is_of_type(env, castValue, napi_string)) { + string value = ArgConverter::ConvertToString(env, castValue); + int byteArg = atoi(value.c_str()); + byteValue = (jbyte) byteArg; + } else { + int byteArg = napi_util::get_int32(env, castValue); + byteValue = (jbyte) byteArg; + } + } + + javaObject = JType::NewByte(jEnv, byteValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Short: + castValue = NumericCasts::GetCastValue(env, jsObj); + shortValue = 0; + if (castValue != nullptr) { + if (napi_util::is_of_type(env, castValue, napi_string)) { + string value = ArgConverter::ConvertToString(env, castValue); + int shortArg = atoi(value.c_str()); + shortValue = (jshort) shortArg; + } else { + int shortArg = napi_util::get_int32(env, castValue); + shortValue = (jshort) shortArg; + } + } + + javaObject = JType::NewShort(jEnv, shortValue); + + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Long: + castValue = NumericCasts::GetCastValue(env, jsObj); + longValue = 0; + if (castValue != nullptr) { + if (napi_util::is_of_type(env, castValue, napi_string)) { + auto strValue = ArgConverter::ConvertToString(env, castValue); + longValue = atoll(strValue.c_str()); + } else { + int64_t longArg; + NAPI_GUARD(napi_get_value_int64(env, castValue, &longArg)) { + return false; + } + longValue = (jlong) longArg; + } + } + javaObject = JType::NewLong(jEnv, longValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Float: + castValue = NumericCasts::GetCastValue(env, jsObj); + floatValue = 0; + if (castValue != nullptr) { + double floatArg; + NAPI_GUARD(napi_get_value_double(env, castValue, &floatArg)) { + return false; + } + floatValue = (jfloat) floatArg; + } + javaObject = JType::NewFloat(jEnv, floatValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::Double: + castValue = NumericCasts::GetCastValue(env, jsObj); + doubleValue = 0; + if (castValue != nullptr) { + double doubleArg; + NAPI_GUARD(napi_get_value_double(env, castValue, &doubleArg)) { + return false; + } + doubleValue = (jdouble) doubleArg; + } + javaObject = JType::NewDouble(jEnv, doubleValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + + case CastType::None: + + obj = objectManager->GetJavaObjectByJsObject(jsObj); + + if (obj.IsNull()) { + bool isArrayBuffer = false; + bool isDataView = false; + bool isTypedArray = false; + + NAPI_GUARD(napi_is_arraybuffer(env, jsObj, &isArrayBuffer)) { + return false; + } + if (!isArrayBuffer) { + NAPI_GUARD(napi_is_typedarray(env, jsObj, &isTypedArray)) { + return false; + } + if (!isTypedArray) { + NAPI_GUARD(napi_is_dataview(env, jsObj, &isDataView)) { + return false; + } + } + } + + if (isArrayBuffer || isDataView || isTypedArray) { + obj = JsArgConverter::GetByteBuffer(env, jsObj, isArrayBuffer, isTypedArray, + isDataView); + } + } + + +#ifdef USE_HOST_OBJECT + if (!data) { +#endif + napi_value privateValue; + NAPI_GUARD(napi_get_named_property(env, jsObj, PROP_KEY_NULL_NODE_NAME, &privateValue)) { + return false; + } + if (!napi_util::is_null_or_undefined(env, privateValue)) { + void *data = nullptr; + NAPI_GUARD(napi_get_value_external(env, privateValue, &data)) { + return false; + } + auto node = reinterpret_cast(data); + if (node == nullptr) { + errMsg = "Cannot get type of the null argument at index " + + std::to_string(index); + success = false; + break; + } + + auto type = node->GetName(); + auto nullObjName = "com/tns/NullObject"; + auto nullObjCtorSig = "(Ljava/lang/Class;)V"; + + jclass nullClazz = jEnv.FindClass(nullObjName); + jmethodID ctor = jEnv.GetMethodID(nullClazz, "", nullObjCtorSig); + jclass clazzToNull = jEnv.FindClass(type); + jobject nullObjType = jEnv.NewObject(nullClazz, ctor, clazzToNull); + + if (nullObjType != nullptr) { + SetConvertedObject(jEnv, index, nullObjType, false); + } else { + SetConvertedObject(jEnv, index, nullptr); + } + + success = true; + return success; + } + +#ifdef USE_HOST_OBJECT + } +#endif + + + success = !obj.IsNull(); + if (success) { + SetConvertedObject(jEnv, index, obj.Move(), obj.IsGlobal()); + } else { + if (napi_util::is_number_object(env, arg)) { + napi_value numValue = napi_util::valueOf(env, arg); + bool isFloat = napi_util::is_float(env, numValue); + if (isFloat) { + double floatArg; + NAPI_GUARD(napi_get_value_double(env, numValue, &floatArg)) { + return false; + } + jfloat value = (jfloat) floatArg; + javaObject = JType::NewFloat(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } else { + int intArg; + NAPI_GUARD(napi_get_value_int32(env, numValue, &intArg)) { + return false; + } + jint value = (jint) intArg; + javaObject = JType::NewInt(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + } + break; + } else if (napi_util::is_string_object(env, arg)) { + napi_value stringValue = napi_util::valueOf(env, arg); + javaObject = ArgConverter::ConvertToJavaString(env, stringValue); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } else if (napi_util::is_boolean_object(env, arg)) { + napi_value boolValue = napi_util::valueOf(env, arg); + bool value = napi_util::get_bool(env, boolValue); + javaObject = JType::NewBoolean(jEnv, value); + SetConvertedObject(jEnv, index, javaObject); + success = true; + break; + } + + if (!success) { + napi_value objStr; + NAPI_GUARD(napi_coerce_to_string(env, jsObj, &objStr)) { + return false; + } + const char *objStrValue = napi_util::get_string_value(env, objStr); + stringstream s; + s << "Cannot marshal JavaScript argument " << objStrValue << " at index " + << index + << " to Java type."; + errMsg = s.str(); + } + } + break; + + default: + throw NativeScriptException("Unsupported cast type"); + } + } else { + errMsg = "Cannot marshal JavaScript argument at index " + std::to_string(index) + + " to Java type."; + success = false; + } + + if (!success) { + m_error.index = index; + // Keep the seeded default when no specific message was built. + if (!errMsg.empty()) { + m_error.msg = std::move(errMsg); + } + } + + return success; +} + +jobject JsArgToArrayConverter::GetConvertedArg() { + return (m_argsLen > 0) ? m_argsAsObject[0] : nullptr; +} + +void JsArgToArrayConverter::SetConvertedObject(JEnv &env, int index, jobject obj, bool isGlobal) { + m_argsAsObject[index] = obj; + if ((obj != nullptr) && !isGlobal) { + m_storedIndexes.push_back(index); + } +} + +int JsArgToArrayConverter::Length() const { + return m_argsLen; +} + +bool JsArgToArrayConverter::IsValid() const { + return m_isValid; +} + +JsArgToArrayConverter::Error JsArgToArrayConverter::GetError() const { + return m_error; +} + +jobjectArray JsArgToArrayConverter::ToJavaArray() { + if ((m_arr == nullptr) && (m_argsLen > 0)) { + if (m_argsLen >= JsArgToArrayConverter::MAX_JAVA_PARAMS_COUNT) { + stringstream ss; + ss << "You are trying to override more than the MAX_JAVA_PARAMS_COUNT: " + << JsArgToArrayConverter::MAX_JAVA_PARAMS_COUNT; + throw NativeScriptException(ss.str()); + } + + JEnv jEnv; + + if (JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS == nullptr) { + JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS = jEnv.FindClass("java/lang/Object"); + } + + JniLocalRef tmpArr( + jEnv.NewObjectArray(m_argsLen, JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS, + nullptr)); + m_arr = (jobjectArray) jEnv.NewGlobalRef(tmpArr); + + for (int i = 0; i < m_argsLen; i++) { + jEnv.SetObjectArrayElement(m_arr, i, m_argsAsObject[i]); + } + } + + return m_arr; +} + +JsArgToArrayConverter::~JsArgToArrayConverter() { + if (m_argsLen > 0) { + JEnv env; + + env.DeleteGlobalRef(m_arr); + + int length = m_storedIndexes.size(); + for (int i = 0; i < length; i++) { + int index = m_storedIndexes[i]; + env.DeleteLocalRef(m_argsAsObject[index]); + } + + if (m_argsAsObject != m_inlineArgs) { + delete[] m_argsAsObject; + } + } +} + +jclass JsArgToArrayConverter::JAVA_LANG_OBJECT_CLASS = nullptr; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.h b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.h new file mode 100644 index 000000000..987dd39c5 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/JsArgToArrayConverter.h @@ -0,0 +1,78 @@ +#ifndef JSARGTOARRAYCONVERTER_H_ +#define JSARGTOARRAYCONVERTER_H_ + +#include "JEnv.h" +#include "JniLocalRef.h" +#include "js_native_api.h" +#include +#include + +namespace tns { +class ObjectManager; + +class JsArgToArrayConverter { + public: + JsArgToArrayConverter(napi_env env, size_t argc, napi_value* argv, bool hasImplementationObject); + + // `objectManager` may be supplied pre-resolved (avoids a locked + // env->runtime lookup); falls back to resolving internally when omitted. + JsArgToArrayConverter(napi_env env, napi_value arg, bool isImplementationObject, int classReturnType, + ObjectManager* objectManager = nullptr); + + ~JsArgToArrayConverter(); + + jobjectArray ToJavaArray(); + + jobject GetConvertedArg(); + + int Length() const; + + bool IsValid() const; + + struct Error; + + Error GetError() const; + + struct Error { + Error() : + index(-1), msg(std::string()) { + } + + int index; + std::string msg; + }; + + private: + bool ConvertArg(napi_env env, napi_value arg, int index); + + void SetConvertedObject(JEnv& env, int index, jobject obj, bool isGlobal = false); + + int m_argsLen; + + int m_return_type; + + bool m_isValid; + + Error m_error; + + std::vector m_storedIndexes; + + jobject* m_argsAsObject; + + // Inline storage for the common small-arity case (esp. the single-arg + // path used per object-array element); heap only when larger. + static const int INLINE_CAPACITY = 8; + jobject m_inlineArgs[INLINE_CAPACITY]; + + // Cached ObjectManager threaded from the caller. + ObjectManager* m_objectManager = nullptr; + + jobjectArray m_arr; + + short MAX_JAVA_PARAMS_COUNT = 256; + + static jclass JAVA_LANG_OBJECT_CLASS; +}; +} + +#endif /* JSARGTOARRAYCONVERTER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/NumericCasts.cpp b/NativeScript/ffi/jni/napi/conversion/NumericCasts.cpp new file mode 100644 index 000000000..a8bed53c0 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/NumericCasts.cpp @@ -0,0 +1,282 @@ +#include "NumericCasts.h" +#include "NativeScriptAssert.h" +#include "Util.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +void NumericCasts::CreateGlobalCastFunctions(napi_env env, napi_value globalObject) { + + napi_status status; + + napi_value longFunc, byteFunc, shortFunc, doubleFunc, floatFunc, charFunc; + + NAPI_GUARD(napi_create_function(env, "long", NAPI_AUTO_LENGTH, NumericCasts::MarkAsLongCallback, nullptr, + &longFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "byte", NAPI_AUTO_LENGTH, NumericCasts::MarkAsByteCallback, nullptr, + &byteFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "short", NAPI_AUTO_LENGTH, NumericCasts::MarkAsShortCallback, nullptr, + &shortFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "double", NAPI_AUTO_LENGTH, NumericCasts::MarkAsDoubleCallback, + nullptr, + &doubleFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "float", NAPI_AUTO_LENGTH, NumericCasts::MarkAsFloatCallback, nullptr, + &floatFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "char", NAPI_AUTO_LENGTH, NumericCasts::MarkAsCharCallback, nullptr, + &charFunc)) { + return; + } + + NAPI_GUARD(napi_set_named_property(env, globalObject, "long", longFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, globalObject, "byte", byteFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, globalObject, "short", shortFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, globalObject, "double", doubleFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, globalObject, "float", floatFunc)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, globalObject, "char", charFunc)) { + return; + } +} + +void NumericCasts::MarkAsLong(napi_env env, napi_value object, napi_value value) { + MarkJsObject(env, object, CastType::Long, value); +} + + +napi_value NumericCasts::MarkAsLongCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + if (argc != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "long(x) should be called with single parameter")) {} + return nullptr; + } + + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, argv[0], &type)) { + return nullptr; + } + + if (type != napi_string && type != napi_number) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "long(x) should be called with single parameter containing a long number representation")) {} + return nullptr; + } + + napi_value value = argv[0]; + + napi_value cast; + NAPI_GUARD(napi_create_object(env, &cast)) { + return nullptr; + } + MarkJsObject(env, cast, CastType::Long, value); + return cast; +} + +napi_value NumericCasts::MarkAsByteCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + if (argc != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "byte(x) should be called with single parameter")) {} + return nullptr; + } + + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, argv[0], &type)) { + return nullptr; + } + + if (type != napi_string && type != napi_number && !napi_util::is_number_object(env, argv[0]) && !napi_util::is_string_object(env, argv[0])) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "byte(x) should be called with single parameter containing a byte number representation")) {} + return nullptr; + } + napi_value value; + if (type == napi_number) { + value = argv[0]; + } else { + NAPI_GUARD(napi_coerce_to_string(env, argv[0], &value)) { + return nullptr; + } + } + + napi_value cast; + NAPI_GUARD(napi_create_object(env, &cast)) { + return nullptr; + } + MarkJsObject(env, cast, CastType::Byte, value); + return cast; +} + +napi_value NumericCasts::MarkAsShortCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + if (argc != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "short(x) should be called with single parameter")) {} + return nullptr; + } + + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, argv[0], &type)) { + return nullptr; + } + + if (type != napi_string && type != napi_number && !napi_util::is_number_object(env, argv[0]) && !napi_util::is_string_object(env, argv[0])) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "short(x) should be called with single parameter containing a byte number representation")) {} + return nullptr; + } + napi_value value; + if (type == napi_number) { + value = argv[0]; + } else { + NAPI_GUARD(napi_coerce_to_string(env, argv[0], &value)) { + return nullptr; + } + } + + napi_value cast; + NAPI_GUARD(napi_create_object(env, &cast)) { + return nullptr; + } + MarkJsObject(env, cast, CastType::Short, value); + return cast; +} + +napi_value NumericCasts::MarkAsCharCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + if (argc != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "char(x) should be called with single parameter")) {} + return nullptr; + } + + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, argv[0], &type)) { + return nullptr; + } + + if (type != napi_string) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "char(x) should be called with single parameter containing a char representation")) {} + return nullptr; + } + + size_t str_len; + NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &str_len)) { + return nullptr; + } + if (str_len != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "char(x) should be called with single parameter containing a single char")) {} + return nullptr; + } + + + napi_value cast; + NAPI_GUARD(napi_create_object(env, &cast)) { + return nullptr; + } + MarkJsObject(env, cast, CastType::Char, argv[0]); + return cast; +} + +napi_value NumericCasts::MarkAsFloatCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + if (argc != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "float(x) should be called with single parameter")) {} + return nullptr; + } + + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, argv[0], &type)) { + return nullptr; + } + + if (type != napi_number) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "float(x) should be called with single parameter containing a float number representation")) {} + return nullptr; + } + + napi_value value = argv[0]; + + napi_value cast; + NAPI_GUARD(napi_create_object(env, &cast)) { + return nullptr; + } + MarkJsObject(env, cast, CastType::Float, value); + return cast; +} + +napi_value NumericCasts::MarkAsDoubleCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + if (argc != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "double(x) should be called with single parameter")) {} + return nullptr; + } + + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, argv[0], &type)) { + return nullptr; + } + + if (type != napi_number) { + NAPI_GUARD(napi_throw_error(env, nullptr, + "double(x) should be called with single parameter containing a double number representation")) {} + return nullptr; + } + + napi_value value = argv[0]; + + napi_value cast; + NAPI_GUARD(napi_create_object(env, &cast)) { + return nullptr; + } + MarkJsObject(env, cast, CastType::Double, value); + return cast; +} + +void +NumericCasts::MarkJsObject(napi_env env, napi_value object, CastType castType, napi_value value) { + napi_status status; + + napi_value type; + NAPI_GUARD(napi_create_int32(env, static_cast(castType), &type)) { + return; + } + + NAPI_GUARD(napi_set_named_property(env, object, s_castMarker, type)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, object, "value", value)) { + return; + } + +// DEBUG_WRITE("MarkJsObject: Marking js object with cast type: %d", castType); +} + +const char *NumericCasts::s_castMarker = "t::cast"; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/conversion/NumericCasts.h b/NativeScript/ffi/jni/napi/conversion/NumericCasts.h new file mode 100644 index 000000000..07dd09406 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/NumericCasts.h @@ -0,0 +1,74 @@ +#ifndef NUMERICCASTS_H_ +#define NUMERICCASTS_H_ + +#include "js_native_api.h" +#include "Runtime.h" +#include + +namespace tns { + enum class CastType { + None, + Char, + Byte, + Short, + Long, + Float, + Double + }; + + class NumericCasts { + public: + void CreateGlobalCastFunctions(napi_env env, napi_value globalObject); + + inline static CastType GetCastType(napi_env env, napi_value object) { + CastType ret = CastType::None; + +#ifdef USE_HOST_OBJECT + bool is_host_object = false; + napi_is_host_object(env, object, &is_host_object); + if (is_host_object) return ret; +#endif + + napi_value hidden; + napi_get_named_property(env, object, s_castMarker, &hidden); + napi_valuetype type; + napi_typeof(env, hidden, &type); + if (type == napi_number) { + napi_get_named_property(env, object, s_castMarker, &hidden); + int32_t castType; + napi_get_value_int32(env, hidden, &castType); + ret = static_cast(castType); + } + + return ret; + } + + inline static napi_value GetCastValue(napi_env env, napi_value object) { + napi_value value; + napi_get_named_property(env, object, "value", &value); + return value; + } + + static void MarkAsLong(napi_env env, napi_value object, napi_value value); + + private: + static napi_value MarkAsLongCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsByteCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsShortCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsCharCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsFloatCallback(napi_env env, napi_callback_info info); + + static napi_value MarkAsDoubleCallback(napi_env env, napi_callback_info info); + + static void + MarkJsObject(napi_env env, napi_value object, CastType castType, napi_value value); + + static const char *s_castMarker; + }; +} + +#endif /* NUMERICCASTS_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/exceptions/NativeScriptAssert.h b/NativeScript/ffi/jni/napi/exceptions/NativeScriptAssert.h new file mode 100644 index 000000000..dc0ad3c91 --- /dev/null +++ b/NativeScript/ffi/jni/napi/exceptions/NativeScriptAssert.h @@ -0,0 +1,22 @@ +/* + * nativescriptassert.h + * + * Created on: 12.11.2013 + * Author: blagoev + */ + +#ifndef NATIVESCRIPTASSERT_H_ +#define NATIVESCRIPTASSERT_H_ + +#include + +namespace tns { +extern bool LogEnabled; + +#define DEBUG_WRITE(fmt, args...) if (tns::LogEnabled) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +// #define DEBUG_WRITE(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +#define DEBUG_WRITE_FORCE(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native", fmt, ##args) +#define DEBUG_WRITE_FATAL(fmt, args...) __android_log_print(ANDROID_LOG_FATAL, "TNS.Native", fmt, ##args) +} + +#endif /* NATIVESCRIPTASSERT_H_ */ diff --git a/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.cpp b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.cpp new file mode 100644 index 000000000..ec11c1b4e --- /dev/null +++ b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.cpp @@ -0,0 +1,401 @@ +#include "Util.h" +#include "NativeScriptException.h" +#include "ArgConverter.h" +#include "NativeScriptAssert.h" +#include "Runtime.h" +#include "ObjectManager.h" +#include + +using namespace std; +using namespace tns; + +NativeScriptException::NativeScriptException(JEnv& env) + : m_javascriptException(nullptr) { + jthrowable thrw = env.ExceptionOccurred(); + m_javaException = JniLocalRef(thrw); + env.ExceptionClear(); + DEBUG_WRITE("%s, %s", GetExceptionMessage(env, m_javaException).c_str(), GetExceptionStackTrace(env, m_javaException).c_str()); +} + +NativeScriptException::NativeScriptException(const string& message) + : m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message) { + + DEBUG_WRITE("%s", m_message.c_str()); +} + +NativeScriptException::NativeScriptException(const string& message, const string& stackTrace) + : m_javascriptException(nullptr), m_javaException(JniLocalRef()), m_message(message), m_stackTrace(stackTrace) { + + DEBUG_WRITE("%s, %s ", m_message.c_str(), m_stackTrace.c_str()); +} + +NativeScriptException::NativeScriptException(napi_env env, napi_value error, const string& message) + : m_javaException(JniLocalRef()) { + napi_status status; + m_javascriptException = nullptr; + NAPI_GUARD(napi_create_reference(env, error, 1, &m_javascriptException)) {} + m_message = GetErrorMessage(env, error, message); + m_stackTrace = GetErrorStackTrace(env, error); + m_fullMessage = GetFullMessage(env, error, m_message); +} + +void NativeScriptException::ReThrowToNapi(napi_env env) { + napi_status status; + napi_value errObj; + + // Fallback message used if the rich error object cannot be materialized — + // ReThrowToNapi must always leave an exception pending, otherwise the failing + // Java call silently appears to succeed to JS. + const std::string& fallback = !m_fullMessage.empty() ? m_fullMessage + : !m_message.empty() ? m_message + : std::string("Unknown native error."); + + if (m_javascriptException != nullptr) { + NAPI_GUARD(napi_get_reference_value(env, m_javascriptException, &errObj)) { + napi_throw_error(env, nullptr, fallback.c_str()); + return; + } + if (napi_util::is_of_type(env, errObj, napi_object)) { + if (!m_fullMessage.empty()) { + NAPI_GUARD(napi_set_named_property(env, errObj, "fullMessage", ArgConverter::convertToJsString(env, m_fullMessage))) {} + } else if (!m_message.empty()) { + NAPI_GUARD(napi_set_named_property(env, errObj, "fullMessage", ArgConverter::convertToJsString(env, m_message))) {} + } + } + } else if (!m_fullMessage.empty()) { + NAPI_GUARD(napi_create_error(env, nullptr, ArgConverter::convertToJsString(env, m_fullMessage), &errObj)) { + napi_throw_error(env, nullptr, fallback.c_str()); + return; + } + } else if (!m_message.empty()) { + NAPI_GUARD(napi_create_error(env, nullptr, ArgConverter::convertToJsString(env, m_message), &errObj)) { + napi_throw_error(env, nullptr, fallback.c_str()); + return; + } + } else if (!m_javaException.IsNull()) { + errObj = WrapJavaToJsException(env); + } else { + NAPI_GUARD(napi_create_error(env, nullptr, ArgConverter::convertToJsString(env, "No javascript exception or message provided."), &errObj)) { + napi_throw_error(env, nullptr, "No javascript exception or message provided."); + return; + } + } + + NAPI_GUARD(napi_throw(env, errObj)) {} + +// JSLeave +} + +void NativeScriptException::ReThrowToJava(napi_env env) { + napi_status status; + if (env) { + NapiScope scope(env); + } + jthrowable ex = nullptr; + JEnv jEnv; + + if (!m_javaException.IsNull()) { + // Static lookup avoids needing the runtime/ObjectManager here, which may + // be unavailable while an exception is being rethrown to Java. + std::string excClassName = ObjectManager::GetClassName((jobject)m_javaException); + + if (excClassName == "com/tns/NativeScriptException") { + ex = m_javaException; + } else { + JniLocalRef msg(jEnv.NewStringUTF("Java Error!")); + JniLocalRef stack(jEnv.NewStringUTF("")); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, (jstring)stack, (jobject)m_javaException)); + } + } else if (m_javascriptException != nullptr && env != nullptr) { + napi_value errObj; + NAPI_GUARD(napi_get_reference_value(env, m_javascriptException, &errObj)) {} + if (napi_util::is_of_type(env, errObj, napi_object)) { + auto exObj = TryGetJavaThrowableObject(jEnv, env, errObj); + ex = (jthrowable)exObj.Move(); + } + + JniLocalRef msg(jEnv.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(jEnv.NewStringUTF(m_stackTrace.c_str())); + + if (ex == nullptr) { + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)stackTrace, reinterpret_cast(m_javascriptException))); + } else { + auto excClassName = ObjectManager::GetClassName(ex); + if (excClassName != "com/tns/NativeScriptException") { + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID, (jstring)msg, (jstring)stackTrace, ex)); + } + } + } else if (!m_message.empty()) { + JniLocalRef msg(jEnv.NewStringUTF(m_message.c_str())); + JniLocalRef stackTrace(jEnv.NewStringUTF(m_stackTrace.c_str())); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)stackTrace, (jlong)0)); + } else { + JniLocalRef msg(jEnv.NewStringUTF("No java exception or message provided.")); + ex = static_cast(jEnv.NewObject(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID, (jstring)msg, (jstring)nullptr, (jlong)0)); + } + jEnv.Throw(ex); +} + +void NativeScriptException::Init() { + JEnv jenv; + + RUNTIME_CLASS = jenv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + THROWABLE_CLASS = jenv.FindClass("java/lang/Throwable"); + assert(THROWABLE_CLASS != nullptr); + + NATIVESCRIPTEXCEPTION_CLASS = jenv.FindClass("com/tns/NativeScriptException"); + assert(NATIVESCRIPTEXCEPTION_CLASS != nullptr); + + NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = jenv.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;J)V"); + assert(NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID != nullptr); + + NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = jenv.GetMethodID(NATIVESCRIPTEXCEPTION_CLASS, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V"); + assert(NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID != nullptr); + + NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = jenv.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getStackTraceAsString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID != nullptr); + + NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = jenv.GetStaticMethodID(NATIVESCRIPTEXCEPTION_CLASS, "getMessage", "(Ljava/lang/Throwable;)Ljava/lang/String;"); + assert(NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID != nullptr); +} + +// ON N-API UNCAUGHT EXCEPTION +void NativeScriptException::OnUncaughtError(napi_env env, napi_value error) { + string errorMessage = GetErrorMessage(env, error); + string stackTrace = GetErrorStackTrace(env, error); + + NativeScriptException e(errorMessage, stackTrace); + e.ReThrowToJava(env); +} + +void NativeScriptException::CallJsFuncWithErr(napi_env env, napi_value errObj, bool isDiscarded) { + napi_status status; + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + + napi_value handler = nullptr; + if (isDiscarded) { + NAPI_GUARD(napi_get_named_property(env, global, "__onDiscardedError", &handler)) {} + } else { + NAPI_GUARD(napi_get_named_property(env, global, "__onUncaughtError", &handler)) {} + } + + if (napi_util::is_of_type(env, handler, napi_function)) { + napi_value result; + NAPI_GUARD(napi_call_function(env, global, handler, 1, &errObj, &result)) {} + } +} + +napi_value NativeScriptException::WrapJavaToJsException(napi_env env) { + napi_status status; + napi_value errObj; + + JEnv jenv; + + string excClassName = ObjectManager::GetClassName((jobject)m_javaException); + if (excClassName == "com/tns/NativeScriptException") { + jfieldID fieldID = jenv.GetFieldID(jenv.GetObjectClass(m_javaException), "jsValueAddress", "J"); + jlong addr = jenv.GetLongField(m_javaException, fieldID); + + if (addr != 0) { + auto pv = reinterpret_cast(addr); + NAPI_GUARD(napi_get_reference_value(env, pv, &errObj)) {} + NAPI_GUARD(napi_delete_reference(env, pv)) {} + } else { + errObj = GetJavaExceptionFromEnv(env, m_javaException, jenv); + } + } else { + errObj = GetJavaExceptionFromEnv(env, m_javaException, jenv); + } + + return errObj; +} + +napi_value NativeScriptException::GetJavaExceptionFromEnv(napi_env env, const JniLocalRef& exc, JEnv& jenv) { + napi_status status; + auto errMsg = GetExceptionMessage(jenv, exc); + auto stackTrace = GetExceptionStackTrace(jenv, exc); + DEBUG_WRITE("Error during java interop errorMessage: %s\n stackTrace:\n %s", errMsg.c_str(), stackTrace.c_str()); + + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + + napi_value msg = ArgConverter::convertToJsString(env, errMsg); + napi_value errObj; + napi_value code = ArgConverter::convertToJsString(env, "0", 1); + NAPI_GUARD(napi_create_error(env, code, msg, &errObj)) { + return nullptr; + } + + jint javaObjectID = objectManager->GetOrCreateObjectId((jobject)exc); + auto nativeExceptionObject = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, nativeExceptionObject)) { + string className = objectManager->GetClassName((jobject)exc); + nativeExceptionObject = objectManager->CreateJSWrapper(javaObjectID, className); + } + + NAPI_GUARD(napi_set_named_property(env, errObj, "nativeException", nativeExceptionObject)) {} + + string jsStackTraceMessage = GetErrorStackTrace(env, errObj); + NAPI_GUARD(napi_set_named_property(env, errObj, "stack", ArgConverter::convertToJsString(env, jsStackTraceMessage))) {} + NAPI_GUARD(napi_set_named_property(env, errObj, "stackTrace", ArgConverter::convertToJsString(env, jsStackTraceMessage + stackTrace) )) {} + + return errObj; +} + +string NativeScriptException::GetFullMessage(napi_env env, napi_value error, const string& jsExceptionMessage) { + napi_status status; + bool isError; + NAPI_GUARD(napi_is_error(env, error, &isError)) {} + if (!isError) { + return jsExceptionMessage; + } + + stringstream ss; + ss << jsExceptionMessage; + + string stackTraceMessage = GetErrorStackTrace(env, error); + + ss << endl << "StackTrace: " << endl << stackTraceMessage << endl; + + string loggedMessage = ss.str(); + + PrintErrorMessage(loggedMessage); + + return loggedMessage; +} + +JniLocalRef NativeScriptException::TryGetJavaThrowableObject(JEnv& env, napi_env napiEnv, napi_value jsObj) { + napi_status status; + JniLocalRef javaThrowableObject; + + auto objectManager = Runtime::GetRuntime(napiEnv)->GetObjectManager(); + + auto javaObj = objectManager->GetJavaObjectByJsObject(jsObj); + JniLocalRef objClass; + + if (!javaObj.IsNull()) { + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } else { + napi_value nativeEx; + NAPI_GUARD(napi_get_named_property(napiEnv, jsObj, "nativeException", &nativeEx)) {} + if (napi_util::is_object(napiEnv, nativeEx)) { + javaObj = objectManager->GetJavaObjectByJsObject(nativeEx); + objClass = JniLocalRef(env.GetObjectClass(javaObj)); + } + } + + auto isThrowable = !objClass.IsNull() ? env.IsAssignableFrom(objClass, THROWABLE_CLASS) : JNI_FALSE; + + if (isThrowable == JNI_TRUE) { + javaThrowableObject = JniLocalRef(env.NewLocalRef(javaObj)); + } + + return javaThrowableObject; +} + +void NativeScriptException::PrintErrorMessage(const string& errorMessage) { + stringstream ss(errorMessage); + string line; + while (getline(ss, line, '\n')) { + DEBUG_WRITE("%s", line.c_str()); + } +} + +string NativeScriptException::GetErrorMessage(napi_env env, napi_value error, const string& prependMessage) { + napi_status status; + bool isError; + NAPI_GUARD(napi_is_error(env, error, &isError)) {} + + if (!isError) { + napi_value err; + NAPI_GUARD(napi_coerce_to_string(env, error, &err)) {} + return napi_util::get_string_value(env, err); + } + + napi_value message; + NAPI_GUARD(napi_get_named_property(env, error, "message", &message)) {} + + string mes = ArgConverter::ConvertToString(env, message); + + stringstream ss; + + if (!prependMessage.empty()) { + ss << prependMessage << endl; + } + + string errMessage; + bool hasFullErrorMessage = false; + napi_value fullMessage; + NAPI_GUARD(napi_get_named_property(env, error, "fullMessage", &fullMessage)) {} + if (napi_util::is_of_type(env, fullMessage, napi_string)) { + hasFullErrorMessage = true; + errMessage = ArgConverter::ConvertToString(env, fullMessage); + ss << errMessage; + } + + if (!mes.empty()) { + if (hasFullErrorMessage) { + ss << endl; + } + ss << mes; + } + + return ss.str(); +} + +string NativeScriptException::GetErrorStackTrace(napi_env env, napi_value error) { + napi_status status; + stringstream ss; + + bool isError; + NAPI_GUARD(napi_is_error(env, error, &isError)) {} + if (!isError) return ""; + + napi_value stack; + NAPI_GUARD(napi_get_named_property(env, error, "stack", &stack)) {} + + + string stackStr = ArgConverter::ConvertToString(env, stack); + ss << stackStr; + + return ss.str(); +} + +string NativeScriptException::GetExceptionMessage(JEnv& env, jthrowable exception) { + string errMsg; + JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID, exception)); + + const char* msgStr = env.GetStringUTFChars(msg, nullptr); + + errMsg.append(msgStr); + + env.ReleaseStringUTFChars(msg, msgStr); + + return errMsg; +} + +string NativeScriptException::GetExceptionStackTrace(JEnv& env, jthrowable exception) { + string errStackTrace; + JniLocalRef msg(env.CallStaticObjectMethod(NATIVESCRIPTEXCEPTION_CLASS, NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID, exception)); + + const char* msgStr = env.GetStringUTFChars(msg, nullptr); + + errStackTrace.append(msgStr); + + env.ReleaseStringUTFChars(msg, msgStr); + + return errStackTrace; +} + +jclass NativeScriptException::RUNTIME_CLASS = nullptr; +jclass NativeScriptException::THROWABLE_CLASS = nullptr; +jclass NativeScriptException::NATIVESCRIPTEXCEPTION_CLASS = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID = nullptr; +jmethodID NativeScriptException::NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID = nullptr; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.h b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.h new file mode 100644 index 000000000..9d4aa2689 --- /dev/null +++ b/NativeScript/ffi/jni/napi/exceptions/NativeScriptException.h @@ -0,0 +1,109 @@ +#ifndef NATIVESCRIPTEXCEPTION_H_ +#define NATIVESCRIPTEXCEPTION_H_ + +#include "js_native_api.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "ObjectManager.h" + +namespace tns { +class NativeScriptException { + public: + /* + * Generates a NativeScriptException with java error from environment + */ + NativeScriptException(JEnv& env); + + /* + * Generates a NativeScriptException with given message + */ + NativeScriptException(const std::string& message); + + /* + * Generates a NativeScriptException with given message and stackTrace + */ + NativeScriptException(const std::string& message, const std::string& stackTrace); + + /* + * Generates a NativeScriptException with javascript error from napi_env and a prepend message if any + */ + NativeScriptException(napi_env env, napi_value error, const std::string& message = ""); + + void ReThrowToNapi(napi_env env); + void ReThrowToJava(napi_env env); + + // The stored message, for logging uncaught native exceptions. + const char* what() const noexcept { return m_message.c_str(); } + + static void Init(); + + /* + * This handler is attached to Node-API to handle uncaught javascript exceptions. + */ + static void OnUncaughtError(napi_env env, napi_value error); + + /* + * Calls the global "__onUncaughtError" or "__onDiscardedError" if such is provided + */ + static void CallJsFuncWithErr(napi_env env, napi_value errObj, bool isDiscarded); + + private: + /* + * Try to get native exception or NativeScriptException from js object + */ + JniLocalRef TryGetJavaThrowableObject(JEnv& env, napi_env napiEnv, napi_value jsObj); + + /* + * Gets java exception message from jthrowable + */ + std::string GetExceptionMessage(JEnv& env, jthrowable exception); + + /* + * Gets java exception stack trace from jthrowable + */ + std::string GetExceptionStackTrace(JEnv& env, jthrowable exception); + + /* + * Gets the member m_javaException, wraps it and creates a javascript error object from it + */ + napi_value WrapJavaToJsException(napi_env env); + + /* + * Gets all the information from a java exception and puts it in a javascript error object + */ + napi_value GetJavaExceptionFromEnv(napi_env env, const JniLocalRef& exc, JEnv& jenv); + + /* + * Gets all the information from a js message and an js error object and puts it in a string + */ + static std::string GetErrorMessage(napi_env env, napi_value error, const std::string& prependMessage = ""); + + /* + * Generates string stack trace from js StackTrace + */ + static std::string GetErrorStackTrace(napi_env env, napi_value stackTrace); + + /* + * Adds a prepend message to the normal message process + */ + std::string GetFullMessage(napi_env env, napi_value error, const std::string& jsExceptionMessage); + + napi_ref m_javascriptException; + JniLocalRef m_javaException; + std::string m_message; + std::string m_stackTrace; + std::string m_fullMessage; + + static jclass RUNTIME_CLASS; + static jclass THROWABLE_CLASS; + static jclass NATIVESCRIPTEXCEPTION_CLASS; + static jmethodID NATIVESCRIPTEXCEPTION_JSVALUE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_THROWABLE_CTOR_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_MESSAGE_METHOD_ID; + static jmethodID NATIVESCRIPTEXCEPTION_GET_STACK_TRACE_AS_STRING_METHOD_ID; + + static void PrintErrorMessage(const std::string& errorMessage); +}; +} + +#endif /* NATIVESCRIPTEXCEPTION_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/finalizer/FinalizerQueue.cpp b/NativeScript/ffi/jni/napi/finalizer/FinalizerQueue.cpp new file mode 100644 index 000000000..3e5586cd4 --- /dev/null +++ b/NativeScript/ffi/jni/napi/finalizer/FinalizerQueue.cpp @@ -0,0 +1,156 @@ +#include "FinalizerQueue.h" +#include "JEnv.h" +#include "NativeScriptException.h" +#include "NativeScriptAssert.h" +#include "Runtime.h" +#include "jsr.h" // NapiScope (resolved to the active engine's implementation) +#include + +using namespace tns; + +jclass FinalizerQueue::HANDLER_CLASS = nullptr; +jmethodID FinalizerQueue::HANDLER_CTOR = nullptr; +jmethodID FinalizerQueue::HANDLER_SCHEDULE = nullptr; +jmethodID FinalizerQueue::HANDLER_RELEASE = nullptr; + +FinalizerQueue::FinalizerQueue(napi_env env) : env_(env) { + JEnv jEnv; + if (HANDLER_CLASS == nullptr) { + HANDLER_CLASS = jEnv.FindClass("com/tns/FinalizerHandler"); + assert(HANDLER_CLASS != nullptr); + HANDLER_CTOR = jEnv.GetMethodID(HANDLER_CLASS, "", "(J)V"); + HANDLER_SCHEDULE = jEnv.GetMethodID(HANDLER_CLASS, "schedule", "()V"); + HANDLER_RELEASE = jEnv.GetMethodID(HANDLER_CLASS, "release", "()V"); + } + + // Bind a FinalizerHandler to the current (runtime) thread's Looper. + jobject localHandler = jEnv.NewObject(HANDLER_CLASS, HANDLER_CTOR, + reinterpret_cast(this)); + handler_ = jEnv.NewGlobalRef(localHandler); +} + +FinalizerQueue::~FinalizerQueue() { + Destroy(); +} + +void FinalizerQueue::Post(napi_finalize cb, void *data, void *hint) { + if (cb == nullptr) { + return; + } + + bool runInline = false; + jobject handler = nullptr; // captured under the lock so Destroy can't free it mid-use + { + std::lock_guard lock(mutex_); + if (stopped_) { + // Teardown: the loop is no longer draining us. Fall back to running the + // cleanup inline (matches the pre-deferral behavior for this edge). + runInline = true; + } else { + queue_.push_back({cb, data, hint}); + // Only wake the loop on the empty -> non-empty transition; further posts + // ride the already-scheduled drain. + if (!scheduled_) { + scheduled_ = true; + handler = handler_; + } + } + } + + if (runInline) { + cb(env_, data, hint); + return; + } + + if (handler != nullptr) { + JEnv jEnv; + jEnv.CallVoidMethod(handler, HANDLER_SCHEDULE); + } +} + +void FinalizerQueue::Drain() { + // Take the whole batch under the lock, then run callbacks outside it: a + // callback may free objects whose GC finalizers Post() again, and that must + // not deadlock on the queue mutex. Re-posted work sets scheduled_ = true and + // wakes the loop for the next tick. + std::vector batch; + { + std::lock_guard lock(mutex_); + scheduled_ = false; + batch.swap(queue_); + } + + for (auto &entry: batch) { + if (entry.cb != nullptr) { + // A handle scope roots any napi values the callback materializes. The + // JS lock/context are already held by the NapiScope opened at the JNI + // entry point (nativeDrainFinalizers). + napi_handle_scope scope; + napi_open_handle_scope(env_, &scope); + entry.cb(env_, entry.data, entry.hint); + napi_close_handle_scope(env_, scope); + } + } +} + +void FinalizerQueue::Destroy() { + // Mark stopped and detach the handler under the lock, so a concurrent Post + // (possible from a background JS thread's GC) either observes stopped_ and + // runs inline, or has already captured the handler before we release it. + std::vector batch; + jobject handler = nullptr; + { + std::lock_guard lock(mutex_); + if (stopped_) { + return; + } + stopped_ = true; + handler = handler_; + handler_ = nullptr; + batch.swap(queue_); + } + + if (handler != nullptr) { + JEnv jEnv; + jEnv.CallVoidMethod(handler, HANDLER_RELEASE); + jEnv.DeleteGlobalRef(handler); + } + + // Run whatever was still queued while env/context are valid; finalizers that + // fire during the subsequent env teardown then run inline via PostFinalizer. + for (auto &entry: batch) { + if (entry.cb != nullptr) { + entry.cb(env_, entry.data, entry.hint); + } + } +} + +void tns::PostFinalizer(napi_env env, napi_finalize cb, void *data, void *hint) { + Runtime::PostFinalizer(env, cb, data, hint); +} + +// Reverse-native for com.tns.FinalizerHandler.nativeDrainFinalizers (bound by +// symbol name). Runs on the runtime thread at a safe, post-GC message-loop tick. +extern "C" JNIEXPORT void JNICALL +Java_com_tns_FinalizerHandler_nativeDrainFinalizers(JNIEnv *jniEnv, jclass clazz, jlong queuePtr) { + auto *queue = reinterpret_cast(queuePtr); + if (queue == nullptr) { + return; + } + try { + // Enter the JS scope (lock + context + handle scope) before running any + // callback, since they make Node-API calls (e.g. napi_delete_reference). + NapiScope scope(queue->Env()); + queue->Drain(); + } catch (NativeScriptException &e) { + e.ReThrowToJava(nullptr); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} diff --git a/NativeScript/ffi/jni/napi/finalizer/FinalizerQueue.h b/NativeScript/ffi/jni/napi/finalizer/FinalizerQueue.h new file mode 100644 index 000000000..dd74aca94 --- /dev/null +++ b/NativeScript/ffi/jni/napi/finalizer/FinalizerQueue.h @@ -0,0 +1,78 @@ +#ifndef TEST_APP_FINALIZER_QUEUE_H +#define TEST_APP_FINALIZER_QUEUE_H + +#include +#include +#include +#include "js_native_api.h" + +namespace tns { + /** + * Engine-agnostic deferral for finalizer cleanup that must touch the JS heap + * (e.g. napi_delete_reference), which is illegal from inside a GC finalizer on + * every engine (V8's InvokeFinalizerFromGC; a reentrant JS_FreeValue during a + * QuickJS sweep corrupts the collector; etc.). + * + * A finalizer calls FinalizerQueue::Post — which only allocates and appends, + * never touching the JS heap, so it is safe to run mid-GC on any thread. The + * queued callbacks are drained on the runtime thread's Java message loop (see + * com.tns.FinalizerHandler), a point guaranteed to be outside any GC sweep + * with JS unwound to the host. This replaces the per-engine + * node_api_post_finalizer dependency with one uniform mechanism. + * + * Owned by Runtime; there is one per runtime/env. Post is thread-safe; Drain + * and Destroy run on the runtime thread. + */ + class FinalizerQueue { + public: + // Binds a com.tns.FinalizerHandler to the CURRENT thread's Looper, so this + // MUST be constructed on the runtime thread. + explicit FinalizerQueue(napi_env env); + + ~FinalizerQueue(); + + // Schedules cb(env, data, hint) to run at the next message-loop tick. + // Thread-safe and safe to call from inside a GC finalizer (no JS-heap + // interaction). A no-op cb is ignored. + void Post(napi_finalize cb, void *data, void *hint); + + // Runs all currently-queued callbacks. Invoked from FinalizerHandler on the + // runtime thread; the caller opens a JS scope first. + void Drain(); + + // Releases the Java handler and runs any still-queued callbacks inline. + // Must run on the runtime thread while env/context are still valid. + // Idempotent. + void Destroy(); + + napi_env Env() const { return env_; } + + private: + struct Entry { + napi_finalize cb; + void *data; + void *hint; + }; + + napi_env env_; + std::mutex mutex_; + std::vector queue_; + bool scheduled_ = false; + bool stopped_ = false; + jobject handler_ = nullptr; // global ref to com.tns.FinalizerHandler + + // Cached (process-wide) FinalizerHandler JNI ids. + static jclass HANDLER_CLASS; + static jmethodID HANDLER_CTOR; + static jmethodID HANDLER_SCHEDULE; + static jmethodID HANDLER_RELEASE; + }; + + // Convenience wrapper: defers cb(env, data, hint) to the owning runtime's + // post-GC finalizer drain. Safe to call from inside a GC finalizer. Falls back + // to running inline if the runtime is unavailable/tearing down. Lets callers + // (e.g. modules) defer without pulling in the heavy Runtime.h. + void PostFinalizer(napi_env env, napi_finalize cb, void *data, void *hint); +} + +#endif //TEST_APP_FINALIZER_QUEUE_H diff --git a/NativeScript/ffi/jni/napi/global/GlobalHelpers.cpp b/NativeScript/ffi/jni/napi/global/GlobalHelpers.cpp new file mode 100644 index 000000000..16e5efe21 --- /dev/null +++ b/NativeScript/ffi/jni/napi/global/GlobalHelpers.cpp @@ -0,0 +1,251 @@ +#include "GlobalHelpers.h" +#include "ArgConverter.h" +#include "CallbackHandlers.h" +#include "Constants.h" +#include "JEnv.h" +#include "NativeScriptException.h" +#include +#include "robin_hood.h" +#include "Util.h" +#include + +using namespace std; + +static robin_hood::unordered_map envToPersistentSmartJSONStringify = robin_hood::unordered_map(); + +napi_value GetSmartJSONStringifyFunction(napi_env env) { + napi_status status; + auto it = envToPersistentSmartJSONStringify.find(env); + if (it != envToPersistentSmartJSONStringify.end()) { + napi_value smartStringifyFunction; + NAPI_GUARD(napi_get_reference_value(env, it->second, &smartStringifyFunction)) { + return nullptr; + } + return smartStringifyFunction; + } + + const char * smartStringifyFunctionScript = R"( + (function () { + function smartStringify(object, handleCirculars) { + if (!handleCirculars) { + return JSON.stringify(object, null, 2); + } + + const seen = []; + var replacer = function (key, value) { + if (value != null && typeof value == "object") { + if (seen.indexOf(value) >= 0) { + if (key) { + return "[Circular]"; + } + return; + } + seen.push(value); + } + return value; + }; + return JSON.stringify(object, replacer, 2); + } + return smartStringify; +})(); +)"; + + + napi_value source; + NAPI_GUARD(napi_create_string_utf8(env, smartStringifyFunctionScript, strlen(smartStringifyFunctionScript), &source)) { + return nullptr; + } + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + + napi_value result; + status = js_execute_script(env, source, "", &result); + if (status != napi_ok) { + return nullptr; + } + + if (!napi_util::is_of_type(env, result, napi_function)) { + return nullptr; + } + + napi_ref smartStringifyPersistentFunction; + NAPI_GUARD(napi_create_reference(env, result, 1, &smartStringifyPersistentFunction)) { + return nullptr; + } + + envToPersistentSmartJSONStringify.emplace(env, smartStringifyPersistentFunction); + + return result; +} + + + +std::string tns::JsonStringifyObject(napi_env env, napi_value value, bool handleCircularReferences) { + if (value == nullptr) { + return ""; + } + + napi_value smartJSONStringifyFunction = GetSmartJSONStringifyFunction(env); + std::string result; + if (smartJSONStringifyFunction != nullptr) { + napi_value resultValue; + napi_value args[2]; + args[0] = value; + args[1] = handleCircularReferences ? napi_util::get_true(env) : napi_util::get_false(env); + napi_status status = napi_call_function(env, napi_util::global(env), smartJSONStringifyFunction, 2, args, &resultValue); + if (status != napi_ok) { + napi_value exception; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &exception)) {} + if (!napi_util::is_null_or_undefined(env, exception)) { + throw NativeScriptException(env, exception, "Error converting object to json"); + } else { + throw NativeScriptException("Error converting object to json"); + } + } + result = ArgConverter::ConvertToString(env, resultValue); + } + + return result; +} + +napi_value tns::JsonParseString(napi_env env, const std::string& value) { + napi_status status; + napi_value global; + napi_value json; + napi_value parse; + + NAPI_GUARD(napi_get_global(env, &global)) { + return nullptr; + } + NAPI_GUARD(napi_get_named_property(env, global, "JSON", &json)) { + return nullptr; + } + NAPI_GUARD(napi_get_named_property(env, json, "parse", &parse)) { + return nullptr; + } + + napi_value args[1]; + args[0] = ArgConverter::convertToJsString(env, value); + napi_value result; + status = napi_call_function(env, json, parse, 1, args, &result); + if (status != napi_ok) { + napi_value exception; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &exception)) {} + if (!napi_util::is_null_or_undefined(env, exception)) { + throw NativeScriptException(env, exception, "Error converting json string to object"); + } else { + throw NativeScriptException("Error converting json string to object"); + } + } + return result; +} + +std::vector tns::BuildStacktraceFrames(napi_env env, napi_value error, int size) { + napi_status status; + std::vector frames; + napi_value stack; + if (error != nullptr) { + NAPI_GUARD(napi_get_named_property(env, error, "stack", &stack)) { + return frames; + } + } else { +#ifndef __HERMES__ + napi_value err; + napi_value msg; + NAPI_GUARD(napi_create_string_utf8(env, "Error", strlen("Error"), &msg)) { + return frames; + } + #ifdef __PRIMJS__ + napi_value error_ctor; + NAPI_GUARD(napi_get_named_property(env, napi_util::global(env), "Error", &error_ctor)) { + return frames; + } + + NAPI_GUARD(napi_new_instance(env, error_ctor, 1, &msg, &err)) { + return frames; + } + #else + NAPI_GUARD(napi_create_error(env, msg, msg, &err)) { + return frames; + } + #endif + NAPI_GUARD(napi_get_named_property(env, err, "stack", &stack)) { + return frames; + } +#else + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return frames; + } + napi_value getErrorStack; + NAPI_GUARD(napi_get_named_property(env, global, "getErrorStack", &getErrorStack)) { + return frames; + } + NAPI_GUARD(napi_call_function(env, global, getErrorStack, 0, nullptr, &stack)) { + return frames; + } +#endif + } + + if (napi_util::is_null_or_undefined(env, stack)) return frames; + + string stackTrace = napi_util::get_string_value(env, stack); + vector stackLines; + Util::SplitString(stackTrace, "\n", stackLines); + + // Source modules carry a full "file://…" URL in every frame, so this matches + // those exactly as before (also covers JSC's "func@file://…:line:col" form). + const regex schemeRegex(R"((file:.*):(\d+):(\d+))"); +#ifdef NS_BYTECODE_ENABLED + // Bytecode modules embed an app-relative source name (e.g. "shared/index.js") + // at compile time — see tools/bytecode-compiler/compile-bytecode.js — because + // the device-absolute path can't be baked in ahead of time. Those frames have + // no scheme, so match a "(path:line:col)" (or leading-space) form and rebuild + // the full runtime URL from the app root. A leading "(" or space anchors the + // path so a function name is never glued on; "@" stays a valid path char so + // scoped modules (tns_modules/@nativescript/…) survive. Bytecode engines + // (Hermes/QuickJS/PrimJS) all emit the parenthesised V8-style frame, so the + // "@"-delimited (JSC) form never reaches here — and JSC has no bytecode. + const regex bareRegex(R"RE([(\s]([^\s():]+):(\d+):(\d+))RE"); +#endif + + int current = 0; + int count = 0; + for (auto &frame : stackLines) { + count++; +#ifdef __HERMES__ + if (error == nullptr && count < 3) continue; +#endif + + smatch match; + std::string filePath; + if (regex_search(frame, match, schemeRegex)) { + filePath = match[1].str(); + } +#ifdef NS_BYTECODE_ENABLED + else if (regex_search(frame, match, bareRegex)) { + filePath = "file://" + Constants::APP_ROOT_FOLDER_PATH + match[1].str(); + } +#endif + else { + continue; + } + current++; + frames.emplace_back(stoi(match[2].str()), + stoi(match[3].str()), + filePath, + frame); + if (current == size) break; + } + return frames; +} + +void tns::GlobalHelpers::onDisposeEnv(napi_env env) { + napi_status status; + auto found = envToPersistentSmartJSONStringify.find(env); + if (found != envToPersistentSmartJSONStringify.end()) { + NAPI_GUARD(napi_delete_reference(env, found->second)) {} + } + envToPersistentSmartJSONStringify.erase(env); +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/global/GlobalHelpers.h b/NativeScript/ffi/jni/napi/global/GlobalHelpers.h new file mode 100644 index 000000000..f2d9ce809 --- /dev/null +++ b/NativeScript/ffi/jni/napi/global/GlobalHelpers.h @@ -0,0 +1,34 @@ +#ifndef NAPI_GLOBALHELPERS_H_ +#define NAPI_GLOBALHELPERS_H_ + +#include "jni.h" +#include "js_native_api.h" +#include +#include +#include + +namespace tns { +std::string JsonStringifyObject(napi_env env, napi_value value, bool handleCircularReferences = true); + +napi_value JsonParseString(napi_env env, const std::string& value); + +struct JsStacktraceFrame { + JsStacktraceFrame(): line(0), col(0) {} + JsStacktraceFrame( + int _line, int _col, std::string _filename, std::string _text + ): line(_line), col(_col), filename(std::move(_filename)), text(std::move(_text)) {} + + int line; + int col; + std::string filename; + std::string text; +}; + +std::vector BuildStacktraceFrames(napi_env env, napi_value error, int size); + +namespace GlobalHelpers { + void onDisposeEnv(napi_env env); +} +} + +#endif /* NAPI_GLOBALHELPERS_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp new file mode 100644 index 000000000..fdbe50d60 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.cpp @@ -0,0 +1,11 @@ +#include "DesugaredInterfaceCompanionClassNameResolver.h" + +std::string DesugaredInterfaceCompanionClassNameResolver::resolveD8InterfaceCompanionClassName( + const std::string& interfaceName) { + return interfaceName + D8_COMPANION_CLASS_SUFFIX; +} + +std::string DesugaredInterfaceCompanionClassNameResolver::resolveBazelInterfaceCompanionClassName( + const std::string& interfaceName) { + return interfaceName + BAZEL_COMPANION_CLASS_SUFFIX; +} diff --git a/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.h b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.h new file mode 100644 index 000000000..052dc8952 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DesugaredInterfaceCompanionClassNameResolver.h @@ -0,0 +1,22 @@ +#ifndef TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H +#define TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H + + +#include + +class DesugaredInterfaceCompanionClassNameResolver { + +public: + std::string resolveD8InterfaceCompanionClassName(const std::string& interfaceName); + + std::string resolveBazelInterfaceCompanionClassName(const std::string& interfaceName); + +private: + const std::string BAZEL_COMPANION_CLASS_SUFFIX = "$$CC"; + const std::string D8_COMPANION_CLASS_SUFFIX = "$-CC"; + + +}; + + +#endif //TEST_APP_DESUGAREDINTERFACECOMPANIONCLASSNAMERESOLVER_H diff --git a/NativeScript/ffi/jni/napi/jni/DirectBuffer.cpp b/NativeScript/ffi/jni/napi/jni/DirectBuffer.cpp new file mode 100644 index 000000000..1b3e6f8a3 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DirectBuffer.cpp @@ -0,0 +1,56 @@ +#include "DirectBuffer.h" +#include "JniLocalRef.h" + +using namespace tns; + +DirectBuffer::DirectBuffer(uint32_t length) { + m_length = length; + + m_data = new int[m_length]; + + m_end = m_data + m_length; + + Reset(); + + int capacity = m_length * sizeof(int); + + JEnv env; + JniLocalRef buff(env.NewDirectByteBuffer(m_data, capacity)); + + m_buff = env.NewGlobalRef(buff); +} + +DirectBuffer::operator jobject() const { + return m_buff; +} + +int* DirectBuffer::GetData() const { + return m_data; +} + +int DirectBuffer::Length() const { + return m_length; +} + +int DirectBuffer::Size() const { + return m_pos - m_data; +} + +void DirectBuffer::Reset() { + m_pos = m_data; +} + +bool DirectBuffer::Write(int value) { + bool canWrite = m_pos < m_end; + if (canWrite) { + int bigEndianInt = __builtin_bswap32(value); + *(m_pos++) = bigEndianInt; + } + return canWrite; +} + +DirectBuffer::~DirectBuffer() { + JEnv env; + env.DeleteGlobalRef(m_buff); + delete[] m_data; +} diff --git a/NativeScript/ffi/jni/napi/jni/DirectBuffer.h b/NativeScript/ffi/jni/napi/jni/DirectBuffer.h new file mode 100644 index 000000000..4de8a8a63 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/DirectBuffer.h @@ -0,0 +1,30 @@ +#ifndef DIRECTBUFFER_H_ +#define DIRECTBUFFER_H_ + +#include "JEnv.h" + +namespace tns { +class DirectBuffer { + public: + DirectBuffer(uint32_t capacity = 65536); + ~DirectBuffer(); + + operator jobject() const; + + int* GetData() const; + int Length() const; + int Size() const; + + void Reset(); + bool Write(int value); + + private: + jobject m_buff; + int* m_data; + jlong m_length; + int* m_pos; + int* m_end; +}; +} + +#endif /* DIRECTBUFFER_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/File.cpp b/NativeScript/ffi/jni/napi/jni/File.cpp new file mode 100644 index 000000000..0dad918da --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/File.cpp @@ -0,0 +1,108 @@ +/* + * File.cpp + * + * Created on: Jun 24, 2015 + * Author: gatanasov + */ + +#include "File.h" +#include +#include +#include +#include + +using namespace std; + +namespace tns { + +string File::ReadText(const string& filePath) { + int len; + bool isNew; + const char* content = ReadText(filePath, len, isNew); + + string s(content, len); + + if (isNew) { + delete[] content; + } + + return s; +} + +void* File::ReadBinary(const string& filePath, int& length) { + length = 0; + + auto file = fopen(filePath.c_str(), READ_BINARY); + if (!file) { + return nullptr; + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + rewind(file); + + uint8_t* data = new uint8_t[length]; + fread(data, sizeof(uint8_t), length, file); + fclose(file); + + return data; +} + +bool File::WriteBinary(const string& filePath, const void* data, int length) { + auto file = fopen(filePath.c_str(), WRITE_BINARY); + if (!file) { + return false; + } + + auto writtenBytes = fwrite(data, sizeof(uint8_t), length, file); + fclose(file); + + return writtenBytes == length; +} + +const char* File::ReadText(const string& filePath, int& charLength, bool& isNew) { + FILE* file = fopen(filePath.c_str(), "rb"); + fseek(file, 0, SEEK_END); + + charLength = ftell(file); + isNew = charLength > BUFFER_SIZE; + + rewind(file); + + if (isNew) { + char* newBuffer = new char[charLength]; + fread(newBuffer, 1, charLength, file); + fclose(file); + + return newBuffer; + } + + fread(Buffer, 1, charLength, file); + fclose(file); + + return Buffer; +} + +std::unique_ptr File::ReadFile(const std::string &filePath, int &length, int extraBuffer) { + FILE *file = fopen(filePath.c_str(), "rb"); + if (!file) { + std::stringstream ss; + ss << "metadata file (" << filePath << ") couldn't be opened! (Error: " << errno << ") "; +// throw NativeScriptException(ss.str()); + } + + fseek(file, 0, SEEK_END); + length = ftell(file); + std::unique_ptr buffer(new char[length + extraBuffer]); + rewind(file); + fread(buffer.get(), 1, length, file); + fclose(file); + + return buffer; + } + +char* File::Buffer = new char[BUFFER_SIZE]; + +const char* File::WRITE_BINARY = "wb"; +const char* File::READ_BINARY = "rb"; +} diff --git a/NativeScript/ffi/jni/napi/jni/File.h b/NativeScript/ffi/jni/napi/jni/File.h new file mode 100644 index 000000000..de9509282 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/File.h @@ -0,0 +1,29 @@ +/* + * File.h + * + * Created on: Jun 24, 2015 + * Author: gatanasov + */ + +#ifndef JNI_FILE_H_ +#define JNI_FILE_H_ + +#include + +namespace tns { +class File { + public: + static const char* ReadText(const std::string& filePath, int& length, bool& isNew); + static std::string ReadText(const std::string& filePath); + static bool WriteBinary(const std::string& filePath, const void* inData, int length); + static void* ReadBinary(const std::string& filePath, int& length); + static std::unique_ptr ReadFile(const std::string &filePath, int &length, int extraBuffer = 0); +private: + static const int BUFFER_SIZE = 1024 * 1024; + static char* Buffer; + static const char* WRITE_BINARY; + static const char* READ_BINARY; +}; +} + +#endif /* JNI_FILE_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JEnv.cpp b/NativeScript/ffi/jni/napi/jni/JEnv.cpp new file mode 100644 index 000000000..190b2817a --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JEnv.cpp @@ -0,0 +1,901 @@ +#include "JEnv.h" +#include +#include "Util.h" +#include "DesugaredInterfaceCompanionClassNameResolver.h" +#include "NativeScriptException.h" + +using namespace tns; +using namespace std; + +JEnv::JEnv() + : m_env(nullptr) { + JNIEnv *env = nullptr; + jint ret = s_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + + if ((ret != JNI_OK) || (env == nullptr)) { + ret = s_jvm->AttachCurrentThread(&env, nullptr); + assert(ret == JNI_OK); + assert(env != nullptr); + } + + m_env = env; +} + +JEnv::JEnv(JNIEnv *jniEnv) { + jint ret = s_jvm->GetEnv(reinterpret_cast(&jniEnv), JNI_VERSION_1_6); + + if ((ret != JNI_OK) || (jniEnv == nullptr)) { + ret = s_jvm->AttachCurrentThread(&jniEnv, nullptr); + assert(ret == JNI_OK); + assert(jniEnv != nullptr); + } + + m_env = jniEnv; +} + +JEnv::~JEnv() { +} + +JEnv::operator JNIEnv* () const { + return m_env; +} + +jmethodID JEnv::GetMethodID(jclass clazz, const string &name, const string &sig) { + jmethodID mid = m_env->GetMethodID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return mid; +} + +jmethodID JEnv::GetStaticMethodID(jclass clazz, const string &name, const string &sig) { + jmethodID mid = m_env->GetStaticMethodID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return mid; +} + +jfieldID JEnv::GetFieldID(jclass clazz, const string &name, const string &sig) { + jfieldID fid = m_env->GetFieldID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return fid; +} + +jfieldID JEnv::GetStaticFieldID(jclass clazz, const string &name, const string &sig) { + jfieldID fid = m_env->GetStaticFieldID(clazz, name.c_str(), sig.c_str()); + CheckForJavaException(); + return fid; +} + +void JEnv::CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + m_env->CallStaticVoidMethodA(clazz, methodID, args); + CheckForJavaException(); +} + +void JEnv::CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + m_env->CallNonvirtualVoidMethodA(obj, clazz, methodID, args); + CheckForJavaException(); +} + +void JEnv::CallVoidMethodA(jobject obj, jmethodID methodID, jvalue *args) { + m_env->CallVoidMethodA(obj, methodID, args); + CheckForJavaException(); +} + +jboolean JEnv::CallStaticBooleanMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallStaticBooleanMethodA(clazz, methodID, args); + CheckForJavaException(); + return jbl; +} + +jboolean +JEnv::CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallNonvirtualBooleanMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jbl; +} + +jboolean JEnv::CallBooleanMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jboolean jbl = m_env->CallBooleanMethodA(obj, methodID, args); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::CallStaticByteMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallStaticByteMethodA(clazz, methodID, args); + CheckForJavaException(); + return jbt; +} + +jbyte JEnv::CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallNonvirtualByteMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jbt; +} + +jbyte JEnv::CallByteMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jbyte jbt = m_env->CallByteMethodA(obj, methodID, args); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::CallStaticCharMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallStaticCharMethodA(clazz, methodID, args); + CheckForJavaException(); + return jch; +} + +jchar JEnv::CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallNonvirtualCharMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jch; +} + +jchar JEnv::CallCharMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jchar jch = m_env->CallCharMethodA(obj, methodID, args); + CheckForJavaException(); + return jch; +} + +jshort JEnv::CallStaticShortMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallStaticShortMethodA(clazz, methodID, args); + CheckForJavaException(); + return jsh; + +} + +jshort +JEnv::CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallNonvirtualShortMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jsh; +} + +jshort JEnv::CallShortMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jshort jsh = m_env->CallShortMethodA(obj, methodID, args); + CheckForJavaException(); + return jsh; +} + +jint JEnv::CallStaticIntMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallStaticIntMethodA(clazz, methodID, args); + CheckForJavaException(); + return ji; + +} + +jint JEnv::CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallNonvirtualIntMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return ji; +} + +jint JEnv::CallIntMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jint ji = m_env->CallIntMethodA(obj, methodID, args); + CheckForJavaException(); + return ji; +} + +jlong JEnv::CallStaticLongMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallStaticLongMethodA(clazz, methodID, args); + CheckForJavaException(); + return jl; +} + +jlong JEnv::CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallNonvirtualLongMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jl; +} + +jlong JEnv::CallLongMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jlong jl = m_env->CallLongMethodA(obj, methodID, args); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::CallStaticFloatMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallStaticFloatMethodA(clazz, methodID, args); + CheckForJavaException(); + return jfl; +} + +jfloat +JEnv::CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallNonvirtualFloatMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jfl; +} + +jfloat JEnv::CallFloatMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jfloat jfl = m_env->CallFloatMethodA(obj, methodID, args); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::CallStaticDoubleMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallStaticDoubleMethodA(clazz, methodID, args); + CheckForJavaException(); + return jdb; +} + +jdouble +JEnv::CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallNonvirtualDoubleMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jdb; +} + +jdouble JEnv::CallDoubleMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jdouble jdb = m_env->CallDoubleMethodA(obj, methodID, args); + CheckForJavaException(); + return jdb; +} + +jobject JEnv::CallStaticObjectMethodA(jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallStaticObjectMethodA(clazz, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject +JEnv::CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallNonvirtualObjectMethodA(obj, clazz, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject JEnv::CallObjectMethodA(jobject obj, jmethodID methodID, jvalue *args) { + jobject jo = m_env->CallObjectMethodA(obj, methodID, args); + CheckForJavaException(); + return jo; +} + +jobject JEnv::GetStaticObjectField(jclass clazz, jfieldID fieldID) { + jobject jo = m_env->GetStaticObjectField(clazz, fieldID); + CheckForJavaException(); + return jo; +} + +jboolean JEnv::GetStaticBooleanField(jclass clazz, jfieldID fieldID) { + jboolean jbl = m_env->GetStaticBooleanField(clazz, fieldID); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::GetStaticByteField(jclass clazz, jfieldID fieldID) { + jbyte jbt = m_env->GetStaticByteField(clazz, fieldID); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::GetStaticCharField(jclass clazz, jfieldID fieldID) { + jchar jch = m_env->GetStaticCharField(clazz, fieldID); + CheckForJavaException(); + return jch; +} + +jshort JEnv::GetStaticShortField(jclass clazz, jfieldID fieldID) { + jshort jsh = m_env->GetStaticShortField(clazz, fieldID); + CheckForJavaException(); + return jsh; +} + +jint JEnv::GetStaticIntField(jclass clazz, jfieldID fieldID) { + jint ji = m_env->GetStaticIntField(clazz, fieldID); + CheckForJavaException(); + return ji; +} + +jlong JEnv::GetStaticLongField(jclass clazz, jfieldID fieldID) { + jlong jl = m_env->GetStaticLongField(clazz, fieldID); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::GetStaticFloatField(jclass clazz, jfieldID fieldID) { + jfloat jfl = m_env->GetStaticFloatField(clazz, fieldID); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::GetStaticDoubleField(jclass clazz, jfieldID fieldID) { + jdouble jd = m_env->GetStaticDoubleField(clazz, fieldID); + CheckForJavaException(); + return jd; +} + +void JEnv::SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) { + m_env->SetStaticObjectField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value) { + m_env->SetStaticBooleanField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value) { + m_env->SetStaticByteField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value) { + m_env->SetStaticCharField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value) { + m_env->SetStaticShortField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticIntField(jclass clazz, jfieldID fieldID, jint value) { + m_env->SetStaticIntField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value) { + m_env->SetStaticLongField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value) { + m_env->SetStaticFloatField(clazz, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value) { + m_env->SetStaticDoubleField(clazz, fieldID, value); + CheckForJavaException(); +} + +jobject JEnv::GetObjectField(jobject obj, jfieldID fieldID) { + jobject jo = m_env->GetObjectField(obj, fieldID); + CheckForJavaException(); + return jo; +} + +jboolean JEnv::GetBooleanField(jobject obj, jfieldID fieldID) { + jboolean jbl = m_env->GetBooleanField(obj, fieldID); + CheckForJavaException(); + return jbl; +} + +jbyte JEnv::GetByteField(jobject obj, jfieldID fieldID) { + jbyte jbt = m_env->GetByteField(obj, fieldID); + CheckForJavaException(); + return jbt; +} + +jchar JEnv::GetCharField(jobject obj, jfieldID fieldID) { + jchar jch = m_env->GetCharField(obj, fieldID); + CheckForJavaException(); + return jch; +} + +jshort JEnv::GetShortField(jobject obj, jfieldID fieldID) { + jshort jsh = m_env->GetShortField(obj, fieldID); + CheckForJavaException(); + return jsh; +} + +jint JEnv::GetIntField(jobject obj, jfieldID fieldID) { + jint ji = m_env->GetIntField(obj, fieldID); + CheckForJavaException(); + return ji; +} + +jlong JEnv::GetLongField(jobject obj, jfieldID fieldID) { + jlong jl = m_env->GetLongField(obj, fieldID); + CheckForJavaException(); + return jl; +} + +jfloat JEnv::GetFloatField(jobject obj, jfieldID fieldID) { + jfloat jfl = m_env->GetFloatField(obj, fieldID); + CheckForJavaException(); + return jfl; +} + +jdouble JEnv::GetDoubleField(jobject obj, jfieldID fieldID) { + jdouble jd = m_env->GetDoubleField(obj, fieldID); + CheckForJavaException(); + return jd; +} + +void JEnv::SetObjectField(jobject obj, jfieldID fieldID, jobject value) { + m_env->SetObjectField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) { + m_env->SetBooleanField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetByteField(jobject obj, jfieldID fieldID, jbyte value) { + m_env->SetByteField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetCharField(jobject obj, jfieldID fieldID, jchar value) { + m_env->SetCharField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetShortField(jobject obj, jfieldID fieldID, jshort value) { + m_env->SetShortField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetIntField(jobject obj, jfieldID fieldID, jint value) { + m_env->SetIntField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetLongField(jobject obj, jfieldID fieldID, jlong value) { + m_env->SetLongField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetFloatField(jobject obj, jfieldID fieldID, jfloat value) { + m_env->SetFloatField(obj, fieldID, value); + CheckForJavaException(); +} + +void JEnv::SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) { + m_env->SetDoubleField(obj, fieldID, value); + CheckForJavaException(); +} + +jstring JEnv::NewString(const jchar *unicodeChars, jsize len) { + jstring jst = m_env->NewString(unicodeChars, len); + CheckForJavaException(); + return jst; +} + +jstring JEnv::NewStringUTF(const char *bytes) { + jstring jst = m_env->NewStringUTF(bytes); + CheckForJavaException(); + return jst; +} + +jobjectArray JEnv::NewObjectArray(jsize length, jclass elementClass, jobject initialElement) { + jobjectArray joa = m_env->NewObjectArray(length, elementClass, initialElement); + CheckForJavaException(); + return joa; +} + +jobject JEnv::GetObjectArrayElement(jobjectArray array, jsize index) { + jobject jo = m_env->GetObjectArrayElement(array, index); + CheckForJavaException(); + return jo; +} + +void JEnv::SetObjectArrayElement(jobjectArray array, jsize index, jobject value) { + m_env->SetObjectArrayElement(array, index, value); + CheckForJavaException(); +} + +const char *JEnv::GetStringUTFChars(jstring str, jboolean *isCopy) { + const char *cc = m_env->GetStringUTFChars(str, isCopy); + CheckForJavaException(); + return cc; +} + +void JEnv::ReleaseStringUTFChars(jstring str, const char *utf) { + m_env->ReleaseStringUTFChars(str, utf); + CheckForJavaException(); +} + +const jchar *JEnv::GetStringChars(jstring str, jboolean *isCopy) { + const jchar *cjc = m_env->GetStringChars(str, isCopy); + CheckForJavaException(); + return cjc; +} + +void JEnv::ReleaseStringChars(jstring str, const jchar *chars) { + m_env->ReleaseStringChars(str, chars); + CheckForJavaException(); +} + +const int JEnv::GetStringLength(jstring str) { + const int ci = m_env->GetStringLength(str); + CheckForJavaException(); + return ci; +} + +const int JEnv::GetStringUTFLength(jstring str) { + const int ci = m_env->GetStringUTFLength(str); + CheckForJavaException(); + return ci; +} + +void JEnv::GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) { + m_env->GetStringUTFRegion(str, start, len, buf); + CheckForJavaException(); +} + +jint JEnv::Throw(jthrowable obj) { + return m_env->Throw(obj); +} + +jint JEnv::ThrowNew(jclass clazz, const string &message) { + return m_env->ThrowNew(clazz, message.c_str()); +} + +jthrowable JEnv::ExceptionOccurred() { + jthrowable jt = m_env->ExceptionOccurred(); + return jt; +} + +void JEnv::ExceptionDescribe() { + m_env->ExceptionDescribe(); + CheckForJavaException(); +} + +void JEnv::ExceptionClear() { + m_env->ExceptionClear(); +} + +jboolean JEnv::IsInstanceOf(jobject obj, jclass clazz) { + jboolean jbl = m_env->IsInstanceOf(obj, clazz); + CheckForJavaException(); + return jbl; +} + +jobjectRefType JEnv::GetObjectRefType(jobject obj) { + jobjectRefType ort = m_env->GetObjectRefType(obj); + CheckForJavaException(); + return ort; +} + +jobject JEnv::NewGlobalRef(jobject obj) { + jobject jo = m_env->NewGlobalRef(obj); +// CheckForJavaException(); + return jo; +} + +jweak JEnv::NewWeakGlobalRef(jobject obj) { + jweak jw = m_env->NewWeakGlobalRef(obj); + CheckForJavaException(); + return jw; +} + +void JEnv::DeleteGlobalRef(jobject globalRef) { + m_env->DeleteGlobalRef(globalRef); + CheckForJavaException(); +} + +void JEnv::DeleteWeakGlobalRef(jweak obj) { + m_env->DeleteWeakGlobalRef(obj); + CheckForJavaException(); +} + +jobject JEnv::NewLocalRef(jobject ref) { + jobject jo = m_env->NewLocalRef(ref); + CheckForJavaException(); + return jo; +} + +void JEnv::DeleteLocalRef(jobject localRef) { + m_env->DeleteLocalRef(localRef); +} + +jbyteArray JEnv::NewByteArray(jsize length) { + jbyteArray jba = m_env->NewByteArray(length); + CheckForJavaException(); + return jba; +} + +jbooleanArray JEnv::NewBooleanArray(jsize length) { + jbooleanArray jba = m_env->NewBooleanArray(length); + CheckForJavaException(); + return jba; +} + +jcharArray JEnv::NewCharArray(jsize length) { + jcharArray jca = m_env->NewCharArray(length); + CheckForJavaException(); + return jca; +} + +jshortArray JEnv::NewShortArray(jsize length) { + jshortArray jsa = m_env->NewShortArray(length); + CheckForJavaException(); + return jsa; +} + +jintArray JEnv::NewIntArray(jsize length) { + jintArray jia = m_env->NewIntArray(length); + CheckForJavaException(); + return jia; +} + +jlongArray JEnv::NewLongArray(jsize length) { + jlongArray jla = m_env->NewLongArray(length); + CheckForJavaException(); + return jla; +} + +jfloatArray JEnv::NewFloatArray(jsize length) { + jfloatArray jfa = m_env->NewFloatArray(length); + CheckForJavaException(); + return jfa; +} + +jdoubleArray JEnv::NewDoubleArray(jsize length) { + jdoubleArray jda = m_env->NewDoubleArray(length); + CheckForJavaException(); + return jda; +} + +jbyte *JEnv::GetByteArrayElements(jbyteArray array, jboolean *isCopy) { + jbyte *jbt = m_env->GetByteArrayElements(array, isCopy); + CheckForJavaException(); + return jbt; +} + +void JEnv::ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode) { + m_env->ReleaseByteArrayElements(array, elems, mode); + CheckForJavaException(); +} + +void JEnv::GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf) { + m_env->GetBooleanArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf) { + m_env->GetByteArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf) { + m_env->GetCharArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf) { + m_env->GetShortArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf) { + m_env->GetIntArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +jint *JEnv::GetIntArrayElements(jintArray array, jboolean *isCopy) { + jint *jin = m_env->GetIntArrayElements(array, isCopy); + CheckForJavaException(); + return jin; +} + +void JEnv::GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf) { + m_env->GetLongArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf) { + m_env->GetFloatArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf) { + m_env->GetDoubleArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf) { + m_env->SetByteArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf) { + m_env->SetBooleanArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf) { + m_env->SetCharArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf) { + m_env->SetShortArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf) { + m_env->SetIntArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf) { + m_env->SetLongArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf) { + m_env->SetFloatArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +void JEnv::SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf) { + m_env->SetDoubleArrayRegion(array, start, len, buf); + CheckForJavaException(); +} + +jclass JEnv::FindClass(const string &className) { + jclass global_class = CheckForClassInCache(className); + + if (global_class == nullptr) { + auto classIsMissing = CheckForClassMissingCache(className); + // class is missing. Set the same JNI error we had when we tried to find it the first time + if (classIsMissing != nullptr) { + m_env->Throw(classIsMissing); + return nullptr; + } + jclass tmp = m_env->FindClass(className.c_str()); + + if (m_env->ExceptionCheck() == JNI_TRUE) { + m_env->ExceptionClear(); + string cannonicalClassName = Util::ConvertFromJniToCanonicalName(className); + jstring s = m_env->NewStringUTF(cannonicalClassName.c_str()); + tmp = static_cast(m_env->CallStaticObjectMethod(RUNTIME_CLASS, + GET_CACHED_CLASS_METHOD_ID, s)); + + m_env->DeleteLocalRef(s); + // we failed our static class check + // if we continue, we will crash (C++ level) + // so just return null and let the runtime deal with the NativeScriptException + if (m_env->ExceptionCheck() == JNI_TRUE) { + auto tmpException = m_env->ExceptionOccurred(); + m_env->ExceptionClear(); + m_env->Throw(InsertClassIntoMissingCache(className, tmpException)); + return nullptr; + } + } + + global_class = InsertClassIntoCache(className, tmp); + } + + return global_class; +} + +jclass JEnv::CheckForClassInCache(const string &className) { + jclass global_class = nullptr; + auto itFound = s_classCache.find(className); + + if (itFound != s_classCache.end()) { + global_class = itFound->second; + } + + return global_class; +} + +jclass JEnv::InsertClassIntoCache(const string &className, jclass &tmp) { + auto global_class = reinterpret_cast(m_env->NewGlobalRef(tmp)); + s_classCache.emplace(className, global_class); + m_env->DeleteLocalRef(tmp); + + return global_class; +} + +jthrowable JEnv::CheckForClassMissingCache(const string &className) { + jthrowable throwable = nullptr; + auto itFound = s_missingClasses.find(className); + + if (itFound != s_missingClasses.end()) { + throwable = itFound->second; + } + + return throwable; +} + +jthrowable JEnv::InsertClassIntoMissingCache(const string &className,const jthrowable &tmp) { + auto throwable = reinterpret_cast(m_env->NewGlobalRef(tmp)); + s_missingClasses.emplace(className, throwable); + m_env->DeleteLocalRef(tmp); + + return throwable; +} + +jobject JEnv::NewDirectByteBuffer(void *address, jlong capacity) { + jobject jo = m_env->NewDirectByteBuffer(address, capacity); + CheckForJavaException(); + return jo; +} + +void *JEnv::GetDirectBufferAddress(jobject buf) { + void *v = m_env->GetDirectBufferAddress(buf); + CheckForJavaException(); + return v; +} + +jlong JEnv::GetDirectBufferCapacity(jobject buf) { + jlong jl = m_env->GetDirectBufferCapacity(buf); + CheckForJavaException(); + return jl; +} + +jboolean JEnv::IsAssignableFrom(jclass clazz1, jclass clazz2) { + jboolean jbl = m_env->IsAssignableFrom(clazz1, clazz2); + CheckForJavaException(); + return jbl; +} + +void JEnv::Init(JavaVM *jvm) { + assert(jvm != nullptr); + s_jvm = jvm; + + JEnv env; + RUNTIME_CLASS = env.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + GET_CACHED_CLASS_METHOD_ID = env.GetStaticMethodID(RUNTIME_CLASS, "getCachedClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + assert(GET_CACHED_CLASS_METHOD_ID != nullptr); +} + +jclass JEnv::GetObjectClass(jobject obj) { + jclass jcl = m_env->GetObjectClass(obj); + CheckForJavaException(); + return jcl; +} + +jsize JEnv::GetArrayLength(jarray array) { + jsize jsz = m_env->GetArrayLength(array); + CheckForJavaException(); + return jsz; +} + +//recursion if we put: CheckForJavaException(); +//in this method +jboolean JEnv::ExceptionCheck() { + return m_env->ExceptionCheck(); +} + +void JEnv::CheckForJavaException() { + if (ExceptionCheck() == JNI_TRUE) { + throw NativeScriptException(*this); + } +} + +JavaVM *JEnv::s_jvm = nullptr; +robin_hood::unordered_map JEnv::s_classCache; +robin_hood::unordered_map JEnv::s_missingClasses; +jclass JEnv::RUNTIME_CLASS = nullptr; +jmethodID JEnv::GET_CACHED_CLASS_METHOD_ID = nullptr; + +std::pair +JEnv::GetInterfaceStaticMethodIDAndJClass(const std::string &interfaceName, + const std::string &methodName, + const std::string &sig) { + + DesugaredInterfaceCompanionClassNameResolver companionClassNameResolver; + std::string possibleCalleeNames[] = {interfaceName, + companionClassNameResolver.resolveBazelInterfaceCompanionClassName( + interfaceName), + companionClassNameResolver.resolveD8InterfaceCompanionClassName( + interfaceName)}; + + for (const std::string& calleeName: possibleCalleeNames) { + jclass clazz = this->FindClass(calleeName); + + if (clazz != NULL) { + jmethodID methodId = m_env->GetStaticMethodID(clazz, methodName.c_str(), sig.c_str()); + + if (ExceptionCheck() == JNI_FALSE) { + return std::make_pair(methodId, clazz); + } + + ExceptionClear(); + } + } + + throw NativeScriptException( + "Could not call static interface method with name: " + methodName + " and signature: " + + sig + " for interface: " + interfaceName); + +} + + diff --git a/NativeScript/ffi/jni/napi/jni/JEnv.h b/NativeScript/ffi/jni/napi/jni/JEnv.h new file mode 100644 index 000000000..b1a544e4c --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JEnv.h @@ -0,0 +1,466 @@ +#ifndef JENV_H_ +#define JENV_H_ + +#include "jni.h" +#include "robin_hood.h" +#include + +namespace tns { + class JEnv { + public: + JEnv(); + + JEnv(JNIEnv *jniEnv); + + // Wrap an already-obtained JNIEnv* WITHOUT re-querying the JavaVM + // (no GetEnv). Use only when the pointer is known to belong to the + // current attached thread (e.g. threaded down from a callback prologue). + enum class Adopt { Trusted }; + JEnv(JNIEnv *jniEnv, Adopt) : m_env(jniEnv) {} + + ~JEnv(); + + operator JNIEnv *() const; + + jclass GetObjectClass(jobject obj); + + jsize GetArrayLength(jarray array); + + inline bool isSameObject(jobject obj1, jobject obj2) { + return m_env->IsSameObject(obj1, obj2) == JNI_TRUE; + } + + jmethodID GetMethodID(jclass clazz, const std::string &name, const std::string &sig); + + jmethodID GetStaticMethodID(jclass clazz, const std::string &name, const std::string &sig); + + std::pair GetInterfaceStaticMethodIDAndJClass( + const std::string &interfaceName, const std::string &methodName, + const std::string &sig); + + jfieldID GetFieldID(jclass clazz, const std::string &name, const std::string &sig); + + jfieldID GetStaticFieldID(jclass clazz, const std::string &name, const std::string &sig); + + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jboolean CallStaticBooleanMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jboolean + CallNonvirtualBooleanMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jboolean CallBooleanMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jbyte CallStaticByteMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jbyte + CallNonvirtualByteMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jbyte CallByteMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jchar CallStaticCharMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jchar + CallNonvirtualCharMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jchar CallCharMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jshort CallStaticShortMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jshort + CallNonvirtualShortMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jshort CallShortMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jint CallStaticIntMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jint CallNonvirtualIntMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jint CallIntMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jlong CallStaticLongMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jlong + CallNonvirtualLongMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jlong CallLongMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jfloat CallStaticFloatMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jfloat + CallNonvirtualFloatMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jfloat CallFloatMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jdouble CallStaticDoubleMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jdouble + CallNonvirtualDoubleMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jdouble CallDoubleMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jobject CallStaticObjectMethodA(jclass clazz, jmethodID methodID, jvalue *args); + + jobject + CallNonvirtualObjectMethodA(jobject obj, jclass clazz, jmethodID methodID, jvalue *args); + + jobject CallObjectMethodA(jobject obj, jmethodID methodID, jvalue *args); + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID); + + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID); + + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID); + + jchar GetStaticCharField(jclass clazz, jfieldID fieldID); + + jshort GetStaticShortField(jclass clazz, jfieldID fieldID); + + jint GetStaticIntField(jclass clazz, jfieldID fieldID); + + jlong GetStaticLongField(jclass clazz, jfieldID fieldID); + + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID); + + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID); + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value); + + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value); + + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value); + + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value); + + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value); + + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value); + + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value); + + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value); + + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value); + + jobject GetObjectField(jobject obj, jfieldID fieldID); + + jboolean GetBooleanField(jobject obj, jfieldID fieldID); + + jbyte GetByteField(jobject obj, jfieldID fieldID); + + jchar GetCharField(jobject obj, jfieldID fieldID); + + jshort GetShortField(jobject obj, jfieldID fieldID); + + jint GetIntField(jobject obj, jfieldID fieldID); + + jlong GetLongField(jobject obj, jfieldID fieldID); + + jfloat GetFloatField(jobject obj, jfieldID fieldID); + + jdouble GetDoubleField(jobject obj, jfieldID fieldID); + + void SetObjectField(jobject obj, jfieldID fieldID, jobject value); + + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value); + + void SetByteField(jobject obj, jfieldID fieldID, jbyte value); + + void SetCharField(jobject obj, jfieldID fieldID, jchar value); + + void SetShortField(jobject obj, jfieldID fieldID, jshort value); + + void SetIntField(jobject obj, jfieldID fieldID, jint value); + + void SetLongField(jobject obj, jfieldID fieldID, jlong value); + + void SetFloatField(jobject obj, jfieldID fieldID, jfloat value); + + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value); + + jstring NewString(const jchar *unicodeChars, jsize len); + + jstring NewStringUTF(const char *bytes); + + jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement); + + jobject GetObjectArrayElement(jobjectArray array, jsize index); + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject value); + + const char *GetStringUTFChars(jstring str, jboolean *isCopy); + + void ReleaseStringUTFChars(jstring str, const char *utf); + + const jchar *GetStringChars(jstring str, jboolean *isCopy); + + void ReleaseStringChars(jstring str, const jchar *chars); + + const int GetStringLength(jstring str); + + const int GetStringUTFLength(jstring str); + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf); + + jint Throw(jthrowable obj); + + jint ThrowNew(jclass clazz, const std::string &message); + + jboolean ExceptionCheck(); + + jthrowable ExceptionOccurred(); + + void ExceptionDescribe(); + + void ExceptionClear(); + + jboolean IsInstanceOf(jobject obj, jclass clazz); + + jobjectRefType GetObjectRefType(jobject obj); + + jobject NewGlobalRef(jobject obj); + + jweak NewWeakGlobalRef(jobject obj); + + void DeleteGlobalRef(jobject globalRef); + + void DeleteWeakGlobalRef(jweak obj); + + jobject NewLocalRef(jobject ref); + + void DeleteLocalRef(jobject localRef); + + jbyteArray NewByteArray(jsize length); + + jbooleanArray NewBooleanArray(jsize length); + + jcharArray NewCharArray(jsize length); + + jshortArray NewShortArray(jsize length); + + jintArray NewIntArray(jsize length); + + jlongArray NewLongArray(jsize length); + + jfloatArray NewFloatArray(jsize length); + + jdoubleArray NewDoubleArray(jsize length); + + jbyte *GetByteArrayElements(jbyteArray array, jboolean *isCopy); + + + void ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode); + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf); + + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf); + + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf); + + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf); + + void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf); + + jint *GetIntArrayElements(jintArray array, jboolean *isCopy); + + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf); + + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf); + + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf); + + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf); + + void + SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf); + + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf); + + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf); + + void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf); + + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf); + + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf); + + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf); + + jclass FindClass(const std::string &className); + + /* + * The "CheckForClassInCache" will check if a class is loaded into the cache + * if it is: it returns a global reference of it + * if it is not: it will return "nullptr". + */ + jclass CheckForClassInCache(const std::string &className); + + /* + * "InsertClassIntoCache" will take care of deleting the LocalReference of passed "jclass& tmp". + * A new GlobalReference object will be created from "tmp". The function returns the global object. + */ + jclass InsertClassIntoCache(const std::string &className, jclass &tmp); + + + /* + * The "CheckForClassMissing" will check if a class has been checked and it was missing, if it is, it will return the original throwable + * this is useful for rethrowing exceptions if they were caught in the previous attempt of loading it. + * if it is not: it will return "nullptr". + */ + jthrowable CheckForClassMissingCache(const std::string &className); + + jthrowable InsertClassIntoMissingCache(const std::string &className, const jthrowable &tmp); + + jobject NewDirectByteBuffer(void *address, jlong capacity); + + void *GetDirectBufferAddress(jobject buf); + + jlong GetDirectBufferCapacity(jobject buf); + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2); + + template + void CallVoidMethod(jobject obj, jmethodID methodID, Args ... args) { + m_env->CallVoidMethod(obj, methodID, args...); + CheckForJavaException(); + } + + template + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, Args ... args) { + m_env->CallStaticVoidMethod(clazz, methodID, args...); + CheckForJavaException(); + } + + template + void CallAppFail(jclass clazz, jmethodID methodID, Args ... args) { + m_env->CallStaticVoidMethod(clazz, methodID, args...); + } + + template + jint CallStaticIntMethod(jclass clazz, jmethodID methodID, Args ... args) { + jint ji = m_env->CallStaticIntMethod(clazz, methodID, args...); + CheckForJavaException(); + return ji; + } + + template + jlong CallStaticLongMethod(jclass clazz, jmethodID methodID, Args ... args) { + jlong jd = m_env->CallStaticLongMethod(clazz, methodID, args...); + CheckForJavaException(); + return jd; + } + + template + jobject CallStaticObjectMethod(jclass clazz, jmethodID methodID, Args ... args) { + jobject jo = m_env->CallStaticObjectMethod(clazz, methodID, args...); + CheckForJavaException(); + return jo; + } + + template + jboolean CallStaticBooleanMethod(jclass clazz, jmethodID methodID, Args ... args) { + jboolean jbl = m_env->CallStaticBooleanMethod(clazz, methodID, args...); + CheckForJavaException(); + return jbl; + } + + template + jobject CallObjectMethod(jobject obj, jmethodID methodID, Args ... args) { + jobject jo = m_env->CallObjectMethod(obj, methodID, args...); + CheckForJavaException(); + return jo; + } + + template + jboolean CallBooleanMethod(jobject obj, jmethodID methodID, Args ... args) { + jboolean jbl = m_env->CallBooleanMethod(obj, methodID, args...); + CheckForJavaException(); + return jbl; + } + + template + jchar CallCharMethod(jobject obj, jmethodID methodID, Args ... args) { + jchar jc = m_env->CallCharMethod(obj, methodID, args...); + CheckForJavaException(); + return jc; + } + + template + jbyte CallByteMethod(jobject obj, jmethodID methodID, Args ... args) { + jbyte jbt = m_env->CallByteMethod(obj, methodID, args...); + CheckForJavaException(); + return jbt; + } + + template + jshort CallShortMethod(jobject obj, jmethodID methodID, Args ... args) { + jshort jsh = m_env->CallShortMethod(obj, methodID, args...); + CheckForJavaException(); + return jsh; + } + + template + jint CallIntMethod(jobject obj, jmethodID methodID, Args ... args) { + jint ji = m_env->CallIntMethod(obj, methodID, args...); + CheckForJavaException(); + return ji; + } + + template + jlong CallLongMethod(jobject obj, jmethodID methodID, Args ... args) { + jlong jl = m_env->CallLongMethod(obj, methodID, args...); + CheckForJavaException(); + return jl; + } + + template + jfloat CallFloatMethod(jobject obj, jmethodID methodID, Args ... args) { + jfloat jf = m_env->CallFloatMethod(obj, methodID, args...); + CheckForJavaException(); + return jf; + } + + template + jdouble CallDoubleMethod(jobject obj, jmethodID methodID, Args ... args) { + jdouble jd = m_env->CallDoubleMethod(obj, methodID, args...); + CheckForJavaException(); + return jd; + } + + template + jobject NewObject(jclass clazz, jmethodID methodID, Args ... args) { + jobject jo = m_env->NewObject(clazz, methodID, args...); + CheckForJavaException(); + return jo; + + } + + jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue *args) { + jobject jo = m_env->NewObjectA(clazz, methodID, args); + CheckForJavaException(); + return jo; + } + + static void Init(JavaVM *jvm); + + private: + void CheckForJavaException(); + + JNIEnv *m_env; + + static JavaVM *s_jvm; + + static jclass RUNTIME_CLASS; + + static jmethodID GET_CACHED_CLASS_METHOD_ID; + + static robin_hood::unordered_map s_classCache; + static robin_hood::unordered_map s_missingClasses; + }; +} + +#endif /* JENV_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JType.cpp b/NativeScript/ffi/jni/napi/jni/JType.cpp new file mode 100644 index 000000000..479bfb0e3 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JType.cpp @@ -0,0 +1,155 @@ +#include "JType.h" +#include "NativeScriptAssert.h" + +namespace tns { +Type JType::getClassType(int retType) { + Type classReturnType = static_cast(retType); + return classReturnType; +} + +jobject JType::NewByte(JEnv env, jbyte value) { + EnsureInstance(env, &Byte, Type::Byte); + return env.NewObject(Byte->clazz, Byte->ctor, value); +} + +jobject JType::NewChar(JEnv env, jchar value) { + EnsureInstance(env, &Char, Type::Char); + return env.NewObject(Char->clazz, Char->ctor, value); +} + +jobject JType::NewBoolean(JEnv env, jboolean value) { + EnsureInstance(env, &Boolean, Type::Boolean); + return env.NewObject(Boolean->clazz, Boolean->ctor, value); +} + +jobject JType::NewShort(JEnv env, jshort value) { + EnsureInstance(env, &Short, Type::Short); + return env.NewObject(Short->clazz, Short->ctor, value); +} + +jobject JType::NewInt(JEnv env, jint value) { + EnsureInstance(env, &Int, Type::Int); + return env.NewObject(Int->clazz, Int->ctor, value); +} + +jobject JType::NewLong(JEnv env, jlong value) { + EnsureInstance(env, &Long, Type::Long); + return env.NewObject(Long->clazz, Long->ctor, value); +} + +jobject JType::NewFloat(JEnv env, jfloat value) { + EnsureInstance(env, &Float, Type::Float); + return env.NewObject(Float->clazz, Float->ctor, value); +} + +jobject JType::NewDouble(JEnv env, jdouble value) { + EnsureInstance(env, &Double, Type::Double); + return env.NewObject(Double->clazz, Double->ctor, value); +} + +jbyte JType::ByteValue(JEnv env, jobject value) { + EnsureInstance(env, &Byte, Type::Byte); + return env.CallByteMethod(value, Byte->valueMethodId); +} + +jchar JType::CharValue(JEnv env, jobject value) { + EnsureInstance(env, &Char, Type::Char); + return env.CallCharMethod(value, Char->valueMethodId); +} + +jboolean JType::BooleanValue(JEnv env, jobject value) { + EnsureInstance(env, &Boolean, Type::Boolean); + return env.CallBooleanMethod(value, Boolean->valueMethodId); +} + +jshort JType::ShortValue(JEnv env, jobject value) { + EnsureInstance(env, &Short, Type::Short); + return env.CallShortMethod(value, Short->valueMethodId); +} + +jint JType::IntValue(JEnv env, jobject value) { + EnsureInstance(env, &Int, Type::Int); + return env.CallIntMethod(value, Int->valueMethodId); +} + +jlong JType::LongValue(JEnv env, jobject value) { + EnsureInstance(env, &Long, Type::Long); + return env.CallLongMethod(value, Long->valueMethodId); +} + +jfloat JType::FloatValue(JEnv env, jobject value) { + EnsureInstance(env, &Float, Type::Float); + return env.CallFloatMethod(value, Float->valueMethodId); +} + +jdouble JType::DoubleValue(JEnv env, jobject value) { + EnsureInstance(env, &Double, Type::Double); + return env.CallDoubleMethod(value, Double->valueMethodId); +} + +void JType::EnsureInstance(JEnv env, JType** instance, Type type) { + if ((*instance) != nullptr) { + return; + } + + *instance = new JType(); + + (*instance)->Init(env, type); +} + +void JType::Init(JEnv env, Type type) { + switch (type) { + case Type::Byte: + this->clazz = env.FindClass("java/lang/Byte"); + this->ctor = env.GetMethodID(this->clazz, "", "(B)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "byteValue", "()B"); + break; + case Type::Char: + this->clazz = env.FindClass("java/lang/Character"); + this->ctor = env.GetMethodID(this->clazz, "", "(C)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "charValue", "()C"); + break; + case Type::Boolean: + this->clazz = env.FindClass("java/lang/Boolean"); + this->ctor = env.GetMethodID(this->clazz, "", "(Z)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "booleanValue", "()Z"); + break; + case Type::Short: + this->clazz = env.FindClass("java/lang/Short"); + this->ctor = env.GetMethodID(this->clazz, "", "(S)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "shortValue", "()S"); + break; + case Type::Int: + this->clazz = env.FindClass("java/lang/Integer"); + this->ctor = env.GetMethodID(this->clazz, "", "(I)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "intValue", "()I"); + break; + case Type::Long: + this->clazz = env.FindClass("java/lang/Long"); + this->ctor = env.GetMethodID(this->clazz, "", "(J)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "longValue", "()J"); + break; + case Type::Float: + this->clazz = env.FindClass("java/lang/Float"); + this->ctor = env.GetMethodID(this->clazz, "", "(F)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "floatValue", "()F"); + break; + case Type::Double: + this->clazz = env.FindClass("java/lang/Double"); + this->ctor = env.GetMethodID(this->clazz, "", "(D)V"); + this->valueMethodId = env.GetMethodID(this->clazz, "doubleValue", "()D"); + break; + default: + break; + } +} + +JType* JType::Byte; +JType* JType::Char; +JType* JType::Boolean; +JType* JType::Short; +JType* JType::Int; +JType* JType::Long; +JType* JType::Float; +JType* JType::Double; +} diff --git a/NativeScript/ffi/jni/napi/jni/JType.h b/NativeScript/ffi/jni/napi/jni/JType.h new file mode 100644 index 000000000..d3bfcf2c9 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JType.h @@ -0,0 +1,66 @@ +#ifndef JNIPRIMITIVETYPE_H_ +#define JNIPRIMITIVETYPE_H_ + +#include "JEnv.h" + +namespace tns { +enum class Type + : int { + Boolean, + Char, + Byte, + Short, + Int, + Long, + Float, + Double, + String, + JsObject, + Null +}; + +class JType { + public: + static jobject NewByte(JEnv env, jbyte value); + static jobject NewChar(JEnv env, jchar value); + static jobject NewBoolean(JEnv env, jboolean value); + static jobject NewShort(JEnv env, jshort value); + static jobject NewInt(JEnv env, jint value); + static jobject NewLong(JEnv env, jlong value); + static jobject NewFloat(JEnv env, jfloat value); + static jobject NewDouble(JEnv env, jdouble value); + + static jbyte ByteValue(JEnv env, jobject value); + static jchar CharValue(JEnv env, jobject value); + static jboolean BooleanValue(JEnv env, jobject value); + static jshort ShortValue(JEnv env, jobject value); + static jint IntValue(JEnv env, jobject value); + static jlong LongValue(JEnv env, jobject value); + static jfloat FloatValue(JEnv env, jobject value); + static jdouble DoubleValue(JEnv env, jobject value); + + static Type getClassType(int retType); + + private: + JType() { + } + + void Init(JEnv env, Type type); + static void EnsureInstance(JEnv env, JType** instance, Type type); + + jclass clazz; + jmethodID ctor; + jmethodID valueMethodId; + + static JType* Byte; + static JType* Char; + static JType* Boolean; + static JType* Short; + static JType* Int; + static JType* Long; + static JType* Float; + static JType* Double; +}; +} + +#endif /* JNIPRIMITIVETYPE_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JniLocalRef.h b/NativeScript/ffi/jni/napi/jni/JniLocalRef.h new file mode 100644 index 000000000..f950740cf --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JniLocalRef.h @@ -0,0 +1,122 @@ +#ifndef JNILOCALREF_H_ +#define JNILOCALREF_H_ + +#include "JEnv.h" +#include "JType.h" + +namespace tns { +class JniLocalRef { + public: + JniLocalRef() + : m_obj(nullptr), m_isGlobal(false) { + } + + JniLocalRef(jobject obj, bool isGlobal = false) + : m_obj(obj), m_isGlobal(isGlobal) { + } + + JniLocalRef(jclass obj) + : m_obj(obj), m_isGlobal(false) { + } + + JniLocalRef(JniLocalRef&& rhs) + : m_obj(rhs.m_obj), m_isGlobal(rhs.m_isGlobal) { + rhs.m_obj = nullptr; + } + + bool IsNull() const { + return m_obj == nullptr; + } + + bool IsGlobal() const { + return m_isGlobal; + } + + jobject Move() { + auto value = m_obj; + m_obj = nullptr; + return value; + } + + JniLocalRef& operator=(JniLocalRef&& rhs) { + m_obj = rhs.m_obj; + m_isGlobal = rhs.m_isGlobal; + rhs.m_obj = nullptr; + return *this; + } + + operator jobject() const { + return m_obj; + } + + operator jstring() const { + return reinterpret_cast(m_obj); + } + + operator jclass() const { + return reinterpret_cast(m_obj); + } + + operator jboolean() const { + JEnv env; + return JType::BooleanValue(env, m_obj); + } + + operator jthrowable() const { + return reinterpret_cast(m_obj); + } + + operator jarray()const { + return reinterpret_cast(m_obj); + } + + operator jbyteArray() const { + return reinterpret_cast(m_obj); + } + + operator jshortArray() const { + return reinterpret_cast(m_obj); + } + + operator jintArray() const { + return reinterpret_cast(m_obj); + } + + operator jlongArray() const { + return reinterpret_cast(m_obj); + } + + operator jfloatArray() const { + return reinterpret_cast(m_obj); + } + + operator jdoubleArray() const { + return reinterpret_cast(m_obj); + } + + operator jbooleanArray() const { + return reinterpret_cast(m_obj); + } + + operator jcharArray() const { + return reinterpret_cast(m_obj); + } + + operator jobjectArray() const { + return reinterpret_cast(m_obj); + } + + ~JniLocalRef() { + if ((m_obj != nullptr) && !m_isGlobal) { + JEnv env; + env.DeleteLocalRef(m_obj); + } + } + + private: + jobject m_obj; + bool m_isGlobal; +}; +} + +#endif /* JNILOCALREF_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/JniSignatureParser.cpp b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.cpp new file mode 100644 index 000000000..3d6df3559 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.cpp @@ -0,0 +1,102 @@ +#include "JniSignatureParser.h" + +#include + +using namespace std; +using namespace tns; + +JniSignatureParser::JniSignatureParser(const string& signature) + : m_signature(signature) { +} + +vector JniSignatureParser::Parse() { + size_t startIdx = m_signature.find_first_of('('); + + assert(startIdx != string::npos); + + size_t endIdx = m_signature.find_first_of(')'); + + assert(endIdx != string::npos); + + vector tokens = ParseParams(startIdx + 1, endIdx); + + return tokens; +} + +vector JniSignatureParser::ParseParams(int stardIdx, int endIdx) { + vector tokens; + + m_pos = stardIdx; + + while (m_pos < endIdx) { + string token = ReadNextToken(endIdx); + tokens.push_back(token); + } + + return tokens; +} + +string JniSignatureParser::ReadNextToken(int endIdx) { + string token; + + char currChar = m_signature[m_pos]; + + int idx; + bool endFound; + bool testNextChar = true; + + switch (currChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + ++m_pos; + token.push_back(currChar); + break; + + case 'L': + idx = m_signature.find(';', m_pos); + assert(idx != string::npos); + token = m_signature.substr(m_pos, idx - m_pos + 1); + m_pos = idx + 1; + break; + + case '[': + idx = m_pos; + endFound = false; + while (!endFound && (idx < endIdx)) { + currChar = m_signature[idx++]; + if (testNextChar) { + switch (currChar) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + endFound = true; + break; + } + testNextChar = currChar == '['; + } else { + endFound = currChar == ';'; + } + } + assert(endFound); + token = m_signature.substr(m_pos, idx - m_pos); + m_pos = idx; + break; + + default: + assert(false); + break; + } + + return token; +} diff --git a/NativeScript/ffi/jni/napi/jni/JniSignatureParser.h b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.h new file mode 100644 index 000000000..9904884ca --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/JniSignatureParser.h @@ -0,0 +1,26 @@ +#ifndef JNISIGNATUREPARSER_H_ +#define JNISIGNATUREPARSER_H_ + +#include +#include + +namespace tns { +class JniSignatureParser { + public: + JniSignatureParser(const std::string& signature); + + std::vector Parse(); + + private: + + std::vector ParseParams(int stardIdx, int endIdx); + + std::string ReadNextToken(int endIdx); + + int m_pos; + + std::string m_signature; +}; +} + +#endif /* JNISIGNATUREPARSER_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/LRUCache.h b/NativeScript/ffi/jni/napi/jni/LRUCache.h new file mode 100644 index 000000000..afceeb132 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/LRUCache.h @@ -0,0 +1,191 @@ +#ifndef LRUCACHE_H_ +#define LRUCACHE_H_ + +/* + Copyright (c) 2010-2011, Tim Day + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +namespace tns { +// Class providing fixed-size (by number of records) +// LRU-replacement cache of a function with signature +// V f(K). +// MAP should be one of std::map or std::unordered_map. +// Variadic template args used to deal with the +// different type argument signatures of those +// containers; the default comparator/hash/allocator +// will be used. +template +class LRUCache { + public: + + typedef K key_type; + typedef V value_type; + + // Key access history, most recent at back + typedef std::list key_tracker_type; + + // Key to value and key history iterator + typedef std::unordered_map< key_type, std::pair > key_to_value_type; + + // Constuctor specifies the cached function and + // the maximum number of records to be stored + LRUCache(value_type (*loadCallback)(const key_type&, void*), void (*evictCallback)(const value_type&, void*), bool (*cacheValidCallback)(const key_type&, const value_type&, void*), size_t capacity, void* state) + : m_loadCallback(loadCallback), m_capacity(capacity), m_evictCallback(evictCallback), m_cacheValidCallback(cacheValidCallback), m_state(state) { + assert(m_loadCallback != nullptr); + assert((0 < m_capacity) && (m_capacity < 10000)); + } + + // Obtain value of the cached function for k + value_type operator()(const key_type& k) { + + // Attempt to find existing record + auto it = m_key_to_value.find(k); + + if (m_cacheValidCallback != nullptr && it != m_key_to_value.end()) { + // Check if the cached value is still valid (e.g. a jweak that no + // longer points to a live object); if not, evict and treat as miss. + if (!m_cacheValidCallback(k, (*it).second.first, m_state)) { + evictKey(k); + it = m_key_to_value.end(); + } + } + + if (it == m_key_to_value.end()) { + + // We don't have it: + + // Evaluate function and create new record + const value_type v = m_loadCallback(k, m_state); + insert(k,v); + + // Return the freshly computed value + return v; + + } else { + // We do have it: + + // Update access record by moving + // accessed key to back of list + m_key_tracker.splice(m_key_tracker.end(), m_key_tracker, (*it).second.second); + + // Return the retrieved value + return (*it).second.first; + } + } + + // Obtain the cached keys, most recently used element + // at head, least recently used at tail. + // This method is provided purely to support testing. + template void get_keys(IT dst) const { + auto src = m_key_tracker.rbegin(); + + while (src != m_key_tracker.rend()) { + *dst++ = *src++; + } + } + + void clear() { + if (m_evictCallback != nullptr) { + for (auto& entry : m_key_to_value) { + m_evictCallback(entry.second.first, m_state); + } + } + m_key_to_value.clear(); + m_key_tracker.clear(); + } + + void update(const key_type& key, const value_type& value) { + jweak ref = m_loadCallback(key, m_state); + insert(key, ref); + } + + private: + + // Evict a specific key (used when a cached value is no longer valid). + void evictKey(const key_type& key) { + auto it = m_key_to_value.find(key); + if (it != m_key_to_value.end()) { + if (m_evictCallback != nullptr) { + m_evictCallback((*it).second.first, m_state); + } + m_key_tracker.erase((*it).second.second); + m_key_to_value.erase(it); + } + } + + // Record a fresh key-value pair in the cache + void insert(const key_type& k, const value_type& v) { + // Method is only called on cache misses + assert(m_key_to_value.find(k) == m_key_to_value.end()); + + // Make space if necessary + if (m_key_to_value.size() == m_capacity) { + evict(); + } + + // Record k as most-recently-used key + auto it = m_key_tracker.insert(m_key_tracker.end(), k); + + // Create the key-value entry, + // linked to the usage record. + m_key_to_value.insert(std::make_pair(k, std::make_pair(v, it))); + // No need to check return, + // given previous assert. + } + + // Purge the least-recently-used element in the cache + void evict() { + // Assert method is never called when cache is empty + assert(!m_key_tracker.empty()); + + // Identify least recently used key + auto it = m_key_to_value.find(m_key_tracker.front()); + assert(it != m_key_to_value.end()); + + if (m_evictCallback != nullptr) { + m_evictCallback((*it).second.first, m_state); + } + + // Erase both elements to completely purge record + m_key_to_value.erase(it); + m_key_tracker.pop_front(); + } + + // The function to be cached + value_type (*m_loadCallback)(const key_type&, void*); + + void (*m_evictCallback)(const value_type&, void*); + + bool (*m_cacheValidCallback)(const key_type&, const value_type&, void*); + + // Maximum number of key-value pairs to be retained + const size_t m_capacity; + + // Key access history + key_tracker_type m_key_tracker; + + // user-defined state to pass to callback + void* m_state; + + // Key-to-value lookup + key_to_value_type m_key_to_value; +}; +} + +#endif /* LRUCACHE_H_ */ diff --git a/NativeScript/ffi/jni/napi/jni/Logger.cpp b/NativeScript/ffi/jni/napi/jni/Logger.cpp new file mode 100644 index 000000000..4e35d4cf9 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/Logger.cpp @@ -0,0 +1,9 @@ +#include "Logger.h" + +using namespace tns; + +Logger::Logger() { +} + +void Logger::Write() { +} diff --git a/NativeScript/ffi/jni/napi/jni/Logger.h b/NativeScript/ffi/jni/napi/jni/Logger.h new file mode 100644 index 000000000..6b77c4f24 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/Logger.h @@ -0,0 +1,14 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +namespace tns { +class Logger { + public: + Logger(); + + void Write(); + private: +}; +} + +#endif /* LOGGER_H_ */ diff --git a/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.cpp b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.cpp new file mode 100644 index 000000000..717cca717 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.cpp @@ -0,0 +1,79 @@ +#include "NativeScriptException.h" +#include "JSONObjectHelper.h" +#include "ArgConverter.h" +#include +#include +#include + +using namespace tns; + +void JSONObjectHelper::RegisterFromFunction(napi_env env, napi_value value) { + napi_status status; + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, value, &type)) { + return; + } + if (type != napi_function && type != napi_object) { + return; + } + + bool hasProperty; + NAPI_GUARD(napi_has_named_property(env, value, "from", &hasProperty)) { + return; + } + if (hasProperty) { + return; + } + + napi_value from = CreateFromFunction(env); + NAPI_GUARD(napi_set_named_property(env, value, "from", from)) {} +} + + +napi_value JSONObjectHelper::CreateFromFunction(napi_env env) { + static const char* source = R"((() => function from(data) { + if (!data) throw new Error("Expected one parameter"); + let store; + switch (typeof data) { + case "string": + case "boolean": + case "number": { + return data; + } + case "object": { + if (!data) { + return null; + } + + if (data instanceof Date) { + return data.toJSON(); + } + + if (Array.isArray(data)) { + store = new org.json.JSONArray(); + data.forEach((item) => store.put(from(item))); + return store; + } + + store = new org.json.JSONObject(); + Object.keys(data).forEach((key) => store.put(key, from(data[key]))); + return store; + } + default: + return null; + } + })();)"; + + napi_status status; + napi_value script; + NAPI_GUARD(napi_create_string_utf8(env, source, NAPI_AUTO_LENGTH, &script)) { + return nullptr; + } + + napi_value result; + NAPI_GUARD(js_execute_script(env, script, "", &result)) { + return nullptr; + } + + return result; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.h b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.h new file mode 100644 index 000000000..cbe730398 --- /dev/null +++ b/NativeScript/ffi/jni/napi/jsonhelper/JSONObjectHelper.h @@ -0,0 +1,17 @@ +#ifndef JSONOBJECTHELPER_H_ +#define JSONOBJECTHELPER_H_ + +#include "js_native_api.h" + +namespace tns { + + class JSONObjectHelper { + public: + static void RegisterFromFunction(napi_env env, napi_value value); + private: + static napi_value CreateFromFunction(napi_env env); + }; + +} + +#endif //JSONOBJECTHELPER_H_ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp new file mode 100644 index 000000000..7cd01694a --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.cpp @@ -0,0 +1,385 @@ +#include "FieldAccessor.h" +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +napi_value +FieldAccessor::GetJavaField(napi_env env, napi_value target, FieldCallbackData *fieldData, + ObjectManager *objectManager, JniLocalRef targetJavaObject) { + JEnv jEnv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + napi_status status; + napi_value fieldResult; + + auto &fieldMetadata = fieldData->metadata; + + const auto &fieldTypeName = fieldMetadata.getSig(); + auto isStatic = fieldMetadata.isStatic; + + auto isPrimitiveType = fieldTypeName.size() == 1; + if (fieldData->fid == nullptr) { + auto isFieldArray = fieldTypeName[0] == '['; + auto fieldJniSig = isPrimitiveType + ? fieldTypeName + : (isFieldArray + ? fieldTypeName + : ("L" + fieldTypeName + ";")); + + if (isStatic) { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + fieldData->fid = jEnv.GetStaticFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + } else { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + fieldData->fid = jEnv.GetFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + } + } + + if (!isStatic) { + // The caller usually pre-resolves this (single probe); only fall back to + // resolving here when it wasn't supplied. + if (targetJavaObject.IsNull()) { + targetJavaObject = objectManager->GetJavaObjectByJsObjectFast(target); + } + + if (targetJavaObject.IsNull()) { + stringstream ss; + ss << "Cannot access property '" << fieldMetadata.getName().c_str() + << "' because there is no corresponding Java object"; + throw NativeScriptException(ss.str()); + } + + } + + + auto fieldId = fieldData->fid; + auto clazz = fieldData->clazz; + + if (isPrimitiveType) { + switch (fieldTypeName[0]) { + case 'Z': { // bool + jboolean result; + if (isStatic) { + result = jEnv.GetStaticBooleanField(clazz, fieldId); + } else { + result = jEnv.GetBooleanField(targetJavaObject, fieldId); + } + fieldResult = + result == JNI_TRUE ? napi_util::get_true(env) : napi_util::get_false(env); + break; + } + case 'B': { // byte + jbyte result; + if (isStatic) { + result = jEnv.GetStaticByteField(clazz, fieldId); + } else { + result = jEnv.GetByteField(targetJavaObject, fieldId); + } + NAPI_GUARD(napi_create_int32(env, result, &fieldResult)) { + return nullptr; + } + break; + } + case 'C': { // char + jchar result; + if (isStatic) { + result = jEnv.GetStaticCharField(clazz, fieldId); + } else { + result = jEnv.GetCharField(targetJavaObject, fieldId); + } + + JniLocalRef str(jEnv.NewString(&result, 1)); + jboolean bol = true; + const char *resP = jEnv.GetStringUTFChars(str, &bol); + fieldResult = ArgConverter::convertToJsString(env, resP, 1); + jEnv.ReleaseStringUTFChars(str, resP); + break; + } + case 'S': { // short + jshort result; + if (isStatic) { + result = jEnv.GetStaticShortField(clazz, fieldId); + } else { + result = jEnv.GetShortField(targetJavaObject, fieldId); + } + NAPI_GUARD(napi_create_int32(env, result, &fieldResult)) { + return nullptr; + } + break; + } + case 'I': { // int + jint result; + if (isStatic) { + result = jEnv.GetStaticIntField(clazz, fieldId); + } else { + result = jEnv.GetIntField(targetJavaObject, fieldId); + } + + NAPI_GUARD(napi_create_int32(env, result, &fieldResult)) { + return nullptr; + } + break; + } + case 'J': { // long + jlong result; + if (isStatic) { + result = jEnv.GetStaticLongField(clazz, fieldId); + } else { + result = jEnv.GetLongField(targetJavaObject, fieldId); + } + + fieldResult = ArgConverter::ConvertFromJavaLong(env, result); + break; + } + case 'F': { // float + jfloat result; + if (isStatic) { + result = jEnv.GetStaticFloatField(clazz, fieldId); + } else { + result = jEnv.GetFloatField(targetJavaObject, fieldId); + } + NAPI_GUARD(napi_create_double(env, (double) result, &fieldResult)) { + return nullptr; + } + break; + } + case 'D': { // double + jdouble result; + if (isStatic) { + result = jEnv.GetStaticDoubleField(clazz, fieldId); + } else { + result = jEnv.GetDoubleField(targetJavaObject, fieldId); + } + NAPI_GUARD(napi_create_double(env, (double) result, &fieldResult)) { + return nullptr; + } + break; + } + default: { + stringstream ss; + ss << "(InternalError): in FieldAccessor::GetJavaField: Unknown field type: '" + << fieldTypeName[0] << "'"; + throw NativeScriptException(ss.str()); + } + } + } else { + jobject result; + + if (isStatic) { + result = jEnv.GetStaticObjectField(clazz, fieldId); + } else { + result = jEnv.GetObjectField(targetJavaObject, fieldId); + } + + if (result != nullptr) { + + bool isString = fieldTypeName == "java/lang/String"; + if (isString) { + fieldResult = ArgConverter::jstringToJsString(env, (jstring) result); + } else { + int javaObjectID = objectManager->GetOrCreateObjectId(result); + auto objectResult = objectManager->GetJsObjectByJavaObject(javaObjectID); + + if (napi_util::is_null_or_undefined(env, objectResult)) { + objectResult = objectManager->CreateJSWrapper(javaObjectID, fieldTypeName, + result); + } + + fieldResult = objectResult; + } + jEnv.DeleteLocalRef(result); + } else { + NAPI_GUARD(napi_get_null(env, &fieldResult)) { + return nullptr; + } + } + } + return fieldResult; +} + +void FieldAccessor::SetJavaField(napi_env env, napi_value target, napi_value value, + FieldCallbackData *fieldData, ObjectManager *objectManager, + JniLocalRef targetJavaObject) { + JEnv jEnv; + + if (objectManager == nullptr) { + objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + auto &fieldMetadata = fieldData->metadata; + + const auto &fieldTypeName = fieldMetadata.getSig(); + auto isStatic = fieldMetadata.isStatic; + + auto isPrimitiveType = fieldTypeName.size() == 1; + auto isFieldArray = fieldTypeName[0] == '['; + + if (fieldData->fid == nullptr) { + auto fieldJniSig = isPrimitiveType + ? fieldTypeName + : (isFieldArray + ? fieldTypeName + : ("L" + fieldTypeName + ";")); + + if (isStatic) { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + assert(fieldData->clazz != nullptr); + fieldData->fid = jEnv.GetStaticFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + assert(fieldData->fid != nullptr); + } else { + fieldData->clazz = jEnv.FindClass(fieldMetadata.getDeclaringType()); + assert(fieldData->clazz != nullptr); + fieldData->fid = jEnv.GetFieldID(fieldData->clazz, fieldMetadata.getName(), + fieldJniSig); + assert(fieldData->fid != nullptr); + } + } + + if (!isStatic) { + // The caller usually pre-resolves this (single probe); only fall back to + // resolving here when it wasn't supplied. + if (targetJavaObject.IsNull()) { + targetJavaObject = objectManager->GetJavaObjectByJsObjectFast(target); + } + + if (targetJavaObject.IsNull()) { + stringstream ss; + ss << "Cannot access property '" << fieldMetadata.getName().c_str() + << "' because there is no corresponding Java object"; + throw NativeScriptException(ss.str()); + } + } + + auto fieldId = fieldData->fid; + auto clazz = fieldData->clazz; + + if (isPrimitiveType) { + switch (fieldTypeName[0]) { + case 'Z': { // bool + // TODO: validate value is a boolean before calling + bool boolValue = napi_util::is_of_type(env, value, napi_boolean) + ? napi_util::get_bool(env, value) : false; + if (isStatic) { + jEnv.SetStaticBooleanField(clazz, fieldId, boolValue); + } else { + jEnv.SetBooleanField(targetJavaObject, fieldId, + boolValue); + } + break; + } + case 'B': { // byte + // TODO: validate value is a byte before calling + jbyte intValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_int32(env, value) : 0; + if (isStatic) { + jEnv.SetStaticByteField(clazz, fieldId, intValue); + } else { + jEnv.SetByteField(targetJavaObject, fieldId, intValue); + } + break; + } + case 'C': { // char + const char *stringValue = napi_util::get_string_value(env, value, 1); + JniLocalRef strValue(jEnv.NewStringUTF(stringValue)); + const char *chars = jEnv.GetStringUTFChars(strValue, 0); + + if (isStatic) { + jEnv.SetStaticCharField(clazz, fieldId, chars[0]); + } else { + jEnv.SetCharField(targetJavaObject, fieldId, chars[0]); + } + jEnv.ReleaseStringUTFChars(strValue, chars); + break; + } + case 'S': { // short + // TODO: validate value is a short before calling + short shortValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_int32(env, value) : 0; + if (isStatic) { + jEnv.SetStaticShortField(clazz, fieldId, shortValue); + } else { + jEnv.SetShortField(targetJavaObject, fieldId, shortValue); + } + break; + } + case 'I': { // int + // TODO: validate value is a int before calling + int intValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_int32(env, value) : 0; + if (isStatic) { + jEnv.SetStaticIntField(clazz, fieldId, intValue); + } else { + jEnv.SetIntField(targetJavaObject, fieldId, intValue); + } + break; + } + case 'J': { // long + jlong longValue = static_cast(ArgConverter::ConvertToJavaLong(env, value)); + if (isStatic) { + jEnv.SetStaticLongField(clazz, fieldId, longValue); + } else { + jEnv.SetLongField(targetJavaObject, fieldId, longValue); + } + break; + } + case 'F': { // float + float floatValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_number(env, + value) : 0.0; + if (isStatic) { + jEnv.SetStaticFloatField(clazz, fieldId, + static_cast(floatValue)); + } else { + jEnv.SetFloatField(targetJavaObject, fieldId, + static_cast(floatValue)); + } + break; + } + case 'D': { // double + double doubleValue = napi_util::is_of_type(env, value, napi_number) + ? napi_util::get_number(env, + value) : 0.0; + if (isStatic) { + jEnv.SetStaticDoubleField(clazz, fieldId, doubleValue); + } else { + jEnv.SetDoubleField(targetJavaObject, fieldId, doubleValue); + } + break; + } + default: { + stringstream ss; + ss << "(InternalError): in FieldAccessor::SetJavaField: Unknown field type: '" + << fieldTypeName[0] << "'"; + throw NativeScriptException(ss.str()); + } + } + } else { + bool isString = fieldTypeName == "java/lang/String"; + JniLocalRef result; + + if (!napi_util::is_null(env, value) && !napi_util::is_undefined(env, value)) { + if (isString) { + // TODO: validate valie is a string; + result = ArgConverter::ConvertToJavaString(env, value); + } else { + result = objectManager->GetJavaObjectByJsObject(value); + } + } + + if (isStatic) { + jEnv.SetStaticObjectField(clazz, fieldId, result); + } else { + jEnv.SetObjectField(targetJavaObject, fieldId, result); + } + } +} diff --git a/NativeScript/ffi/jni/napi/metadata/FieldAccessor.h b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.h new file mode 100644 index 000000000..758847a2a --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/FieldAccessor.h @@ -0,0 +1,26 @@ +#ifndef FIELDACCESSOR_H_ +#define FIELDACCESSOR_H_ + +#include "JEnv.h" +#include +#include "ObjectManager.h" +#include "FieldCallbackData.h" + +namespace tns { +class FieldAccessor { + public: + // `objectManager` and `targetJavaObject` may be supplied pre-resolved by + // the caller (the accessor callback) so this avoids a locked env->runtime + // lookup and a second host-object probe. Both fall back to resolving + // internally when omitted. + napi_value GetJavaField(napi_env env, napi_value target, FieldCallbackData* fieldData, + ObjectManager* objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); + + void SetJavaField(napi_env env, napi_value target, napi_value value, FieldCallbackData* fieldData, + ObjectManager* objectManager = nullptr, + JniLocalRef targetJavaObject = JniLocalRef()); +}; +} + +#endif /* FIELDACCESSOR_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/FieldCallbackData.h b/NativeScript/ffi/jni/napi/metadata/FieldCallbackData.h new file mode 100644 index 000000000..26c2e8174 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/FieldCallbackData.h @@ -0,0 +1,31 @@ +#ifndef FIELDCALLBACKDATA_H_ +#define FIELDCALLBACKDATA_H_ + +#include "jni.h" +#include "js_native_api.h" +#include "MetadataEntry.h" + +namespace tns { + class ObjectManager; + + struct FieldCallbackData { + FieldCallbackData(MetadataEntry metadata) + : + metadata(metadata), fid(nullptr), clazz(nullptr) { + + } + + MetadataEntry metadata; + jfieldID fid; + jclass clazz; + // Cached prototype the accessor lives on; used to detect + // Class.prototype. access when host objects are disabled. + napi_ref prototype = nullptr; + // Cached per-env ObjectManager (this data is created per env, so the + // pointer's lifetime matches it) — avoids a locked env->runtime lookup. + tns::ObjectManager *objectManager = nullptr; + }; + +} + +#endif /* FIELDCALLBACKDATA_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.cpp new file mode 100644 index 000000000..02422aa70 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.cpp @@ -0,0 +1,136 @@ +// +// Created by Ammar Ahmed on 28/09/2024. +// + +#include "MetadataBuilder.h" +#include +#include +#include +#include +#include +#include +#include +#include "NativeScriptException.h" +#include "NativeScriptAssert.h" +#include "File.h" +#include "CallbackHandlers.h" + + +using namespace tns; + +MetadataReader MetadataBuilder::BuildMetadata(const std::string &filesPath) { + timeval time1; + gettimeofday(&time1, nullptr); + + string baseDir = filesPath; + baseDir.append("/metadata"); + + DIR* dir = opendir(baseDir.c_str()); + + if(dir == nullptr){ + stringstream ss; + ss << "metadata folder couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + + // TODO: Is there a way to detect if the screen is locked as verification + // We assume based on the error that this is the only way to get this specific error here at this point + if (errno == ENOENT || errno == EACCES) { + // Log the error with error code + __android_log_print(ANDROID_LOG_ERROR, "TNS.error", "%s", ss.str().c_str()); + + // While the screen is locked after boot; we cannot access our own apps directory on Android 9+ + // So the only thing to do at this point is just exit normally w/o crashing! + + // The only reason we should be in this specific path; is if: + // 1) android:directBootAware="true" flag is set on receiver + // 2) android.intent.action.LOCKED_BOOT_COMPLETED intent is set in manifest on above receiver + // See: https://developer.android.com/guide/topics/manifest/receiver-element + // and: https://developer.android.com/training/articles/direct-boot + // This specific path occurs if you using the NativeScript-Local-Notification plugin, the + // receiver code runs fine, but the app actually doesn't need to startup. The Native code tries to + // startup because the receiver is triggered. So even though we are exiting, the receiver will have + // done its job + + _Exit(0); + } + else { + throw NativeScriptException(ss.str()); + } + } + + string nodesFile = baseDir + "/treeNodeStream.dat"; + string namesFile = baseDir + "/treeStringsStream.dat"; + string valuesFile = baseDir + "/treeValueStream.dat"; + + FILE* f = fopen(nodesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeNodeStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenNodes = ftell(f); + assert((lenNodes % sizeof(MetadataTreeNodeRawData)) == 0); + char* nodes = new char[lenNodes]; + rewind(f); + fread(nodes, 1, lenNodes, f); + fclose(f); + + const int _512KB = 524288; + + f = fopen(namesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeStringsStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenNames = ftell(f); + char* names = new char[lenNames + _512KB]; + rewind(f); + fread(names, 1, lenNames, f); + fclose(f); + + f = fopen(valuesFile.c_str(), "rb"); + if (f == nullptr) { + stringstream ss; + ss << "metadata file (treeValueStream.dat) couldn't be opened! (Error: "; + ss << errno; + ss << ") "; + throw NativeScriptException(ss.str()); + } + fseek(f, 0, SEEK_END); + int lenValues = ftell(f); + char* values = new char[lenValues + _512KB]; + rewind(f); + fread(values, 1, lenValues, f); + fclose(f); + + timeval time2; + gettimeofday(&time2, nullptr); + + DEBUG_WRITE("lenNodes=%d, lenNames=%d, lenValues=%d", lenNodes, lenNames, lenValues); + + long millis1 = (time1.tv_sec * 1000) + (time1.tv_usec / 1000); + long millis2 = (time2.tv_sec * 1000) + (time2.tv_usec / 1000); + + DEBUG_WRITE("time=%ld", (millis2 - millis1)); + + auto reader = BuildMetadata(lenNodes, reinterpret_cast(nodes), lenNames, reinterpret_cast(names), lenValues, reinterpret_cast(values)); + delete[] nodes; + return reader; +} + +MetadataReader MetadataBuilder::BuildMetadata(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData) { + return MetadataReader(nodesLength, nodeData, nameLength, nameData, valueLength, + valueData, CallbackHandlers::GetTypeMetadata); + + +} diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.h b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.h new file mode 100644 index 000000000..d6e85baa0 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataBuilder.h @@ -0,0 +1,25 @@ +// +// Created by Ammar Ahmed on 28/09/2024. +// + +#ifndef TESTAPPNAPI_METADATABUILDER_H +#define TESTAPPNAPI_METADATABUILDER_H + +#include +#include "MetadataReader.h" + +namespace tns { + + class MetadataBuilder { + public: + static MetadataReader BuildMetadata(const std::string &filesPath); + + private: + static MetadataReader + BuildMetadata(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData); + }; + +} // tns + +#endif //TESTAPPNAPI_METADATABUILDER_H diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp new file mode 100644 index 000000000..c3f8badeb --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp @@ -0,0 +1,139 @@ +#include "MetadataNode.h" +#include "MetadataEntry.h" +#include "MetadataMethodInfo.h" +#include "MetadataReader.h" + +using namespace tns; + +MetadataEntry::MetadataEntry(MetadataTreeNode *m_treeNode, NodeType nodeType) : + treeNode(m_treeNode), type(nodeType), isExtensionFunction(false), isStatic(false), + isTypeMember(false), memberId(nullptr), clazz(nullptr), mi(nullptr),fi(nullptr), sfi(nullptr), + retType(MethodReturnType::Unknown), + paramCount(-1), isFinal(false), isResolved(false), retTypeParsed(false), + isFinalSet(false), isResolvedSet(false) {} + +std::string &MetadataEntry::getName() { + if (!name.empty()) return name; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Field) { + name = reader->ReadName(fi->nameOffset); + } else if (type == NodeType::StaticField) { + name = reader->ReadName(sfi->nameOffset); + } else if (type == NodeType::Method) { + name = mi.GetName(); + } + + return name; +} + +std::string &MetadataEntry::getSig() { + if (!sig.empty()) return sig; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Field) { + sig = reader->ReadTypeName(fi->nodeId); + } else if (type == NodeType::StaticField) { + sig = reader->ReadTypeName(sfi->nodeId); + } else if (type == NodeType::Method) { + uint8_t sigLength = mi.GetSignatureLength(); + if (sigLength > 0) + sig = mi.GetSignature(); + + } + + return sig; +} + +std::string &MetadataEntry::getReturnType() { + if (!returnType.empty()) return returnType; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method) { + if (mi.GetSignatureLength() > 0) { + returnType = MetadataReader::ParseReturnType(this->getSig()); + } + } else { + return returnType; + } + + return returnType; +} + +MethodReturnType MetadataEntry::getRetType() { + if (retTypeParsed && retType != MethodReturnType::Unknown) { + return retType; + } + + auto &methodReturnType = this->getReturnType(); + if (!methodReturnType.empty()) { + retType = MetadataReader::GetReturnType(methodReturnType); + retTypeParsed = retType != MethodReturnType::Unknown; + return retType; + } + + retType = MethodReturnType::Unknown; + retTypeParsed = false; + return MethodReturnType::Unknown; +} + +std::string &MetadataEntry::getDeclaringType() { + if (!declaringType.empty()) return declaringType; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::StaticField) { + declaringType = reader->ReadTypeName(sfi->declaringType); + } else if (type == NodeType::Method && isStatic) { + declaringType = mi.GetDeclaringType(); + } + + return declaringType; +} + +int MetadataEntry::getParamCount() { + if (paramCount != -1) return paramCount; + + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method) { + auto sigLength = mi.GetSignatureLength(); + if (sigLength > 0) { + paramCount = sigLength - 1; + } else { + paramCount = 0; + } + } + + return paramCount; +} + +bool MetadataEntry::getIsFinal() { + if (isFinalSet) return isFinal; + + if (type == NodeType::Field) { + isFinal = fi->finalModifier == MetadataTreeNode::FINAL; + } else if (type == NodeType::StaticField) { + isFinal = sfi->finalModifier == MetadataTreeNode::FINAL; + } + + isFinalSet = true; + + return isFinal; +} + +bool MetadataEntry::getIsResolved() { + if (isResolvedSet) return isResolved; + + auto reader = MetadataNode::getMetadataReader(); + if (type == NodeType::Method) { + isResolved = mi.CheckIsResolved() == 1; + } + + isResolvedSet = true; + + return isResolved; +} diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h new file mode 100644 index 000000000..1cb79b071 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h @@ -0,0 +1,117 @@ +#ifndef METADATAENTRY_H_ +#define METADATAENTRY_H_ + +#include +#include "jni.h" +#include "MetadataTreeNode.h" +#include "MetadataMethodInfo.h" +#include "MetadataFieldInfo.h" + +namespace tns { + enum class NodeType { + Package, + Class, + Interface, + Method, + Field, + StaticField + }; + + enum class MethodReturnType { + Unknown, + Void, + Byte, + Short, + Int, + Long, + Float, + Double, + Char, + Boolean, + String, + Object + }; + + class MetadataEntry { + public: + + MetadataEntry(MetadataTreeNode *m_treeNode, NodeType nodeType); + + MetadataEntry(const MetadataEntry &other) = default; + + MetadataEntry &operator=(const MetadataEntry &other) { + if (this != &other) { + treeNode = other.treeNode; + type = other.type; + isExtensionFunction = other.isExtensionFunction; + isStatic = other.isStatic; + isTypeMember = other.isTypeMember; + memberId = other.memberId; + clazz = other.clazz; + parsedSig = other.parsedSig; + mi = other.mi; + fi = other.fi; + sfi = other.sfi; + name = other.name; + sig = other.sig; + returnType = other.returnType; + retType = other.retType; + retTypeParsed = other.retTypeParsed; + declaringType = other.declaringType; + paramCount = other.paramCount; + isFinal = other.isFinal; + isResolved = other.isResolved; + isResolvedSet = other.isResolvedSet; + isFinalSet = other.isFinalSet; + } + return *this; + } + + std::string &getName(); + + std::string &getSig(); + + std::string &getReturnType(); + + MethodReturnType getRetType(); + + std::string &getDeclaringType(); + + int getParamCount(); + + bool getIsFinal(); + + bool getIsResolved(); + + MetadataTreeNode *treeNode; + NodeType type; + bool isExtensionFunction; + bool isStatic; + bool isTypeMember; + void *memberId; + jclass clazz; + std::vector parsedSig; + + MethodInfo mi; + FieldInfo *fi; + StaticFieldInfo *sfi; + + std::string name; + std::string sig; + std::string returnType; + MethodReturnType retType; + std::string declaringType; + int paramCount; + bool isFinal; + bool isResolved; + + private: + + bool retTypeParsed; + bool isFinalSet; + bool isResolvedSet; + + }; +} + +#endif /* METADATAENTRY_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataFieldInfo.h b/NativeScript/ffi/jni/napi/metadata/MetadataFieldInfo.h new file mode 100644 index 000000000..6c428960d --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataFieldInfo.h @@ -0,0 +1,28 @@ +#ifndef METADATAFIELDINFO_H_ +#define METADATAFIELDINFO_H_ + +#include + +namespace tns { +struct __attribute__ ((__packed__)) FieldInfo { + FieldInfo() + : +nameOffset(0), nodeId(0), finalModifier(0) { +} + +uint32_t nameOffset; +uint16_t nodeId; +uint8_t finalModifier; +}; + +struct __attribute__ ((__packed__)) StaticFieldInfo: FieldInfo { + StaticFieldInfo() + : +FieldInfo(), declaringType(0) { +} + +uint16_t declaringType; +}; +} + +#endif /* METADATAFIELDINFO_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.cpp new file mode 100644 index 000000000..42289ab89 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.cpp @@ -0,0 +1,99 @@ +#include "MetadataMethodInfo.h" +#include "MetadataNode.h" + + +using namespace tns; + +std::string MethodInfo::GetName() { + string methodName = MetadataNode::getMetadataReader()->ReadName(nameOffset); + return methodName; +} + +uint8_t MethodInfo::CheckIsResolved() { + return resolvedData; +} + +uint16_t MethodInfo::GetSignatureLength() { + return m_signatureLength; +} + +std::string MethodInfo::GetSignature() { //use nodeId's to read the whole signature + auto m_reader = MetadataNode::getMetadataReader(); + string signature = "("; + string ret; + for (int i = 0; i < m_signatureLength; i++) { + uint16_t nodeId = nodeIds[i]; + string curArgTypeName = m_reader->ReadTypeName(nodeId); + MetadataTreeNode* node = m_reader->GetNodeById(nodeId); + + uint8_t nodeType = m_reader->GetNodeType(node); + bool isRefType = m_reader->IsNodeTypeClass(nodeType) || m_reader->IsNodeTypeInterface(nodeType); + if (i == 0) { + if ((curArgTypeName[0] != '[') && isRefType) { + ret.append("L"); + } + ret.append(curArgTypeName); + if ((curArgTypeName[0] != '[') && isRefType) { + ret.append(";"); + } + } else { + if ((curArgTypeName[0] != '[') && isRefType) { + signature.append("L"); + } + signature.append(curArgTypeName); + if ((curArgTypeName[0] != '[') && isRefType) { + signature.append(";"); + } + } + } + if (ret.empty()) { + ret = "V"; + } + signature += ")" + ret; + + return signature; +} + +std::string MethodInfo::GetDeclaringType() { + auto m_reader = MetadataNode::getMetadataReader(); + + return m_reader->ReadTypeName(declaringNodeId); +} + +int MethodInfo::GetSizeOfReadMethodInfo() { + + if (!sizeMeasured) { + sizeMeasured = true; + // name + nameOffset = *reinterpret_cast(m_pData); + m_pData += sizeof(uint32_t); + // resolved data + resolvedData = *reinterpret_cast(m_pData); + m_pData += sizeof(uint8_t); + // sig length + m_signatureLength = *reinterpret_cast(m_pData); + m_pData += sizeof(uint16_t); + + // signature + if (m_signatureLength > 0) { + uint16_t* nodeIdPtr = reinterpret_cast(m_pData); + nodeIds.resize(m_signatureLength); + for (int i = 0; i < m_signatureLength; i++) { + nodeIds[i] = *nodeIdPtr++; + } + m_pData += m_signatureLength * sizeof(uint16_t); + } + + // declaring type + if (isStatic) { + auto declaringTypePtr = reinterpret_cast(m_pData); + declaringNodeId = *declaringTypePtr; + m_pData += sizeof(uint16_t); + } + + + + } + + return m_pData - m_pStartData; +} \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.h b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.h new file mode 100644 index 000000000..aadda536e --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataMethodInfo.h @@ -0,0 +1,66 @@ +#ifndef METHODINFOSMARTPOINTER_H_ +#define METHODINFOSMARTPOINTER_H_ + +#include +#include +#include + +using namespace std; + +namespace tns { + class MethodInfo { + public: + + MethodInfo(uint8_t *pValue) + : isStatic(false), m_pData(pValue), m_pStartData(pValue), m_signatureLength(0), + sizeMeasured(false), nameOffset(0), resolvedData(0), + declaringNodeId(0){ + } + + MethodInfo(const MethodInfo& other) = default; + + MethodInfo& operator=(const MethodInfo& other) { + if (this != &other) { + isStatic = other.isStatic; + m_pData = other.m_pData; + m_pStartData = other.m_pStartData; + m_signatureLength = other.m_signatureLength; + sizeMeasured = other.sizeMeasured; + nameOffset = other.nameOffset; + resolvedData = other.resolvedData; + declaringNodeId = other.declaringNodeId; + nodeIds = other.nodeIds; + } + return *this; + } + + std::string GetName(); + + uint8_t CheckIsResolved(); + + uint16_t GetSignatureLength(); + + std::string GetSignature(); + + std::string GetDeclaringType(); //used only for static methods + + int GetSizeOfReadMethodInfo(); + + bool isStatic; + + private: + uint8_t *m_pData; //where we currently read + uint8_t *m_pStartData; // pointer to the beginning + uint16_t m_signatureLength; + bool sizeMeasured; + + uint32_t nameOffset; + uint8_t resolvedData; + uint16_t declaringNodeId; + std::vector nodeIds; + + + }; +} + +#endif /* METHODINFOSMARTPOINTER_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp new file mode 100644 index 000000000..bb201181c --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp @@ -0,0 +1,3152 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "NativeScriptException.h" +#include "MetadataNode.h" +#include "CallbackHandlers.h" +#include "NativeScriptAssert.h" +#include "File.h" +#include "Runtime.h" +#include "ArgConverter.h" +#include "FieldCallbackData.h" +#include "MetadataBuilder.h" +#include "ArgsWrapper.h" +#include "Util.h" +#include "GlobalHelpers.h" +#include "JSONObjectHelper.h" + +using namespace std; + +namespace { +napi_value EnsureConstructorThis(napi_env env, napi_value jsThis, napi_value prototype) { + if (!napi_util::is_null_or_undefined(env, jsThis)) { + return jsThis; + } + + auto runtime = Runtime::GetRuntime(env); + auto receiver = runtime->GetObjectManager()->GetEmptyObject(); + if (!napi_util::is_null_or_undefined(env, receiver) && + !napi_util::is_null_or_undefined(env, prototype)) { + napi_util::setPrototypeOf(env, receiver, prototype); + } + + return receiver; +} +} + +void MetadataNode::Init(napi_env env) { + auto cache = GetMetadataNodeCache(env); +} + +napi_value MetadataNode::CreateArrayObjectConstructor(napi_env env) { + auto it = s_arrayObjects.find(env); + if (it != s_arrayObjects.end()) { + auto value = napi_util::get_ref_value(env, it->second); + if (!napi_util::is_null_or_undefined(env, value)) return value; + } + + auto node = GetOrCreate("java/lang/Object"); + auto objectConstructor = node->GetConstructorFunction(env); + + napi_status status; + napi_value arrayConstructor; + const char *name = "ArrayObjectWrapper"; + NAPI_GUARD(napi_define_class(env, name, strlen(name), + [](napi_env env, napi_callback_info info) -> napi_value { + NAPI_CALLBACK_BEGIN(0) + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + return EnsureConstructorThis(env, jsThis, receiverPrototype); + }, nullptr, 0, nullptr, &arrayConstructor)) { + return nullptr; + } + napi_value proto = napi_util::get_prototype(env, arrayConstructor); + ObjectManager::MarkObject(env, proto); + + napi_util::napi_set_function(env, proto, "setValueAtIndex", ArraySetterCallback, nullptr); + napi_util::napi_set_function(env, proto, "getValueAtIndex", ArrayGetterCallback, nullptr); + napi_util::napi_set_function(env, proto, "getAllValues", ArrayGetAllValuesCallback, nullptr); + napi_util::define_property(env, proto, "length", nullptr, ArrayLengthCallback); + + // Native helpers (previously synthesized by the JS getNativeArrayProp). + napi_util::napi_set_function(env, proto, "map", ArrayMapCallback, nullptr); + napi_util::napi_set_function(env, proto, "forEach", ArrayForEachCallback, nullptr); + napi_util::napi_set_function(env, proto, "toString", ArrayToStringCallback, nullptr); + { + napi_value globalObj, symbolCtor, symbolIterator, iteratorFn; + NAPI_GUARD(napi_get_global(env, &globalObj)) {} + NAPI_GUARD(napi_get_named_property(env, globalObj, "Symbol", &symbolCtor)) {} + NAPI_GUARD(napi_get_named_property(env, symbolCtor, "iterator", &symbolIterator)) {} + NAPI_GUARD(napi_create_function(env, "[Symbol.iterator]", NAPI_AUTO_LENGTH, + ArraySymbolIteratorCallback, nullptr, &iteratorFn)) {} + NAPI_GUARD(napi_set_property(env, proto, symbolIterator, iteratorFn)) {} + } + + napi_util::napi_inherits(env, arrayConstructor, objectConstructor); + + s_arrayObjects.emplace(env, napi_util::make_ref(env, arrayConstructor)); + + return arrayConstructor; +} + +napi_value MetadataNode::CreateExtendedJSWrapper(napi_env env, ObjectManager *objectManager, + const std::string &proxyClassName, + int javaObjectID, MetadataNode **outNode) { + napi_value extInstance = nullptr; + + auto cacheData = GetCachedExtendedClassData(env, proxyClassName); + + if (cacheData.node != nullptr) { + + extInstance = objectManager->GetEmptyObject(); + if (napi_util::is_null_or_undefined(env, extInstance)) { + return nullptr; + } + ObjectManager::MarkSuperCall(env, extInstance); + napi_value extendedCtorFunc = napi_util::get_ref_value(env, + cacheData.extendedCtorFunction); + napi_value extendedPrototype = napi_util::get_prototype(env, extendedCtorFunc); + napi_util::setPrototypeOf(env, extInstance, extendedPrototype); + + napi_status status; + NAPI_GUARD(napi_set_named_property(env, extInstance, CONSTRUCTOR, extendedCtorFunc)) {} + + SetInstanceMetadata(env, extInstance, cacheData.node); + *outNode = cacheData.node; + } + + return extInstance; +} + +string MetadataNode::GetTypeMetadataName(napi_env env, napi_value value) { + napi_status status; + napi_value typeMetadataName; + NAPI_GUARD(napi_get_named_property(env, value, PRIVATE_TYPE_NAME, &typeMetadataName)) { + return ""; + } + + return napi_util::get_string_value(env, typeMetadataName); +} + + +bool MetadataNode::isArray() { + return m_isArray; +} + +napi_value MetadataNode::CreateJSWrapper(napi_env env, ObjectManager *objectManager) { + napi_status status; + napi_value obj; + + if (m_isArray) { + obj = CreateArrayWrapper(env); + } else { + obj = objectManager->GetEmptyObject(); + napi_value ctorFunc = GetConstructorFunction(env); + NAPI_GUARD(napi_set_named_property(env, obj, CONSTRUCTOR, ctorFunc)) {} + napi_util::setPrototypeOf(env, obj, napi_util::get_prototype(env, ctorFunc)); + SetInstanceMetadata(env, obj, this); + } + + return obj; +} + +napi_value MetadataNode::ArrayGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + try { + + napi_value index = argv[0]; + int32_t indexValue; + NAPI_GUARD(napi_get_value_int32(env, index, &indexValue)) { + return nullptr; + } + auto node = GetInstanceMetadata(env, jsThis); + + return CallbackHandlers::GetArrayElement(env, jsThis, indexValue, node->m_name); + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArrayGetAllValuesCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + try { + auto node = GetInstanceMetadata(env, jsThis); + auto length = CallbackHandlers::GetArrayLength(env, jsThis); + napi_value arr; + NAPI_GUARD(napi_create_array(env, &arr)) { + return nullptr; + } + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(jsThis); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + napi_value element = CallbackHandlers::GetArrayElement(env, jsThis, i, node->m_name, + objectManager, javaArrObj); + NAPI_GUARD(napi_set_element(env, arr, i, element)) {} + } + + return arr; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArraySetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(2); + + try { + + napi_value index = argv[0]; + napi_value value = argv[1]; + + int32_t indexValue; + NAPI_GUARD(napi_get_value_int32(env, index, &indexValue)) { + return nullptr; + } + auto node = GetInstanceMetadata(env, jsThis); + + CallbackHandlers::SetArrayElement(env, jsThis, indexValue, node->m_name, value); + return value; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArrayLengthCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + int length = CallbackHandlers::GetArrayLength(env, jsThis); + + napi_value len; + NAPI_GUARD(napi_create_int32(env, length, &len)) { + return nullptr; + } + return len; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArrayMapCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + try { + napi_value callback = argv[0]; + auto node = GetInstanceMetadata(env, jsThis); + int length = CallbackHandlers::GetArrayLength(env, jsThis); + + napi_value result; + NAPI_GUARD(napi_create_array_with_length(env, length, &result)) { + return nullptr; + } + + napi_value undefined; + NAPI_GUARD(napi_get_undefined(env, &undefined)) { + return nullptr; + } + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(jsThis); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + napi_value element = + CallbackHandlers::GetArrayElement(env, jsThis, i, node->m_name, + objectManager, javaArrObj); + napi_value index; + NAPI_GUARD(napi_create_int32(env, i, &index)) { + return nullptr; + } + napi_value cbArgs[3] = {element, index, jsThis}; + napi_value mapped; + NAPI_GUARD(napi_call_function(env, undefined, callback, 3, cbArgs, &mapped)) { + return nullptr; + } + NAPI_GUARD(napi_set_element(env, result, i, mapped)) {} + } + + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ArrayForEachCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + try { + napi_value callback = argv[0]; + auto node = GetInstanceMetadata(env, jsThis); + int length = CallbackHandlers::GetArrayLength(env, jsThis); + + napi_value undefined; + NAPI_GUARD(napi_get_undefined(env, &undefined)) { + return nullptr; + } + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(jsThis); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + napi_value element = + CallbackHandlers::GetArrayElement(env, jsThis, i, node->m_name, + objectManager, javaArrObj); + napi_value index; + NAPI_GUARD(napi_create_int32(env, i, &index)) { + return nullptr; + } + napi_value cbArgs[3] = {element, index, jsThis}; + napi_value ignored; + NAPI_GUARD(napi_call_function(env, undefined, callback, 3, cbArgs, &ignored)) { + return nullptr; + } + } + + return undefined; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +// Builds a real JS array snapshot of all elements (native get loop). +static napi_value BuildArraySnapshot(napi_env env, napi_value jsThis, + const std::string &signature) { + napi_status status; + int length = CallbackHandlers::GetArrayLength(env, jsThis); + napi_value values; + NAPI_GUARD(napi_create_array_with_length(env, length, &values)) { + return nullptr; + } + + // Resolve the manager + backing array once for the whole loop. + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + JniLocalRef javaArr = objectManager->GetJavaObjectByJsObjectFast(jsThis); + jobject javaArrObj = javaArr; + + for (int i = 0; i < length; i++) { + napi_value element = + CallbackHandlers::GetArrayElement(env, jsThis, i, signature, + objectManager, javaArrObj); + NAPI_GUARD(napi_set_element(env, values, i, element)) {} + } + return values; +} + +napi_value MetadataNode::ArrayToStringCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + auto node = GetInstanceMetadata(env, jsThis); + napi_value values = BuildArraySnapshot(env, jsThis, node->m_name); + + // values.join(",") + napi_value joinFn; + NAPI_GUARD(napi_get_named_property(env, values, "join", &joinFn)) { + return nullptr; + } + napi_value comma; + NAPI_GUARD(napi_create_string_utf8(env, ",", 1, &comma)) { + return nullptr; + } + napi_value result; + NAPI_GUARD(napi_call_function(env, values, joinFn, 1, &comma, &result)) { + return nullptr; + } + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value +MetadataNode::ArraySymbolIteratorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + auto node = GetInstanceMetadata(env, jsThis); + napi_value values = BuildArraySnapshot(env, jsThis, node->m_name); + + // return values[Symbol.iterator]() -> delegate to the real array iterator + napi_value globalObj, symbolCtor, symbolIterator, iterMethod, iterator; + NAPI_GUARD(napi_get_global(env, &globalObj)) { + return nullptr; + } + NAPI_GUARD(napi_get_named_property(env, globalObj, "Symbol", &symbolCtor)) { + return nullptr; + } + NAPI_GUARD(napi_get_named_property(env, symbolCtor, "iterator", &symbolIterator)) { + return nullptr; + } + NAPI_GUARD(napi_get_property(env, values, symbolIterator, &iterMethod)) { + return nullptr; + } + NAPI_GUARD(napi_call_function(env, values, iterMethod, 0, nullptr, &iterator)) { + return nullptr; + } + return iterator; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::CreateArrayWrapper(napi_env env) { + napi_status status; + napi_value constructor = CreateArrayObjectConstructor(env); + napi_value instance; + NAPI_GUARD(napi_new_instance(env, constructor, 0, nullptr, &instance)) { + return nullptr; + } + SetInstanceMetadata(env, instance, this); + return instance; +} + +napi_value MetadataNode::GetImplementationObject(napi_env env, napi_value object) { + napi_status status; + auto target = object; + napi_value currentPrototype = target; + + napi_value implementationObject; + + NAPI_GUARD(napi_get_named_property(env, currentPrototype, CLASS_IMPLEMENTATION_OBJECT, + &implementationObject)) {} + + if (implementationObject != nullptr && !napi_util::is_undefined(env, implementationObject)) { + return implementationObject; + } + + bool hasProperty; + + napi_value prototypeImplObjectKey; + NAPI_GUARD(napi_create_string_utf8(env, PROP_KEY_IS_PROTOTYPE_IMPLEMENTATION_OBJECT, NAPI_AUTO_LENGTH, + &prototypeImplObjectKey)) {} + NAPI_GUARD(napi_has_own_property(env, object, prototypeImplObjectKey, &hasProperty)) {} + + if (hasProperty) { + bool maybeHasOwnProperty; + napi_value prototypeKey; + NAPI_GUARD(napi_create_string_utf8(env, PROTOTYPE, NAPI_AUTO_LENGTH, &prototypeKey)) {} + NAPI_GUARD(napi_has_own_property(env, object, prototypeKey, &maybeHasOwnProperty)) {} + + if (!maybeHasOwnProperty) { + return nullptr; + } + + return napi_util::get_prototype(env, object); + } + + napi_value activityImplementationObject; + NAPI_GUARD(napi_get_named_property(env, object, "t::ActivityImplementationObject", + &activityImplementationObject)) {} + + if (activityImplementationObject != nullptr && + !napi_util::is_undefined(env, activityImplementationObject)) { + return activityImplementationObject; + } + + napi_value lastPrototype; + + bool prototypeCycleDetected = false; + + bool foundImplementationObject = false; + + while (!foundImplementationObject) { + currentPrototype = napi_util::get_prototype(env, currentPrototype); + + if (napi_util::is_null(env, currentPrototype)) { + break; + } + + if (lastPrototype == currentPrototype) { + auto abovePrototype = napi_util::get_prototype(env, currentPrototype); + prototypeCycleDetected = abovePrototype == currentPrototype; + break; + } + + if (currentPrototype == nullptr || napi_util::is_null(env, currentPrototype) || + prototypeCycleDetected) { + return nullptr; + } else { + napi_value implObject; + NAPI_GUARD(napi_get_named_property(env, currentPrototype, CLASS_IMPLEMENTATION_OBJECT, + &implObject)) {} + + if (implObject != nullptr && !napi_util::is_undefined(env, implObject)) { + foundImplementationObject = true; + return currentPrototype; + } + } + lastPrototype = currentPrototype; + } + + return implementationObject; +} + +void MetadataNode::SetInstanceMetadata(napi_env env, napi_value object, MetadataNode *node) { +#ifdef USE_HOST_OBJECT + // node now lives on the per-instance JSInstanceInfo (set in + // ObjectManager::Link / GetOrCreateProxy); the "#instance_metadata" property + // is no longer used on the host path. + (void) env; + (void) object; + (void) node; +#else + napi_status status; + napi_value external; + NAPI_GUARD(napi_create_external(env, node, [](napi_env env, void *d1, void *d2) {}, node, &external)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, object, "#instance_metadata", external)) {} +#endif +// napi_wrap(env, object, node, nullptr, nullptr, nullptr); +} + + +napi_value MetadataNode::ExtendedClassConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + try { + napi_value newTarget; + // Throw (not return nullptr) so a JS exception is left pending; otherwise + // the constructor trampoline maps a null result to `undefined` and + // `new X()` silently yields undefined. + NAPI_GUARD(napi_get_new_target(env, info, &newTarget)) { + throw NativeScriptException("Failed to read new.target in constructor call."); + } + if (napi_util::is_null_or_undefined(env, newTarget)) return nullptr; + napi_value receiver = EnsureConstructorThis(env, jsThis, napi_util::get_prototype(env, newTarget)); + if (napi_util::is_null_or_undefined(env, receiver)) return nullptr; + + auto extData = reinterpret_cast(data); + SetInstanceMetadata(env, receiver, extData->node); + + napi_value implementationObject = napi_util::get_ref_value(env, + extData->implementationObject); + ObjectManager::MarkSuperCall(env, receiver); + + string fullClassName = extData->fullClassName; + + ArgsWrapper argWrapper(argv, argc, ArgType::Class); + napi_value jsThisProxy; + bool success = CallbackHandlers::RegisterInstance(env, receiver, fullClassName, argWrapper, + implementationObject, false, + &jsThisProxy, extData->node->m_name, + extData->node); + + return jsThisProxy; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::InterfaceConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + try { + + napi_valuetype arg1Type; + napi_valuetype arg2Type; + + // Throw so an exception is left pending (a null result would otherwise + // surface as `undefined` from `new`). + NAPI_GUARD(napi_typeof(env, argv[0], &arg1Type)) { + throw NativeScriptException("Failed to read constructor argument type."); + } + + if (argc == 2) { + NAPI_GUARD(napi_typeof(env, argv[1], &arg2Type)) { + throw NativeScriptException("Failed to read constructor argument type."); + } + } + + napi_value implementationObject; + napi_value interfaceName; + + if (argc == 1) { + if (arg1Type != napi_object) { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be an object if only one argument is provided")); + return nullptr; + } + implementationObject = argv[0]; + } else if (argc == 2) { + if (arg1Type != napi_string) { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be a string if only two argument is provided")); + return nullptr; + } + + if (arg2Type != napi_object) { + throw NativeScriptException( + string("Invalid arguments provided, second argument must be an object if only one argument is provided")); + return nullptr; + } + + interfaceName = argv[0]; + implementationObject = argv[1]; + } else { + throw NativeScriptException( + string("Invalid arguments provided, first argument must be a string and second argument must be an object")); + } + + auto node = reinterpret_cast(data); + + auto className = node->m_implType; + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + napi_value receiver = EnsureConstructorThis(env, jsThis, receiverPrototype); + if (napi_util::is_null_or_undefined(env, receiver)) return nullptr; + + SetInstanceMetadata(env, receiver, node); + + ObjectManager::MarkSuperCall(env, receiver); + + + napi_util::setPrototypeOf(env, implementationObject, + napi_util::getPrototypeOf(env, receiver)); + + napi_util::setPrototypeOf(env, receiver, implementationObject); + + NAPI_GUARD(napi_set_named_property(env, receiver, CLASS_IMPLEMENTATION_OBJECT, implementationObject)) {} + + ArgsWrapper argsWrapper(argv, argc, ArgType::Interface); + + napi_value jsThisProxy; + auto success = CallbackHandlers::RegisterInstance(env, receiver, className, argsWrapper, + implementationObject, true, &jsThisProxy, + std::string(), node); + return jsThisProxy; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ClassConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + try { + + auto node = reinterpret_cast(data); + napi_value newTarget; + napi_get_new_target(env, info, &newTarget); + napi_value receiverPrototype = !napi_util::is_null_or_undefined(env, newTarget) + ? napi_util::get_prototype(env, newTarget) + : nullptr; + napi_value receiver = EnsureConstructorThis(env, jsThis, receiverPrototype); + if (napi_util::is_null_or_undefined(env, receiver)) return nullptr; + + SetInstanceMetadata(env, receiver, node); + + // Plain construction has no extend name, so the full class name equals the + // base class name; skip CreateFullClassName (a string copy) and use the + // node's name directly for both. + const string &className = node->m_name; + + ArgsWrapper argsWrapper(argv, argc, ArgType::Class); + napi_value jsThisProxy; + bool success = CallbackHandlers::RegisterInstance(env, receiver, className, argsWrapper, + nullptr, false, &jsThisProxy, className, + node); + + return jsThisProxy; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +string MetadataNode::CreateFullClassName(const std::string &className, + const std::string &extendNameAndLocation = "") { + string fullClassName = className; + + // create a class name consisting only of the base class name + last file name part + line + column + variable identifier + if (!extendNameAndLocation.empty()) { + string tempClassName = className; + fullClassName = Util::ReplaceAll(tempClassName, "$", "_"); + fullClassName += "_" + extendNameAndLocation; + } + + return fullClassName; +} + +// The compile-time policy, which is also the floor: a build that hard-wires +// content keying cannot be talked out of it by an app's config. +static bool ContentKeyedBindingsForcedOn() { +#if defined(NS_CONTENT_KEYED_BINDINGS) + return true; +#else + return false; +#endif +} + +// Resolved once, before any JS runs. The naming scheme has to match what the +// static binding generator used for this app, so the value comes from the build +// that ran the generator (app/package.json -> AppConfig -> Runtime::Init) rather +// than from anything decided here. +static bool s_contentKeyedBindings = ContentKeyedBindingsForcedOn(); + +void MetadataNode::SetContentKeyedBindings(bool enabled) { + // Only ever widens: an app may turn content keying on, never off under a + // runtime that was built to require it. + s_contentKeyedBindings = s_contentKeyedBindings || enabled; +} + +bool MetadataNode::ContentKeyedBindingsEnabled() { + return s_contentKeyedBindings; +} + +// A class-name token derived from what the generated binding is actually made +// of, rather than from where the extend() call happens to sit. +// +// The static binding generator writes a class out of exactly three inputs: the +// base class, the implemented interfaces, and the overridden method names. Two +// extend() sites that agree on all three produce byte-identical Java, so hashing +// them is enough to name the class -- and two sites that collide are meant to +// share, which is why the generator already treats a duplicate file with equal +// content as a warning rather than an error. +// +// Location keys cannot do this once an embedder owns the bundler: the generator +// reads source coordinates and the runtime reads bundle coordinates. A content +// key needs no shared coordinate system at all. +// +// Sorted, because the runtime's order is JS own-property order and the +// generator's is source order; the binding treats both sets as unordered. +// FNV-1a rather than std::hash, which is not stable across implementations and +// so could not be reproduced by the generator. +// Package separators and nested-type separators both become '.' -- see the +// comment in CreateContentKey. +static string Canonicalize(const string &name) { + string canonical = name; + canonical = Util::ReplaceAll(canonical, std::string("/"), std::string(".")); + return Util::ReplaceAll(canonical, std::string("$"), std::string(".")); +} + +string MetadataNode::CreateContentKey(const string &baseClassName, + vector interfaceNames, + vector methodNames) { + // Canonicalized before sorting, not after: '$' and '.' are different bytes, + // so sorting the binary form and sorting the dotted form can disagree on + // order even once the names themselves match. + for (auto &interfaceName: interfaceNames) { + interfaceName = Canonicalize(interfaceName); + } + + sort(interfaceNames.begin(), interfaceNames.end()); + sort(methodNames.begin(), methodNames.end()); + + // Nested types reach here in their binary form -- `Pack200$Unpacker`, and + // `/` for package separators on the interface side. The generator reads the + // same names out of JS source, where both are written with a dot and there + // is no way to tell a nested type from a package. So the dotted form is the + // canonical one for hashing; the JNI arrays that actually load the classes + // keep the binary form, which is what Class.forName needs. + string canonicalBase = Canonicalize(baseClassName); + + string payload = canonicalBase; + payload += "|"; + for (size_t i = 0; i < interfaceNames.size(); i++) { + if (i > 0) payload += ","; + payload += interfaceNames[i]; + } + payload += "|"; + for (size_t i = 0; i < methodNames.size(); i++) { + if (i > 0) payload += ","; + payload += methodNames[i]; + } + + uint64_t hash = 14695981039346656037ULL; + for (unsigned char c: payload) { + hash ^= static_cast(c); + hash *= 1099511628211ULL; + } + + char buffer[19]; + snprintf(buffer, sizeof(buffer), "h%016llx", static_cast(hash)); + + DEBUG_WRITE("CreateContentKey: %s -> %s", payload.c_str(), buffer); + + return string(buffer); +} + +bool MetadataNode::IsValidExtendName(napi_env env, napi_value name) { + string extendName = ArgConverter::ConvertToString(env, name); + + for (char currentSymbol: extendName) { + bool isValidExtendNameSymbol = isalpha(currentSymbol) || + isdigit(currentSymbol) || + currentSymbol == '_'; + if (!isValidExtendNameSymbol) { + return false; + } + } + + return true; +} + + +bool +MetadataNode::GetExtendLocation(napi_env env, string &extendLocation, bool isTypeScriptExtend) { + stringstream extendLocationStream; + + auto frames = tns::BuildStacktraceFrames(env, nullptr, 4); + tns::JsStacktraceFrame *frame; + if (isTypeScriptExtend) { + if (Util::Contains(frames[2].text, "call_super")) { + frame = &frames[3]; + } else { + frame = &frames[2]; // the _super.apply call to ts_helpers will always be the third call frame + } + } else { + frame = &frames[0]; + } + + if (frame == NULL) { + DEBUG_WRITE("%s", "FRAME IS NULL!"); + return true; + } + + string srcFileName = Util::ReplaceAll(frame->filename, "file://", ""); + + string fullPathToFile; + if (srcFileName == "" || srcFileName == "" || srcFileName == "JavaScript") { + fullPathToFile = "script"; + } else { + string hardcodedPathToSkip = Constants::APP_ROOT_FOLDER_PATH; + int startIndex = hardcodedPathToSkip.length(); + int strToTakeLen = srcFileName.length() - startIndex - 3; + fullPathToFile = srcFileName.substr(startIndex, strToTakeLen); + fullPathToFile = srcFileName; + replace(fullPathToFile.begin(), fullPathToFile.end(), '/', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), '.', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), '-', '_'); + replace(fullPathToFile.begin(), fullPathToFile.end(), ' ', '_'); + + vector pathParts; + Util::SplitString(fullPathToFile, "_", pathParts); + fullPathToFile = + pathParts.back() == "js" ? pathParts[pathParts.size() - 2] : pathParts.back(); + } + + if (frame->line < 0) { + extendLocationStream << fullPathToFile << " unknown line number"; + extendLocation = extendLocationStream.str(); + return false; + } + + if (frame->col < 0) { + extendLocationStream << fullPathToFile << " line:" << frame->line + << " unknown column number"; + extendLocation = extendLocationStream.str(); + return false; + } + int column = frame->col; + if (frame->line == 1) { + column -= ModuleInternal::MODULE_PROLOGUE_LENGTH; + } + +#ifdef __HERMES__ + column = column - 6; +#endif + + extendLocationStream << fullPathToFile << "_" << frame->line << "_" << column << "_"; + extendLocation = extendLocationStream.str(); + return true; +} + + +bool MetadataNode::ValidateExtendArguments(napi_env env, size_t argc, napi_value *argv, + bool extendLocationFound, string &extendLocation, + napi_value *extendName, napi_value *implementationObject, + bool isTypeScriptExtend) { + + if (argc == 1) { + if (!extendLocationFound) { + stringstream ss; + ss << "Invalid extend() call. No name specified for extend at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!napi_util::is_object(env, argv[0])) { + stringstream ss; + ss << "Invalid extend() call. No implementation object specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + *implementationObject = argv[0]; + } else if (argc == 2 || isTypeScriptExtend) { + if (!napi_util::is_of_type(env, argv[0], napi_string)) { + stringstream ss; + ss << "Invalid extend() call. No name for extend specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!napi_util::is_object(env, argv[1])) { + stringstream ss; + ss + << "Invalid extend() call. Named extend should be called with second object parameter containing overridden methods at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + DEBUG_WRITE("ExtendsCallMethodHandler: getting extend name"); + + *extendName = argv[0]; + bool isValidExtendName = IsValidExtendName(env, *extendName); + if (!isValidExtendName) { + stringstream ss; + ss << "The extend name \"" << ArgConverter::ConvertToString(env, *extendName) + << "\" you provided contains invalid symbols. Try using the symbols [a-z, A-Z, 0-9, _]." + << endl; + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + *implementationObject = argv[1]; + } else { + stringstream ss; + ss << "Invalid extend() call at location: " << extendLocation.c_str(); + string exceptionMessage = ss.str(); + throw NativeScriptException(exceptionMessage); + } + + return true; +} + +MetadataNode::ExtendedClassCacheData +MetadataNode::GetCachedExtendedClassData(napi_env env, const string &proxyClassName) { + auto cache = GetMetadataNodeCache(env); + ExtendedClassCacheData cacheData; + auto itFound = cache->ExtendedCtorFuncCache.find(proxyClassName); + if (itFound != cache->ExtendedCtorFuncCache.end()) { + cacheData = itFound->second; + } + + return cacheData; +} + +MetadataNode::MetadataNodeCache *MetadataNode::GetMetadataNodeCache(napi_env env) { + auto cache = s_metadata_node_cache.Get(env); + if (cache) return cache; + cache = new MetadataNodeCache; + s_metadata_node_cache.Insert(env, cache); + return cache; +} + +MetadataNode::MetadataNode(MetadataTreeNode *treeNode) : m_treeNode(treeNode) { + uint8_t nodeType = s_metadataReader.GetNodeType(treeNode); + + m_name = s_metadataReader.ReadTypeName(m_treeNode); + + uint8_t parentNodeType = s_metadataReader.GetNodeType(treeNode->parent); + + m_isArray = s_metadataReader.IsNodeTypeArray(parentNodeType); + + bool isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + + if (!m_isArray && isInterface) { + bool isPrefix; + auto impTypeName = s_metadataReader.ReadInterfaceImplementationTypeName(m_treeNode, + isPrefix); + m_implType = isPrefix + ? (impTypeName + m_name) + : impTypeName; + } +} + +void MetadataNode::CreateTopLevelNamespaces(napi_env env) { + napi_status status; + napi_value global; + + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + + auto root = s_metadataReader.GetRoot(); + + const auto &children = *root->children; + + for (auto treeNode: children) { + uint8_t nodeType = s_metadataReader.GetNodeType(treeNode); + + if (nodeType == MetadataTreeNode::PACKAGE) { + auto node = GetOrCreateInternal(treeNode); + + napi_value packageObj = node->CreateWrapper(env); + + string nameSpace = node->m_treeNode->name; + // if the namespaces matches a javascript keyword, prefix it with $ to avoid TypeScript and JavaScript errors + if (IsJavascriptKeyword(nameSpace)) { + nameSpace = "$" + nameSpace; + } + bool exists = false; + if (napi_has_named_property(env, global, nameSpace.c_str(), &exists) == napi_ok && exists) { + string fallback = "$" + nameSpace; + __android_log_print(ANDROID_LOG_WARN, "TNS.Runtime", + "Java package '%s' is exposed as '%s': the global '%s' already exists.", + nameSpace.c_str(), fallback.c_str(), nameSpace.c_str()); + nameSpace = fallback; + } + NAPI_GUARD(napi_set_named_property(env, global, nameSpace.c_str(), packageObj)) {} + } + } +} + +MetadataTreeNode *MetadataNode::GetOrCreateTreeNodeByName(const string &className) { + MetadataTreeNode *result = nullptr; + + auto itFound = s_name2TreeNodeCache.find(className); + + if (itFound != s_name2TreeNodeCache.end()) { + result = itFound->second; + } else { + result = s_metadataReader.GetOrCreateTreeNodeByName(className); + + s_name2TreeNodeCache.emplace(className, result); + } + + return result; +} + +string MetadataNode::GetName() { + return m_name; +} + +MetadataNode *MetadataNode::GetOrCreate(const string &className) { + MetadataNode *node = nullptr; + + auto it = s_name2NodeCache.find(className); + + if (it == s_name2NodeCache.end()) { + MetadataTreeNode *treeNode = GetOrCreateTreeNodeByName(className); + + node = GetOrCreateInternal(treeNode); + + s_name2NodeCache.emplace(className, node); + } else { + node = it->second; + } + + return node; +} + +MetadataNode *MetadataNode::GetOrCreateInternal(MetadataTreeNode *treeNode) { + MetadataNode *result = nullptr; + + auto it = s_treeNode2NodeCache.find(treeNode); + + if (it != s_treeNode2NodeCache.end()) { + result = it->second; + } else { + auto name = GetJniClassName(treeNode); + if (!name.empty()) { + auto it2 = s_name2NodeCache.find(name); + if ( it2 != s_name2NodeCache.end()) { + result = it2->second; + } + } + + if (!result) { + result = new MetadataNode(treeNode); + s_treeNode2NodeCache.emplace(treeNode, result); + if (!result->m_name.empty()) { + s_name2NodeCache.emplace(result->m_name, result); + } + } + } + + auto found = s_treeNode2NodeCache.find(treeNode); + if (found == s_treeNode2NodeCache.end()) { + s_treeNode2NodeCache.emplace(treeNode, result); + } + + return result; +} + +MetadataEntry MetadataNode::GetChildMetadataForPackage(MetadataNode *node, const char *propName) { + assert(node->m_treeNode->children != nullptr); + + MetadataEntry child(nullptr, NodeType::Class); + + const auto &children = *node->m_treeNode->children; + + for (auto treeNodeChild: children) { + if (strcmp(treeNodeChild->name.c_str(), propName) == 0) { + child.name = propName; + child.treeNode = treeNodeChild; + child.type = static_cast(s_metadataReader.GetNodeType(treeNodeChild)); + + if (s_metadataReader.IsNodeTypeInterface((uint8_t) child.type)) { + bool isPrefix; + string declaringType = s_metadataReader.ReadInterfaceImplementationTypeName( + treeNodeChild, isPrefix); + child.declaringType = isPrefix + ? (declaringType + + s_metadataReader.ReadTypeName(child.treeNode)) + : declaringType; + } + } + } + + return child; +} + +bool MetadataNode::IsJavascriptKeyword(const std::string &word) { + static set keywords; + + if (keywords.empty()) { + string kw[]{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", + "class", "const", "continue", "debugger", "default", "delete", "do", + "double", "else", "enum", "eval", "export", "extends", "false", "final", + "finally", "float", "for", "function", "goto", "if", "implements", + "import", "in", "instanceof", "int", "interface", "let", "long", "native", + "new", "null", "package", "private", "protected", "public", "return", + "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", + "transient", "true", "try", "typeof", "var", "void", "volatile", "while", + "with", "yield"}; + + keywords = set(kw, kw + sizeof(kw) / sizeof(kw[0])); + } + + return keywords.find(word) != keywords.end(); +} + +napi_value MetadataNode::CreateWrapper(napi_env env) { + napi_value result; + uint8_t nodeType = s_metadataReader.GetNodeType(m_treeNode); + bool isClass = s_metadataReader.IsNodeTypeClass(nodeType), + isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + napi_status status; + + if (isClass || isInterface) { + result = GetConstructorFunction(env); + } else if (s_metadataReader.IsNodeTypePackage(nodeType)) { + result = CreatePackageObject(env); + } else { + std::stringstream ss; + ss << "(InternalError): Can't create proxy for this type=" << static_cast(nodeType); + throw NativeScriptException(ss.str()); + } + + return result; +} + +napi_value MetadataNode::PackageGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + auto childTreeNode = static_cast(data); + DEBUG_WRITE("Get package item: %s", childTreeNode->name.c_str()); + + auto childNode = MetadataNode::GetOrCreateInternal(childTreeNode); + napi_value value = childNode->CreateWrapper(env); + + uint8_t childNodeType = s_metadataReader.GetNodeType(childTreeNode); + if (s_metadataReader.IsNodeTypeInterface(childNodeType)) { + // For all java interfaces we register the special Symbol.hasInstance property + // which is invoked by the instanceof operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance). + // For example: + // + // Object.defineProperty(android.view.animation.Interpolator, Symbol.hasInstance, { + // value: function(obj) { + // return true; + // } + // }); + RegisterSymbolHasInstanceCallback(env, childTreeNode, value); + } + + // org.json.JSONObject special-case. Cheap name check first so the parent + // lookup only happens for the one class that needs it. + if (childTreeNode->name == "JSONObject") { + auto parentNode = GetOrCreateInternal(childTreeNode->parent); + if (parentNode->m_name == "org/json") { + JSONObjectHelper::RegisterFromFunction(env, value); + } + } + + // Replace this accessor on the receiver with the resolved value as a plain + // (configurable) data property, so every subsequent `pkg.Child` access is a + // direct, inline-cacheable property load instead of re-invoking this getter. + napi_property_descriptor dataProp = { + childTreeNode->name.c_str(), nullptr, nullptr, nullptr, nullptr, + value, napi_default_jsproperty, nullptr}; + NAPI_GUARD(napi_define_properties(env, jsThis, 1, &dataProp)) {} + + return value; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +void MetadataNode::RegisterSymbolHasInstanceCallback(napi_env env, const MetadataTreeNode *treeNode, + napi_value interface) { + if (napi_util::is_undefined(env, interface) || napi_util::is_null(env, interface)) { + return; + } + + JEnv jEnv; + + auto className = GetJniClassName(treeNode); + auto clazz = jEnv.FindClass(className); + if (clazz == nullptr) { + return; + } + + napi_status status; + napi_value hasInstance; + napi_value symbol; + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + NAPI_GUARD(napi_get_named_property(env, global, "Symbol", &symbol)) { + return; + } + NAPI_GUARD(napi_get_named_property(env, symbol, "hasInstance", &hasInstance)) { + return; + } + // NOTE: the napi `data` pointer must be a heap-allocated pointer, NOT a raw + // JNI reference. PrimJS's napi boxes the callback data into 48 bits and + // reconstructs it with a fixed top-16-bit heap tag (0xb400...) on retrieval; + // that is lossless for real heap pointers but corrupts a JNI global ref + // (top bits 0x0000), yielding a bogus jclass and a CheckJNI "invalid jobject" + // abort. Wrap the class ref in a heap holder so `data` is always a heap + // pointer (engine-neutral; matches the MethodCallbackData pattern). + auto *holder = new SymbolHasInstanceData{clazz}; + napi_value method; + NAPI_GUARD(napi_create_function(env, "hasInstance", NAPI_AUTO_LENGTH, SymbolHasInstanceCallback, holder, + &method)) { + delete holder; + return; + } + + napi_property_descriptor desc = { + nullptr, // utf8name + hasInstance, // name + nullptr, // method + nullptr, // getter + nullptr, // setter + method, // value + napi_default, // attributes + nullptr // data + }; + NAPI_GUARD(napi_define_properties(env, interface, 1, &desc)) {} +} + +napi_value MetadataNode::SymbolHasInstanceCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(2); + if (argc != 1) { + throw NativeScriptException(string("Symbol.hasInstance must take exactly 1 argument")); + return nullptr; + } + + napi_value object = argv[0]; + + if (!napi_util::is_object(env, object)) { + return napi_util::get_false(env); + } + + auto clazz = reinterpret_cast(data)->clazz; + auto runtime = Runtime::GetRuntime(env); + + auto objectManager = runtime->GetObjectManager(); + auto obj = objectManager->GetJavaObjectByJsObject(object); + + if (obj.IsNull()) { + // Couldn't find a corresponding java instance counterpart. This could happen + // if the "instanceof" operator is invoked on a pure javascript instance + return napi_util::get_false(env); + } + + JEnv jEnv; + auto isInstanceOf = jEnv.IsInstanceOf(obj, clazz); + + napi_value result; + NAPI_GUARD(napi_get_boolean(env, isInstanceOf, &result)) { + return nullptr; + } + + return result; + +} + + +std::string MetadataNode::GetJniClassName(const MetadataTreeNode *node) { + std::stack s; + + while (node != nullptr && !node->name.empty()) { + s.push(node->name); + node = node->parent; + } + + string fullClassName; + while (!s.empty()) { + auto top = s.top(); + fullClassName = (fullClassName.empty()) ? top : fullClassName + "/" + top; + s.pop(); + } + + return fullClassName; +} + +napi_value MetadataNode::CreatePackageObject(napi_env env) { + napi_status status; + napi_value packageObj; + NAPI_GUARD(napi_create_object(env, &packageObj)) { + return nullptr; + } + + auto ptrChildren = this->m_treeNode->children; + + if (ptrChildren != nullptr) { + const auto &children = *ptrChildren; + auto lastChildName = ""; + for (auto childNode: children) { + if (strcmp(childNode->name.c_str(), lastChildName) == 0) { + continue; + } + lastChildName = childNode->name.c_str(); + napi_property_descriptor descriptor{ + childNode->name.c_str(), + nullptr, + nullptr, + PackageGetterCallback, + nullptr, + nullptr, + napi_default_jsproperty, + childNode}; + NAPI_GUARD(napi_define_properties(env, packageObj, 1, &descriptor)) {} + } + } + + return packageObj; +} + +std::vector MetadataNode::SetClassMembers( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + + if (treeNode->metadata != nullptr) { + return SetInstanceMembersFromRuntimeMetadata( + env, constructor, instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); + } + + return SetClassMembersFromStaticMetadata( + env, constructor, instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); +} + +std::vector MetadataNode::SetClassMembersFromStaticMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + + napi_status status; + std::vector instanceMethodData; + + uint8_t *curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1; + + auto nodeType = s_metadataReader.GetNodeType(treeNode); + auto curType = s_metadataReader.ReadTypeName(treeNode); + curPtr += sizeof(uint16_t /* baseClassId */); + + if (s_metadataReader.IsNodeTypeInterface(nodeType)) { + curPtr += sizeof(uint8_t) + sizeof(uint32_t); + } + + std::string lastMethodName; + MethodCallbackData *callbackData = nullptr; + + robin_hood::unordered_map collectedExtensionMethods; + + napi_value prototype = napi_util::get_prototype(env, constructor); + + // Strong reference to the prototype shared by every instance field/property + // accessor below. When host objects are disabled the accessors use it to + // identity-compare the receiver and short-circuit Class.prototype. + // access. The prototype lives for the class' lifetime, so this never frees. + napi_ref prototypeRef = nullptr; + NAPI_GUARD(napi_create_reference(env, prototype, 1, &prototypeRef)) {} + + auto objectManager = Runtime::GetObjectManager(env); + auto extensionFunctionsCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + collectedExtensionMethods.reserve(extensionFunctionsCount); + + for (auto i = 0; i < extensionFunctionsCount; i++) { + auto entry = MetadataReader::ReadExtensionFunctionEntry(&curPtr); + + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = tryGetExtensionMethodCallbackData(collectedExtensionMethods, + methodName); + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + + napi_value method; + NAPI_GUARD(napi_create_function(env, methodName.c_str(), methodName.size(), MethodCallback, + callbackData, &method)) {} + + napi_util::define_property_value(env, prototype, methodName.c_str(), method, napi_default_method); + lastMethodName = methodName; + collectedExtensionMethods.emplace(methodName, callbackData); + + } + } + + callbackData->candidates.push_back(std::move(entry)); + callbackData->objectManager = objectManager; + } + + auto instanceMethodCount = *reinterpret_cast(curPtr); + collectedExtensionMethods.reserve(instanceMethodCount); + curPtr += sizeof(uint16_t); + + for (auto i = 0; i < instanceMethodCount; i++) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = tryGetExtensionMethodCallbackData(collectedExtensionMethods, + methodName); + + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + napi_value method; + NAPI_GUARD(napi_create_function(env, methodName.c_str(), methodName.size(), MethodCallback, + callbackData, &method)) {} + napi_util::define_property_value(env, prototype, methodName.c_str(), method, napi_default_method); + collectedExtensionMethods.emplace(methodName, callbackData); + } + + instanceMethodData.push_back(callbackData); + instanceMethodsCallbackData.push_back(callbackData); + + auto itFound = std::find_if(baseInstanceMethodsCallbackData.begin(), + baseInstanceMethodsCallbackData.end(), + [&methodName](MethodCallbackData *x) { + return x->candidates.front().name == methodName; + }); + if (itFound != baseInstanceMethodsCallbackData.end()) { + callbackData->parent = *itFound; + } + + lastMethodName = methodName; + } + + callbackData->candidates.push_back(std::move(entry)); + callbackData->objectManager = objectManager; + } + for (const auto &extension: collectedExtensionMethods) { + if (std::find(instanceMethodData.begin(), instanceMethodData.end(), extension.second) == + instanceMethodData.end()) { + GetMetadataNodeCache(env)->methodCallbackData.push_back(extension.second); + } + } + auto instanceFieldCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < instanceFieldCount; i++) { + auto entry = MetadataReader::ReadInstanceFieldEntry(&curPtr); + auto &fieldName = entry.getName(); + auto fieldInfo = new FieldCallbackData(entry); + fieldInfo->metadata.declaringType = curType; + fieldInfo->prototype = prototypeRef; + fieldInfo->objectManager = objectManager; + napi_util::define_property(env, prototype, fieldName.c_str(), nullptr, + FieldAccessorGetterCallback, FieldAccessorSetterCallback, + fieldInfo); + + MetadataNode::GetMetadataNodeCache(env)->fieldCallbackData.push_back(fieldInfo); + + } + + auto kotlinPropertiesCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (int i = 0; i < kotlinPropertiesCount; ++i) { + uint32_t nameOffset = *reinterpret_cast(curPtr); + auto propertyName = s_metadataReader.ReadName(nameOffset); + curPtr += sizeof(uint32_t); + + auto hasGetter = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + + // Keep the full method entry (not just its name) so the accessor can call + // CallJavaMethod directly instead of looking up + invoking the JS method. + MetadataEntry *getterEntry = nullptr; + std::string getterMethodName; + if (hasGetter >= 1) { + getterEntry = new MetadataEntry(MetadataReader::ReadInstanceMethodEntry(&curPtr)); + getterMethodName = getterEntry->getName(); + } + + auto hasSetter = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + + MetadataEntry *setterEntry = nullptr; + std::string setterMethodName; + if (hasSetter >= 1) { + setterEntry = new MetadataEntry(MetadataReader::ReadInstanceMethodEntry(&curPtr)); + setterMethodName = setterEntry->getName(); + } + + auto propertyInfo = new PropertyCallbackData(propertyName, getterMethodName, + setterMethodName); + propertyInfo->prototype = prototypeRef; + propertyInfo->getterEntry = getterEntry; + propertyInfo->setterEntry = setterEntry; + propertyInfo->node = this; + propertyInfo->objectManager = objectManager; + napi_util::define_property(env, prototype, propertyName.c_str(), nullptr, + PropertyAccessorGetterCallback, PropertyAccessorSetterCallback, + propertyInfo); + } + + // Set static class members on constructor + lastMethodName.clear(); + callbackData = nullptr; + + auto origin = Constants::APP_ROOT_FOLDER_PATH + this->m_name; + + // get candidates from static methods metadata + auto staticMethodCout = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < staticMethodCout; i++) { + auto entry = MetadataReader::ReadStaticMethodEntry(&curPtr); + // In java there can be multiple methods of same name with different parameters. + auto &methodName = entry.getName(); + if (methodName != lastMethodName) { + callbackData = new MethodCallbackData(this); + GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + napi_value method; + NAPI_GUARD(napi_create_function(env, methodName.c_str(), methodName.size(), MethodCallback, + callbackData, &method)) {} + + napi_util::define_property_value(env, constructor, methodName.c_str(), method, napi_default_method); + lastMethodName = methodName; + } + callbackData->candidates.push_back(std::move(entry)); + callbackData->objectManager = objectManager; + } + + napi_value extendMethod; + NAPI_GUARD(napi_create_function(env, PROP_KEY_EXTEND, sizeof(PROP_KEY_EXTEND), ExtendMethodCallback, this, + &extendMethod)) {} + NAPI_GUARD(napi_set_named_property(env, constructor, PROP_KEY_EXTEND, extendMethod)) {} + + // Brand the runtime's native extend() so ts_helpers can reliably tell a native class's + // extend from a user/JS extend. It must NOT rely on Function.prototype.toString() sniffing + // "[native code]": in release builds JS is compiled to bytecode and every function + // (native or JS) stringifies to "[native code]", so a plain JS class with a static method + // named "extend" would be misdetected as native. This brand is a real, non-enumerable + // property set by the runtime, so it works identically for source and bytecode on all engines. + napi_value nativeExtendBrand; + NAPI_GUARD(napi_get_boolean(env, true, &nativeExtendBrand)) {} + NAPI_GUARD(napi_util::define_property_value(env, extendMethod, "__isNativeExtend__", nativeExtendBrand, napi_default)) {} + + // get candidates from static fields metadata + auto staticFieldCout = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + for (auto i = 0; i < staticFieldCout; i++) { + auto entry = MetadataReader::ReadStaticFieldEntry(&curPtr); + auto &fieldName = entry.getName(); + auto fieldInfo = new FieldCallbackData(entry); + napi_value method; + napi_util::define_property(env, constructor, fieldName.c_str(), nullptr, + FieldAccessorGetterCallback, FieldAccessorSetterCallback, + fieldInfo); + MetadataNode::GetMetadataNodeCache(env)->fieldCallbackData.push_back(fieldInfo); + fieldInfo->objectManager = objectManager; + } + + + napi_util::define_property(env, constructor, PROP_KEY_NULLOBJECT, nullptr, + NullObjectAccessorGetterCallback, nullptr, this); + + + std::string tname = s_metadataReader.ReadTypeName(treeNode); + NAPI_GUARD(napi_set_named_property(env, constructor, PRIVATE_TYPE_NAME, + ArgConverter::convertToJsString(env, tname))) {} + + SetClassAccessor(env, constructor); + + return instanceMethodData; +} + +MetadataNode::MethodCallbackData *MetadataNode::tryGetExtensionMethodCallbackData( + const robin_hood::unordered_map &collectedMethodCallbackData, + const std::string &lookupName) { + + if (collectedMethodCallbackData.empty()) { + return nullptr; + } + + auto itFound = collectedMethodCallbackData.find(lookupName); + if (itFound != collectedMethodCallbackData.end()) { + return itFound->second; + } + + return nullptr; +} + +bool MetadataNode::IsNodeTypeInterface() { + uint8_t nodeType = s_metadataReader.GetNodeType(m_treeNode); + return s_metadataReader.IsNodeTypeInterface(nodeType); +} + +std::vector MetadataNode::SetInstanceMembersFromRuntimeMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode) { + assert(treeNode->metadata != nullptr); + + napi_status status; + std::vector instanceMethodData; + + std::string line; + const std::string &metadata = *treeNode->metadata; + std::stringstream s(metadata); + + std::string kind; + std::string name; + std::string signature; + int paramCount; + + std::getline(s, line); // type line + std::getline(s, line); // base class line + + std::string lastMethodName; + MethodCallbackData *callbackData = nullptr; + + napi_value proto = napi_util::get_prototype(env, constructor); + while (std::getline(s, line)) { + std::stringstream tmp(line); + tmp >> kind >> name >> signature >> paramCount; + + char chKind = kind[0]; + + assert((chKind == 'M') || (chKind == 'F')); + + MetadataEntry entry(nullptr, NodeType::Field); + + entry.name = name; + entry.sig = signature; + entry.paramCount = paramCount; + entry.isStatic = false; + if (chKind == 'M') { + entry.type = NodeType::Method; + if (entry.name != lastMethodName) { + callbackData = new MethodCallbackData(this); + instanceMethodData.push_back(callbackData); + instanceMethodsCallbackData.push_back(callbackData); + + auto itFound = std::find_if(baseInstanceMethodsCallbackData.begin(), + baseInstanceMethodsCallbackData.end(), + [&entry](MethodCallbackData *x) { + return x->candidates.front().name == entry.name; + }); + if (itFound != baseInstanceMethodsCallbackData.end()) { + callbackData->parent = *itFound; + } + + napi_value method; + NAPI_GUARD(napi_create_function(env, entry.name.c_str(), NAPI_AUTO_LENGTH, MethodCallback, + callbackData, &method)) {} + NAPI_GUARD(napi_set_named_property(env, proto, entry.name.c_str(), method)) {} + + lastMethodName = entry.name; + } + callbackData->candidates.push_back(std::move(entry)); + } else if (chKind == 'F') { + entry.type = NodeType::Field; + auto *fieldInfo = new FieldCallbackData(entry); + napi_util::define_property(env, proto, entry.name.c_str(), nullptr, + FieldAccessorGetterCallback, FieldAccessorSetterCallback, + fieldInfo); + + MetadataNode::GetMetadataNodeCache(env)->fieldCallbackData.push_back(fieldInfo); + } + } + + return instanceMethodData; +} + +void MetadataNode::SetClassAccessor(napi_env env, napi_value constructor) { + napi_util::define_property(env, constructor, PROP_KEY_CLASS, nullptr, + ClassAccessorGetterCallback, nullptr, nullptr); +} + +napi_value MetadataNode::ClassAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + try { + napi_value name; + NAPI_GUARD(napi_get_named_property(env, jsThis, PRIVATE_TYPE_NAME, &name)) { + return nullptr; + } + const char *nameValue = napi_util::get_string_value(env, name); + return CallbackHandlers::FindClass(env, nameValue); + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::GetConstructorFunction(napi_env env) { + std::vector instanceMethodsCallbackData; + return GetConstructorFunctionInternal(env, m_treeNode, instanceMethodsCallbackData); +} + +napi_value MetadataNode::GetConstructorFunctionInternal(napi_env env, MetadataTreeNode *treeNode, + std::vector instanceMethodsCallbackData) { + + napi_status status; + auto cache = GetMetadataNodeCache(env); + auto itFound = cache->CtorFuncCache.find(treeNode); + if (itFound != cache->CtorFuncCache.end()) { + if (itFound->second.constructorFunction != nullptr) { + auto value = napi_util::get_ref_value(env, itFound->second.constructorFunction); + if (!napi_util::is_null_or_undefined(env, value)) { + instanceMethodsCallbackData = itFound->second.instanceMethodCallbacks; + return value; + } + } + } + + if (itFound != cache->CtorFuncCache.end()) { +#ifndef __JSC__ + for (auto data: itFound->second.instanceMethodCallbacks) { + delete data; + } +#endif + itFound->second.instanceMethodCallbacks.clear(); + if (itFound->second.constructorFunction != nullptr) { + NAPI_GUARD(napi_delete_reference(env, itFound->second.constructorFunction)) {} + } + cache->CtorFuncCache.erase(itFound); + } + + auto node = GetOrCreateInternal(treeNode); + + JEnv jEnv; + // if we already have an exception (which will be rethrown later) + // then we don't want to ignore the next exception + bool ignoreFindClassException = jEnv.ExceptionCheck() == JNI_FALSE; + auto currentClass = jEnv.FindClass(node->m_name); + if (ignoreFindClassException && jEnv.ExceptionCheck()) { + jEnv.ExceptionClear(); + // JNI found an exception looking up this class + // but we don't care, because this means this class doesn't exist + // like when you try to get a class that only exists in a higher API level + CtorCacheData ctorCacheItem(nullptr, instanceMethodsCallbackData); + cache->CtorFuncCache.emplace(treeNode, ctorCacheItem); + return nullptr; + }; + + auto currentNode = treeNode; + std::string finalName(currentNode->name); + while (currentNode->parent) { + if (!currentNode->parent->name.empty()) { + finalName = currentNode->parent->name + "." + finalName; + } + currentNode = currentNode->parent; + } + + // 1. Create the class and get the constructor + + napi_value constructor; + auto isInterface = s_metadataReader.IsNodeTypeInterface(treeNode->type); + NAPI_GUARD(napi_define_class(env, finalName.c_str(), NAPI_AUTO_LENGTH, + isInterface ? InterfaceConstructorCallback : ClassConstructorCallback, + node, 0, nullptr, &constructor)) { + return nullptr; + } + + // Mark this constructor's prototype as a runtime object. + ObjectManager::MarkObject(env, napi_util::get_prototype(env, constructor)); + + // 2. Create the base constructor if it doesn't exist and inherit from it. + napi_value baseConstructor; + std::vector baseInstanceMethodsCallbackData; + auto tmpTreeNode = treeNode; + std::vector skippedBaseTypes; + + while (true) { + auto baseTreeNode = s_metadataReader.GetBaseClassNode(tmpTreeNode); + if (CheckClassHierarchy(jEnv, currentClass, treeNode, baseTreeNode, skippedBaseTypes)) { + tmpTreeNode = baseTreeNode; + continue; + } + + if ((baseTreeNode != treeNode) && (baseTreeNode != nullptr) && + (baseTreeNode->offsetValue > 0)) { + baseConstructor = GetConstructorFunctionInternal(env, baseTreeNode, + baseInstanceMethodsCallbackData); + + + if (baseConstructor != nullptr) { + napi_util::napi_inherits(env, constructor, baseConstructor); + } + } else { + baseConstructor = nullptr; + } + break; + } + + // 3. Define the class members now. + auto instanceMethodData = node->SetClassMembers(env, constructor, + instanceMethodsCallbackData, + baseInstanceMethodsCallbackData, treeNode); + + if (!skippedBaseTypes.empty()) { + // If there is a mismatch between base type of this class in metadata compared to the class + // at runtime, we will add methods of base class to this class's prototype. + node->SetMissingBaseMethods(env, skippedBaseTypes, instanceMethodData, constructor); + } + + + SetInnerTypes(env, constructor, treeNode); + + napi_ref constructorRef = napi_util::make_ref(env, constructor); + + if (baseConstructor != nullptr && !napi_util::is_undefined(env, baseConstructor)) { + napi_util::setPrototypeOf(env, constructor, baseConstructor); + } + + CtorCacheData ctorCacheItem(constructorRef, instanceMethodsCallbackData); + cache->CtorFuncCache.emplace(treeNode, ctorCacheItem); + + return constructor; +} + +void MetadataNode::SetInnerTypes(napi_env env, napi_value constructor, MetadataTreeNode *treeNode) { + if (treeNode->children != nullptr) { + const auto &children = *treeNode->children; + std::vector childNames(children.size()); + + napi_status status; + for (auto curChild: children) { + bool hasOwnProperty = false; + napi_value childName; + NAPI_GUARD(napi_create_string_utf8(env, curChild->name.c_str(), curChild->name.size(), &childName)) {} + NAPI_GUARD(napi_has_own_property(env, constructor, childName, &hasOwnProperty)) {} + if (!hasOwnProperty) { + napi_util::define_property(env, constructor, curChild->name.c_str(), nullptr, + InnerTypeGetterCallback, nullptr, curChild); + } + } + } +} + +napi_value MetadataNode::InnerTypeGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + auto curChild = reinterpret_cast(data); + auto childNode = GetOrCreateInternal(curChild); + // GetConstructorFunction caches per node (CtorFuncCache); inner types are + // always class/interface, both resolved here. + napi_value constructor = childNode->GetConstructorFunction(env); + + // Java interfaces need Symbol.hasInstance for `instanceof` support, just + // like package-level interfaces in PackageGetterCallback. + uint8_t childNodeType = s_metadataReader.GetNodeType(curChild); + if (s_metadataReader.IsNodeTypeInterface(childNodeType)) { + RegisterSymbolHasInstanceCallback(env, curChild, constructor); + } + + // Replace this accessor on the receiver (the outer type) with the resolved + // inner class/interface as a plain (configurable) data property, so every + // subsequent Outer.Inner access is a direct, inline-cacheable property load + // instead of re-invoking this getter. + napi_property_descriptor dataProp = { + curChild->name.c_str(), nullptr, nullptr, nullptr, nullptr, + constructor, napi_default_jsproperty, nullptr}; + NAPI_GUARD(napi_define_properties(env, jsThis, 1, &dataProp)) {} + + return constructor; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +MetadataReader *MetadataNode::getMetadataReader() { + return &MetadataNode::s_metadataReader; +} + +napi_value MetadataNode::NullObjectAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + + bool value; + napi_value nullNodeKey; + NAPI_GUARD(napi_create_string_utf8(env, PROP_KEY_NULL_NODE_NAME, NAPI_AUTO_LENGTH, &nullNodeKey)) { + return nullptr; + } + NAPI_GUARD(napi_has_own_property(env, jsThis, nullNodeKey, &value)) { + return nullptr; + } + + if (!value) { + auto node = reinterpret_cast(data); + napi_value external; + NAPI_GUARD(napi_create_external(env, node, [](napi_env env, void *d1, void *d2) {}, node, + &external)) { + return nullptr; + } + NAPI_GUARD(napi_set_named_property(env, jsThis, PROP_KEY_NULL_NODE_NAME, external)) {} + + napi_util::napi_set_function(env, + jsThis, + "valueOf", MetadataNode::NullValueOfCallback); + } + + return jsThis; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::NullValueOfCallback(napi_env env, napi_callback_info info) { + napi_status status; + napi_value nullValue; + NAPI_GUARD(napi_get_null(env, &nullValue)) { + return nullptr; + } + return nullValue; +} + +bool MetadataNode::IsInstanceReceiver(napi_env env, napi_value jsThis, napi_ref prototypeRef) { +#ifdef USE_HOST_OBJECT + // Real instances are host-object proxies; the class prototype is not. A + // non-host receiver means someone touched Class.prototype.. + (void) prototypeRef; + napi_status status; + bool isHostObject = false; + NAPI_GUARD(napi_is_host_object(env, jsThis, &isHostObject)) {} + return isHostObject; +#else + // Fallback: identity-compare the receiver against the cached prototype. + if (prototypeRef == nullptr) return true; + napi_value prototype = napi_util::get_ref_value(env, prototypeRef); + napi_status status; + bool isHolder = false; + NAPI_GUARD(napi_strict_equals(env, jsThis, prototype, &isHolder)) {} + return !isHolder; +#endif +} + +napi_value MetadataNode::FieldAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + try { + auto fieldData = reinterpret_cast(data); + auto &fieldMetadata = fieldData->metadata; + + if (fieldMetadata.getDeclaringType().empty()) { + return UNDEFINED; + } + + if (fieldData->objectManager == nullptr) { + fieldData->objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + if (fieldMetadata.isStatic) { + return CallbackHandlers::GetJavaField(env, jsThis, fieldData, + fieldData->objectManager); + } + + // A single probe both validates the receiver and resolves the java + // object; null + non-host means Class.prototype. access. + JniLocalRef target = fieldData->objectManager->GetJavaObjectByJsObjectFast(jsThis); + if (target.IsNull() && !IsInstanceReceiver(env, jsThis, fieldData->prototype)) { + return UNDEFINED; + } + return CallbackHandlers::GetJavaField(env, jsThis, fieldData, + fieldData->objectManager, std::move(target)); + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return UNDEFINED; +} +napi_ref propRef = nullptr; +napi_value MetadataNode::FieldAccessorSetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1); + + try { + auto fieldData = reinterpret_cast(data); + auto &fieldMetadata = fieldData->metadata; + + if (fieldData->objectManager == nullptr) { + fieldData->objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + } + + // A single probe both validates the receiver and resolves the java + // object; null + non-host means Class.prototype. access. + JniLocalRef target; + if (!fieldMetadata.isStatic) { + target = fieldData->objectManager->GetJavaObjectByJsObjectFast(jsThis); + if (target.IsNull() && !IsInstanceReceiver(env, jsThis, fieldData->prototype)) { + return UNDEFINED; + } + } + + if (fieldMetadata.getIsFinal()) { + stringstream ss; + ss << "You are trying to set \"" << fieldMetadata.getName() + << "\" which is a final field! Final fields can only be read."; + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } else { + CallbackHandlers::SetJavaField(env, jsThis, argv[0], fieldData, + fieldData->objectManager, std::move(target)); + return argv[0]; + } + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return UNDEFINED; +} + +napi_value MetadataNode::PropertyAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + auto propertyCallbackData = reinterpret_cast(data); + + if (propertyCallbackData->getterEntry == nullptr) { + return nullptr; + } + + if (!IsInstanceReceiver(env, jsThis, propertyCallbackData->prototype)) { + return nullptr; + } + + // Call the Java getter directly — no JS method lookup, no nested + // MethodCallback. Invariants are resolved once and cached. + if (propertyCallbackData->cachedIsFromInterface < 0) { + propertyCallbackData->cachedIsFromInterface = + propertyCallbackData->node->IsNodeTypeInterface() ? 1 : 0; + } + if (propertyCallbackData->objectManager == nullptr) { + propertyCallbackData->objectManager = + Runtime::GetRuntime(env)->GetObjectManager(); + } + return CallbackHandlers::CallJavaMethod( + env, jsThis, propertyCallbackData->node->m_name, + propertyCallbackData->getterMethodName, propertyCallbackData->getterEntry, + propertyCallbackData->cachedIsFromInterface == 1, + propertyCallbackData->getterEntry->isStatic, info, 0, nullptr, + propertyCallbackData->objectManager); + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::PropertyAccessorSetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(1) + + try { + auto propertyCallbackData = reinterpret_cast(data); + + if (propertyCallbackData->setterEntry == nullptr) { + return nullptr; + } + + if (!IsInstanceReceiver(env, jsThis, propertyCallbackData->prototype)) { + return nullptr; + } + + // Call the Java setter directly — no JS method lookup, no nested + // MethodCallback. Invariants are resolved once and cached. + if (propertyCallbackData->cachedIsFromInterface < 0) { + propertyCallbackData->cachedIsFromInterface = + propertyCallbackData->node->IsNodeTypeInterface() ? 1 : 0; + } + if (propertyCallbackData->objectManager == nullptr) { + propertyCallbackData->objectManager = + Runtime::GetRuntime(env)->GetObjectManager(); + } + return CallbackHandlers::CallJavaMethod( + env, jsThis, propertyCallbackData->node->m_name, + propertyCallbackData->setterMethodName, propertyCallbackData->setterEntry, + propertyCallbackData->cachedIsFromInterface == 1, + propertyCallbackData->setterEntry->isStatic, info, 1, &argv[0], + propertyCallbackData->objectManager); + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::ExtendMethodCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + try { + napi_value extendName; + napi_value implementationObject; + string extendLocation; + + auto hasDot = false; + auto isTypeScriptExtend = false; + + if (argc == 2) { + if (!napi_util::is_of_type(env, argv[0], napi_string)) { + stringstream ss; + ss << "Invalid extend() call. No name for extend specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + if (!napi_util::is_of_type(env, argv[1], napi_object)) { + stringstream ss; + ss << "Invalid extend() call. No implementation object specified at location: " + << extendLocation.c_str(); + string exceptionMessage = ss.str(); + + throw NativeScriptException(exceptionMessage); + } + + string strName = napi_util::get_string_value(env, argv[0]); + hasDot = strName.find('.') != string::npos; + } else if (argc == 3) { + if (napi_util::is_of_type(env, argv[2], napi_boolean)) { + NAPI_GUARD(napi_get_value_bool(env, argv[2], &isTypeScriptExtend)) {} + }; + } + auto node = reinterpret_cast(data); + + auto useContentKey = false; + + if (hasDot) { + extendName = argv[0]; + implementationObject = argv[1]; + } else { + useContentKey = ContentKeyedBindingsEnabled(); + + // Skipped outright when content keying is on: this walks the stack + // purely to build a name, on every class creation. + bool validExtend = + useContentKey || GetExtendLocation(env, extendLocation, isTypeScriptExtend); + + if (!validExtend) { + // No usable call site -- an anonymous frame, or a bundle the + // generator never indexed. Naming by content still works here, + // where before this was fatal. + useContentKey = true; + validExtend = true; + extendLocation.clear(); + } + + NAPI_GUARD(napi_create_string_utf8(env, "", 0, &extendName)) { + return nullptr; + } + auto validArgs = ValidateExtendArguments(env, argc, argv, validExtend, + extendLocation, + &extendName, &implementationObject, + isTypeScriptExtend); + if (!validArgs) { + return nullptr; + } + } + string baseClassName = node->m_name; + + // An explicitly named extend keeps its name in front of the hash. The + // generator reads the same name out of the bundle, so the two still + // agree, and it buys back two things the bare hash costs: a class name + // that says what it is in a stack trace, and uniqueness for the classes + // that need it. Anything Java instantiates by name -- an Activity, a + // Fragment, a Worker, anything reached by reflection -- cannot share a + // generated class, because the runtime has only the class name to find + // the JS implementation with. Naming such a class is what makes it + // unique; unnamed extends stay shared, which is the common case and the + // point of the scheme. + string extendNameString = ArgConverter::ConvertToString(env, extendName); + string extendNameAndLocation; + + if (useContentKey) { + auto interfaceNames = CallbackHandlers::CollectImplementedInterfaceNames( + env, implementationObject); + auto methodNames = CallbackHandlers::CollectMethodOverrideNames( + env, implementationObject, /* functionsOnly */ false); + string contentKey = + CreateContentKey(baseClassName, + interfaceNames, methodNames); + extendNameAndLocation = + extendNameString.empty() ? contentKey : extendNameString + "_" + contentKey; + } else { + extendNameAndLocation = extendLocation + extendNameString; + } + + string fullClassName; + if (!hasDot) { + fullClassName = TNS_PREFIX + CreateFullClassName(baseClassName, extendNameAndLocation); + } else { + fullClassName = ArgConverter::ConvertToString(env, argv[0]); + } + + uint8_t nodeType = s_metadataReader.GetNodeType(node->m_treeNode); + bool isInterface = s_metadataReader.IsNodeTypeInterface(nodeType); + auto clazz = CallbackHandlers::ResolveClass(env, baseClassName, fullClassName, + implementationObject, isInterface); + auto fullExtendedName = CallbackHandlers::ResolveClassName(env, clazz); + + // Which JS constructor to reuse is a different question from which Java + // class to load, and only the second one has to agree with the static + // binding generator. + // + // Two extend() calls with the same base, interfaces and overridden names + // share one generated Java class -- the Java is identical, and sharing it + // is the point. Their JS bodies are not identical, though, so caching the + // constructor under the class name would hand the second call the first + // call's implementation. Under content keying that is not a rare + // collision but the common case, so the constructor is cached per + // implementation object instead. + // The stamp identifies the object, not the class it was used to build: + // combining it with fullExtendedName keeps a second extend() of the same + // object against a *different* base a cache miss, so it still reaches the + // "used to extend another class" check below instead of silently + // returning the first class. + string ctorCacheKey = fullExtendedName; + if (useContentKey) { + napi_value stamped; + NAPI_GUARD(napi_get_named_property(env, implementationObject, EXTEND_CTOR_CACHE_KEY, + &stamped)) {} + string implObjectId; + if (napi_util::is_of_type(env, stamped, napi_string)) { + implObjectId = ArgConverter::ConvertToString(env, stamped); + } else { + static std::atomic s_implObjectCounter{0}; + implObjectId = std::to_string(s_implObjectCounter.fetch_add(1)); + NAPI_GUARD(napi_set_named_property( + env, implementationObject, EXTEND_CTOR_CACHE_KEY, + ArgConverter::convertToJsString(env, implObjectId))) {} + } + ctorCacheKey = fullExtendedName + "#" + implObjectId; + } + + auto cachedData = GetCachedExtendedClassData(env, ctorCacheKey); + if (cachedData.extendedCtorFunction != nullptr) { + auto value = napi_util::get_ref_value(env, cachedData.extendedCtorFunction); + if (!napi_util::is_null_or_undefined(env, value)) return value; + } + + napi_value implementationObjectName; + NAPI_GUARD(napi_get_named_property(env, implementationObject, CLASS_IMPLEMENTATION_OBJECT, + &implementationObjectName)) { + return nullptr; + } + + if (napi_util::is_null_or_undefined(env, implementationObjectName)) { + NAPI_GUARD(napi_set_named_property(env, implementationObject, CLASS_IMPLEMENTATION_OBJECT, + ArgConverter::convertToJsString(env, fullExtendedName))) {} + } else { + string usedClassName = ArgConverter::ConvertToString(env, implementationObjectName); + stringstream s; + s << "This object is used to extend another class '" << usedClassName << "'"; + throw NativeScriptException(s.str()); + } + + auto baseClassCtorFunction = node->GetConstructorFunction(env); + + napi_value extendFuncCtor; + NAPI_GUARD(napi_define_class(env, fullExtendedName.c_str(), NAPI_AUTO_LENGTH, + MetadataNode::ExtendedClassConstructorCallback, + new ExtendedClassCallbackData(node, extendNameAndLocation, + napi_util::make_ref(env, + implementationObject), + fullClassName), 0, nullptr, + &extendFuncCtor)) { + return nullptr; + } + napi_value extendFuncPrototype = napi_util::get_prototype(env, extendFuncCtor); + ObjectManager::MarkObject(env, extendFuncPrototype); + + napi_util::setPrototypeOf(env, implementationObject, + napi_util::get_prototype(env, baseClassCtorFunction)); + + napi_util::define_property( + env, implementationObject, PROP_KEY_SUPER, nullptr, SuperAccessorGetterCallback, + nullptr, nullptr); + + napi_util::setPrototypeOf(env, extendFuncPrototype, implementationObject); + + napi_util::setPrototypeOf(env, extendFuncCtor, baseClassCtorFunction); + + SetClassAccessor(env, extendFuncCtor); + + NAPI_GUARD(napi_set_named_property(env, extendFuncCtor, PRIVATE_TYPE_NAME, + ArgConverter::convertToJsString(env, fullExtendedName))) {} + + s_name2NodeCache.emplace(fullExtendedName, node); + + auto cache = GetMetadataNodeCache(env); + + // Two entries, because there are two ways in. + // + // From JS, extend() asks "have I already built a constructor for this + // implementation object?" -- keyed per object, so two identical-looking + // extends keep their own bodies. + // + // From Java, createJSInstanceNative has only the class name of the + // instance it is wrapping (CreateExtendedJSWrapper), so the class name + // has to resolve too. emplace leaves an existing entry alone, so for a + // shared generated class the first implementation registered is the one + // Java-side instantiation finds. That is unambiguous for a named extend + // -- naming it makes the class unique -- and inherently ambiguous for an + // unnamed one, which is why anything Java constructs by name should be + // named. + ExtendedClassCacheData byImplObject(napi_util::make_ref(env, extendFuncCtor), + fullExtendedName, node); + cache->ExtendedCtorFuncCache.emplace(ctorCacheKey, byImplObject); + + if (ctorCacheKey != fullExtendedName) { + ExtendedClassCacheData byClassName(napi_util::make_ref(env, extendFuncCtor), + fullExtendedName, node); + cache->ExtendedCtorFuncCache.emplace(fullExtendedName, byClassName); + } + + return extendFuncCtor; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + + +napi_value MetadataNode::SuperAccessorGetterCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + + try { + + napi_value superValue; + NAPI_GUARD(napi_get_named_property(env, jsThis, PROP_KEY_SUPERVALUE, &superValue)) { + return nullptr; + } + + if (napi_util::is_null_or_undefined(env, superValue)) { + auto objectManager = Runtime::GetRuntime(env)->GetObjectManager(); + superValue = objectManager->GetEmptyObject(); + + NAPI_GUARD(napi_delete_property(env, superValue, + ArgConverter::convertToJsString(env, PROP_KEY_TOSTRING), nullptr)) {} + NAPI_GUARD(napi_delete_property(env, superValue, + ArgConverter::convertToJsString(env, PROP_KEY_VALUEOF), nullptr)) {} + ObjectManager::MarkSuperCall(env, superValue); + + napi_value superProto = napi_util::getPrototypeOf(env, napi_util::getPrototypeOf(env, + napi_util::getPrototypeOf( + env, + jsThis))); + + napi_util::setPrototypeOf(env, superValue, superProto); + objectManager->CloneLink(jsThis, superValue); + auto node = GetInstanceMetadata(env, jsThis); + SetInstanceMetadata(env, superValue, node); + + int javaObjectID = -1; + objectManager->GetJavaObjectByJsObject(jsThis, &javaObjectID); + if (javaObjectID != -1) { + superValue = objectManager->GetOrCreateProxyWeak(javaObjectID, superValue); + } + NAPI_GUARD(napi_set_named_property(env, jsThis, PROP_KEY_SUPERVALUE, superValue)) {} + } + + return superValue; + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value MetadataNode::MethodCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + try { + MetadataEntry *entry = nullptr; + + auto callbackData = reinterpret_cast(data); + auto initialCallbackData = reinterpret_cast(data); + MethodCallbackData *selectedCallbackData = nullptr; + std::vector arityCandidates; + + string *className; + auto &first = callbackData->candidates.front(); + auto &methodName = first.getName(); + + // Fast path for the overwhelmingly common single-overload, non-extension + // method with no parent chain: skip the candidate-search loop entirely. + if (callbackData->parent == nullptr && + callbackData->candidates.size() == 1 && + !first.isExtensionFunction && + first.getParamCount() == argc) { + className = &callbackData->node->m_name; + selectedCallbackData = callbackData; + arityCandidates.push_back(&first); + entry = &first; + } + + while ((callbackData != nullptr) && (entry == nullptr)) { + auto &candidates = callbackData->candidates; + + className = &callbackData->node->m_name; + + arityCandidates.clear(); + for (auto &c: candidates) { + if ((!c.isExtensionFunction && c.getParamCount() == argc) || + (c.isExtensionFunction && c.getParamCount() == argc + 1)) { + arityCandidates.push_back(&c); + } + } + + if (!arityCandidates.empty()) { + selectedCallbackData = callbackData; + if (arityCandidates.front()->isExtensionFunction) { + className = &arityCandidates.front()->getDeclaringType(); + } + break; + } else { + callbackData = callbackData->parent; + } + } + + if (arityCandidates.size() == 1) { + entry = arityCandidates.front(); + } else if (arityCandidates.size() > 1) { + entry = arityCandidates.front(); + } + + + if (initialCallbackData->cachedIsValueOf < 0) { + initialCallbackData->cachedIsValueOf = + (methodName == PROP_KEY_VALUEOF) ? 1 : 0; + } + if (argc == 0 && initialCallbackData->cachedIsValueOf == 1) { + return jsThis; + } else { +// Runtime::GetRuntime(env)->clearPendingError(); + if (initialCallbackData->cachedIsFromInterface < 0) { + initialCallbackData->cachedIsFromInterface = + initialCallbackData->node->IsNodeTypeInterface() ? 1 : 0; + } + bool isFromInterface = initialCallbackData->cachedIsFromInterface == 1; + if (initialCallbackData->objectManager == nullptr) { + initialCallbackData->objectManager = + Runtime::GetRuntime(env)->GetObjectManager(); + } + + + size_t metadataMatches = 0; + MetadataEntry *metadataMatch = nullptr; + bool metadataSignatureIsUnambiguous = false; + bool forceReflectionFallback = false; + bool preserveGenericObject = false; + bool hasObjectCandidate = false; + bool hasSpecificInterfaceCandidate = false; + bool hasExactStringCandidate = false; + for (auto *candidate : arityCandidates) { + const auto signature = candidate->getSig(); + hasObjectCandidate |= signature.find("Ljava/lang/Object;") != std::string::npos; + hasSpecificInterfaceCandidate |= signature.find("Ljava/io/Serializable;") != std::string::npos || + signature.find("Ljava/lang/CharSequence;") != std::string::npos; + hasExactStringCandidate |= signature.find("(Ljava/lang/String;") == 0; + } + preserveGenericObject = hasObjectCandidate && hasSpecificInterfaceCandidate && !hasExactStringCandidate; + struct MetadataCandidateMatch { + MetadataEntry *entry; + std::vector parameterClasses; + }; + std::vector metadataCandidateMatches; + JEnv metadataEnv; + bool hasNullArgument = false; + for (size_t i = 0; i < argc; i++) { + napi_valuetype valueType; + if (napi_typeof(env, argv[i], &valueType) != napi_ok) { + hasNullArgument = true; + break; + } + if (valueType != napi_object && valueType != napi_function) { + continue; + } + napi_value nullNode = nullptr; + if (napi_get_named_property(env, argv[i], PROP_KEY_NULL_NODE_NAME, &nullNode) == napi_ok && + !napi_util::is_null_or_undefined(env, nullNode)) { + hasNullArgument = true; + break; + } + } + constexpr int kMetadataMismatch = -1; + constexpr int kMetadataUnknown = 0; + constexpr int kMetadataMatch = 1; + auto metadataTypesMatch = [&](MetadataEntry &candidate, + std::vector ¶meterClasses) -> int { + const auto signature = candidate.getSig(); + if (signature.empty() || signature[0] != '(') { + return kMetadataMismatch; + } + + std::vector parameterTypes; + size_t index = 1; + while (index < signature.size() && signature[index] != ')') { + const size_t start = index; + if (signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else if (signature[index] == '[') { + index++; + if (index < signature.size() && signature[index] == 'L') { + const auto end = signature.find(';', index); + if (end == std::string::npos) return kMetadataMismatch; + index = end + 1; + } else { + index++; + } + } else { + index++; + } + parameterTypes.emplace_back(signature.substr(start, index - start)); + } + + if (candidate.isExtensionFunction) { + if (parameterTypes.empty()) return kMetadataMismatch; + parameterTypes.erase(parameterTypes.begin()); + } + if (parameterTypes.size() != argc) return kMetadataMismatch; + + parameterClasses.assign(argc, nullptr); + bool hasUnknownArgument = false; + for (size_t i = 0; i < argc; i++) { + const auto ¶meterType = parameterTypes[i]; + napi_valuetype valueType; + napi_typeof(env, argv[i], &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + // JsArgConverter maps null/undefined for any parameter + // type; nothing to check statically. + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == 'L' || parameterType[0] == '[') { + jclass expectedClass = nullptr; + auto expectedName = parameterType; + if (expectedName[0] == 'L') { + expectedName = expectedName.substr(1, expectedName.size() - 2); + } + expectedClass = metadataEnv.FindClass(expectedName); + if (expectedClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + parameterClasses[i] = expectedClass; + + if (valueType == napi_string) { + if (expectedName != "java/lang/String") { + return kMetadataMismatch; + } + auto stringClass = metadataEnv.FindClass("java/lang/String"); + if (stringClass == nullptr) { + metadataEnv.ExceptionClear(); + return kMetadataMismatch; + } + if (metadataEnv.IsAssignableFrom(expectedClass, stringClass) != JNI_TRUE) { + return kMetadataMismatch; + } + } else { + if (valueType != napi_object && valueType != napi_function) return kMetadataMismatch; + napi_value nullNode = nullptr; + if (napi_get_named_property(env, argv[i], PROP_KEY_NULL_NODE_NAME, &nullNode) == napi_ok && + !napi_util::is_null_or_undefined(env, nullNode)) { + return kMetadataMismatch; + } + auto actualObject = initialCallbackData->objectManager->GetJavaObjectByJsObject(argv[i]); + if (actualObject.IsNull()) { + // JS array for an array parameter, or a JS value + // without a Java peer: only the lenient + // JsArgConverter can marshal these. + hasUnknownArgument = true; + continue; + } + if (parameterType[0] == '[' && expectedName == "[Ljava/lang/Object;") { + if (ObjectManager::GetClassName((jobject) actualObject) != expectedName) { + return kMetadataMismatch; + } + } + bool hasSerializableCandidate = false; + for (auto *arityCandidate : arityCandidates) { + if (arityCandidate->getSig().find("Ljava/io/Serializable;") != std::string::npos) { + hasSerializableCandidate = true; + break; + } + } + if (expectedName == "java/lang/Object" && !hasSerializableCandidate) { + return kMetadataMismatch; + } + if (metadataEnv.IsInstanceOf(actualObject, expectedClass) != JNI_TRUE) return kMetadataMismatch; + } + } else if (parameterType[0] == 'Z') { + if (valueType != napi_boolean) return kMetadataMismatch; + } else if (valueType != napi_number || + (parameterType[0] != 'B' && parameterType[0] != 'C' && + parameterType[0] != 'S' && parameterType[0] != 'I' && + parameterType[0] != 'J' && parameterType[0] != 'F' && + parameterType[0] != 'D')) { + return kMetadataMismatch; + } + } + return hasUnknownArgument ? kMetadataUnknown : kMetadataMatch; + }; + if (!hasNullArgument && selectedCallbackData != nullptr && arityCandidates.size() > 1 && !preserveGenericObject) { + for (auto *candidate : arityCandidates) { + if (candidate->isStatic != first.isStatic) { + continue; + } + std::vector parameterClasses; + if (metadataTypesMatch(*candidate, parameterClasses) == kMetadataMatch) { + metadataCandidateMatches.push_back({candidate, std::move(parameterClasses)}); + } + } + + for (size_t i = 0; i < metadataCandidateMatches.size(); i++) { + bool dominated = false; + for (size_t j = 0; j < metadataCandidateMatches.size(); j++) { + if (i == j) continue; + + const auto &candidate = metadataCandidateMatches[i]; + const auto &other = metadataCandidateMatches[j]; + for (size_t parameter = 0; parameter < argc; parameter++) { + auto candidateClass = candidate.parameterClasses[parameter]; + auto otherClass = other.parameterClasses[parameter]; + if (candidateClass == nullptr || otherClass == nullptr) { + if (candidate.entry->getSig() != other.entry->getSig()) { + break; + } + continue; + } + + if (metadataEnv.IsAssignableFrom(candidateClass, otherClass) == JNI_TRUE) { + if (metadataEnv.IsAssignableFrom(otherClass, candidateClass) != JNI_TRUE) { + dominated = true; + break; + } + } else { + break; + } + } + if (dominated) break; + } + if (!dominated) { + metadataMatches++; + metadataMatch = metadataCandidateMatches[i].entry; + } + } + metadataSignatureIsUnambiguous = metadataMatches == 1 && metadataMatch != nullptr; + bool hasExactStringCandidate = false; + for (const auto &candidate : metadataCandidateMatches) { + if (candidate.entry->getSig().find("(Ljava/lang/String;") == 0) { + hasExactStringCandidate = true; + break; + } + } + if (metadataCandidateMatches.size() > 1 && !hasExactStringCandidate) { + metadataSignatureIsUnambiguous = false; + metadataMatch = nullptr; + forceReflectionFallback = false; + } + if (!metadataSignatureIsUnambiguous && !metadataCandidateMatches.empty()) { + for (const auto &candidate : metadataCandidateMatches) { + const auto signature = candidate.entry->getSig(); + const auto closeParen = signature.find(')'); + if (closeParen != std::string::npos && closeParen > 1 && + signature[1] == '[') { + forceReflectionFallback = true; + break; + } + if (closeParen != std::string::npos && closeParen > 1 && + signature[1] != 'L' && signature[1] != 'Z') { + forceReflectionFallback = true; + break; + } + } + } + if (!metadataSignatureIsUnambiguous && metadataCandidateMatches.empty()) { + for (auto *candidate : arityCandidates) { + if (candidate->isExtensionFunction) { + continue; + } + const auto signature = candidate->getSig(); + const auto closeParen = signature.find(')'); + const bool hasObjectParameter = signature.find("Ljava/lang/Object;") != std::string::npos; + const bool hasArrayParameter = signature.find('[') != std::string::npos; + const bool hasPrimitiveParameter = closeParen != std::string::npos && closeParen > 1 && + signature[1] != 'L' && signature[1] != '[' && signature[1] != 'Z'; + if (!hasObjectParameter && !hasArrayParameter && !hasPrimitiveParameter) { + continue; + } + for (size_t i = 0; i < argc; i++) { + napi_valuetype valueType; + napi_typeof(env, argv[i], &valueType); + if (valueType == napi_object || valueType == napi_function || valueType == napi_number) { + forceReflectionFallback = true; + break; + } + } + if (forceReflectionFallback) { + break; + } + } + } + } + if (!hasNullArgument && entry != nullptr && !entry->isExtensionFunction && arityCandidates.size() == 1) { + std::vector parameterClasses; + const int compatibility = metadataTypesMatch(*entry, parameterClasses); + if (compatibility == kMetadataMatch) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } else if (compatibility == kMetadataMismatch) { + // A provable mismatch against the only metadata candidate + // means the metadata may be stale (the runtime class can + // have overloads the generator never saw). Let reflection + // resolve against the real class. + const auto signature = entry->getSig(); + if (entry->isStatic || signature.find("Ljava/lang/Object;") == std::string::npos) { + entry = nullptr; + } + } + // kMetadataUnknown: keep the entry. A generator-resolved entry is + // invoked directly through the lenient JsArgConverter (the + // pre-metadata-first behavior); unresolved ones still use + // reflection. Reflection can't marshal JS arrays or null for + // primitives, so demoting here would turn valid calls into + // "Failed resolving method" errors. + } + if (!hasNullArgument && entry != nullptr && entry->isExtensionFunction) { + metadataMatch = entry; + metadataSignatureIsUnambiguous = true; + } + if (metadataSignatureIsUnambiguous) { + entry = metadataMatch; + } else if (forceReflectionFallback) { + entry = nullptr; + } + const bool selectedIsStatic = entry != nullptr + ? entry->isStatic + : (arityCandidates.size() == 1 + ? arityCandidates.front()->isStatic + : first.isStatic); + napi_value result = CallbackHandlers::CallJavaMethod(env, jsThis, *className, methodName, entry, + isFromInterface, selectedIsStatic, info, + argc, argv, initialCallbackData->objectManager, + metadataSignatureIsUnambiguous); +// napi_value error; +// error = Runtime::GetRuntime(env)->getPendingError(); +// if (error) { +// throw NativeScriptException(env, error); +// } + return result; + } + + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +/** + * Compare class hierarchy in metadata with that at runtime. If a base class is missing + * at runtime, we must add all it's methods to the current class. + */ +bool +MetadataNode::CheckClassHierarchy(JEnv &env, jclass currentClass, MetadataTreeNode *currentTreeNode, + MetadataTreeNode *baseTreeNode, + std::vector &skippedBaseTypes) { + auto shouldSkipBaseClass = false; + if ((currentClass != nullptr) && (baseTreeNode != currentTreeNode) && + (baseTreeNode != nullptr) && + (baseTreeNode->offsetValue > 0)) { + auto baseNode = GetOrCreateInternal(baseTreeNode); + auto baseClass = env.FindClass(baseNode->m_name); + if (baseClass != nullptr) { + auto isBaseClass = env.IsAssignableFrom(currentClass, baseClass) == JNI_TRUE; + if (!isBaseClass) { + skippedBaseTypes.push_back(baseTreeNode); + shouldSkipBaseClass = true; + } + } + } + return shouldSkipBaseClass; +} + +void MetadataNode::SetMissingBaseMethods( + napi_env env, const std::vector &skippedBaseTypes, + const std::vector &instanceMethodData, + napi_value constructor) { + napi_status status; + for (auto treeNode: skippedBaseTypes) { + uint8_t *curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1; + + auto nodeType = s_metadataReader.GetNodeType(treeNode); + auto curType = s_metadataReader.ReadTypeName(treeNode); + curPtr += sizeof(uint16_t /* baseClassId */); + + if (s_metadataReader.IsNodeTypeInterface(nodeType)) { + curPtr += sizeof(uint8_t) + sizeof(uint32_t); + } + + // Get candidates from instance methods metadata + auto instanceMethodCount = *reinterpret_cast(curPtr); + curPtr += sizeof(uint16_t); + MethodCallbackData *callbackData = nullptr; + + for (auto i = 0; i < instanceMethodCount; i++) { + auto entry = MetadataReader::ReadInstanceMethodEntry(&curPtr); + auto &methodName = entry.getName(); + auto isConstructor = methodName == ""; + if (isConstructor) { + continue; + } + + for (auto data: instanceMethodData) { + if (data->candidates.front().name == methodName) { + callbackData = data; + break; + } + } + + if (callbackData == nullptr) { + callbackData = new MethodCallbackData(this); + GetMetadataNodeCache(env)->methodCallbackData.push_back(callbackData); + napi_value proto = napi_util::get_prototype(env, constructor); + napi_value method; + NAPI_GUARD(napi_create_function(env, methodName.c_str(), NAPI_AUTO_LENGTH, MethodCallback, + callbackData, &method)) {} + NAPI_GUARD(napi_set_named_property(env, proto, methodName.c_str(), method)) {} + } + + bool foundSameSig = false; + for (auto &m: callbackData->candidates) { + foundSameSig = m.getSig() == entry.getSig(); + if (foundSameSig) { + break; + } + } + + if (!foundSameSig) { + callbackData->candidates.push_back(std::move(entry)); + } + } + } +} + +void MetadataNode::BuildMetadata(const std::string &filesPath) { + s_metadataReader = MetadataBuilder::BuildMetadata(filesPath); +} + +void MetadataNode::onDisposeEnv(napi_env env) { + napi_status status; + { + auto it = s_metadata_node_cache.Get(env); + if (it != nullptr) { + for (const auto &entry: it->CtorFuncCache) { + if (entry.second.constructorFunction == nullptr) { + NAPI_GUARD(napi_delete_reference(env, entry.second.constructorFunction)) {} + } + for (const auto data: entry.second.instanceMethodCallbacks) { + delete data; + } + } + it->CtorFuncCache.clear(); + + for (const auto &entry: it->ExtendedCtorFuncCache) { + if (entry.second.extendedCtorFunction == nullptr) { + NAPI_GUARD(napi_delete_reference(env, entry.second.extendedCtorFunction)) {} + } + } + it->ExtendedCtorFuncCache.clear(); + + for (const auto &entry: it->fieldCallbackData) { + delete entry; + } + for (const auto &entry: it->methodCallbackData) { + delete entry; + } + } + s_metadata_node_cache.Remove(env); + delete it; + } + { + auto it = s_arrayObjects.find(env); + if (it != s_arrayObjects.end()) { + if (it->second != nullptr) { + NAPI_GUARD(napi_delete_reference(env, it->second)) {} + } + s_arrayObjects.erase(it); + } + } +} + + +string MetadataNode::TNS_PREFIX = "com/tns/gen/"; +MetadataReader MetadataNode::s_metadataReader; +robin_hood::unordered_map MetadataNode::s_name2NodeCache; +robin_hood::unordered_map MetadataNode::s_name2TreeNodeCache; +robin_hood::unordered_map MetadataNode::s_treeNode2NodeCache; +tns::ConcurrentMap MetadataNode::s_metadata_node_cache; +robin_hood::unordered_map MetadataNode::s_arrayObjects; + +bool MetadataNode::s_profilerEnabled = false; diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataNode.h b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h new file mode 100644 index 000000000..fabe0f507 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h @@ -0,0 +1,372 @@ +#ifndef METADATA_NODE_H +#define METADATA_NODE_H + +#include +#include "MetadataTreeNode.h" +#include "MetadataEntry.h" +#include "robin_hood.h" +#include "MetadataReader.h" +#include "Runtime.h" +#include "ObjectManager.h" + +#include "FieldCallbackData.h" +using namespace tns; + +class MetadataNode { +public: + // Set once from Runtime::Init, out of the app's config: the static binding + // generator picked the naming scheme at build time and the runtime has to + // look names up the same way. Only ever widens -- a runtime compiled to + // require content keys cannot be turned back off by an app. + static void SetContentKeyedBindings(bool enabled); + + static void Init(napi_env env); + + static void BuildMetadata(const std::string &filesPath); + + static void CreateTopLevelNamespaces(napi_env env); + + napi_value CreateWrapper(napi_env env); + + napi_value CreateJSWrapper(napi_env env, tns::ObjectManager *objectManager); + + napi_value CreateArrayWrapper(napi_env env); + + static MetadataNode *GetOrCreate(const std::string &className); + + static MetadataReader *getMetadataReader(); + + static napi_value GetImplementationObject(napi_env env, napi_value object); + + inline static MetadataNode* GetInstanceMetadata(napi_env env, napi_value object) { +#ifdef USE_HOST_OBJECT + // Host build: metadata lives on the per-instance JSInstanceInfo (reached + // via host data on the proxy, or the wrap on a raw instance) — no + // interceptor-forwarded "#instance_metadata" property get. + return tns::Runtime::GetRuntime(env)->GetObjectManager()->GetInstanceNode(object); +#else + void *node = nullptr; + napi_value external; + napi_get_named_property(env, object, "#instance_metadata", &external); + + if (napi_util::is_null_or_undefined(env, external)) return nullptr; + + napi_get_value_external(env, external, &node); + if (node == nullptr) + return nullptr; + return reinterpret_cast(node); +#endif + } + + inline static MetadataNode* GetNodeFromHandle(napi_env env, napi_value value) { + auto node = GetInstanceMetadata(env, value); + return node; + } + + static string GetTypeMetadataName(napi_env env, napi_value value); + + static napi_value CreateExtendedJSWrapper(napi_env env, ObjectManager *objectManager, + const std::string &proxyClassName, int javaObjectID, + MetadataNode **outNode = nullptr); + + std::string GetName(); + + static void onDisposeEnv(napi_env env); + + bool isArray(); + +private: + struct CtorCacheData; + struct PackageGetterMethodData; + struct MethodCallbackData; + struct ExtendedClassCallbackData; + struct ExtendedClassCacheData; + struct MetadataNodeCache; + + static string CreateFullClassName(const std::string& className, const std::string& extendNameAndLocation); + + static string CreateContentKey(const std::string& baseClassName, + std::vector interfaceNames, + std::vector methodNames); + + // Whether an unnamed extend() names its class by content rather than by call + // site. Always available as a fallback when no usable location exists. + static bool ContentKeyedBindingsEnabled(); + + static napi_value CreateArrayObjectConstructor(napi_env env); + + static void SetInstanceMetadata(napi_env env, napi_value object, MetadataNode* node); + + + static bool + CheckClassHierarchy(JEnv &env, jclass currentClass, MetadataTreeNode *currentTreeNode, + MetadataTreeNode *baseTreeNode, + std::vector &skippedBaseTypes); + + static MetadataNode *GetOrCreateInternal(MetadataTreeNode *treeNode); + + static MetadataNodeCache *GetMetadataNodeCache(napi_env env); + + explicit MetadataNode(MetadataTreeNode *treeNode); + + void SetMissingBaseMethods( + napi_env env, const std::vector &skippedBaseTypes, + const std::vector &instanceMethodData, + napi_value ctor); + + + + + napi_value GetConstructorFunction(napi_env env); + + napi_value GetConstructorFunctionInternal(napi_env env, MetadataTreeNode *treeNode, + std::vector instanceMethodsCallbackData); + + napi_value CreatePackageObject(napi_env env); + + + static bool IsValidExtendName(napi_env env, napi_value name); + static bool GetExtendLocation(napi_env env, std::string& extendLocation, bool isTypeScriptExtend); + static ExtendedClassCacheData GetCachedExtendedClassData(napi_env env, const std::string& proxyClassName); + static std::string GetJniClassName(const MetadataTreeNode* node); + + static void SetClassAccessor(napi_env env, napi_value constructor); + + static MetadataEntry GetChildMetadataForPackage(MetadataNode *node, const char *propName); + + static MetadataTreeNode *GetOrCreateTreeNodeByName(const std::string &className); + + bool IsNodeTypeInterface(); + + std::vector SetClassMembersFromStaticMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + std::vector SetInstanceMembersFromRuntimeMetadata( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + + inline static MethodCallbackData *tryGetExtensionMethodCallbackData( + const robin_hood::unordered_map &collectedMethodCallbackData, + const std::string &lookupName); + + std::vector SetClassMembers( + napi_env env, napi_value constructor, + std::vector &instanceMethodsCallbackData, + const std::vector &baseInstanceMethodsCallbackData, + MetadataTreeNode *treeNode); + + + static napi_value NullObjectAccessorGetterCallback(napi_env env, napi_callback_info info); + + // Returns true if `jsThis` is a real backed instance rather than the class + // prototype (i.e. someone did Class.prototype.). With host objects + // enabled, every real instance is a host-object proxy and the prototype is + // not; otherwise we identity-compare against the cached prototype. + static bool IsInstanceReceiver(napi_env env, napi_value jsThis, napi_ref prototypeRef); + + static napi_value FieldAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value FieldAccessorSetterCallback(napi_env env, napi_callback_info info); + + static napi_value ArraySetterCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayGetterCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayGetAllValuesCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayLengthCallback(napi_env env, napi_callback_info info); + + // Native equivalents of the helpers that used to live in getNativeArrayProp. + static napi_value ArrayMapCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayForEachCallback(napi_env env, napi_callback_info info); + + static napi_value ArrayToStringCallback(napi_env env, napi_callback_info info); + + static napi_value + ArraySymbolIteratorCallback(napi_env env, napi_callback_info info); + + static napi_value PropertyAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value PropertyAccessorSetterCallback(napi_env env, napi_callback_info info); + + static napi_value ExtendMethodCallback(napi_env env, napi_callback_info info); + + static napi_value MethodCallback(napi_env env, napi_callback_info info); + + static napi_value ClassAccessorGetterCallback(napi_env env, napi_callback_info info); + + static napi_value PackageGetterCallback(napi_env env, napi_callback_info info); + + static napi_value ExtendedClassConstructorCallback(napi_env env, napi_callback_info info); + + static napi_value InterfaceConstructorCallback(napi_env env, napi_callback_info info); + + static napi_value ClassConstructorCallback(napi_env env, napi_callback_info info); + + static void SetInnerTypes(napi_env env, napi_value constructor, MetadataTreeNode *treeNode); + + static napi_value InnerTypeGetterCallback(napi_env env, napi_callback_info info); + + static napi_value NullValueOfCallback(napi_env env, napi_callback_info info); + + + // Heap holder for the Symbol.hasInstance callback data. The napi `data` + // pointer must be a real heap pointer: PrimJS boxes callback data into 48 + // bits and rebuilds it with a fixed top-16-bit heap tag on retrieval, which + // would corrupt a raw JNI global ref passed directly. See + // RegisterSymbolHasInstanceCallback. + struct SymbolHasInstanceData { + jclass clazz; + }; + + static void RegisterSymbolHasInstanceCallback(napi_env env, const MetadataTreeNode *treeNode, napi_value interface); + + static napi_value SymbolHasInstanceCallback(napi_env env, napi_callback_info info); + + static napi_value SuperAccessorGetterCallback(napi_env env, napi_callback_info info); + + static bool ValidateExtendArguments(napi_env env, size_t argc, napi_value * argv, bool extendLocationFound, string &extendLocation, napi_value* extendName, napi_value* implementationObject, bool isTypeScriptExtend); + + MetadataTreeNode *m_treeNode; + + std::string m_name; + std::string m_implType; + bool m_isArray; + + static bool IsJavascriptKeyword(const std::string &word); + + static std::string TNS_PREFIX; + static MetadataReader s_metadataReader; + + static robin_hood::unordered_map s_name2NodeCache; + static robin_hood::unordered_map s_name2TreeNodeCache; + static robin_hood::unordered_map s_treeNode2NodeCache; + static tns::ConcurrentMap s_metadata_node_cache; + static robin_hood::unordered_map s_arrayObjects; + + struct CtorCacheData { + CtorCacheData(napi_ref _constructorFunction, + std::vector _instanceMethodCallbacks) + : + constructorFunction(_constructorFunction), + instanceMethodCallbacks(_instanceMethodCallbacks) { + } + + napi_ref constructorFunction; + std::vector instanceMethodCallbacks; + }; + + struct MethodCallbackData { + MethodCallbackData() + : + node(nullptr), parent(nullptr), isSuper(false) { + } + + MethodCallbackData(MetadataNode *_node) + : + node(_node), parent(nullptr), isSuper(false) { + } + + std::vector candidates; + MetadataNode *node; + MethodCallbackData *parent; + bool isSuper; + // Lazily-cached, per-class invariants resolved on first dispatch + // (-1 = not yet computed, 0 = false, 1 = true). + int8_t cachedIsFromInterface = -1; + int8_t cachedIsValueOf = -1; + // Cached per-env ObjectManager (this data is created per env, so the + // pointer's lifetime matches it — no staleness across envs). + tns::ObjectManager *objectManager = nullptr; + }; + + struct PackageGetterMethodData { + PackageGetterMethodData() : utf8name(nullptr), node(nullptr), value(nullptr) {} + + PackageGetterMethodData(const char *_utf8name, MetadataNode *_node, napi_ref _value) + : utf8name(_utf8name), node(_node), value(_value) {} + + const char *utf8name; + MetadataNode *node; + napi_ref value; + }; + + struct ExtendedClassCacheData { + ExtendedClassCacheData() + : + extendedCtorFunction(nullptr), node(nullptr) { + } + + ExtendedClassCacheData(napi_ref extCtorFunc, const std::string &_extendedName, + MetadataNode *_node) + : + extendedName(_extendedName), node(_node) { + extendedCtorFunction = extCtorFunc; + } + + napi_ref extendedCtorFunction; + std::string extendedName; + MetadataNode *node; + }; + + struct PropertyCallbackData { + PropertyCallbackData(std::string _propertyName, std::string _getterMethodName, + std::string _setterMethodName) + : + propertyName(_propertyName), getterMethodName(_getterMethodName), + setterMethodName(_setterMethodName) { + + } + + std::string propertyName; + std::string getterMethodName; + std::string setterMethodName; + // Cached prototype the accessor lives on; used to detect + // Class.prototype. access when host objects are disabled. + napi_ref prototype = nullptr; + // Direct-dispatch support: the resolved getter/setter method entries plus + // cached invariants let the accessor call CallJavaMethod directly, with no + // JS method lookup or nested MethodCallback. nullptr => no getter/setter. + MetadataEntry *getterEntry = nullptr; + MetadataEntry *setterEntry = nullptr; + MetadataNode *node = nullptr; + int8_t cachedIsFromInterface = -1; + tns::ObjectManager *objectManager = nullptr; + }; + + struct ExtendedClassCallbackData { + ExtendedClassCallbackData(MetadataNode *_node, const std::string &_extendedName, + napi_ref _implementationObject, std::string _fullClassName) + : + node(_node), extendedName(_extendedName), fullClassName(_fullClassName) { + implementationObject = _implementationObject; + } + + MetadataNode *node; + std::string extendedName; + napi_ref implementationObject; + + std::string fullClassName; + }; + + struct MetadataNodeCache { + robin_hood::unordered_map CtorFuncCache; + robin_hood::unordered_map ExtendedCtorFuncCache; + std::vector fieldCallbackData; + // Method callback data no CtorFuncCache entry owns: static methods, + // extension-only methods and methods copied from a skipped base class. + std::vector methodCallbackData; + }; + + static bool s_profilerEnabled; + +}; + +#endif //METADATA_NODE_H diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp new file mode 100644 index 000000000..daacbe8a9 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp @@ -0,0 +1,367 @@ +#include "MetadataReader.h" +#include "MetadataMethodInfo.h" +#include +#include "Util.h" +#include + +using namespace std; +using namespace tns; + +MetadataReader::MetadataReader() : m_root(nullptr), m_nodesLength(0), m_nameLength(0), + m_valueLength(0), + m_nodeData(nullptr), m_nameData(nullptr), m_valueData(nullptr), + m_getTypeMetadataCallback(nullptr) {} + +MetadataReader::MetadataReader(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData, + GetTypeMetadataCallback getTypeMetadataCallback) + : + m_nodesLength(nodesLength), m_nameLength(nameLength), + m_valueLength(valueLength), m_nodeData(nodeData), m_nameData(nameData), + m_valueData(valueData), + m_getTypeMetadataCallback(getTypeMetadataCallback) { + m_root = BuildTree(); +} + + + +// helper debug function when need to convert a metadata node to its full name +//std::string toFullName(MetadataTreeNode* p) { +// std::string final = p->name; +// while((p = p->parent) && !p->name.empty()) { +// final.insert(0,p->name + "."); +// }; +// return final; +//} + +MetadataTreeNode *MetadataReader::BuildTree() { + MetadataTreeNodeRawData *rootNodeData = reinterpret_cast(m_nodeData); + + MetadataTreeNodeRawData *curNodeData = rootNodeData; + + int len = m_nodesLength / sizeof(MetadataTreeNodeRawData); + + m_v.resize(len + 1000); + MetadataTreeNode *emptyNode = nullptr; + fill(m_v.begin(), m_v.end(), emptyNode); + + for (int i = 0; i < len; i++) { + MetadataTreeNode *node = GetNodeById(i); + if (nullptr == node) { + node = new MetadataTreeNode; + node->name = ReadName(curNodeData->offsetName); + node->offsetValue = curNodeData->offsetValue; + m_v[i] = node; + } + + uint16_t curNodeDataId = curNodeData - rootNodeData; + + if (curNodeDataId != curNodeData->firstChildId) { + node->children = new vector; + MetadataTreeNodeRawData *childNodeData = rootNodeData + curNodeData->firstChildId; + while (true) { + + uint16_t childNodeDataId = childNodeData - rootNodeData; + + MetadataTreeNode *childNode; + // node (and its next siblings) already visited, so we don't need to visit it again + if (m_v[childNodeDataId] != emptyNode) { + childNode = m_v[childNodeDataId]; + __android_log_print(ANDROID_LOG_ERROR, "TNS.error", + "Consistency error in metadata. A child should never have been visited before its parent. Parent: %s Child: %s. Child metadata id: %u", + node->name.c_str(), childNode->name.c_str(), + childNodeDataId); + break; + } else { + childNode = new MetadataTreeNode; + childNode->name = ReadName(childNodeData->offsetName); + childNode->offsetValue = childNodeData->offsetValue; + } + childNode->parent = node; + + node->children->push_back(childNode); + + m_v[childNodeDataId] = childNode; + + if (childNodeDataId == childNodeData->nextSiblingId) { + break; + } + + childNodeData = rootNodeData + childNodeData->nextSiblingId; + } + } + + curNodeData++; + } + + return GetNodeById(0); +} + +MetadataTreeNode *MetadataReader::GetNodeById(uint16_t nodeId) { + return m_v[nodeId]; +} + + +string MetadataReader::ReadTypeName(MetadataTreeNode *treeNode) { + string name; + + auto itFound = m_typeNameCache.find(treeNode); + + if (itFound != m_typeNameCache.end()) { + name = itFound->second; + } else { + name = ReadTypeNameInternal(treeNode); + + m_typeNameCache.emplace(treeNode, name); + } + + return name; +} + +string MetadataReader::ReadTypeNameInternal(MetadataTreeNode *treeNode) { + string name; + + uint8_t prevNodeType; + + while (treeNode->parent != nullptr) { + int curNodeType = GetNodeType(treeNode); + + bool isArrayElement = treeNode->offsetValue > ARRAY_OFFSET; + + if (isArrayElement) { + uint16_t forwardNodeId = treeNode->offsetValue - ARRAY_OFFSET; + MetadataTreeNode *forwardNode = GetNodeById(forwardNodeId); + name = ReadTypeName(forwardNode); + uint8_t forwardNodeType = GetNodeType(forwardNode); + if (IsNodeTypeInterface(forwardNodeType) || IsNodeTypeClass(forwardNodeType)) { + name = "L" + name + ";"; + } + } else { + if (!name.empty()) { + if (!IsNodeTypeArray(curNodeType)) { + if ((IsNodeTypeClass(prevNodeType) || IsNodeTypeInterface(prevNodeType)) + && (IsNodeTypeClass(curNodeType) || IsNodeTypeInterface(curNodeType))) { + name = "$" + name; + } else { + name = "/" + name; + } + } + } + + name = treeNode->name + name; + + prevNodeType = curNodeType; + } + + treeNode = treeNode->parent; + } + + return name; +} + +uint8_t *MetadataReader::GetValueData() const { + return m_valueData; +} + +uint16_t MetadataReader::GetNodeId(MetadataTreeNode *treeNode) { + auto itFound = find(m_v.begin(), m_v.end(), treeNode); + assert(itFound != m_v.end()); + uint16_t nodeId = itFound - m_v.begin(); + + return nodeId; +} + +MetadataTreeNode *MetadataReader::GetRoot() const { + return m_root; +} + +uint8_t MetadataReader::GetNodeType(MetadataTreeNode *treeNode) { + if (treeNode->type == MetadataTreeNode::INVALID_TYPE) { + uint8_t nodeType; + + uint32_t offsetValue = treeNode->offsetValue; + + if (offsetValue == 0) { + nodeType = MetadataTreeNode::PACKAGE; + } else if ((0 < offsetValue) && (offsetValue < ARRAY_OFFSET)) { + nodeType = *(m_valueData + offsetValue); + } else if (offsetValue == ARRAY_OFFSET) { + nodeType = MetadataTreeNode::ARRAY; + } else { + uint16_t nodeId = offsetValue - ARRAY_OFFSET; + MetadataTreeNode *arrElemNode = GetNodeById(nodeId); + nodeType = *(m_valueData + arrElemNode->offsetValue); + } + + treeNode->type = nodeType; + } + + return treeNode->type; +} + +MetadataTreeNode *MetadataReader::GetOrCreateTreeNodeByName(const string &className) { + MetadataTreeNode *treeNode = GetRoot(); + + int arrayIdx = -1; + string arrayName = "["; + + while (className[++arrayIdx] == '[') { + MetadataTreeNode *child = treeNode->GetChild(arrayName); + + if (child == nullptr) { + vector *children = treeNode->children; + if (children == nullptr) { + children = treeNode->children = new vector; + } + + child = new MetadataTreeNode; + child->name = "["; + child->parent = treeNode; + child->offsetValue = ARRAY_OFFSET; + + children->push_back(child); + m_v.push_back(child); + } + + treeNode = child; + } + + string cn = className.substr(arrayIdx); + + if (arrayIdx > 0) { + char last = *cn.rbegin(); + if (last == ';') { + cn = cn.substr(1, cn.length() - 2); + } + } + + vector names; + Util::SplitString(cn, "/$", names); + + if (arrayIdx > 0) { + bool found = false; + MetadataTreeNode *forwardedNode = GetOrCreateTreeNodeByName(cn); + + uint16_t forwardedNodeId = GetNodeId(forwardedNode); + if (treeNode->children == nullptr) { + treeNode->children = new vector(); + } + vector &children = *treeNode->children; + for (auto childNode: children) { + uint32_t childNodeId = (childNode->offsetValue >= ARRAY_OFFSET) + ? (childNode->offsetValue - ARRAY_OFFSET) + : + GetNodeId(childNode); + + if (childNodeId == forwardedNodeId) { + treeNode = childNode; + found = true; + break; + } + } + + if (!found) { + MetadataTreeNode *forwardNode = new MetadataTreeNode; + forwardNode->offsetValue = forwardedNodeId + ARRAY_OFFSET; + forwardNode->parent = treeNode; + + m_v.push_back(forwardNode); + children.push_back(forwardNode); + + treeNode = forwardNode; + } + + return treeNode; + } + + int curIdx = 0; + for (auto it = names.begin(); it != names.end(); ++it) { + MetadataTreeNode *child = treeNode->GetChild(*it); + + if (child == nullptr) { + vector api = m_getTypeMetadataCallback(cn, curIdx); + + for (const auto &part: api) { + vector *children = treeNode->children; + if (children == nullptr) { + children = treeNode->children = new vector; + } + + child = new MetadataTreeNode; + child->name = *it++; + child->parent = treeNode; + // Rebuilt by reflection because the metadata files did not + // describe it -- see the field's comment. + child->synthesizedAtRuntime = true; + + string line; + string kind; + string name; + stringstream s(part); + + getline(s, line); + stringstream typeLine(line); + typeLine >> kind >> name; + auto cKind = kind[0]; + + // package, class, interface + assert((cKind == 'P') || (cKind == 'C') || (cKind == 'I')); + + if ((cKind == 'C') || (cKind == 'I')) { + child->metadata = new string(part); + child->type = (cKind == 'C') ? MetadataTreeNode::CLASS + : MetadataTreeNode::INTERFACE; + if (name == "S") { + child->type |= MetadataTreeNode::STATIC; + } + + getline(s, line); + stringstream baseClassLine(line); + baseClassLine >> kind >> name; + cKind = kind[0]; + + assert(cKind == 'B'); + auto baseClassTreeNode = GetOrCreateTreeNodeByName(name); + auto baseClassNodeId = GetNodeId(baseClassTreeNode); + + child->offsetValue = m_valueLength; + m_valueData[m_valueLength++] = child->type; + *reinterpret_cast(m_valueData + m_valueLength) = baseClassNodeId; + m_valueLength += sizeof(uint16_t); + } else { + child->type = MetadataTreeNode::PACKAGE; + } + + m_v.push_back(child); + children->push_back(child); + + treeNode = child; + } + + return treeNode; + } else { + treeNode = child; + } + ++curIdx; + } + + return treeNode; +} + +MetadataTreeNode *MetadataReader::GetBaseClassNode(MetadataTreeNode *treeNode) { + MetadataTreeNode *baseClassNode = nullptr; + + if (treeNode != nullptr) { + uint16_t baseClassNodeId = *reinterpret_cast(m_valueData + + treeNode->offsetValue + 1); + + size_t nodeCount = m_v.size(); + + assert(baseClassNodeId < nodeCount); + + baseClassNode = GetNodeById(baseClassNodeId); + } + + return baseClassNode; +} + diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataReader.h b/NativeScript/ffi/jni/napi/metadata/MetadataReader.h new file mode 100644 index 000000000..8d19683d7 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataReader.h @@ -0,0 +1,238 @@ +#ifndef METADATAREADER_H_ +#define METADATAREADER_H_ + +#include "MetadataEntry.h" +#include "MetadataFieldInfo.h" +#include +#include +#include +#include "robin_hood.h" + +namespace tns { + typedef std::vector (*GetTypeMetadataCallback)(const std::string &classname, + int index); + + class MetadataReader { + public: + MetadataReader(); + + MetadataReader(uint32_t nodesLength, uint8_t *nodeData, uint32_t nameLength, + uint8_t *nameData, uint32_t valueLength, uint8_t *valueData, + GetTypeMetadataCallback getTypeMetadataCallack); + + inline static MetadataEntry ReadInstanceFieldEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Field); + entry.fi = *reinterpret_cast(data); + entry.isStatic = false; + entry.isTypeMember = false; + + *data += sizeof(FieldInfo); + + return entry; + } + + inline static MetadataEntry ReadStaticFieldEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::StaticField); + entry.sfi = *reinterpret_cast(data); + entry.isStatic = true; + entry.isTypeMember = false; + + *data += sizeof(StaticFieldInfo); + + return entry; + } + + inline static MetadataEntry ReadInstanceMethodEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + entry.isTypeMember = true; + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline static MetadataEntry ReadStaticMethodEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + entry.isTypeMember = true; + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + entry.mi.isStatic = true; + entry.isStatic = true; + + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline static MetadataEntry ReadExtensionFunctionEntry(uint8_t **data) { + MetadataEntry entry(nullptr, NodeType::Method); + + entry.mi = MethodInfo(*data); // Assign MethodInfo object directly + entry.mi.isStatic = true; + entry.isExtensionFunction = true; + entry.isStatic = true; + + *data += entry.mi.GetSizeOfReadMethodInfo(); + + return entry; + } + + inline std::string ReadTypeName(uint16_t nodeId) { + MetadataTreeNode *treeNode = GetNodeById(nodeId); + + return ReadTypeName(treeNode); + } + + std::string ReadTypeName(MetadataTreeNode *treeNode); + + inline std::string ReadName(uint32_t offset) { + uint16_t length = *reinterpret_cast(m_nameData + offset); + + std::string name(reinterpret_cast(m_nameData + offset + sizeof(uint16_t)), + length); + + return name; + } + + inline std::string + ReadInterfaceImplementationTypeName(MetadataTreeNode *treeNode, bool &isPrefix) { + uint8_t *data = + m_valueData + treeNode->offsetValue + sizeof(uint8_t) + sizeof(uint16_t); + + isPrefix = *data == 1; + + uint32_t pos = *reinterpret_cast(data + sizeof(uint8_t)); + + uint16_t len = *reinterpret_cast(m_nameData + pos); + + char *ptr = reinterpret_cast(m_nameData + pos + sizeof(uint16_t)); + + std::string name(ptr, len); + + assert(name.length() == len); + + return name; + } + + uint8_t *GetValueData() const; + + uint8_t GetNodeType(MetadataTreeNode *treeNode); + + uint16_t GetNodeId(MetadataTreeNode *treeNode); + + MetadataTreeNode *GetRoot() const; + + MetadataTreeNode *GetOrCreateTreeNodeByName(const std::string &className); + + MetadataTreeNode *GetBaseClassNode(MetadataTreeNode *treeNode); + + MetadataTreeNode *GetNodeById(uint16_t nodeId); + + inline bool IsNodeTypeArray(uint8_t type) { + bool isArray = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::ARRAY) == MetadataTreeNode::ARRAY)); + + return isArray; + } + + inline bool IsNodeTypeStatic(uint8_t type) { + bool isStatic = (type & MetadataTreeNode::STATIC) == MetadataTreeNode::STATIC; + + return isStatic; + } + + inline bool IsNodeTypeClass(uint8_t type) { + bool isClass = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::CLASS) == MetadataTreeNode::CLASS)); + + return isClass; + } + + inline bool IsNodeTypeInterface(uint8_t type) { + bool isInterface = (((type & MetadataTreeNode::PRIMITIVE) == 0) && + ((type & MetadataTreeNode::INTERFACE) == + MetadataTreeNode::INTERFACE)); + + return isInterface; + } + + inline bool IsNodeTypePackage(uint8_t type) { + bool isPackage = type == MetadataTreeNode::PACKAGE; + + return isPackage; + } + + inline static std::string ParseReturnType(const std::string &signature) { + int idx = signature.find(')'); + auto returnType = signature.substr(idx + 1); + return returnType; + } + + inline static MethodReturnType GetReturnType(const std::string &returnType) { + MethodReturnType retType; + char retTypePrefix = returnType[0]; + switch (retTypePrefix) { + case 'V': + retType = MethodReturnType::Void; + break; + case 'B': + retType = MethodReturnType::Byte; + break; + case 'S': + retType = MethodReturnType::Short; + break; + case 'I': + retType = MethodReturnType::Int; + break; + case 'J': + retType = MethodReturnType::Long; + break; + case 'F': + retType = MethodReturnType::Float; + break; + case 'D': + retType = MethodReturnType::Double; + break; + case 'C': + retType = MethodReturnType::Char; + break; + case 'Z': + retType = MethodReturnType::Boolean; + break; + case '[': + case 'L': + retType = (returnType == "Ljava/lang/String;") + ? MethodReturnType::String + : MethodReturnType::Object; + break; + default: + assert(false); + break; + } + return retType; + } + + private: +// static const uint32_t ARRAY_OFFSET = 1000000000; + static const uint32_t ARRAY_OFFSET = INT32_MAX; // 2147483647 + + MetadataTreeNode *BuildTree(); + + std::string ReadTypeNameInternal(MetadataTreeNode *treeNode); + + MetadataTreeNode *m_root; + uint32_t m_nodesLength; + uint32_t m_nameLength; + uint32_t m_valueLength; + uint8_t *m_nodeData; + uint8_t *m_nameData; + uint8_t *m_valueData; + std::vector m_v; + GetTypeMetadataCallback m_getTypeMetadataCallback; + + robin_hood::unordered_map m_typeNameCache; + }; +} + +#endif /* METADATAREADER_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.cpp new file mode 100644 index 000000000..d8f909084 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.cpp @@ -0,0 +1,26 @@ +#include "MetadataTreeNode.h" + +using namespace std; +using namespace tns; + +MetadataTreeNode::MetadataTreeNode() + : + children(nullptr), parent(nullptr), metadata(nullptr), offsetValue(0), type(INVALID_TYPE) { +} + +MetadataTreeNode* MetadataTreeNode::GetChild(const string& childName) { + MetadataTreeNode* child = nullptr; + + if (children != nullptr) { + auto itEnd = children->end(); + auto itFound = find_if(children->begin(), itEnd, [&childName] (MetadataTreeNode *x) { + return x->name == childName; + }); + if (itFound != itEnd) { + child = *itFound; + } + } + + return child; +} + diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h new file mode 100644 index 000000000..ed07707ec --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h @@ -0,0 +1,56 @@ +#ifndef TREENODE_H_ +#define TREENODE_H_ + +#include +#include + +namespace tns { +struct MetadataTreeNode { + MetadataTreeNode(); + + MetadataTreeNode* GetChild(const std::string& name); + + std::string name; + MetadataTreeNode* parent; + uint32_t offsetValue; + std::vector* children; + // + std::string* metadata; + uint8_t type; + + // True when this node was not read from the metadata files but rebuilt at + // runtime by MetadataReader::GetOrCreateTreeNodeByName, via reflection. + // That path is the reason a class missing from filtered metadata can still + // be marshalled: it is slower, not broken. Only the usage tracer reads + // this, to tell "recovered" apart from "would have thrown". + bool synthesizedAtRuntime = false; + + static const uint8_t PACKAGE = 0; + static const uint8_t CLASS = 1 << 0; + static const uint8_t INTERFACE = 1 << 1; + static const uint8_t STATIC = 1 << 2; + static const uint8_t ARRAY = 1 << 3; + static const uint8_t PRIMITIVE = 1 << 4; + + static const uint8_t FINAL = 1; + + static const uint8_t PRIMITIVE_BYTE = 1 + PRIMITIVE; + static const uint8_t PRIMITIVE_SHORT = 2 + PRIMITIVE; + static const uint8_t PRIMITIVE_INT = 3 + PRIMITIVE; + static const uint8_t PRIMITIVE_LONG = 4 + PRIMITIVE; + static const uint8_t PRIMITIVE_FLOAT = 5 + PRIMITIVE; + static const uint8_t PRIMITIVE_DOUBLE = 6 + PRIMITIVE; + static const uint8_t PRIMITIVE_BOOL = 7 + PRIMITIVE; + static const uint8_t PRIMITIVE_CHAR = 8 + PRIMITIVE; + static const uint8_t INVALID_TYPE = 0xFF; +}; + +struct MetadataTreeNodeRawData { + uint16_t firstChildId; + uint16_t nextSiblingId; + uint32_t offsetName; + uint32_t offsetValue; +}; +} + +#endif /* TREENODE_H_ */ diff --git a/NativeScript/ffi/jni/napi/metadata/MethodCache.cpp b/NativeScript/ffi/jni/napi/metadata/MethodCache.cpp new file mode 100644 index 000000000..24a1f0f4b --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MethodCache.cpp @@ -0,0 +1,34 @@ +#include "MethodCache.h" +#include "JniLocalRef.h" +#include "JsArgToArrayConverter.h" +#include "MetadataNode.h" +#include "NativeScriptAssert.h" +#include "Util.h" +#include "ArgConverter.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include + +using namespace std; +using namespace tns; + +void MethodCache::Init() +{ + JEnv jEnv; + + RUNTIME_CLASS = jEnv.FindClass("com/tns/Runtime"); + assert(RUNTIME_CLASS != nullptr); + + RESOLVE_METHOD_OVERLOAD_METHOD_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveMethodOverload", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;"); + assert(RESOLVE_METHOD_OVERLOAD_METHOD_ID != nullptr); + + RESOLVE_CONSTRUCTOR_SIGNATURE_ID = jEnv.GetMethodID(RUNTIME_CLASS, "resolveConstructorSignature", "(Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/String;"); + assert(RESOLVE_CONSTRUCTOR_SIGNATURE_ID != nullptr); +} + + +robin_hood::unordered_map MethodCache::s_method_ctor_signature_cache; +jclass MethodCache::RUNTIME_CLASS = nullptr; +jmethodID MethodCache::RESOLVE_METHOD_OVERLOAD_METHOD_ID = nullptr; +jmethodID MethodCache::RESOLVE_CONSTRUCTOR_SIGNATURE_ID = nullptr; diff --git a/NativeScript/ffi/jni/napi/metadata/MethodCache.h b/NativeScript/ffi/jni/napi/metadata/MethodCache.h new file mode 100644 index 000000000..a573d247c --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MethodCache.h @@ -0,0 +1,372 @@ +#ifndef METHODCACHE_H_ +#define METHODCACHE_H_ + +#include +#include +#include "JEnv.h" +#include "MetadataEntry.h" +#include "ArgsWrapper.h" +#include "NativeScriptAssert.h" +#include "MetadataReader.h" +#include "Runtime.h" +#include "MetadataNode.h" +#include "NumericCasts.h" +#include "NativeScriptException.h" +#include "JsArgToArrayConverter.h" +#include "Util.h" + +namespace tns { +/* + * MethodCache: class dealing with method/constructor resolution. + */ +class MethodCache { + public: + /* + * CacheMethodInfo: struct holding resolved methods/constructor resolution + */ + struct CacheMethodInfo { + CacheMethodInfo() + : + retType(MethodReturnType::Unknown), mid(nullptr), clazz(nullptr), isStatic(false) { + } + std::string signature; + std::string returnType; + MethodReturnType retType; + jmethodID mid; + jclass clazz; + bool isStatic; + }; + + static void Init(); + + inline static MethodCache::CacheMethodInfo ResolveMethodSignature(napi_env env, const string &className, const string &methodName, size_t argc, napi_value* argv, bool isStatic) + { + CacheMethodInfo method_info; + auto encoded_method_signature = EncodeSignature(env, className, methodName,argc, argv, isStatic); + auto it = s_method_ctor_signature_cache.find(encoded_method_signature); + + if (it == s_method_ctor_signature_cache.end()) + { + auto signature = ResolveJavaMethod(env, argc, argv, className, methodName); + + DEBUG_WRITE("ResolveMethodSignature %s='%s'", encoded_method_signature.c_str(), signature.c_str()); + + if (!signature.empty()) + { + JEnv jEnv; + auto clazz = jEnv.FindClass(className); + assert(clazz != nullptr); + method_info.clazz = clazz; + method_info.signature = signature; + method_info.returnType = MetadataReader::ParseReturnType(method_info.signature); + method_info.retType = MetadataReader::GetReturnType(method_info.returnType); + method_info.isStatic = isStatic; + method_info.mid = isStatic + ? jEnv.GetStaticMethodID(clazz, methodName, signature) + : jEnv.GetMethodID(clazz, methodName, signature); + + s_method_ctor_signature_cache.emplace(encoded_method_signature, method_info); + } + } + else + { + method_info = (*it).second; + } + + return method_info; + } + + inline static MethodCache::CacheMethodInfo ResolveConstructorSignature(napi_env env, const ArgsWrapper &argWrapper, const string &fullClassName, jclass javaClass, bool isInterface) + { + CacheMethodInfo constructor_info; + + auto encoded_ctor_signature = EncodeSignature(env, fullClassName, "", argWrapper.argc, argWrapper.argv, false); + auto it = s_method_ctor_signature_cache.find(encoded_ctor_signature); + + if (it == s_method_ctor_signature_cache.end()) + { + auto signature = ResolveConstructor(env, argWrapper.argc, argWrapper.argv, javaClass, isInterface); + + DEBUG_WRITE("ResolveConstructorSignature %s='%s'", encoded_ctor_signature.c_str(), signature.c_str()); + + if (!signature.empty()) + { + JEnv jEnv; + constructor_info.clazz = javaClass; + constructor_info.signature = signature; + constructor_info.mid = jEnv.GetMethodID(javaClass, "", signature); + + s_method_ctor_signature_cache.emplace(encoded_ctor_signature, constructor_info); + } + } + else + { + constructor_info = (*it).second; + } + + return constructor_info; + } + +private: + MethodCache() { + } + + // Encoded signature .S/I....<...> + inline static string EncodeSignature(napi_env env, const string &className, const string &methodName, size_t argc, napi_value* argv, bool isStatic) + { + string sig(className); + sig.append("."); + if (isStatic) + { + sig.append("S."); + } + else + { + sig.append("I."); + } + sig.append(methodName); + sig.append("."); + + stringstream s; + s << argc; + sig.append(s.str()); + + for (int i = 0; i < argc; i++) + { + sig.append("."); + sig.append(GetType(env, argv[i])); + } + + return sig; + } + + inline static string GetType(napi_env env, napi_value value) + { + napi_valuetype valueType; + napi_typeof(env, value, &valueType); + string type = ""; + + if (valueType == napi_object || valueType == napi_function) + { + + napi_value nullNode = nullptr; + if (napi_get_named_property(env, value, PROP_KEY_NULL_NODE_NAME, &nullNode) == napi_ok && + !napi_util::is_null_or_undefined(env, nullNode)) + { + void *data = nullptr; + napi_get_value_external(env, nullNode, &data); + auto treeNode = reinterpret_cast(data); + + type = (treeNode != nullptr) ? treeNode->GetName() : ""; + + DEBUG_WRITE("Parameter of type %s with NULL value is passed to the method.", type.c_str()); + return type; + } + } + + + if (valueType == napi_string) { + type = "string"; + } else if (valueType == napi_null) { + type = "null"; + } else if (valueType == napi_undefined) { + type = "undefined"; + } else if (valueType == napi_number) { + type = "number"; + } else if (valueType == napi_object) { + type = "object"; + } else if (napi_util::is_array(env, value)) { + type = "array"; + } else if (valueType == napi_function) { + type = "function"; + } else if (napi_util::is_typedarray(env, value)) { + type = "typedarray"; + } else if (valueType == napi_boolean) { + type = "bool"; + } else if (napi_util::is_dataview(env, value)) { + type = "view"; + } else if (napi_util::is_date(env, value)) { + type = "date"; + } + + // Handle special cases for typed arrays + if (type == "typedarray") + { + napi_typedarray_type arrayType; + napi_get_typedarray_info(env, value, &arrayType, nullptr, nullptr, nullptr, nullptr); + switch (arrayType) + { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + type = "bytebuffer"; + break; + case napi_int16_array: + case napi_uint16_array: + type = "shortbuffer"; + break; + case napi_int32_array: + case napi_uint32_array: + type = "intbuffer"; + break; + case napi_bigint64_array: + case napi_biguint64_array: + type = "longbuffer"; + break; + case napi_float32_array: + type = "floatbuffer"; + break; + case napi_float64_array: + type = "doublebuffer"; + break; + default: + type = ""; + } + } + + // Handle special cases for numbers + if (type == "number") + { + double d; + napi_get_value_double(env, value, &d); + int64_t i = (int64_t)d; + bool isInteger = d == i; + type = isInteger ? "intnumber" : "doublenumber"; + } + + // Handle special cases for objects + if (type == "object" || type == "function") + { + auto castType = NumericCasts::GetCastType(env, value); + MetadataNode *node; + + switch (castType) + { + case CastType::Char: + type = "char"; + break; + case CastType::Byte: + type = "byte"; + break; + case CastType::Short: + type = "short"; + break; + case CastType::Long: + type = "long"; + break; + case CastType::Float: + type = "float"; + break; + case CastType::Double: + type = "double"; + break; + case CastType::None: + node = MetadataNode::GetNodeFromHandle(env, value); + type = (node != nullptr) ? node->GetName() : ""; + + if (type == "") { + if (napi_util::is_number_object(env, value)) { + napi_value numValue = napi_util::valueOf(env, value); + bool isFloat = napi_util::is_float(env, numValue); + if (isFloat) { + type = "float"; + } else { + type = "int"; + } + } else if (napi_util::is_string_object(env, value)) { + type = "string"; + } else if (napi_util::is_number_object(env, value)) { + type = "bool"; + } + } + + break; + default: + throw NativeScriptException("Unsupported cast type"); + } + } + + if (type == "undefined") { + type = "null"; + } + + return type; + } + + inline static string ResolveJavaMethod(napi_env env , size_t argc, napi_value* argv, const string &className, const string &methodName) + { + JEnv jEnv; + + JsArgToArrayConverter argConverter(env, argc, argv, false); + + auto canonicalClassName = Util::ConvertFromJniToCanonicalName(className); + JniLocalRef jsClassName(jEnv.NewStringUTF(canonicalClassName.c_str())); + JniLocalRef jsMethodName(jEnv.NewStringUTF(methodName.c_str())); + + jobjectArray arrArgs = argConverter.ToJavaArray(); + auto runtime = Runtime::GetRuntime(env); + + jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_METHOD_OVERLOAD_METHOD_ID, (jstring)jsClassName, (jstring)jsMethodName, arrArgs); + + if (signature == nullptr || jEnv.ExceptionCheck() == JNI_TRUE) { + if (jEnv.ExceptionCheck() == JNI_TRUE) { + jEnv.ExceptionClear(); + } + return string(); + } + + string resolvedSignature; + + const char *str = jEnv.GetStringUTFChars(signature, nullptr); + resolvedSignature = string(str); + jEnv.ReleaseStringUTFChars(signature, str); + + jEnv.DeleteLocalRef(signature); + + return resolvedSignature; + } + + inline static string ResolveConstructor(napi_env env, size_t argc, napi_value* argv, jclass javaClass, bool isInterface) + { + JEnv jEnv; + string resolvedSignature; + + JsArgToArrayConverter argConverter(env, argc, argv, isInterface); + if (argConverter.IsValid()) + { + jobjectArray javaArgs = argConverter.ToJavaArray(); + + auto runtime = Runtime::GetRuntime(env); + + jstring signature = (jstring)jEnv.CallObjectMethod(runtime->GetJavaRuntime(), RESOLVE_CONSTRUCTOR_SIGNATURE_ID, javaClass, javaArgs); + + const char *str = jEnv.GetStringUTFChars(signature, nullptr); + resolvedSignature = string(str); + jEnv.ReleaseStringUTFChars(signature, str); + jEnv.DeleteLocalRef(signature); + } + else + { + JsArgToArrayConverter::Error err = argConverter.GetError(); + throw NativeScriptException(err.msg); + } + + return resolvedSignature; + } + + static jclass RUNTIME_CLASS; + + static jmethodID RESOLVE_METHOD_OVERLOAD_METHOD_ID; + + static jmethodID RESOLVE_CONSTRUCTOR_SIGNATURE_ID; + + /* + * "s_method_ctor_signature_cache" holding all resolved CacheMethodInfo against an encoded_signature string. + * Used for caching the resolved constructor or method signature. + * The encoded signature has template: .S/I....<...> + */ + static robin_hood::unordered_map s_method_ctor_signature_cache; +}; +} + +#endif /* METHODCACHE_H_ */ diff --git a/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp new file mode 100644 index 000000000..7bafbfe41 --- /dev/null +++ b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp @@ -0,0 +1,1100 @@ +#include "ObjectManager.h" +#include "NativeScriptAssert.h" +#include "MetadataNode.h" +#include "ArgConverter.h" +#include "Util.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "CallbackHandlers.h" +#include +#include + +using namespace std; +using namespace tns; + +// GetClassName is static so exception handling can resolve a Java class name +// without retrieving the runtime/ObjectManager (which may be unavailable +// mid-exception). These JNI ids are process-global once looked up. +jclass ObjectManager::JAVA_LANG_CLASS = nullptr; +jmethodID ObjectManager::GET_NAME_METHOD_ID = nullptr; + +ObjectManager::ObjectManager(jobject javaRuntimeObject) : + m_javaRuntimeObject(javaRuntimeObject), + m_cache(NewWeakGlobalRefCallback, DeleteWeakGlobalRefCallback, ValidateWeakGlobalRefCallback, 1000, this), + m_currentObjectId(0), + m_jsObjectProxyCreator(nullptr), + m_jsObjectCtor(nullptr), + m_env(nullptr) { + + JEnv env; + auto runtimeClass = env.FindClass("com/tns/Runtime"); + assert(runtimeClass != nullptr); + + GET_JAVAOBJECT_BY_ID_METHOD_ID = env.GetMethodID(runtimeClass, "getJavaObjectByID", + "(I)Ljava/lang/Object;"); + assert(GET_JAVAOBJECT_BY_ID_METHOD_ID != nullptr); + + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID = env.GetMethodID(runtimeClass, + "getOrCreateJavaObjectID", + "(Ljava/lang/Object;)I"); + assert(GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(I)V"); + assert(MAKE_INSTANCE_WEAK_METHOD_ID != nullptr); + + MAKE_INSTANCE_WEAK_BATCH_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceWeak", + "(Ljava/nio/ByteBuffer;IZ)V"); + assert(MAKE_INSTANCE_WEAK_BATCH_METHOD_ID != nullptr); + + MAKE_INSTANCE_STRONG_METHOD_ID = env.GetMethodID(runtimeClass, "makeInstanceStrong", + "(I)V"); + assert(MAKE_INSTANCE_STRONG_METHOD_ID != nullptr); + + JAVA_LANG_CLASS = env.FindClass("java/lang/Class"); + assert(JAVA_LANG_CLASS != nullptr); + + GET_NAME_METHOD_ID = env.GetMethodID(JAVA_LANG_CLASS, "getName", "()Ljava/lang/String;"); + assert(GET_NAME_METHOD_ID != nullptr); +} + + +void ObjectManager::Init(napi_env env) { + napi_status status; + m_env = env; + napi_value jsObjectCtor; + NAPI_GUARD(napi_define_class(env, "JSObject", NAPI_AUTO_LENGTH, JSObjectConstructorCallback, nullptr, + 0, + nullptr, &jsObjectCtor)) { + return; + } + + NAPI_GUARD(napi_set_named_property(env, napi_util::get_prototype(env, jsObjectCtor), PRIVATE_IS_NAPI, + napi_util::get_true(env))) {} + m_jsObjectCtor = napi_util::make_ref(env, jsObjectCtor, 1); +} + + +void ObjectManager::OnDisposeEnv() { + napi_status status; + JEnv jEnv; + if (this->m_jsObjectCtor) { NAPI_GUARD(napi_delete_reference(m_env, this->m_jsObjectCtor)) {} } + if (this->m_jsObjectProxyCreator) { NAPI_GUARD(napi_delete_reference(m_env, this->m_jsObjectProxyCreator)) {} } + + for (auto &entry: m_idToProxy) { + if (!entry.second) continue; + NAPI_GUARD(napi_delete_reference(m_env, entry.second)) {} + } + m_idToProxy.clear(); + + for (auto &entry: m_idToObject) { + if (!entry.second) continue; + NAPI_GUARD(napi_delete_reference(m_env, entry.second)) {} + } + m_idToObject.clear(); + + // Its destructor leaves the weak global refs alive, and the process-wide + // weak reference table is finite. + m_cache.clear(); +} + +napi_value ObjectManager::GetOrCreateProxyWeak(jint javaObjectID, napi_value instance) { + napi_value proxy = nullptr; +#ifdef USE_HOST_OBJECT + // An unwrap miss is expected here (the instance may carry no wrap), so the + // status is deliberately ignored — data stays null and CreateHostObjectProxy + // handles that. + void* data = nullptr; + napi_unwrap(m_env, instance, &data); + // Transient (weak) proxy: borrows the instance's existing JSInstanceInfo. + proxy = CreateHostObjectProxy(instance, reinterpret_cast(data), + /*isPrimary=*/false); +#else + napi_status status; + napi_value argv[2]; + argv[0] = instance; + NAPI_GUARD(napi_create_int32(m_env, javaObjectID, &argv[1])) { + return nullptr; + } + + if (!this->m_jsObjectProxyCreator) { + napi_value jsObjectProxyCreator; + NAPI_GUARD(napi_get_named_property(m_env, napi_util::global(m_env), "__createNativeProxy", + &jsObjectProxyCreator)) { + return nullptr; + } + this->m_jsObjectProxyCreator = napi_util::make_ref(m_env, jsObjectProxyCreator); + } + + NAPI_GUARD(napi_call_function(m_env, napi_util::global(m_env), + napi_util::get_ref_value(m_env, this->m_jsObjectProxyCreator), + 2, argv, &proxy)) {} + +#endif + return proxy; +} + +napi_value ObjectManager::GetOrCreateProxy(jint javaObjectID, napi_value instance) { + napi_status status; + napi_value proxy = nullptr; + auto it = m_idToProxy.find(javaObjectID); + if (it != m_idToProxy.end() && it->second != nullptr) { + proxy = napi_util::get_ref_value(m_env, it->second); + if (!napi_util::is_null_or_undefined(m_env, proxy)) { + return proxy; + } else { + NAPI_GUARD(napi_delete_reference(m_env, it->second)) {} + m_idToProxy.erase(javaObjectID); + } + } + + DEBUG_WRITE("%s %d", "Creating a new proxy for java object with id:", javaObjectID); + +#ifdef USE_HOST_OBJECT + // Primary (cached) proxy: owns a fresh JSInstanceInfo and marks the java + // instance weak when collected. + auto info = new JSInstanceInfo(javaObjectID, nullptr); + // Carry the class metadata from the raw instance's JSInstanceInfo (set in + // Link) so GetInstanceMetadata resolves it from the proxy. + // Unwrap miss is expected (weak/non-wrapped instances); ignore the status. + void *rawInfo = nullptr; + napi_unwrap(m_env, instance, &rawInfo); + if (rawInfo != nullptr) { + info->node = reinterpret_cast(rawInfo)->node; + } + proxy = CreateHostObjectProxy(instance, info, /*isPrimary=*/true); + +#else + napi_value argv[2]; + argv[0] = instance; + NAPI_GUARD(napi_create_int32(m_env, javaObjectID, &argv[1])) { + return nullptr; + } + + if (!this->m_jsObjectProxyCreator) { + napi_value jsObjectProxyCreator; + NAPI_GUARD(napi_get_named_property(m_env, napi_util::global(m_env), "__createNativeProxy", + &jsObjectProxyCreator)) { + return nullptr; + } + this->m_jsObjectProxyCreator = napi_util::make_ref(m_env, jsObjectProxyCreator); + } + + NAPI_GUARD(napi_call_function(m_env, napi_util::global(m_env), + napi_util::get_ref_value(m_env, this->m_jsObjectProxyCreator), + 2, argv, &proxy)) {} + + if (!proxy) { + DEBUG_WRITE("Failed to create proxy for javaObjectId %d", javaObjectID); + return nullptr; + } + + + auto data = new JSInstanceInfo(javaObjectID, nullptr); + + napi_value external; + NAPI_GUARD(napi_create_external(m_env, data, JSObjectProxyFinalizerCallback, data, &external)) {} + NAPI_GUARD(napi_set_named_property(m_env, proxy, "[[external]]", external)) {} + + +#endif + + auto javaObjectIdFound = m_weakObjectIds.find(javaObjectID); + if (javaObjectIdFound != m_weakObjectIds.end()) { + m_weakObjectIds.erase(javaObjectID); + JEnv jenv; + jenv.CallVoidMethod(m_javaRuntimeObject, + MAKE_INSTANCE_STRONG_METHOD_ID, + javaObjectID); + DEBUG_WRITE("Making instance strong: %d", javaObjectID); + } + + m_idToProxy.emplace(javaObjectID, napi_util::make_ref(m_env, proxy, 0)); + + return proxy; +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObject(napi_value object, int *objectId, bool *isSuper) { + napi_status status; + int32_t javaObjectId = (objectId) ? *objectId : -1; + // Cache slot for the super-call flag on whichever per-object info we resolve; + // resolved once from PRIVATE_CALLSUPER, then read from the cached field. + int8_t *superSlot = nullptr; + +#ifdef USE_HOST_OBJECT + // Non-host object → miss (an error status on some engines); expected, ignore. + void* data = nullptr; + napi_get_host_object_data(m_env, object, &data); + if (data) { + auto proxy = (HostObjectProxy *) data; + if (proxy->instanceInfo) javaObjectId = proxy->instanceInfo->JavaObjectID; + superSlot = &proxy->isSuper; + } else { + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + if (jsInstanceInfo != nullptr) { + javaObjectId = jsInstanceInfo->JavaObjectID; + superSlot = &jsInstanceInfo->isSuper; + } + } +#else + if (javaObjectId == -1) { + JSInstanceInfo *jsInstanceInfo = GetJSInstanceInfo(object); + if (jsInstanceInfo != nullptr) { + javaObjectId = jsInstanceInfo->JavaObjectID; + superSlot = &jsInstanceInfo->isSuper; + } + } +#endif + + if (isSuper) { + if (superSlot != nullptr) { + if (*superSlot < 0) { + napi_value superValue; + NAPI_GUARD(napi_get_named_property(m_env, object, PRIVATE_CALLSUPER, &superValue)) {} + *superSlot = napi_util::get_bool(m_env, superValue) ? 1 : 0; + } + *isSuper = (*superSlot == 1); + } else { + *isSuper = false; + } + } + + if (objectId) { + *objectId = javaObjectId; + } + + if (javaObjectId != -1) { + try { + return {GetJavaObjectByID(javaObjectId), true}; + } catch (NativeScriptException &e) { + // Surface which object failed instead of a bare error — this usually + // means the id belongs to a different runtime/thread. + throw NativeScriptException("Failed to get Java object by ID. id=" + + std::to_string(javaObjectId) + ". " + e.what()); + } + } + + return {}; +} + +JniLocalRef ObjectManager::GetJavaObjectByJsObjectFast(napi_value object) { +#ifdef USE_HOST_OBJECT + // A non-host object yields a miss here (an error status on some engines); + // that is expected, so ignore the status and fall through. + void *hostData = nullptr; + napi_get_host_object_data(m_env, object, &hostData); + if (hostData) { + auto proxy = reinterpret_cast(hostData); + if (proxy->instanceInfo) { + return {GetJavaObjectByID(proxy->instanceInfo->JavaObjectID), true}; + } + } +#endif + + // Unwrap miss is the common, expected case (the object may not be wrapped); + // ignore the status and fall back to the slow lookup below. + void *data = nullptr; + napi_unwrap(m_env, object, &data); + + if (data) { + auto info = reinterpret_cast(data); + return {GetJavaObjectByID(info->JavaObjectID), true}; + } + + return GetJavaObjectByJsObject(object); +} + +ObjectManager::JSInstanceInfo *ObjectManager::GetJSInstanceInfo(napi_value object) { + #ifdef USE_HOST_OBJECT + // Non-host object → miss (an error status on some engines); expected, ignore. + void *hostData = nullptr; + napi_get_host_object_data(m_env, object, &hostData); + if (hostData) { + auto proxy = reinterpret_cast(hostData); + if (proxy->instanceInfo) { + return proxy->instanceInfo; + } + } + #endif + + if (!IsRuntimeJsObject(object)) return nullptr; + return GetJSInstanceInfoFromRuntimeObject(object); +} + +MetadataNode *ObjectManager::GetInstanceNode(napi_value object) { + JSInstanceInfo *info = GetJSInstanceInfo(object); + return info != nullptr ? info->node : nullptr; +} + +bool ObjectManager::IsHostObject(napi_value object) { +#ifdef USE_HOST_OBJECT + napi_status status; + bool isHostObject; + NAPI_GUARD(napi_is_host_object(m_env, object, &isHostObject)) { + return false; + } + return isHostObject; +#endif + return false; +} + +#ifdef USE_HOST_OBJECT +// ---------------------------------------------------------------------------- +// Host object proxy: callbacks + lifecycle +// +// The new napi_create_host_object takes no target/getter/setter, so the proxy +// forwards to the wrapped `instance` (kept in HostObjectProxy::target) via +// these callbacks. Array-like instances route get/set through the JS helpers +// getNativeArrayProp/setNativeArrayProp, mirroring the old implementation. +// ---------------------------------------------------------------------------- + +// Recognise a canonical array index in a host-object trap key. V8 routes numeric +// indices through a dedicated indexed interceptor (the key is a napi_number), but +// QuickJS(-NG) delivers every key to get()/set() as a string, so an index arrives +// as its canonical decimal string ("0", "1", ...). Accept both forms: a +// non-negative integer number, or the canonical decimal string of a uint32 in +// [0, 2^32-2] (the valid array-index range, no leading zeros). +static bool TryGetArrayIndex(napi_env env, napi_value property, uint32_t &outIndex) { + napi_status status; + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, property, &type)) { return false; } + + if (type == napi_number) { + double d = 0; + NAPI_GUARD(napi_get_value_double(env, property, &d)) { return false; } + if (d < 0 || d > 4294967294.0 || d != (double) (uint32_t) d) return false; + outIndex = (uint32_t) d; + return true; + } + if (type == napi_string) { + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf8(env, property, nullptr, 0, &len)) { return false; } + if (len == 0 || len > 10) return false; // a uint32 has at most 10 digits + char buf[11]; + NAPI_GUARD(napi_get_value_string_utf8(env, property, buf, sizeof(buf), nullptr)) { return false; } + if (buf[0] == '0') { // canonical form has no leading zeros; only "0" itself + if (len != 1) return false; + outIndex = 0; + return true; + } + uint64_t v = 0; + for (size_t i = 0; i < len; i++) { + if (buf[i] < '0' || buf[i] > '9') return false; + v = v * 10 + (uint64_t) (buf[i] - '0'); + } + if (v > 4294967294ULL) return false; // max array index is 2^32 - 2 + outIndex = (uint32_t) v; + return true; + } + return false; +} + +napi_value ObjectManager::HostObjectGet(napi_env env, napi_value host, + napi_value property, void *data) { + napi_status status; + auto *proxy = reinterpret_cast(data); + try { + // Numeric keys on arrays: straight into the native element accessor. On V8 + // these arrive via the indexed interceptor (HostObjectIndexedGet); engines + // that route everything through get() (e.g. QuickJS, which passes the index + // as its decimal string) hit it here. + uint32_t index = 0; + if (proxy->isArray && !proxy->arraySignature.empty() && + TryGetArrayIndex(env, property, index)) { + return HostObjectIndexedGet(env, host, index, data); + } + + // Everything else (incl. map/forEach/toString/Symbol.iterator/length, which + // are now native methods on the array prototype) forwards to the instance. + napi_value target = napi_util::get_ref_value(env, proxy->target); + napi_value result = nullptr; + NAPI_GUARD(napi_get_property(env, target, property, &result)) {} + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectGet").ReThrowToNapi(env); + } + return nullptr; +} + +void ObjectManager::HostObjectSet(napi_env env, napi_value host, + napi_value property, napi_value value, + void *data) { + napi_status status; + auto *proxy = reinterpret_cast(data); + try { + uint32_t index = 0; + if (proxy->isArray && !proxy->arraySignature.empty() && + TryGetArrayIndex(env, property, index)) { + HostObjectIndexedSet(env, host, index, value, data); + return; + } + napi_value target = napi_util::get_ref_value(env, proxy->target); + NAPI_GUARD(napi_set_property(env, target, property, value)) {} + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectSet").ReThrowToNapi(env); + } +} + +int ObjectManager::HostObjectHas(napi_env env, napi_value host, + napi_value property, void *data) { + napi_status status; + auto *proxy = reinterpret_cast(data); + try { + // Numeric keys on java arrays: an index is "present" when it is within the + // array's bounds, matching JS array semantics. This is load-bearing on JSC: + // its JSCallbackObject consults the hasProperty callback BEFORE getProperty + // and only fetches the value when hasProperty returns true, so returning + // false here makes arr[i] read back as undefined. (V8 routes indexed reads + // through a dedicated interceptor and QuickJS calls get() directly, so this + // only affects `in`/hasOwnProperty there — which is also more correct.) + uint32_t index = 0; + if (proxy->isArray && !proxy->arraySignature.empty() && + TryGetArrayIndex(env, property, index)) { + // A Java array has a fixed length, so resolve "length" once and cache it + // on the proxy. This is the JSC hot path: JSCallbackObject calls + // hasProperty before every getProperty, so an uncached length fetch + // (JSString alloc + property get + double read) was paid per element read. + if (proxy->arrayLength < 0) { + napi_value target = napi_util::get_ref_value(env, proxy->target); + napi_value lengthVal = nullptr; + NAPI_GUARD(napi_get_named_property(env, target, "length", &lengthVal)) { return false; } + double length = 0; + NAPI_GUARD(napi_get_value_double(env, lengthVal, &length)) { return false; } + proxy->arrayLength = (int64_t) length; + } + return (int64_t) index < proxy->arrayLength; + } + + napi_value target = napi_util::get_ref_value(env, proxy->target); + bool result = false; + NAPI_GUARD(napi_has_property(env, target, property, &result)) {} + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectHas").ReThrowToNapi(env); + } + return false; +} + +int ObjectManager::HostObjectDelete(napi_env env, napi_value host, + napi_value property, void *data) { + napi_status status; + auto *proxy = reinterpret_cast(data); + try { + napi_value target = napi_util::get_ref_value(env, proxy->target); + bool result = false; + NAPI_GUARD(napi_delete_property(env, target, property, &result)) {} + return result; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectDelete").ReThrowToNapi(env); + } + return false; +} + +napi_value ObjectManager::HostObjectOwnKeys(napi_env env, napi_value host, + void *data) { + napi_status status; + auto *proxy = reinterpret_cast(data); + try { + napi_value target = napi_util::get_ref_value(env, proxy->target); + napi_value names = nullptr; + NAPI_GUARD(napi_get_property_names(env, target, &names)) {} + return names; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectOwnKeys").ReThrowToNapi(env); + } + return nullptr; +} + +// Fast path for numeric indices on java arrays: call straight into the native +// array element accessor (the same code getValueAtIndex/setValueAtIndex run), +// skipping the JS method dispatch entirely. The host object is passed as the +// `array` receiver so CallbackHandlers can resolve the backing java array. +napi_value ObjectManager::HostObjectIndexedGet(napi_env env, napi_value host, + uint32_t index, void *data) { + auto *proxy = reinterpret_cast(data); + try { + // The proxy already knows the java object id + ObjectManager, so resolve + // the backing array directly (no locked env->runtime lookup, no host probe). + jobject arr = proxy->instanceInfo + ? (jobject) proxy->objectManager->GetJavaObjectByID( + proxy->instanceInfo->JavaObjectID) + : nullptr; + return CallbackHandlers::GetArrayElement(env, host, index, proxy->arraySignature, + proxy->objectManager, arr); + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectIndexedGet").ReThrowToNapi(env); + } + return nullptr; +} + +void ObjectManager::HostObjectIndexedSet(napi_env env, napi_value host, + uint32_t index, napi_value value, + void *data) { + auto *proxy = reinterpret_cast(data); + try { + jobject arr = proxy->instanceInfo + ? (jobject) proxy->objectManager->GetJavaObjectByID( + proxy->instanceInfo->JavaObjectID) + : nullptr; + CallbackHandlers::SetArrayElement(env, host, index, proxy->arraySignature, + value, proxy->objectManager, arr); + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectIndexedSet").ReThrowToNapi(env); + } +} + +// Mirrors the old "super" accessor: `proxy.super` resolves to `target.super`. +napi_value ObjectManager::HostObjectSuperGetter(napi_env env, + napi_callback_info info) { + napi_status status; + void *data = nullptr; + NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data)) { + return nullptr; + } + auto *proxy = reinterpret_cast(data); + try { + napi_value target = napi_util::get_ref_value(env, proxy->target); + napi_value superValue = nullptr; + NAPI_GUARD(napi_get_named_property(env, target, "super", &superValue)) {} + return superValue; + } catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what(); + NativeScriptException(ss.str()).ReThrowToNapi(env); + } catch (...) { + NativeScriptException("Error: unknown c++ exception in HostObjectSuperGetter").ReThrowToNapi(env); + } + return nullptr; +} + +void ObjectManager::HostObjectProxyFinalizer(napi_env env, void *data, + void *hint) { + auto *proxy = reinterpret_cast(data); + if (proxy == nullptr) return; + + // The cleanup deletes a napi_ref, which is illegal from inside the GC + // finalizer pass on every engine (V8's InvokeFinalizerFromGC; a JS_FreeValue + // during a QuickJS sweep corrupts the collector). Defer it to the runtime's + // engine-agnostic post-GC drain (message-loop tick). + Runtime::PostFinalizer(env, HostObjectProxyPostFinalizer, proxy, hint); +} + +void ObjectManager::HostObjectProxyPostFinalizer(napi_env env, void *data, + void *hint) { + napi_status status; + auto *proxy = reinterpret_cast(data); + if (proxy == nullptr) return; + + auto rt = Runtime::GetRuntimeUnchecked(env); + // Once the runtime is tearing down (or its env is already off the cache), the + // env-dispose path owns every outstanding napi_ref: OnDisposeEnv clears the + // id maps and js_free_napi_env frees whatever remains in env->referencesList. + // Deleting proxy->target here in that window double-frees it (a use-after-free + // in the reference-free loop). So only release the reference during normal, + // per-object finalization; the C++ cleanup below still runs in both cases. + bool destroying = (rt == nullptr) || rt->is_destroying; + + if (proxy->target != nullptr && !destroying) { + NAPI_GUARD(napi_delete_reference(env, proxy->target)) {} + } + + // Primary (cached) proxies own their JSInstanceInfo and mark the java + // instance weak on collection (the old JSObjectProxyFinalizerCallback role). + if (proxy->isPrimary && proxy->instanceInfo) { + if (!destroying) { + auto objManager = rt->GetObjectManager(); + auto javaObjectID = proxy->instanceInfo->JavaObjectID; + if (objManager->m_weakObjectIds.find(javaObjectID) == + objManager->m_weakObjectIds.end()) { + objManager->m_weakObjectIds.emplace(javaObjectID); + JEnv jEnv; + jEnv.CallVoidMethod(objManager->m_javaRuntimeObject, + objManager->MAKE_INSTANCE_WEAK_METHOD_ID, + javaObjectID); + } + } + delete proxy->instanceInfo; + } + + delete proxy; +} + +napi_value ObjectManager::CreateHostObjectProxy(napi_value instance, + JSInstanceInfo *instanceInfo, + bool isPrimary) { + napi_status status; + auto *proxy = new HostObjectProxy(); + proxy->objectManager = this; + proxy->instanceInfo = instanceInfo; + proxy->isPrimary = isPrimary; + proxy->env = m_env; + proxy->target = napi_util::make_ref(m_env, instance, 1); + proxy->isArray = false; + + napi_host_object_methods methods = { + HostObjectGet, + HostObjectSet, + HostObjectHas, + HostObjectDelete, + HostObjectOwnKeys, + nullptr, // indexed_get (set below for arrays) + nullptr, // indexed_set + }; + + NAPI_GUARD(napi_has_named_property(m_env, instance, "__is__javaArray", &proxy->isArray)) {} + if (proxy->isArray) { + // Cache the jni array signature so numeric index access goes straight + // into the native element accessor (no JS getValueAtIndex dispatch). + // node is already on the raw instance's JSInstanceInfo (set in Link). + MetadataNode *node = GetInstanceNode(instance); + if (node != nullptr) { + proxy->arraySignature = node->GetName(); + methods.indexed_get = HostObjectIndexedGet; + methods.indexed_set = HostObjectIndexedSet; + } + } + + napi_value proxyObject = nullptr; + NAPI_GUARD(napi_create_host_object(m_env, HostObjectProxyFinalizer, proxy, &methods, + &proxyObject)) {} + + // The napi layer no longer touches the prototype chain or installs the + // "super" accessor for host objects, so do both here to preserve behaviour + // (instanceof checks, super dispatch). + napi_util::setPrototypeOf(m_env, proxyObject, + napi_util::getPrototypeOf(m_env, instance)); + + napi_property_descriptor superDesc = { + "super", nullptr, nullptr, HostObjectSuperGetter, nullptr, nullptr, + napi_default, proxy}; + NAPI_GUARD(napi_define_properties(m_env, proxyObject, 1, &superDesc)) {} + + return proxyObject; +} +#endif // USE_HOST_OBJECT + +ObjectManager::JSInstanceInfo * +ObjectManager::GetJSInstanceInfoFromRuntimeObject(napi_value object) { + napi_status status; + napi_value jsInfo; + NAPI_GUARD(napi_get_named_property(m_env, object, PRIVATE_JSINFO, &jsInfo)) {} + + if (napi_util::is_null_or_undefined(m_env, jsInfo)) { + napi_value proto = napi_util::get__proto__(m_env, object); + //Typescript object layout has an object instance as child of the actual registered instance. checking for that + if (!napi_util::is_null_or_undefined(m_env, proto)) { + if (IsRuntimeJsObject(proto)) { + NAPI_GUARD(napi_get_named_property(m_env, proto, PRIVATE_JSINFO, &jsInfo)) {} + } + } + } + + if (!napi_util::is_null_or_undefined(m_env, jsInfo)) { + void *data = nullptr; + NAPI_GUARD(napi_get_value_external(m_env, jsInfo, &data)) {} + auto info = reinterpret_cast(data); + return info; + } + return nullptr; +} + +bool ObjectManager::IsRuntimeJsObject(napi_value object) { + if (object == nullptr) return false; + + napi_status status; + bool result = false; + NAPI_GUARD(napi_has_named_property(m_env, object, PRIVATE_IS_NAPI, &result)) { + return false; + } + return result; +} + +jweak ObjectManager::GetJavaObjectByID(uint32_t javaObjectID) { + return m_cache(javaObjectID); +} + +jobject ObjectManager::GetJavaObjectByIDImpl(uint32_t javaObjectID) { + JEnv env; + jobject object = env.CallObjectMethod(m_javaRuntimeObject, GET_JAVAOBJECT_BY_ID_METHOD_ID, + javaObjectID); + return object; +} + +void ObjectManager::UpdateCache(int objectID, jobject obj) { + m_cache.update(objectID, obj); +} + +jclass ObjectManager::GetJavaClass(napi_value value) { + JSInstanceInfo *jsInfo = GetJSInstanceInfo(value); + jclass clazz = jsInfo->ObjectClazz; + + return clazz; +} + +void ObjectManager::SetJavaClass(napi_value value, jclass clazz) { + JSInstanceInfo *jsInfo = GetJSInstanceInfo(value); + jsInfo->ObjectClazz = clazz; +} + +int ObjectManager::GetOrCreateObjectId(jobject object) { + JEnv env; + jint javaObjectID = env.CallIntMethod(m_javaRuntimeObject, + GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID, object); + return javaObjectID; +} + +napi_value ObjectManager::GetJsObjectByJavaObject(int javaObjectID) { + auto it = m_idToObject.find(javaObjectID); + if (it == m_idToObject.end()) { + return nullptr; + } + + napi_value instance = napi_util::get_ref_value(m_env, it->second); + if (napi_util::is_null_or_undefined(m_env, instance)) return nullptr; + return GetOrCreateProxy(javaObjectID, instance); +} + + +napi_value +ObjectManager::CreateJSWrapper(jint javaObjectID, const std::string &typeName) { + return CreateJSWrapperHelper(javaObjectID, typeName, nullptr); +} + +napi_value +ObjectManager::CreateJSWrapper(jint javaObjectID, const std::string &typeName, jobject instance) { + JEnv jenv; + JniLocalRef clazz(jenv.GetObjectClass(instance)); + + return CreateJSWrapperHelper(javaObjectID, typeName, clazz); +} + +napi_value +ObjectManager::CreateJSWrapperHelper(jint javaObjectID, const std::string &typeName, jclass clazz) { + napi_status status; + auto className = (clazz != nullptr) ? GetClassName(clazz) : typeName; + + auto node = MetadataNode::GetOrCreate(className); + napi_value proxy = nullptr; + napi_value jsWrapper = node->CreateJSWrapper(m_env, this); + if (jsWrapper != nullptr) { + // Reuse the class we already resolved via GetObjectClass on the instance + // path instead of re-resolving it with a JNI FindClass. The class is only + // stored on JSInstanceInfo::ObjectClazz, which nothing on this path reads, + // so a fresh FindClass is pure overhead; only fall back to it for the + // typeName-only overload where no instance class was available. + jclass linkClazz = clazz; + if (linkClazz == nullptr) { + JEnv jenv; + linkClazz = jenv.FindClass(className); + } + Link(jsWrapper, javaObjectID, linkClazz, node); + if (node->isArray()) { + NAPI_GUARD(napi_set_named_property(m_env, jsWrapper, "__is__javaArray", + napi_util::get_true(m_env))) {} + } + proxy = GetOrCreateProxy(javaObjectID, jsWrapper); + } + + return proxy; +} + +void ObjectManager::Link(napi_value object, uint32_t javaObjectID, jclass clazz, + MetadataNode *node) { + if (!IsRuntimeJsObject(object)) { + std::string errMsg("Trying to link invalid 'this' to a Java object"); + throw NativeScriptException(errMsg); + } + + DEBUG_WRITE("Linking js object and java instance id: %d", javaObjectID); + + napi_status status; + auto jsInstanceInfo = new JSInstanceInfo(javaObjectID, clazz); + jsInstanceInfo->node = node; + + napi_ref objectHandle = napi_util::make_ref(m_env, object, 1); + + napi_value jsInfo; + NAPI_GUARD(napi_create_external(m_env, jsInstanceInfo, JSObjectFinalizerCallback, jsInstanceInfo, &jsInfo)) {} + NAPI_GUARD(napi_set_named_property(m_env, object, PRIVATE_JSINFO, jsInfo)) {} + + // Wrapped but does not handle data lifecycle. only used for fast access. + NAPI_GUARD(napi_wrap(m_env, object, jsInstanceInfo, [](napi_env env, void *data, void *hint) {}, jsInstanceInfo, + nullptr)) {} + + m_idToObject.emplace(javaObjectID, objectHandle); +} + +bool ObjectManager::CloneLink(napi_value src, napi_value dest) { + napi_status status; + auto jsInfo = GetJSInstanceInfo(src); + + auto success = jsInfo != nullptr; + + if (success) { + napi_value external; + NAPI_GUARD(napi_create_external(m_env, jsInfo, [](napi_env env, void* d1, void*d2) {}, jsInfo, &external)) {} + NAPI_GUARD(napi_set_named_property(m_env, dest, PRIVATE_JSINFO, external)) {} + NAPI_GUARD(napi_wrap(m_env, dest, jsInfo, [](napi_env env, void *data, void *hint) {}, jsInfo, + nullptr)) {} + } + + return success; +} + +string ObjectManager::GetClassName(jobject javaObject) { + JEnv env; + JniLocalRef objectClass(env.GetObjectClass(javaObject)); + + return GetClassName((jclass) objectClass); +} + +string ObjectManager::GetClassName(jclass clazz) { + JEnv env; + JniLocalRef javaCanonicalName(env.CallObjectMethod(clazz, GET_NAME_METHOD_ID)); + + string className = ArgConverter::jstringToString(javaCanonicalName); + + std::replace(className.begin(), className.end(), '.', '/'); + + return className; +} + +void +ObjectManager::JSObjectFinalizerCallback(napi_env env, void *finalizeData, void *finalizeHint) { + #ifdef __HERMES__ + if (finalizeHint == nullptr) return; + auto data = reinterpret_cast(finalizeHint); + #else + if (finalizeData == nullptr) return; + auto data = reinterpret_cast(finalizeData); + #endif + + DEBUG_WRITE("JS Object finalizer called for object id: %d", data->JavaObjectID); + delete data; +} + +void ObjectManager::JSObjectProxyFinalizerCallback(napi_env env, void *finalizeData, + void *finalizeHint) { + +#ifdef __HERMES__ + if (finalizeHint == nullptr) return; + auto state = reinterpret_cast(finalizeHint); +#else + if (finalizeData == nullptr) return; + auto state = reinterpret_cast(finalizeData); +#endif + + auto rt = Runtime::GetRuntimeUnchecked(env); + if (rt && !rt->is_destroying) { + + auto objManager = rt->GetObjectManager(); + auto itFound = objManager->m_weakObjectIds.find(state->JavaObjectID); + + DEBUG_WRITE("JS Proxy finalizer called for object id: %d", state->JavaObjectID); + if (itFound == objManager->m_weakObjectIds.end()) { + objManager->m_weakObjectIds.emplace(state->JavaObjectID); + JEnv jEnv; + jEnv.CallVoidMethod(objManager->m_javaRuntimeObject, + objManager->MAKE_INSTANCE_WEAK_METHOD_ID, + state->JavaObjectID); + + } + } + delete state; +} + +int ObjectManager::GenerateNewObjectID() { + const int one = 1; + int oldValue = __sync_fetch_and_add(&m_currentObjectId, one); + return oldValue; +} + +jweak ObjectManager::NewWeakGlobalRefCallback(const int &javaObjectID, void *state) { + auto objManager = reinterpret_cast(state); + JniLocalRef obj(objManager->GetJavaObjectByIDImpl(javaObjectID)); + JEnv jEnv; + jweak weakRef = jEnv.NewWeakGlobalRef(obj); + + return weakRef; +} + +void ObjectManager::DeleteWeakGlobalRefCallback(const jweak &object, void *state) { + JEnv jEnv; + jEnv.DeleteWeakGlobalRef(object); +} + +bool ObjectManager::ValidateWeakGlobalRefCallback(const int &javaObjectID, const jweak &object, + void *state) { + JEnv jEnv; + // A weak ref that is now IsSameObject(NULL) points to a collected object and + // must not be reused; report it as invalid so the cache evicts it. + return !jEnv.isSameObject(object, NULL); +} + +napi_value ObjectManager::GetEmptyObject() { + napi_status status; + napi_value emptyObjCtorFunc = napi_util::get_ref_value(m_env, m_jsObjectCtor); + + napi_value ex; + NAPI_GUARD(napi_get_and_clear_last_exception(m_env, &ex)) {} + + napi_value jsWrapper = nullptr; + status = napi_new_instance(m_env, emptyObjCtorFunc, 0, nullptr, &jsWrapper); + if (status == napi_ok && !napi_util::is_null_or_undefined(m_env, jsWrapper)) { + return jsWrapper; + } + + napi_get_and_clear_last_exception(m_env, &ex); + + status = napi_create_object(m_env, &jsWrapper); + if (status != napi_ok || jsWrapper == nullptr) return nullptr; + + MarkObject(m_env, jsWrapper); + auto prototype = napi_util::get_prototype(m_env, emptyObjCtorFunc); + if (!napi_util::is_null_or_undefined(m_env, prototype)) { + napi_util::setPrototypeOf(m_env, jsWrapper, prototype); + } + + if (napi_util::is_null_or_undefined(m_env, jsWrapper)) { + return nullptr; + } + + return jsWrapper; +} + +napi_value ObjectManager::JSObjectConstructorCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0); + return jsThis; +} + +void ObjectManager::ReleaseObjectNow(napi_env env, int javaObjectId) { + napi_status status; + auto rt = Runtime::GetRuntimeUnchecked(env); + if (!rt || rt->is_destroying) return; + ObjectManager *objMgr = rt->GetObjectManager(); + + auto itFound = objMgr->m_weakObjectIds.find(javaObjectId); + if (itFound == objMgr->m_weakObjectIds.end()) { + JEnv jEnv; + jEnv.CallVoidMethod(objMgr->m_javaRuntimeObject, objMgr->MAKE_INSTANCE_WEAK_METHOD_ID, + javaObjectId); + objMgr->m_weakObjectIds.emplace(javaObjectId); + } + + auto found = objMgr->m_idToProxy.find(javaObjectId); + if (found != objMgr->m_idToProxy.end()) { + NAPI_GUARD(napi_delete_reference(env, found->second)) {} + objMgr->m_idToProxy.erase(javaObjectId); + } + + found = objMgr->m_idToObject.find(javaObjectId); + if (found != objMgr->m_idToObject.end()) { + NAPI_GUARD(napi_delete_reference(env, found->second)) {} + objMgr->m_idToObject.erase(javaObjectId); + } + + Runtime::GetRuntime(env)->js_method_cache->cleanupObject(javaObjectId); +} + +void ObjectManager::ReleaseNativeObject(napi_env env, napi_value object) { + napi_status status; + int32_t javaObjectId = -1; + JSInstanceInfo *jsInstanceInfo; + +#ifdef USE_HOST_OBJECT + // Non-host object → miss (an error status on some engines); expected, ignore. + void* data = nullptr; + napi_get_host_object_data(env, object, &data); + if (data) { + jsInstanceInfo = reinterpret_cast(data)->instanceInfo; + } else { +#endif + jsInstanceInfo = GetJSInstanceInfo(object); +#ifdef USE_HOST_OBJECT + } +#endif + + if (jsInstanceInfo) { + javaObjectId = jsInstanceInfo->JavaObjectID; + } + + if (javaObjectId == -1) { + NAPI_GUARD(napi_throw_error(env, "0", "Trying to release a non native object!")) {} + return; + } + + ReleaseObjectNow(env, javaObjectId); +} + +void ObjectManager::OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids) { + napi_status status; + JEnv jenv(jEnv); + jsize length = jenv.GetArrayLength(object_ids); + int *cppArray = jenv.GetIntArrayElements(object_ids, nullptr); + for (jsize i = 0; i < length; i++) { + auto rt = Runtime::GetRuntimeUnchecked(m_env); + if (rt && rt->is_destroying) return; + int javaObjectId = cppArray[i]; + auto itFound = this->m_idToObject.find(javaObjectId); + if (itFound != this->m_idToObject.end()) { + NAPI_GUARD(napi_delete_reference(m_env, itFound->second)) {} + this->m_idToObject.erase(javaObjectId); + + if (rt && !rt->is_destroying) { + rt->js_method_cache->cleanupObject(javaObjectId); + } + + DEBUG_WRITE("JS Object released for object id: %d", javaObjectId); + // auto found = this->m_idToProxy.find(javaObjectId); + // if (found != this->m_idToProxy.end()) { + // napi_delete_reference(m_env, found->second); + // this->m_idToProxy.erase(javaObjectId); + // } + } + + } +} diff --git a/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.h b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.h new file mode 100644 index 000000000..b8e4b3938 --- /dev/null +++ b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.h @@ -0,0 +1,222 @@ +#ifndef OBJECTMANAGER_H_ +#define OBJECTMANAGER_H_ + +#include "js_native_api.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "JniLocalRef.h" +#include "DirectBuffer.h" +#include "LRUCache.h" +#include +#include +#include +#include +#include +#include "Constants.h" + +class MetadataNode; + +namespace tns { + class ObjectManager { + public: + ObjectManager(jobject javaRuntimeObject); + + void OnDisposeEnv(); + + void Init(napi_env env); + + JniLocalRef GetJavaObjectByJsObject(napi_value object, int *objectId = nullptr, + bool *isSuper = nullptr); + + + JniLocalRef GetJavaObjectByJsObjectFast(napi_value object); + + void UpdateCache(int objectID, jobject obj); + + jclass GetJavaClass(napi_value value); + + void SetJavaClass(napi_value instance, jclass clazz); + + int GetOrCreateObjectId(jobject object); + + napi_value GetJsObjectByJavaObject(int javaObjectID); + + napi_value + CreateJSWrapper(jint javaObjectID, const std::string &typeName); + + napi_value + CreateJSWrapper(jint javaObjectID, const std::string &typeName, jobject instance); + + napi_value GetOrCreateProxy(jint javaObjectID, napi_value instance); + + napi_value GetOrCreateProxyWeak(jint javaObjectID, napi_value instance); + + void Link(napi_value object, uint32_t javaObjectID, jclass clazz, + MetadataNode *node = nullptr); + + // Returns the class metadata stored on the per-instance JSInstanceInfo + // (host proxy's, or the raw instance's wrap). Used by + // MetadataNode::GetInstanceMetadata under USE_HOST_OBJECT. + MetadataNode *GetInstanceNode(napi_value object); + + bool CloneLink(napi_value src, napi_value dest); + + bool IsRuntimeJsObject(napi_value object); + + static std::string GetClassName(jobject javaObject); + + static std::string GetClassName(jclass clazz); + + int GenerateNewObjectID(); + + napi_value GetEmptyObject(); + + inline static void MarkObject(napi_env env, napi_value object) { + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_set_named_property(env, object, PRIVATE_IS_NAPI, marker); + } + + inline static void MarkSuperCall(napi_env env, napi_value object) { + napi_value marker; + napi_get_boolean(env, true, &marker); + napi_set_named_property(env, object, PRIVATE_CALLSUPER, marker); + } + + void OnGarbageCollected(JNIEnv *jEnv, jintArray object_ids); + + void ReleaseNativeObject(napi_env env, napi_value object); + + inline static void ReleaseObjectNow(napi_env env, int javaObjectId); + + bool IsHostObject(napi_value object); + + private: + static napi_value JSObjectConstructorCallback(napi_env env, napi_callback_info info); + + struct JSInstanceInfo { + public: + JSInstanceInfo(uint32_t javaObjectID, jclass claz) + : JavaObjectID(javaObjectID), ObjectClazz(claz) { + } + + uint32_t JavaObjectID; + jclass ObjectClazz; + // Cached super-call flag (-1 = unresolved, 0 = false, 1 = true). + int8_t isSuper = -1; + // Per-instance class metadata. Under USE_HOST_OBJECT this replaces the + // "#instance_metadata" property; reachable via GetInstanceNode. + MetadataNode *node = nullptr; + }; + +#ifdef USE_HOST_OBJECT + // Backing context for a host-object proxy. The new napi_create_host_object + // takes no target/getter/setter, so everything needed to proxy to the + // underlying instance is carried here in the `data` pointer. + struct HostObjectProxy { + ObjectManager *objectManager; + JSInstanceInfo *instanceInfo; // java object id holder + bool isPrimary; // owns instanceInfo + marks weak on GC + napi_env env; + napi_ref target; // strong ref to the wrapped instance + bool isArray; + std::string arraySignature; // jni array signature (arrays only) + int8_t isSuper = -1; // cached super-call flag (-1=unresolved) + int64_t arrayLength = -1; // cached fixed length (arrays only; -1=unresolved) + }; + + napi_value CreateHostObjectProxy(napi_value instance, + JSInstanceInfo *instanceInfo, + bool isPrimary); + + // napi_host_object_methods callbacks (transparent proxy to `target`). + static napi_value HostObjectGet(napi_env env, napi_value host, + napi_value property, void *data); + static void HostObjectSet(napi_env env, napi_value host, + napi_value property, napi_value value, + void *data); + static int HostObjectHas(napi_env env, napi_value host, + napi_value property, void *data); + static int HostObjectDelete(napi_env env, napi_value host, + napi_value property, void *data); + static napi_value HostObjectOwnKeys(napi_env env, napi_value host, + void *data); + // Fast numeric index access: native get/setValueAtIndex (arrays only). + static napi_value HostObjectIndexedGet(napi_env env, napi_value host, + uint32_t index, void *data); + static void HostObjectIndexedSet(napi_env env, napi_value host, + uint32_t index, napi_value value, + void *data); + static napi_value HostObjectSuperGetter(napi_env env, + napi_callback_info info); + static void HostObjectProxyFinalizer(napi_env env, void *data, + void *hint); + // Actual cleanup, deferred to the runtime's safe post-GC finalizer drain + // (Runtime::PostFinalizer) so its reference-deleting Node-API calls are legal. + static void HostObjectProxyPostFinalizer(napi_env env, void *data, + void *hint); +#endif + + + JSInstanceInfo *GetJSInstanceInfo(napi_value object); + + JSInstanceInfo *GetJSInstanceInfoFromRuntimeObject(napi_value object); + + napi_value + CreateJSWrapperHelper(jint javaObjectID, const std::string &typeName, jclass clazz); + + static void JSObjectFinalizerCallback(napi_env env, void *finalizeData, void *finalizeHint); + + static void + JSObjectProxyFinalizerCallback(napi_env env, void *finalizeData, void *finalizeHint); + + jweak GetJavaObjectByID(uint32_t javaObjectID); + + jobject GetJavaObjectByIDImpl(uint32_t javaObjectID); + + static jweak NewWeakGlobalRefCallback(const int &javaObjectID, void *state); + + static void DeleteWeakGlobalRefCallback(const jweak &object, void *state); + + static bool ValidateWeakGlobalRefCallback(const int &javaObjectID, const jweak &object, void *state); + + jobject m_javaRuntimeObject; + + napi_env m_env; + + robin_hood::unordered_map m_idToProxy; + robin_hood::unordered_map m_idToObject; + robin_hood::unordered_set m_weakObjectIds; + robin_hood::unordered_set m_markedAsWeakIds; + + LRUCache m_cache; + + volatile int m_currentObjectId; + + DirectBuffer m_buff; + + DirectBuffer m_outBuff; + + static jclass JAVA_LANG_CLASS; + + static jmethodID GET_NAME_METHOD_ID; + + jmethodID GET_JAVAOBJECT_BY_ID_METHOD_ID; + + jmethodID GET_OR_CREATE_JAVA_OBJECT_ID_METHOD_ID; + + jmethodID MAKE_INSTANCE_WEAK_BATCH_METHOD_ID; + + jmethodID MAKE_INSTANCE_WEAK_METHOD_ID; + + jmethodID MAKE_INSTANCE_STRONG_METHOD_ID; + + napi_ref m_jsObjectCtor; + + napi_ref m_jsObjectProxyCreator; + + napi_ref jid; + }; +} + +#endif /* OBJECTMANAGER_H_ */ \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/weakref/WeakRef.cpp b/NativeScript/ffi/jni/napi/weakref/WeakRef.cpp new file mode 100644 index 000000000..e2c63d6b6 --- /dev/null +++ b/NativeScript/ffi/jni/napi/weakref/WeakRef.cpp @@ -0,0 +1,107 @@ +// +// Created by Ammar Ahmed on 03/12/2024. +// + +#include "WeakRef.h" +#include "native_api_util.h" + +using namespace tns; + +WeakRef::WeakRef(napi_env env, napi_value value) : env_(env), ref_(nullptr) { + napi_status status; + NAPI_GUARD(napi_create_reference(env, value, 1, &ref_)) {} +} + +WeakRef::~WeakRef() { + if (ref_ != nullptr) { + napi_status status; + NAPI_GUARD(napi_delete_reference(env_, ref_)) {} + } +} + +void WeakRef::Init(napi_env env) { + napi_status status; + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + napi_property_descriptor properties[] = { + { "get", 0, Deref, 0, 0, 0, napi_default, 0 }, + { "deref", 0, Deref, 0, 0, 0, napi_default, 0 } + }; + + napi_value wr; + NAPI_GUARD(napi_get_named_property(env, global, "WeakRef", &wr)) { + return; + } + if (napi_util::is_null_or_undefined(env, wr)) { + napi_value cons; + NAPI_GUARD(napi_define_class(env, "WeakRef", NAPI_AUTO_LENGTH, New, nullptr, 2, properties, &cons)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, global, "WeakRef", cons)) {} + } +} + +napi_value WeakRef::New(napi_env env, napi_callback_info info) { + napi_status status; + napi_value target; + // Leave a pending exception before bailing so `new WeakRef(...)` throws + // instead of silently evaluating to undefined. + NAPI_GUARD(napi_get_new_target(env, info, &target)) { + napi_throw_error(env, nullptr, "Failed to construct WeakRef."); + return nullptr; + } + bool is_constructor = target != nullptr; + + if (!is_constructor) { + NAPI_GUARD(napi_throw_error(env, nullptr, "WeakRef must be called as a constructor")) {} + return nullptr; + } + + size_t arg_len; + NAPI_GUARD(napi_get_cb_info(env, info, &arg_len, nullptr, nullptr, nullptr)) { + napi_throw_error(env, nullptr, "Failed to construct WeakRef."); + return nullptr; + } + + if (arg_len != 1) { + NAPI_GUARD(napi_throw_error(env, nullptr, "WeakRef constructor must be called with one argument")) {} + return nullptr; + } + + size_t argc = 1; + napi_value args[1]; + napi_value jsThis; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr)) { + napi_throw_error(env, nullptr, "Failed to construct WeakRef."); + return nullptr; + } + + auto obj = new WeakRef(env, args[0]); + NAPI_GUARD(napi_wrap(env, jsThis, reinterpret_cast(obj), [](napi_env env, void* data, void* hint) { + delete reinterpret_cast(data); + }, nullptr, nullptr)) {} + + return jsThis; +} + +napi_value WeakRef::Deref(napi_env env, napi_callback_info info) { + napi_status status; + napi_value jsThis; + NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr)) { + return nullptr; + } + + WeakRef* obj; + NAPI_GUARD(napi_unwrap(env, jsThis, reinterpret_cast(&obj))) { + return nullptr; + } + + napi_value result; + NAPI_GUARD(napi_get_reference_value(env, obj->ref_, &result)) { + return nullptr; + } + + return result; +} diff --git a/NativeScript/ffi/jni/napi/weakref/WeakRef.h b/NativeScript/ffi/jni/napi/weakref/WeakRef.h new file mode 100644 index 000000000..08579a40c --- /dev/null +++ b/NativeScript/ffi/jni/napi/weakref/WeakRef.h @@ -0,0 +1,28 @@ +// +// Created by Ammar Ahmed on 03/12/2024. +// + +#ifndef TEST_APP_WEAKREF_H +#define TEST_APP_WEAKREF_H + +#include "js_native_api.h" + +namespace tns { + class WeakRef { + public: + static void Init(napi_env env); + static napi_value New(napi_env env, napi_callback_info info); + + private: + explicit WeakRef(napi_env env, napi_value value); + ~WeakRef(); + + napi_env env_; + napi_ref ref_; + + static napi_value Get(napi_env env, napi_callback_info info); + static napi_value Deref(napi_env env, napi_callback_info info); + }; + +} +#endif //TEST_APP_WEAKREF_H diff --git a/NativeScript/ffi/jsc/NativeApiJSC.h b/NativeScript/ffi/jsc/NativeApiJSC.h deleted file mode 100644 index 0bf60c969..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSC.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H -#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H - -#include "ffi/shared/direct/NativeApiDirect.h" -#include - -namespace nativescript { - -using NativeApiJSCConfig = NativeApiDirectConfig; - -void InstallNativeApiJSC(JSGlobalContextRef context, - const NativeApiJSCConfig& config = NativeApiJSCConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSC(JSGlobalContextRef context, - const char* metadataPath); - -#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H diff --git a/NativeScript/ffi/jsc/NativeApiJSC.mm b/NativeScript/ffi/jsc/NativeApiJSC.mm deleted file mode 100644 index b1a6fa398..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSC.mm +++ /dev/null @@ -1,70 +0,0 @@ -#include "NativeApiJSC.h" - -#ifdef TARGET_ENGINE_JSC - -#include "NativeApiJSCRuntime.h" - -namespace nativescript { - -using NativeApiJsiConfig = NativeApiDirectConfig; -using NativeApiJsiScheduler = NativeApiDirectScheduler; - -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -#include "jsi/NativeApiJsiHostObjects.h" -// clang-format on -#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 - -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::make_shared(runtime.state()); -} - -// clang-format off -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -void InstallNativeApiJSC(JSGlobalContextRef context, const NativeApiJSCConfig& config) { - if (context == nullptr) { - return; - } - Runtime runtime(context); - InstallNativeApiJSI(runtime, config); -} - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiJSC(JSGlobalContextRef context, - const char* metadataPath) { - nativescript::NativeApiJSCConfig config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiJSC(context, config); -} - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/jsc/NativeApiJSCHostObjects.mm b/NativeScript/ffi/jsc/NativeApiJSCHostObjects.mm deleted file mode 100644 index ab2da20a8..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCHostObjects.mm +++ /dev/null @@ -1,182 +0,0 @@ -#include "NativeApiJSCRuntime.h" - -#ifdef TARGET_ENGINE_JSC - -namespace facebook { -namespace jsi { - -namespace jscdirect { - -JSClassRef hostClass(Runtime& runtime); -JSClassRef functionClass(Runtime& runtime); - -JSValueRef hostGetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, - JSValueRef* exception) { - auto* holder = static_cast(JSObjectGetPrivate(object)); - if (holder == nullptr || holder->hostObject == nullptr) { - return nullptr; - } - Runtime runtime(holder->state); - try { - Value result = holder->hostObject->get(runtime, PropNameID(stringToUtf8(propertyName))); - return result.isUndefined() ? nullptr : result.local(runtime); - } catch (const std::exception& error) { - setException(context, exception, error); - return JSValueMakeUndefined(context); - } -} - -bool hostSetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, - JSValueRef value, JSValueRef* exception) { - auto* holder = static_cast(JSObjectGetPrivate(object)); - if (holder == nullptr || holder->hostObject == nullptr) { - return false; - } - Runtime runtime(holder->state); - try { - holder->hostObject->set(runtime, PropNameID(stringToUtf8(propertyName)), Value(runtime, value)); - return true; - } catch (const std::exception& error) { - setException(context, exception, error); - return true; - } -} - -void hostGetPropertyNames(JSContextRef, JSObjectRef object, - JSPropertyNameAccumulatorRef propertyNames) { - auto* holder = static_cast(JSObjectGetPrivate(object)); - if (holder == nullptr || holder->hostObject == nullptr) { - return; - } - Runtime runtime(holder->state); - try { - for (const auto& property : holder->hostObject->getPropertyNames(runtime)) { - JSStringRef name = makeJSString(property.utf8(runtime)); - JSPropertyNameAccumulatorAddName(propertyNames, name); - JSStringRelease(name); - } - } catch (const std::exception&) { - } -} - -void hostFinalize(JSObjectRef object) { - delete static_cast(JSObjectGetPrivate(object)); -} - -JSValueRef functionCall(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, - size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { - auto* holder = static_cast(JSObjectGetPrivate(function)); - if (holder == nullptr || !holder->callback) { - return JSValueMakeUndefined(context); - } - Runtime runtime(holder->state); - std::vector args; - args.reserve(argumentCount); - for (size_t i = 0; i < argumentCount; i++) { - args.emplace_back(runtime, arguments[i]); - } - try { - Value thisValue(runtime, thisObject); - Value result = - holder->callback(runtime, thisValue, args.empty() ? nullptr : args.data(), args.size()); - return result.local(runtime); - } catch (const std::exception& error) { - setException(context, exception, error); - return JSValueMakeUndefined(context); - } -} - -void functionFinalize(JSObjectRef object) { - delete static_cast(JSObjectGetPrivate(object)); -} - -JSClassRef hostClass(Runtime& runtime) { - auto state = runtime.state(); - if (state->hostClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.className = "NativeScriptDirectHostObject"; - definition.getProperty = hostGetProperty; - definition.setProperty = hostSetProperty; - definition.getPropertyNames = hostGetPropertyNames; - definition.finalize = hostFinalize; - state->hostClass = JSClassCreate(&definition); - } - return state->hostClass; -} - -JSClassRef functionClass(Runtime& runtime) { - auto state = runtime.state(); - if (state->functionClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.className = "NativeScriptDirectFunction"; - definition.callAsFunction = functionCall; - definition.finalize = functionFinalize; - state->functionClass = JSClassCreate(&definition); - } - return state->functionClass; -} - -void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function) { - if (context == nullptr || function == nullptr) { - return; - } - - JSValueRef exception = nullptr; - JSStringRef functionName = makeJSString("Function"); - JSValueRef functionValue = - JSObjectGetProperty(context, JSContextGetGlobalObject(context), functionName, &exception); - JSStringRelease(functionName); - if (exception != nullptr || functionValue == nullptr || - !JSValueIsObject(context, functionValue)) { - return; - } - - exception = nullptr; - JSObjectRef functionConstructor = JSValueToObject(context, functionValue, &exception); - if (exception != nullptr || functionConstructor == nullptr) { - return; - } - - JSStringRef prototypeName = makeJSString("prototype"); - JSValueRef prototypeValue = - JSObjectGetProperty(context, functionConstructor, prototypeName, &exception); - JSStringRelease(prototypeName); - if (exception != nullptr || prototypeValue == nullptr || - !JSValueIsObject(context, prototypeValue)) { - return; - } - - JSObjectSetPrototype(context, function, prototypeValue); -} - -} // namespace jscdirect - -Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken) { - auto* holder = new jscdirect::HostObjectHolder(runtime.state(), std::move(host), typeToken); - JSObjectRef object = JSObjectMake(runtime.context(), jscdirect::hostClass(runtime), holder); - return Object::fromValueStorage(Value(runtime, object).storage_); -} - -Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback) { - auto* holder = new jscdirect::FunctionHolder(runtime.state(), std::move(callback)); - JSObjectRef function = JSObjectMake(runtime.context(), jscdirect::functionClass(runtime), holder); - jscdirect::setFunctionPrototype(runtime.context(), function); - std::string functionName = name.utf8(runtime); - if (!functionName.empty()) { - JSStringRef property = jscdirect::makeJSString("name"); - JSStringRef valueString = jscdirect::makeJSString(functionName); - JSValueRef value = JSValueMakeString(runtime.context(), valueString); - JSObjectSetProperty(runtime.context(), function, property, value, kJSPropertyAttributeReadOnly, - nullptr); - JSStringRelease(valueString); - JSStringRelease(property); - } - return Function(Object::fromValueStorage(Value(runtime, function).storage_)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/jsc/NativeApiJSCRuntime.h deleted file mode 100644 index 305399f41..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCRuntime.h +++ /dev/null @@ -1,839 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H -#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H - -#ifdef TARGET_ENGINE_JSC - -#import -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace facebook { -namespace jsi { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual void set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace jscdirect { - -inline std::string stringToUtf8(JSStringRef string) { - if (string == nullptr) { - return {}; - } - size_t capacity = JSStringGetMaximumUTF8CStringSize(string); - std::string result(capacity, '\0'); - size_t written = JSStringGetUTF8CString(string, result.data(), capacity); - if (written == 0) { - return {}; - } - result.resize(written - 1); - return result; -} - -inline JSStringRef makeJSString(const std::string& value) { - NSString* string = [[NSString alloc] initWithBytes:value.data() - length:value.size() - encoding:NSUTF8StringEncoding]; - if (string == nil) { - return JSStringCreateWithUTF8CString(value.c_str()); - } - - NSUInteger length = [string length]; - std::vector characters(length); - if (length > 0) { - [string getCharacters:characters.data() range:NSMakeRange(0, length)]; - } - [string release]; - return JSStringCreateWithCharacters(characters.data(), length); -} - -inline JSStringRef makeJSString(const char* value) { - return JSStringCreateWithUTF8CString(value != nullptr ? value : ""); -} - -inline std::string valueToUtf8(JSContextRef context, JSValueRef value) { - if (value == nullptr) { - return {}; - } - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(context, value, &exception); - if (string == nullptr || exception != nullptr) { - if (string != nullptr) { - JSStringRelease(string); - } - return {}; - } - std::string result = stringToUtf8(string); - JSStringRelease(string); - return result; -} - -inline JSValueRef makeError(JSContextRef context, const std::string& message) { - JSStringRef string = makeJSString(message); - JSValueRef argument = JSValueMakeString(context, string); - JSStringRelease(string); - JSValueRef exception = nullptr; - JSObjectRef error = JSObjectMakeError(context, 1, &argument, &exception); - if (error != nullptr && exception == nullptr) { - return error; - } - return argument; -} - -inline void setException(JSContextRef context, JSValueRef* exception, const std::exception& error) { - if (exception != nullptr) { - *exception = makeError(context, error.what()); - } -} - -struct RuntimeState { - explicit RuntimeState(JSGlobalContextRef context) : context(context) {} - - ~RuntimeState() { - if (hostClass != nullptr) { - JSClassRelease(hostClass); - } - if (functionClass != nullptr) { - JSClassRelease(functionClass); - } - } - - JSGlobalContextRef context = nullptr; - JSClassRef hostClass = nullptr; - JSClassRef functionClass = nullptr; -}; - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - JSC, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - - ~ValueStorage() { - if (context != nullptr && value != nullptr) { - JSValueUnprotect(context, value); - } - } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - JSGlobalContextRef context = nullptr; - JSValueRef value = nullptr; -}; - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - - std::shared_ptr state; - HostFunctionType callback; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - - std::shared_ptr buffer; -}; - -} // namespace jscdirect - -class Runtime { - public: - explicit Runtime(JSGlobalContextRef context) - : state_(std::make_shared(context)) {} - - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - - JSGlobalContextRef context() const { return state_->context; } - std::shared_ptr state() const { return state_; } - - Object global(); - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - void drainMicrotasks() {} - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, JSStringRef string); - - static String createFromUtf8(Runtime& runtime, const char* value) { - JSStringRef string = jscdirect::makeJSString(value); - String result(runtime, string); - JSStringRelease(string); - return result; - } - - static String createFromUtf8(Runtime& runtime, const std::string& value) { - JSStringRef string = jscdirect::makeJSString(value); - String result(runtime, string); - JSStringRelease(string); - return result; - } - - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - std::string text(reinterpret_cast(value), length); - return createFromUtf8(runtime, text); - } - - std::string utf8(Runtime& runtime) const; - JSValueRef local(Runtime& runtime) const { return storage_->value; } - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() - : storage_( - std::make_shared(jscdirect::ValueStorage::Kind::Undefined)) {} - - Value(bool value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::Bool)) { - storage_->boolValue = value; - } - - Value(double value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::Number)) { - storage_->numberValue = value; - } - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) : storage_(value.storage_) {} - Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) {} - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - Value(Runtime& runtime, JSValueRef value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); - JSValueProtect(runtime.context(), storage_->value); - } - - static Value undefined() { return Value(); } - static Value null() { - Value value; - value.storage_ = std::make_shared(jscdirect::ValueStorage::Kind::Null); - return value; - } - - bool isUndefined() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Undefined || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsUndefined(storage_->context, storage_->value)); - } - bool isNull() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Null || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsNull(storage_->context, storage_->value)); - } - bool isBool() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Bool || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsBoolean(storage_->context, storage_->value)); - } - bool getBool() const { - if (storage_->kind == jscdirect::ValueStorage::Kind::Bool) { - return storage_->boolValue; - } - if (storage_->kind == jscdirect::ValueStorage::Kind::JSC) { - return JSValueToBoolean(storage_->context, storage_->value); - } - return false; - } - bool isNumber() const { - return storage_->kind == jscdirect::ValueStorage::Kind::Number || - (storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsNumber(storage_->context, storage_->value)); - } - double getNumber() const { - if (storage_->kind == jscdirect::ValueStorage::Kind::Number) { - return storage_->numberValue; - } - if (storage_->kind == jscdirect::ValueStorage::Kind::JSC) { - return JSValueToNumber(storage_->context, storage_->value, nullptr); - } - return 0; - } - - bool isObject() const { - return storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsObject(storage_->context, storage_->value); - } - bool isString() const { - return storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsString(storage_->context, storage_->value); - } - bool isBigInt() const { - if (storage_->kind != jscdirect::ValueStorage::Kind::JSC) { - return false; - } - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - return JSValueIsBigInt(storage_->context, storage_->value); - } - return false; - } - bool isSymbol() const { - return storage_->kind == jscdirect::ValueStorage::Kind::JSC && - JSValueIsSymbol(storage_->context, storage_->value); - } - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - JSValueRef local(Runtime& runtime) const { - switch (storage_->kind) { - case jscdirect::ValueStorage::Kind::Undefined: - return JSValueMakeUndefined(runtime.context()); - case jscdirect::ValueStorage::Kind::Null: - return JSValueMakeNull(runtime.context()); - case jscdirect::ValueStorage::Kind::Bool: - return JSValueMakeBoolean(runtime.context(), storage_->boolValue); - case jscdirect::ValueStorage::Kind::Number: - return JSValueMakeNumber(runtime.context(), storage_->numberValue); - case jscdirect::ValueStorage::Kind::JSC: - return storage_->value; - } - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = JSObjectMake(runtime.context(), nullptr, nullptr); - JSValueProtect(runtime.context(), storage_->value); - } - - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - jscdirect::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - JSStringRef property = jscdirect::makeJSString(name); - JSValueRef exception = nullptr; - JSValueRef result = - JSObjectGetProperty(runtime.context(), local(runtime), property, &exception); - JSStringRelease(property); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - - Value getProperty(Runtime& runtime, const Value& key) const { - JSValueRef exception = nullptr; - JSValueRef result = JSObjectGetPropertyForKey(runtime.context(), local(runtime), - key.local(runtime), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - JSStringRef property = jscdirect::makeJSString(name); - JSValueRef exception = nullptr; - JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), - kJSPropertyAttributeNone, &exception); - JSStringRelease(property); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - } - - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - JSValueRef exception = nullptr; - JSObjectSetPropertyForKey(runtime.context(), local(runtime), key.local(runtime), - value.local(runtime), kJSPropertyAttributeNone, &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - } - - bool hasProperty(Runtime& runtime, const char* name) const { - JSStringRef property = jscdirect::makeJSString(name); - bool result = JSObjectHasProperty(runtime.context(), local(runtime), property); - JSStringRelease(property); - return result; - } - - bool isFunction(Runtime& runtime) const { - return JSObjectIsFunction(runtime.context(), local(runtime)); - } - - bool isArray(Runtime& runtime) const { - JSStringRef name = jscdirect::makeJSString("Array"); - JSValueRef constructorValue = JSObjectGetProperty( - runtime.context(), JSContextGetGlobalObject(runtime.context()), name, nullptr); - JSStringRelease(name); - if (constructorValue == nullptr || !JSValueIsObject(runtime.context(), constructorValue)) { - return false; - } - JSObjectRef constructor = JSValueToObject(runtime.context(), constructorValue, nullptr); - JSValueRef exception = nullptr; - bool result = - JSValueIsInstanceOfConstructor(runtime.context(), local(runtime), constructor, &exception); - return exception == nullptr && result; - } - - bool isArrayBuffer(Runtime& runtime) const { - JSValueRef exception = nullptr; - JSTypedArrayType type = - JSValueGetTypedArrayType(runtime.context(), storage_->value, &exception); - return exception == nullptr && type == kJSTypedArrayTypeArrayBuffer; - } - - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == jscdirect::hostObjectTypeToken(); - } - - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != jscdirect::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - - JSObjectRef local(Runtime& runtime) const { - return reinterpret_cast(const_cast(storage_->value)); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - - explicit Object(std::shared_ptr storage) - : storage_(std::move(storage)) {} - - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - - jscdirect::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { - return static_cast(JSObjectGetPrivate(local(runtime))); - } - - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - - Value call(Runtime& runtime, const Value* args, size_t count) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsFunction( - runtime.context(), local(runtime), JSContextGetGlobalObject(runtime.context()), argv.size(), - argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = - JSObjectCallAsFunction(runtime.context(), local(runtime), thisObject.local(runtime), - argv.size(), argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsConstructor(runtime.context(), local(runtime), argv.size(), - argv.empty() ? nullptr : argv.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - std::vector initial(size, JSValueMakeUndefined(runtime.context())); - JSValueRef exception = nullptr; - storage_->context = runtime.context(); - storage_->value = - JSObjectMakeArray(runtime.context(), initial.size(), initial.data(), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - JSValueProtect(runtime.context(), storage_->value); - } - - explicit Array(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { - Value length = getProperty(runtime, "length"); - return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; - } - - Value getValueAtIndex(Runtime& runtime, size_t index) const { - JSValueRef exception = nullptr; - JSValueRef result = JSObjectGetPropertyAtIndex(runtime.context(), local(runtime), - static_cast(index), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - return Value(runtime, result); - } - - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - JSValueRef exception = nullptr; - JSObjectSetPropertyAtIndex(runtime.context(), local(runtime), static_cast(index), - value.local(runtime), &exception); - if (exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - } - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, JSValueRef value) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = value; - JSValueProtect(runtime.context(), storage_->value); - } - - static BigInt fromInt64(Runtime& runtime, int64_t value) { - JSValueRef exception = nullptr; - JSValueRef result = nullptr; - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - result = JSBigIntCreateWithInt64(runtime.context(), value, &exception); - } - if (result == nullptr || exception != nullptr) { - result = JSValueMakeNumber(runtime.context(), static_cast(value)); - } - return BigInt(runtime, result); - } - - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - JSValueRef exception = nullptr; - JSValueRef result = nullptr; - if (__builtin_available(macOS 15.0, iOS 18.0, *)) { - result = JSBigIntCreateWithUInt64(runtime.context(), value, &exception); - } - if (result == nullptr || exception != nullptr) { - result = JSValueMakeNumber(runtime.context(), static_cast(value)); - } - return BigInt(runtime, result); - } - - String toString(Runtime& runtime, int) const { - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); - if (string == nullptr || exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - String result(runtime, string); - JSStringRelease(string); - return result; - } - - JSValueRef local(Runtime& runtime) const { return storage_->value; } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - auto* holder = new jscdirect::ArrayBufferHolder(std::move(buffer)); - JSValueRef exception = nullptr; - storage_->context = runtime.context(); - storage_->value = JSObjectMakeArrayBufferWithBytesNoCopy( - runtime.context(), holder->buffer->data(), holder->buffer->size(), - [](void*, void* deallocatorContext) { - delete static_cast(deallocatorContext); - }, - holder, &exception); - if (exception != nullptr) { - delete holder; - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - JSValueProtect(runtime.context(), storage_->value); - } - - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { - JSValueRef exception = nullptr; - return JSObjectGetArrayBufferByteLength(runtime.context(), local(runtime), &exception); - } - - uint8_t* data(Runtime& runtime) const { - JSValueRef exception = nullptr; - return static_cast( - JSObjectGetArrayBufferBytesPtr(runtime.context(), local(runtime), &exception)); - } -}; -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC - -#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H diff --git a/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm b/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm deleted file mode 100644 index 3396af697..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm +++ /dev/null @@ -1,30 +0,0 @@ -#include "NativeApiJSCRuntime.h" - -#ifdef TARGET_ENGINE_JSC - -namespace facebook { -namespace jsi { - -Object Runtime::global() { - return Object::fromValueStorage(Value(*this, JSContextGetGlobalObject(context())).storage_); -} - -Value Runtime::evaluateJavaScript(std::shared_ptr buffer, - const std::string& sourceURL) { - JSStringRef source = JSStringCreateWithUTF8CString( - buffer != nullptr ? std::string(buffer->data(), buffer->size()).c_str() : ""); - JSStringRef url = jscdirect::makeJSString(sourceURL); - JSValueRef exception = nullptr; - JSValueRef result = JSEvaluateScript(context(), source, nullptr, url, 1, &exception); - JSStringRelease(source); - JSStringRelease(url); - if (exception != nullptr) { - throw JSError(*this, jscdirect::valueToUtf8(context(), exception)); - } - return Value(*this, result); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/jsc/NativeApiJSCValue.mm b/NativeScript/ffi/jsc/NativeApiJSCValue.mm deleted file mode 100644 index ca4071567..000000000 --- a/NativeScript/ffi/jsc/NativeApiJSCValue.mm +++ /dev/null @@ -1,83 +0,0 @@ -#include "NativeApiJSCRuntime.h" - -#ifdef TARGET_ENGINE_JSC - -namespace facebook { -namespace jsi { - -Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } -void HostObject::set(Runtime&, const PropNameID&, const Value&) {} -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } - -String::String(Runtime& runtime, JSStringRef string) - : storage_(std::make_shared(jscdirect::ValueStorage::Kind::JSC)) { - storage_->context = runtime.context(); - storage_->value = JSValueMakeString(runtime.context(), string); - JSValueProtect(runtime.context(), storage_->value); -} - -std::string String::utf8(Runtime& runtime) const { - return jscdirect::valueToUtf8(runtime.context(), storage_->value); -} - -String::operator Value() const { - Value value; - value.storage_ = storage_; - return value; -} - -Value::Value(Runtime&, const Object& object) : storage_(object.storage_) {} -Value::Value(Runtime&, const Function& function) : storage_(function.storage_) {} -Value::Value(Runtime&, const Array& array) : storage_(array.storage_) {} -Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) : storage_(arrayBuffer.storage_) {} -Value::Value(Runtime&, const BigInt& bigint) : storage_(bigint.storage_) {} - -Object Value::asObject(Runtime&) const { return Object::fromValueStorage(storage_); } - -String Value::asString(Runtime& runtime) const { - JSValueRef exception = nullptr; - JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); - if (string == nullptr || exception != nullptr) { - throw JSError(runtime, jscdirect::valueToUtf8(runtime.context(), exception)); - } - String result(runtime, string); - JSStringRelease(string); - return result; -} - -BigInt Value::getBigInt(Runtime& runtime) const { return BigInt(runtime, local(runtime)); } - -Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime).asFunction(runtime); -} -Function Object::asFunction(Runtime&) const { return Function(*this); } -Array Object::getArray(Runtime&) const { return Array(*this); } -ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } - -Array Object::getPropertyNames(Runtime& runtime) const { - JSPropertyNameArrayRef propertyNames = - JSObjectCopyPropertyNames(runtime.context(), local(runtime)); - size_t count = JSPropertyNameArrayGetCount(propertyNames); - Array result(runtime, count); - for (size_t i = 0; i < count; i++) { - JSStringRef name = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); - result.setValueAtIndex(runtime, i, String(runtime, name)); - } - JSPropertyNameArrayRelease(propertyNames); - return result; -} - -void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/napi/Cif.mm b/NativeScript/ffi/napi/Cif.mm deleted file mode 100644 index dfe18c8c3..000000000 --- a/NativeScript/ffi/napi/Cif.mm +++ /dev/null @@ -1,536 +0,0 @@ -#include "Cif.h" -#include -#include -#include -#include -#include -#include -#include -#include "Metadata.h" -#include "MetadataReader.h" -#include "ObjCBridge.h" -#include "TypeConv.h" -#include "Util.h" - -namespace nativescript { -namespace { - -constexpr uint64_t kFNV64OffsetBasis = 14695981039346656037ull; -constexpr uint64_t kFNV64Prime = 1099511628211ull; - -uint64_t hashBytesFnv1a(const void* data, size_t size, uint64_t seed = kFNV64OffsetBasis) { - const auto* bytes = static_cast(data); - uint64_t hash = seed; - for (size_t i = 0; i < size; i++) { - hash ^= static_cast(bytes[i]); - hash *= kFNV64Prime; - } - return hash; -} - -MDTypeKind canonicalizeSignatureTypeKind(MDTypeKind kind) { - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return mdTypeAnyObject; - default: - return kind; - } -} - -template -void appendIntegralToHash(uint64_t* hash, T value) { - using Unsigned = typename std::make_unsigned::type; - Unsigned unsignedValue = static_cast(value); - for (size_t i = 0; i < sizeof(Unsigned); i++) { - const uint8_t byte = static_cast((unsignedValue >> (i * 8)) & 0xFF); - *hash = hashBytesFnv1a(&byte, sizeof(byte), *hash); - } -} - -bool appendMetadataSignatureHash(MDMetadataReader* reader, MDSectionOffset signatureOffset, - std::unordered_set* activeSignatures, - uint64_t* hash); - -inline bool typeRequiresSlowGeneratedNapiDispatch(const std::shared_ptr& type) { - if (type == nullptr) { - return false; - } - - switch (type->kind) { - case mdTypeUChar: - case mdTypeUInt8: - case mdTypeString: - case mdTypePointer: - case mdTypeStruct: - case mdTypeArray: - case mdTypeBlock: - case mdTypeFunctionPointer: - case mdTypeVector: - case mdTypeExtVector: - case mdTypeComplex: - return true; - default: - return false; - } -} - -inline bool typeKindMayUseRoundTripCache(MDTypeKind kind) { - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return true; - default: - return false; - } -} - -inline void updateGeneratedNapiDispatchCompatibility(Cif* cif) { - if (cif == nullptr) { - return; - } - - cif->skipGeneratedNapiDispatch = false; - cif->generatedDispatchHasRoundTripCacheArgument = false; - cif->generatedDispatchUsesObjectReturnStorage = false; - - if (cif->returnType != nullptr) { - cif->generatedDispatchUsesObjectReturnStorage = - typeKindMayUseRoundTripCache(cif->returnType->kind); - } - - cif->skipGeneratedNapiDispatch = typeRequiresSlowGeneratedNapiDispatch(cif->returnType); - if (cif->skipGeneratedNapiDispatch) { - return; - } - - for (const auto& argType : cif->argTypes) { - if (argType != nullptr && typeKindMayUseRoundTripCache(argType->kind)) { - cif->generatedDispatchHasRoundTripCacheArgument = true; - } - if (typeRequiresSlowGeneratedNapiDispatch(argType)) { - cif->skipGeneratedNapiDispatch = true; - return; - } - } -} - -bool appendMetadataTypeHash(MDMetadataReader* reader, MDSectionOffset* offset, - std::unordered_set* activeSignatures, uint64_t* hash) { - if (reader == nullptr || offset == nullptr || hash == nullptr || activeSignatures == nullptr) { - return false; - } - - const MDTypeKind kindWithFlags = reader->getTypeKind(*offset); - *offset += sizeof(MDTypeKind); - const MDTypeKind rawKind = - static_cast((kindWithFlags & ~mdTypeFlagNext) & ~mdTypeFlagVariadic); - - appendIntegralToHash(hash, 0xB0); - const MDTypeKind canonicalKind = canonicalizeSignatureTypeKind(rawKind); - appendIntegralToHash(hash, static_cast(canonicalKind)); - - switch (rawKind) { - case mdTypeArray: - case mdTypeVector: - case mdTypeExtVector: - case mdTypeComplex: { - const auto arraySize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - appendIntegralToHash(hash, arraySize); - if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { - return false; - } - break; - } - - case mdTypeStruct: { - const auto structOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - appendIntegralToHash(hash, structOffset); - break; - } - - case mdTypeClassObject: { - auto classOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool hasNext = (classOffset & mdSectionOffsetNext) != 0; - while (hasNext) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - hasNext = (protocolOffset & mdSectionOffsetNext) != 0; - } - break; - } - - case mdTypeProtocolObject: { - bool hasNext = true; - while (hasNext) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - hasNext = (protocolOffset & mdSectionOffsetNext) != 0; - } - break; - } - - case mdTypePointer: - if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { - return false; - } - break; - - case mdTypeBlock: - case mdTypeFunctionPointer: { - const auto nestedSignatureOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - if (nestedSignatureOffset != MD_SECTION_OFFSET_NULL) { - const auto nestedAbsoluteOffset = reader->signaturesOffset + nestedSignatureOffset; - if (!appendMetadataSignatureHash(reader, nestedAbsoluteOffset, activeSignatures, hash)) { - return false; - } - } - break; - } - - default: - break; - } - - appendIntegralToHash(hash, 0xBF); - return true; -} - -bool appendMetadataSignatureHash(MDMetadataReader* reader, MDSectionOffset signatureOffset, - std::unordered_set* activeSignatures, - uint64_t* hash) { - if (reader == nullptr || hash == nullptr || activeSignatures == nullptr) { - return false; - } - - if (activeSignatures->find(signatureOffset) != activeSignatures->end()) { - appendIntegralToHash(hash, 0xEE); - return true; - } - activeSignatures->insert(signatureOffset); - - MDSectionOffset offset = signatureOffset; - const MDTypeKind returnTypeKind = reader->getTypeKind(offset); - bool next = (returnTypeKind & mdTypeFlagNext) != 0; - const bool isVariadic = (returnTypeKind & mdTypeFlagVariadic) != 0; - - appendIntegralToHash(hash, 0xA0); - appendIntegralToHash(hash, isVariadic ? 1 : 0); - - if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { - activeSignatures->erase(signatureOffset); - return false; - } - - uint32_t argCount = 0; - while (next) { - const MDTypeKind argTypeKind = reader->getTypeKind(offset); - next = (argTypeKind & mdTypeFlagNext) != 0; - if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { - activeSignatures->erase(signatureOffset); - return false; - } - argCount++; - } - - appendIntegralToHash(hash, argCount); - appendIntegralToHash(hash, 0xAF); - - activeSignatures->erase(signatureOffset); - return true; -} - -} // namespace - -// Essentially, we cache libffi structures per unique method signature, -// this helps us avoid the overhead of creating them on the fly for each -// invocation. -Cif* ObjCBridgeState::getMethodCif(napi_env env, Method method) { - auto encoding = std::string(method_getTypeEncoding(method)); - auto find = this->cifs[encoding]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, method); - this->cifs[encoding] = cif; - - return cif; -} - -Cif* ObjCBridgeState::getMethodCif(napi_env env, MDSectionOffset offset) { - auto find = this->mdMethodSignatureCache[offset]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, metadata, offset, true, false); - this->mdMethodSignatureCache[offset] = cif; - - return cif; -} - -Cif* ObjCBridgeState::getBlockCif(napi_env env, MDSectionOffset offset) { - auto find = this->mdBlockSignatureCache[offset]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, metadata, offset, false, true); - this->mdBlockSignatureCache[offset] = cif; - - return cif; -} - -Cif* ObjCBridgeState::getCFunctionCif(napi_env env, MDSectionOffset offset) { - auto find = this->mdFunctionSignatureCache[offset]; - if (find != nullptr) { - return find; - } - - auto cif = new Cif(env, metadata, offset, false, false); - this->mdFunctionSignatureCache[offset] = cif; - - return cif; -} - -Cif::Cif(napi_env env, std::string encoding, unsigned int implicitArgc) { - auto signature = [NSMethodSignature signatureWithObjCTypes:encoding.c_str()]; - unsigned long numberOfArguments = signature.numberOfArguments; - unsigned long skippedArgs = std::min(numberOfArguments, implicitArgc); - this->argc = (int)(numberOfArguments - skippedArgs); - this->argv = (napi_value*)malloc(sizeof(napi_value) * this->argc); - - unsigned int totalArgc = (unsigned int)numberOfArguments; - - const char* returnType = signature.methodReturnType; - this->returnType = TypeConv::Make(env, &returnType); - - ffi_type* rtype = this->returnType->type; - this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); - - unsigned long methodReturnLength = signature.methodReturnLength; - unsigned long frameLength = signature.frameLength; - - this->rvalue = malloc(methodReturnLength); - this->rvalueLength = methodReturnLength; - this->frameLength = frameLength; - - this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; - if (this->avalues != nullptr) { - memset(this->avalues, 0, sizeof(void*) * this->argc); - } - this->shouldFree = (bool*)malloc(sizeof(bool) * this->argc); - memset(this->shouldFree, false, sizeof(bool) * this->argc); - this->shouldFreeAny = false; - this->avaluesAllocStart = 0; - this->avaluesAllocCount = 0; - - for (int i = 0; i < numberOfArguments; i++) { - const char* argenc = [signature getArgumentTypeAtIndex:i]; - - auto argTypeInfo = TypeConv::Make(env, &argenc); - this->atypes[i] = argTypeInfo->ffiTypeForArgument(); - - if (i >= skippedArgs) { - this->argTypes.push_back(argTypeInfo); - } - } - - ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); - - if (status != FFI_OK) { - std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; - return; - } - - for (unsigned int i = 0; i < this->argc; i++) { - this->avalues[i] = malloc(cif.arg_types[i + skippedArgs]->size); - this->avaluesAllocCount++; - } - - updateGeneratedNapiDispatchCompatibility(this); -} - -Cif::Cif(napi_env env, Method method) { - const unsigned int totalArgc = method_getNumberOfArguments(method); - this->argc = totalArgc >= 2 ? totalArgc - 2 : 0; - this->argv = this->argc > 0 ? (napi_value*)malloc(sizeof(napi_value) * this->argc) : nullptr; - - char* returnTypeEnc = method_copyReturnType(method); - const char* returnTypePtr = returnTypeEnc; - this->returnType = TypeConv::Make(env, &returnTypePtr); - if (returnTypeEnc != nullptr) { - free(returnTypeEnc); - } - - ffi_type* rtype = this->returnType->type; - this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); - - this->rvalueLength = std::max(1, rtype->size); - this->rvalue = malloc(this->rvalueLength); - this->frameLength = 0; - - this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; - if (this->avalues != nullptr) { - memset(this->avalues, 0, sizeof(void*) * this->argc); - } - - this->shouldFree = this->argc > 0 ? (bool*)malloc(sizeof(bool) * this->argc) : nullptr; - if (this->shouldFree != nullptr) { - memset(this->shouldFree, false, sizeof(bool) * this->argc); - } - this->shouldFreeAny = false; - this->avaluesAllocStart = 0; - this->avaluesAllocCount = 0; - - for (unsigned int i = 0; i < totalArgc; i++) { - char* argEnc = method_copyArgumentType(method, i); - const char* argEncPtr = argEnc; - auto argTypeInfo = TypeConv::Make(env, &argEncPtr); - if (argEnc != nullptr) { - free(argEnc); - } - - this->atypes[i] = argTypeInfo->ffiTypeForArgument(); - if (i >= 2) { - this->argTypes.push_back(argTypeInfo); - } - } - - ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); - if (status != FFI_OK) { - std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; - return; - } - - for (unsigned int i = 0; i < this->argc; i++) { - this->avalues[i] = malloc(cif.arg_types[i + 2]->size); - this->avaluesAllocCount++; - } - - updateGeneratedNapiDispatchCompatibility(this); -} - -Cif::Cif(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, bool isMethod, - bool isBlock) { - MDSectionOffset signatureStart = offset; - auto returnTypeKind = reader->getTypeKind(offset); - bool next = ((MDTypeFlag)returnTypeKind & mdTypeFlagNext) != 0; - isVariadic = ((MDTypeFlag)returnTypeKind & mdTypeFlagVariadic) != 0; - - returnType = TypeConv::Make(env, reader, &offset); - - auto implicitArgs = isMethod ? 2 : isBlock ? 1 : 0; - - shouldFreeAny = false; - atypes = nullptr; - avaluesAllocStart = 0; - avaluesAllocCount = 0; - - if (next || isMethod || isBlock) { - while (next) { - auto argTypeKind = reader->getTypeKind(offset); - next = ((MDTypeFlag)argTypeKind & mdTypeFlagNext) != 0; - auto argTypeInfo = TypeConv::Make(env, reader, &offset); - std::string enc; - argTypeInfo->encode(&enc); - argTypes.push_back(argTypeInfo); - } - - argc = (int)argTypes.size(); - - auto totalArgc = argc + implicitArgs; - - argv = (napi_value*)malloc(sizeof(napi_value) * argc); - shouldFree = (bool*)malloc(sizeof(bool) * argc); - - atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); - avalues = (void**)malloc(sizeof(void*) * argc); - memset(avalues, 0, sizeof(void*) * argc); - - if (isMethod) { - atypes[0] = &ffi_type_pointer; - atypes[1] = &ffi_type_pointer; - } - - if (isBlock) { - atypes[0] = &ffi_type_pointer; - } - - for (int i = 0; i < argc; i++) { - atypes[i + implicitArgs] = argTypes[i]->ffiTypeForArgument(); - shouldFree[i] = false; - } - } else { - argc = 0; - argv = nullptr; - avalues = nullptr; - shouldFree = nullptr; - } - - ffi_status status = - ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argc + implicitArgs, returnType->type, atypes); - - if (status != FFI_OK) { - std::cout << "Failed to prepare CIF, libffi returned error: " << status << std::endl; - return; - } - - for (int i = 0; i < argc; i++) { - avalues[i] = malloc(cif.arg_types[i + implicitArgs]->size); - avaluesAllocCount++; - } - - rvalue = malloc(cif.rtype->size); - rvalueLength = cif.rtype->size; - - if (signatureStart != MD_SECTION_OFFSET_NULL) { - uint64_t canonicalSignatureHash = kFNV64OffsetBasis; - std::unordered_set activeSignatures; - if (appendMetadataSignatureHash(reader, signatureStart, &activeSignatures, - &canonicalSignatureHash)) { - signatureHash = canonicalSignatureHash; - } - } - - updateGeneratedNapiDispatchCompatibility(this); -} - -Cif::~Cif() { - if (rvalue != nullptr) { - free(rvalue); - } - if (argv != nullptr) { - free(argv); - } - if (avalues != nullptr) { - for (unsigned int i = 0; i < avaluesAllocCount; i++) { - auto index = avaluesAllocStart + i; - if (avalues[index] != nullptr) { - free(avalues[index]); - } - } - free(avalues); - } - if (atypes != nullptr) { - free(atypes); - } - if (shouldFree != nullptr) { - free(shouldFree); - } -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/Class.mm b/NativeScript/ffi/napi/Class.mm deleted file mode 100644 index 12aea8230..000000000 --- a/NativeScript/ffi/napi/Class.mm +++ /dev/null @@ -1,1034 +0,0 @@ -#include "Class.h" -#include "ClassBuilder.h" -#include "ClassMember.h" -#include "Interop.h" -#include "Metadata.h" -#include "MetadataReader.h" -#include "ObjCBridge.h" -#include "Protocol.h" -#include "Util.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#include -#include - -namespace nativescript { - -napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo); - -inline bool tryGetInteropPointerArg(napi_env env, napi_value value, void** out) { - if (out == nullptr || value == nullptr) { - return false; - } - - *out = nullptr; - - napi_valuetype argType = napi_undefined; - napi_typeof(env, value, &argType); - - if (argType == napi_external) { - return napi_get_value_external(env, value, out) == napi_ok && *out != nullptr; - } - - if (argType == napi_bigint) { - uint64_t raw = 0; - bool lossless = false; - if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { - return false; - } - *out = reinterpret_cast(raw); - return *out != nullptr; - } - - if (argType != napi_object) { - return false; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *out = ptr != nullptr ? ptr->data : nullptr; - return *out != nullptr; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *out = ref != nullptr ? ref->data : nullptr; - return *out != nullptr; - } - - return false; -} - -void ObjCBridgeState::registerClassGlobals(napi_env env, napi_value global) { - MDSectionOffset offset = metadata->classesOffset; - while (offset < metadata->structsOffset) { - MDSectionOffset originalOffset = offset; - auto nameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - auto runtimeNameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto name = metadata->resolveString(nameOffset); - auto runtimeName = name; - if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { - runtimeName = metadata->resolveString(runtimeNameOffset); - } - while (hasProtocols) { - auto protocolOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; - } - - auto superclass = metadata->getOffset(offset); - offset += sizeof(superclass); - - bool next = (superclass & mdSectionOffsetNext) != 0; - - while (next) { - auto flags = metadata->getMemberFlag(offset); - next = (flags & mdMemberNext) != 0; - offset += sizeof(flags); - - if ((flags & mdMemberProperty) != 0) { - bool readonly = (flags & mdMemberReadonly) != 0; - offset += sizeof(MDSectionOffset); // name - offset += sizeof(MDSectionOffset); // getterSelector - offset += sizeof(MDSectionOffset); // getterSignature - if (!readonly) { - offset += sizeof(MDSectionOffset); // setterSelector - offset += sizeof(MDSectionOffset); // setterSignature - } - } else { - offset += sizeof(MDSectionOffset); // selector - offset += sizeof(MDSectionOffset); // signature - } - } - - auto nativeClass = objc_getClass(runtimeName); - if (nativeClass != nil) { - mdClassesByPointer[nativeClass] = originalOffset; - } - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = JS_classGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - - napi_define_properties(env, global, 1, &prop); - } -} - -NAPI_FUNCTION(registerClass) { - NAPI_CALLBACK_BEGIN(1) - - // In case no arguments are passed, we just return the NativeObject class. - // This is to support both @NativeClass and @NativeClass() syntaxes. - // Maybe we should support more in future like - // @NativeClass(NSApplicationDelegate). - if (argc == 0) { - napi_value func; - napi_create_function(env, "NativeClass", NAPI_AUTO_LENGTH, JS_registerClass, nullptr, &func); - return func; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - // bridgeState->registerClass(env, argv[0]); - - ClassBuilder* builder = new ClassBuilder(env, argv[0]); - // It gets lazily built when a static method is called. - // builder->build(); - bridgeState->registerRuntimeClass(builder, builder->nativeClass); - - return nullptr; -} - -const char* ObjCClassDecorator = R"( -globalThis.ObjCClass = function ObjCClass(...protocols) { - return function (target) { - if (target.ObjCProtocols) { - target.ObjCProtocols.push(...protocols); - } else { - target.ObjCProtocols = protocols; - } - - return globalThis.NativeClass(target); - }; -}; -)"; - -void setupObjCClassDecorator(napi_env env) { - napi_value global, script; - napi_get_global(env, &global); - napi_create_string_utf8(env, ObjCClassDecorator, NAPI_AUTO_LENGTH, &script); - napi_run_script(env, script, &global); -} - -char class_name[256]; - -// Get a Bridged Class by metadata offset, creating it if it doesn't exist. -// This is used to cache ObjCClass instances. -ObjCClass* ObjCBridgeState::getClass(napi_env env, MDSectionOffset offset) { - auto find = this->classes[offset]; - if (find != nullptr) { - return find; - } - - auto cls = new ObjCClass(env, offset); - this->classes[offset] = cls; - - return cls; -} - -NAPI_FUNCTION(import) { - NAPI_CALLBACK_BEGIN(1) - - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], class_name, 256, nullptr)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - std::string name = class_name; - - if (!name.starts_with("/")) { - name = "/System/Library/Frameworks/" + name + ".framework"; - } - - NSBundle* bundle = [NSBundle bundleWithPath:[NSString stringWithUTF8String:name.c_str()]]; - if (bundle == nil) { - NSLog(@"Could not find bundle: %s", name.c_str()); - return nullptr; - } - - bool loaded = [bundle load]; - - if (!loaded) { - NSLog(@"Could not load bundle: %s", name.c_str()); - return nullptr; - } - - return nullptr; -} - -NAPI_FUNCTION(classGetter) { - void* data; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); - MDSectionOffset offset = (MDSectionOffset)((size_t)data); - auto bridgeState = ObjCBridgeState::InstanceData(env); - - auto cached = bridgeState->mdValueCache[offset]; - if (cached != nullptr) { - return get_ref_value(env, cached); - } - - // std::string name = bridgeState->metadata->getString(offset); - auto cls = bridgeState->getClass(env, offset); - - if (cls != nullptr) { - bridgeState->mdValueCache[offset] = cls->constructor; - } else { - return nullptr; - } - - return get_ref_value(env, cls->constructor); -} - -NAPI_FUNCTION(classSuperclassFallback) { - napi_value jsThis; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, nullptr); - - Class currentClass = nil; - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && jsThis != nullptr) { - bridgeState->tryResolveBridgedClassConstructor(env, jsThis, ¤tClass); - } - if (currentClass == nil) { - napi_unwrap(env, jsThis, (void**)¤tClass); - } - if (currentClass == nil) { - return nullptr; - } - - Class superClass = class_getSuperclass(currentClass); - if (superClass == nil) { - return nullptr; - } - - auto find = bridgeState->classesByPointer.find(superClass); - if (find != bridgeState->classesByPointer.end()) { - return get_ref_value(env, find->second->constructor); - } - - auto mdClass = bridgeState->mdClassesByPointer.find(superClass); - if (mdClass != bridgeState->mdClassesByPointer.end()) { - auto bridgedClass = bridgeState->getClass(env, mdClass->second); - return get_ref_value(env, bridgedClass->constructor); - } - - const char* runtimeName = class_getName(superClass); - if (runtimeName != nullptr && runtimeName[0] != '\0') { - napi_value global = nullptr; - napi_value constructor = nullptr; - bool hasGlobal = false; - napi_get_global(env, &global); - if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && - napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && - constructor != nullptr) { - return constructor; - } - } - - return bridgeState->getObject(env, (id)superClass, kUnownedObject, 0, nullptr); -} - -NAPI_FUNCTION(classHasInstance) { - size_t argc = 1; - napi_value argv[1] = {nullptr}; - napi_value jsThis = nullptr; - napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, nullptr); - - Class expectedClass = nil; - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && jsThis != nullptr) { - bridgeState->tryResolveBridgedClassConstructor(env, jsThis, &expectedClass); - } - if (expectedClass == nil) { - napi_unwrap(env, jsThis, (void**)&expectedClass); - } - - bool isInstance = false; - if (expectedClass != nil && argc > 0 && argv[0] != nullptr) { - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, argv[0], &valueType) == napi_ok && - (valueType == napi_object || valueType == napi_function)) { - id instance = nil; - napi_status unwrapStatus = napi_invalid_arg; - if (Pointer::isInstance(env, argv[0])) { - Pointer* pointer = Pointer::unwrap(env, argv[0]); - instance = pointer != nullptr ? static_cast(pointer->data) : nil; - unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; - } else if (Reference::isInstance(env, argv[0])) { - Reference* reference = Reference::unwrap(env, argv[0]); - instance = reference != nullptr ? static_cast(reference->data) : nil; - unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; - } else { - unwrapStatus = napi_unwrap(env, argv[0], (void**)&instance); - } - - if (unwrapStatus != napi_ok || instance == nil) { - napi_value nativePointer = nullptr; - if (napi_get_named_property(env, argv[0], "__ns_native_ptr", &nativePointer) == napi_ok && - Pointer::isInstance(env, nativePointer)) { - Pointer* pointer = Pointer::unwrap(env, nativePointer); - instance = pointer != nullptr ? static_cast(pointer->data) : nil; - } - } - - if (instance != nil) { - Class currentClass = object_getClass(instance); - while (currentClass != nil) { - if (currentClass == expectedClass) { - isInstance = true; - break; - } - currentClass = class_getSuperclass(currentClass); - } - } - } - } - - napi_value result = nullptr; - napi_get_boolean(env, isInstance, &result); - return result; -} - -NAPI_FUNCTION(BridgedConstructor) { - NAPI_CALLBACK_BEGIN(16) - - napi_value newTarget = nullptr; - napi_get_new_target(env, cbinfo, &newTarget); - - napi_valuetype thisType = napi_undefined; - if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || - (thisType != napi_object && thisType != napi_function)) { - napi_create_object(env, &jsThis); - - napi_value prototypeOwner = newTarget; - if (prototypeOwner == nullptr || napi_typeof(env, prototypeOwner, &thisType) != napi_ok || - (thisType != napi_function && thisType != napi_object)) { - prototypeOwner = nullptr; - } - - if (prototypeOwner != nullptr) { - napi_value prototype = nullptr; - if (napi_get_named_property(env, prototypeOwner, "prototype", &prototype) == napi_ok && - prototype != nullptr) { - napi_value global = nullptr; - napi_value objectCtor = nullptr; - napi_value setPrototypeOf = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - napi_value setPrototypeArgs[2] = {jsThis, prototype}; - napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); - } - } - } - - napi_valuetype jsType = napi_undefined; - if (argc > 0) { - napi_typeof(env, argv[0], &jsType); - } - - id object = nil; - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - auto ensureWrappedThis = [&](id nativeObject) { - if (jsThis == nullptr || nativeObject == nil) { - return; - } - - if (newTarget != nullptr) { - // Fresh constructor receivers should not need an unwrap probe here. - // On V8, reentrant constructor calls from ObjC->JS block callbacks can - // stall inside napi_unwrap() on that receiver. - napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); - return; - } - - void* existingWrapped = nullptr; - napi_status unwrapStatus = napi_unwrap(env, jsThis, &existingWrapped); - if (unwrapStatus == napi_ok && existingWrapped != nullptr) { - return; - } - - napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); - }; - - Class cls = (Class)data; - - napi_value constructor = newTarget; - if (constructor == nullptr && jsThis != nullptr) { - napi_get_named_property(env, jsThis, "constructor", &constructor); - } - - if (constructor != nullptr) { - Class newTargetCls = nil; - if (!(bridgeState != nullptr && - bridgeState->tryResolveBridgedClassConstructor(env, constructor, &newTargetCls))) { - napi_unwrap(env, constructor, (void**)&newTargetCls); - } - - if (newTargetCls != nil) { - cls = newTargetCls; - } - } - - if (jsType == napi_external) { - return jsThis; - } else { - // Backward compatibility: allow `new Class(pointer)` to wrap an existing - // native instance instead of running initializer resolution. - if (argc == 1 && argv[0] != nullptr) { - void* rawPointer = nullptr; - if (tryGetInteropPointerArg(env, argv[0], &rawPointer) && rawPointer != nullptr) { - if (napi_value cached = bridgeState->getCachedHandleObject(env, rawPointer); - cached != nullptr) { - return cached; - } - - object = (id)rawPointer; - if (napi_value existing = bridgeState->findCachedObjectWrapper(env, object); - existing != nullptr) { - return existing; - } - - ensureWrappedThis(object); - jsThis = bridgeState->proxyNativeObject(env, jsThis, object); - napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); - return jsThis; - } - } - - bool supercall = class_conformsToProtocol(cls, @protocol(ObjCBridgeClassBuilderProtocol)); - - if (supercall) { - ClassBuilder* builder = (ClassBuilder*)bridgeState->classesByPointer[cls]; - if (!builder->isFinal) builder->build(); - } - - // Allocate first, then run initializer resolution through the bridged - // JS "init" method so constructor arguments participate in selector - // matching (including Swift-style token objects). - object = [cls alloc]; - ensureWrappedThis(object); - jsThis = bridgeState->proxyNativeObject(env, jsThis, object); - } - - napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); - - napi_value initMethod; - napi_status initStatus = napi_get_named_property(env, jsThis, "init", &initMethod); - if (initStatus != napi_ok || initMethod == nullptr) { - return jsThis; - } - - napi_value initResult; - initStatus = napi_call_function(env, jsThis, initMethod, argc, argv, &initResult); - if (initStatus != napi_ok) { - return nullptr; - } - - return initResult != nullptr ? initResult : jsThis; -} - -// Used to display the description of a native object in console.log. -// It's just implementation of nodejs.util.inspect.custom. -NAPI_FUNCTION(CustomInspect) { - napi_value jsThis; - void* data; - size_t argc = 0; - - napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); - - id self = nil; - napi_unwrap(env, jsThis, (void**)&self); - - if (self == nil) { - napi_value result; - napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); - return result; - } - - auto description = [self description]; - auto descriptionString = [description UTF8String]; - - napi_value result; - napi_create_string_utf8(env, descriptionString, NAPI_AUTO_LENGTH, &result); - - return result; -} - -// Used for Symbol.dispose (using statement support). -NAPI_FUNCTION(releaseObject) { - napi_value jsThis; - void* data; - napi_get_cb_info(env, cbinfo, nil, nil, &jsThis, &data); - id self; - napi_unwrap(env, jsThis, (void**)&self); - auto bridgeState = ObjCBridgeState::InstanceData(env); - bridgeState->unregisterObject(self); - return nullptr; -} - -// Implemented in JS to minimize calls into native. Fast enumeration -// makes use of buffers, so we only fill up the buffer once via -// native call and only do it again if needed via _fillStack. -static const char* FastEnumerationIteratorFactorySource = R"( - (function () { - return { - stack: new Array(16), - stacklen: -1, - stackptr: -1, - done: false, - - next() { - if (this.stackptr < 0 && !this.done) { - this.stacklen = this._fillStack(this.stack); - if (this.stacklen == 0) { - this.done = true; - this.stackptr = -1; - } else { - this.stackptr = 0; - } - } - - if (this.done) { - return { done: true }; - } - - const result = { value: this.stack[this.stackptr++], done: false }; - if (this.stackptr >= this.stacklen) { - this.stackptr = -1; - } - - return result; - }, - }; - }) -)"; - -void initFastEnumeratorIteratorFactory(napi_env env, ObjCBridgeState* bridgeState) { - napi_value result, script; - napi_create_string_utf8(env, FastEnumerationIteratorFactorySource, NAPI_AUTO_LENGTH, &script); - napi_run_script(env, script, &result); - bridgeState->createFastEnumeratorIterator = make_ref(env, result); -} - -class FastEnumerationIterator { - public: - FastEnumerationIterator(id collection) : collection(collection) {} - - static void finalize(napi_env env, void* data, void* hint) { - FastEnumerationIterator* iterator = (FastEnumerationIterator*)data; - delete iterator; - } - - static napi_value fillStack(napi_env env, napi_callback_info cbinfo) { - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - - napi_value jsThis; - void* data; - size_t argc = 1; - napi_value stackArray; - - napi_get_cb_info(env, cbinfo, &argc, &stackArray, &jsThis, &data); - - FastEnumerationIterator* self = nil; - napi_unwrap(env, jsThis, (void**)&self); - - NSUInteger count = [self->collection countByEnumeratingWithState:&self->state - objects:self->stackbuf - count:16]; - - for (uint32_t index = 0; index < static_cast(count); index++) { - id obj = self->state.itemsPtr[index]; - napi_value jsObj = bridgeState->getObject(env, obj); - napi_set_element(env, stackArray, index, jsObj); - } - - napi_value result; - napi_create_uint32(env, static_cast(count), &result); - - return result; - } - - napi_value toJS(napi_env env) { - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - - napi_value createIterator = get_ref_value(env, bridgeState->createFastEnumeratorIterator); - - napi_value result; - napi_call_function(env, createIterator, createIterator, 0, nullptr, &result); - - napi_property_descriptor fillStack = { - .utf8name = "_fillStack", - .name = nil, - .method = FastEnumerationIterator::fillStack, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }; - - napi_define_properties(env, result, 1, &fillStack); - - napi_ref ref; - napi_wrap(env, result, this, FastEnumerationIterator::finalize, nullptr, &ref); - - return result; - } - - id collection; - NSFastEnumerationState state = {0}; - id stackbuf[16]; - BOOL firstLoop = YES; - long mutationsPtrValue; -}; - -NAPI_FUNCTION(fastEnumeration) { - napi_value jsThis; - void* data; - size_t argc = 0; - - napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); - - id self = nil; - napi_unwrap(env, jsThis, (void**)&self); - - if (self == nil) { - napi_value result; - napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); - return result; - } - - if (![self conformsToProtocol:@protocol(NSFastEnumeration)]) { - napi_throw_error(env, nil, "Object does not conform to NSFastEnumeration"); - return nullptr; - } - - auto iterator = new FastEnumerationIterator((id)self); - return iterator->toJS(env); -} - -std::string NativeObjectName = "NativeObject"; - -// Bridge an Objective-C class to JavaScript on the fly. Runtime introspection -// is used to determine the class's properties and methods. -// In an overview, we define two versions of same class. One is the "normal" -// one, and the other is the "supercall" one. The supercall one is used to call -// superclasses' methods. The normal one is used to call the class's own -// methods. The supercall one is used automatically when the normal one is -// extended by a JS class. -// Every Bridged Class extends the NativeObject class. - -void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value constructor, - ObjCProtocol* protocol, ObjCClass* cls) { - ObjCClassMember::defineMembers(env, members, protocol->membersOffset, constructor, cls); - for (auto protocol : protocol->protocols) { - defineProtocolMembers(env, members, constructor, protocol, cls); - } -} - -ObjCClass::ObjCClass(napi_env env, MDSectionOffset offset) { - NAPI_PREAMBLE - - this->env = env; - - metadataOffset = offset; - - bridgeState = ObjCBridgeState::InstanceData(env); - bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; - - bool isNativeObject = offset == MD_SECTION_OFFSET_NULL; - - std::vector protocolOffsets; - - MDSectionOffset superClassOffset = MD_SECTION_OFFSET_NULL; - bool hasMembers = false; - std::string jsConstructorName; - if (isNativeObject) { - name = NativeObjectName; - jsConstructorName = name; - nativeClass = nil; - } else { - auto nameOffset = bridgeState->metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - auto runtimeNameOffset = bridgeState->metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - name = bridgeState->metadata->resolveString(nameOffset); - const char* runtimeName = name.c_str(); - if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { - runtimeName = bridgeState->metadata->resolveString(runtimeNameOffset); - } - jsConstructorName = runtimeName; - if (jsConstructorName.empty()) { - jsConstructorName = name; - } - nativeClass = objc_getClass(runtimeName); - while (hasProtocols) { - auto protocolOffset = bridgeState->metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; - protocolOffset &= ~mdSectionOffsetNext; - if (protocolOffset != MD_SECTION_OFFSET_NULL) protocolOffsets.push_back(protocolOffset); - } - superClassOffset = bridgeState->metadata->getOffset(offset); - hasMembers = (superClassOffset & mdSectionOffsetNext) != 0; - superClassOffset &= ~mdSectionOffsetNext; - offset += sizeof(MDSectionOffset); - } - - napi_value constructor, prototype; - - napi_define_class(env, jsConstructorName.c_str(), jsConstructorName.length(), - JS_BridgedConstructor, (void*)nativeClass, 0, nil, &constructor); - - if (nativeClass != nil) { - napi_wrap(env, constructor, (void*)nativeClass, nil, nil, nil); - bridgeState->registerRuntimeClass(this, nativeClass); - } - - napi_get_named_property(env, constructor, "prototype", &prototype); - - napi_value superConstructor = nil, superPrototype = nil; - - // If the class requested isn't NativeObject - the class which every bridged - // class extends - we need to find the super class and inherit from it, if it - // exists. - if (!isNativeObject) { - if (superClassOffset != MD_SECTION_OFFSET_NULL) { - superClassOffset += bridgeState->metadata->classesOffset; - } - - // If class offset is 0, it means that the class doesn't have a super class. - // But we just inherit NativeObject class in that case. - superclass = bridgeState->getClass(env, superClassOffset); - if (superclass != nil) { - superConstructor = get_ref_value(env, superclass->constructor); - superPrototype = get_ref_value(env, superclass->prototype); - napi_inherits(env, constructor, superConstructor); - } - - for (auto protocolOffset : protocolOffsets) { - auto protocol = - bridgeState->getProtocol(env, protocolOffset + bridgeState->metadata->protocolsOffset); - if (protocol == nil) continue; - defineProtocolMembers(env, members, constructor, protocol, this); - } - } else { - superclass = nullptr; - } - - napi_value constructorNameValue = nullptr; - napi_create_string_utf8(env, jsConstructorName.c_str(), jsConstructorName.length(), - &constructorNameValue); - napi_property_descriptor constructorNameProp = { - .utf8name = "name", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = constructorNameValue, - .attributes = napi_default, - .data = nullptr, - }; - napi_define_properties(env, constructor, 1, &constructorNameProp); - - this->constructor = make_ref(env, constructor); - this->prototype = make_ref(env, prototype); - - if (isNativeObject) { - napi_value global, Symbol, SymbolDispose, SymbolIterator; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Symbol", &Symbol); - napi_get_named_property(env, Symbol, "iterator", &SymbolIterator); - napi_get_named_property(env, Symbol, "dispose", &SymbolDispose); - napi_valuetype type; - napi_typeof(env, SymbolDispose, &type); - - napi_value sizeofValue; - napi_create_int32(env, sizeof(id), &sizeofValue); - - napi_property_descriptor properties[] = { - { - .utf8name = nil, - .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), - .method = JS_CustomInspect, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }, - { - .utf8name = "toString", - .name = nil, - .method = JS_CustomInspect, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }, - { - .utf8name = nil, - .name = jsSymbolFor(env, "sizeof"), - .method = nil, - .getter = nil, - .setter = nil, - .value = sizeofValue, - .attributes = napi_enumerable, - .data = nil, - }, - { - .utf8name = nil, - .name = SymbolIterator, - .method = JS_fastEnumeration, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = napi_enumerable, - .data = nil, - }}; - - napi_define_properties(env, prototype, 4, properties); - - napi_define_properties(env, constructor, 1, &properties[2]); - - bool hasSuperclass = false; - napi_value superclassKey = nullptr; - napi_create_string_utf8(env, "superclass", NAPI_AUTO_LENGTH, &superclassKey); - napi_has_own_property(env, constructor, superclassKey, &hasSuperclass); - if (!hasSuperclass) { - napi_property_descriptor superclassProperty = { - .utf8name = "superclass", - .name = nil, - .method = JS_classSuperclassFallback, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = - (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &superclassProperty); - } - - if (type == napi_symbol) { - properties[0].name = SymbolDispose; - properties[0].method = JS_releaseObject; - - napi_define_properties(env, prototype, 1, properties); - } - - return; - } - - // Add the 'extend' static method to all native classes (not NativeObject) - if (!isNativeObject) { - napi_value extendMethod; - napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr, - &extendMethod); - napi_set_named_property(env, constructor, "extend", extendMethod); - } - - if (!hasMembers) return; - - ObjCClassMember::defineMembers(env, members, offset, constructor, this); - - auto hasOwnNamedProperty = [&](napi_value object, const char* propertyName) { - napi_value key = nullptr; - napi_create_string_utf8(env, propertyName, NAPI_AUTO_LENGTH, &key); - bool hasOwn = false; - napi_has_own_property(env, object, key, &hasOwn); - return hasOwn; - }; - - if (!hasOwnNamedProperty(constructor, "name")) { - napi_value classNameValue = nullptr; - napi_create_string_utf8(env, jsConstructorName.c_str(), NAPI_AUTO_LENGTH, &classNameValue); - napi_property_descriptor nameProperty = { - .utf8name = "name", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = classNameValue, - .attributes = (napi_property_attributes)(napi_configurable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &nameProperty); - } - - if (!hasOwnNamedProperty(constructor, "length")) { - napi_value zeroValue = nullptr; - napi_create_int32(env, 0, &zeroValue); - napi_property_descriptor lengthProperty = { - .utf8name = "length", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = zeroValue, - .attributes = (napi_property_attributes)(napi_configurable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &lengthProperty); - } - - if (!hasOwnNamedProperty(constructor, "alloc")) { - napi_property_descriptor allocProperty = { - .utf8name = "alloc", - .name = nil, - .method = JS_NSObject_alloc, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = - (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &allocProperty); - } - - if (!hasOwnNamedProperty(constructor, "arguments") || - !hasOwnNamedProperty(constructor, "caller")) { - napi_value undefinedValue = nullptr; - napi_get_undefined(env, &undefinedValue); - napi_property_descriptor slots[] = { - { - .utf8name = "arguments", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = undefinedValue, - .attributes = (napi_property_attributes)(napi_configurable | napi_writable), - .data = nil, - }, - { - .utf8name = "caller", - .name = nil, - .method = nil, - .getter = nil, - .setter = nil, - .value = undefinedValue, - .attributes = (napi_property_attributes)(napi_configurable | napi_writable), - .data = nil, - }, - }; - napi_define_properties(env, constructor, 2, slots); - } - - napi_value global = nullptr; - napi_value symbolCtor = nullptr; - napi_value hasInstanceSymbol = nullptr; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Symbol", &symbolCtor); - napi_get_named_property(env, symbolCtor, "hasInstance", &hasInstanceSymbol); - bool hasOwnHasInstance = false; - napi_has_own_property(env, constructor, hasInstanceSymbol, &hasOwnHasInstance); - if (!hasOwnHasInstance) { - napi_property_descriptor hasInstanceProperty = { - .utf8name = nil, - .name = hasInstanceSymbol, - .method = JS_classHasInstance, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = (napi_property_attributes)(napi_configurable), - .data = nil, - }; - napi_define_properties(env, constructor, 1, &hasInstanceProperty); - } - - if (!hasOwnNamedProperty(prototype, "toString")) { - napi_property_descriptor toStringProperty = { - .utf8name = "toString", - .name = nil, - .method = JS_CustomInspect, - .getter = nil, - .setter = nil, - .value = nil, - .attributes = - (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), - .data = nil, - }; - napi_define_properties(env, prototype, 1, &toStringProperty); - } -} - -ObjCClass::~ObjCClass() { - if (env != nullptr && - (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { - DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, constructor); - DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, prototype); - } - constructor = nullptr; - prototype = nullptr; - bridgeState = nullptr; - bridgeStateToken = 0; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/Closure.h b/NativeScript/ffi/napi/Closure.h deleted file mode 100644 index 7ceafd49c..000000000 --- a/NativeScript/ffi/napi/Closure.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef CLOSURE_H -#define CLOSURE_H - -#include - -#include -#include -#include - -#include "MetadataReader.h" -#include "TypeConv.h" -#include "ffi.h" -#include "node_api_util.h" -#include "objc/runtime.h" - -namespace nativescript { - -class ObjCBridgeState; - -class Closure { - public: - static void callBlockFromMainThread(napi_env env, napi_value js_cb, - void* context, void* data); - static void destroyOnOwningThread(Closure* closure); - - Closure(napi_env env, std::string typeEncoding, bool isBlock, bool isMethod = false); - Closure(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, - bool isBlock = false, std::string* encoding = nullptr, - bool isMethod = false, bool isGetter = false, bool isSetter = false); - - ~Closure(); - void retain(); - void release(); - - napi_env env = nullptr; - ObjCBridgeState* bridgeState = nullptr; - uint64_t bridgeStateToken = 0; - napi_ref thisConstructor; - napi_ref func = nullptr; - bool isGetter = false; - bool isSetter = false; - std::string propertyName; - SEL selector = nullptr; - napi_threadsafe_function tsfn = nullptr; - - std::thread::id jsThreadId = std::this_thread::get_id(); - CFRunLoopRef jsRunLoop = CFRunLoopGetCurrent(); - std::atomic retainCount{1}; - - ffi_cif cif; - ffi_closure* closure; - void* fnptr; - ffi_type** atypes = nullptr; // Track malloc'd atypes array - - std::shared_ptr returnType; - std::vector> argTypes; -}; - -} // namespace nativescript - -#endif /* CLOSURE_H */ diff --git a/NativeScript/ffi/napi/Enum.mm b/NativeScript/ffi/napi/Enum.mm deleted file mode 100644 index da6ce6add..000000000 --- a/NativeScript/ffi/napi/Enum.mm +++ /dev/null @@ -1,207 +0,0 @@ -#include "Enum.h" -#import -#include -#include -#include -#include -#include "ObjCBridge.h" - -namespace nativescript { - -namespace { -inline void defineConstantIfMissing(napi_env env, napi_value object, const std::string& name, - napi_value value, - napi_property_attributes attributes = napi_enumerable) { - bool hasProperty = false; - napi_has_named_property(env, object, name.c_str(), &hasProperty); - if (hasProperty) { - return; - } - - napi_property_descriptor prop = { - .utf8name = name.c_str(), - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = value, - .attributes = attributes, - .data = nullptr, - }; - napi_define_properties(env, object, 1, &prop); -} - -inline bool startsWith(const std::string& value, const std::string& prefix) { - return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0; -} - -inline std::string stripEnumSuffix(const std::string& enumName) { - static const std::vector suffixes = { - "Options", "Option", "Enums", "Enum", "Result", "Direction", "Orientation", - "Style", "Mask", "Type", "Status", "Modes", "Mode", "s"}; - - for (const auto& suffix : suffixes) { - if (enumName.size() > suffix.size() && - enumName.compare(enumName.size() - suffix.size(), suffix.size(), suffix) == 0) { - return enumName.substr(0, enumName.size() - suffix.size()); - } - } - - return enumName; -} - -inline bool isNSComparisonResultOrderingName(const std::string& enumName, - const std::string& member) { - if (enumName != "NSComparisonResult") { - return false; - } - return member == "Ascending" || member == "Same" || member == "Descending"; -} -} // namespace - -void ObjCBridgeState::registerEnumGlobals(napi_env env, napi_value global) { - MDSectionOffset offset = metadata->enumsOffset; - while (offset < metadata->signaturesOffset) { - MDSectionOffset originalOffset = offset; - auto name = metadata->getString(offset); - offset += sizeof(MDSectionOffset); - - bool next = true; - while (next) { - auto nameOffset = metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto memberName = metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - int64_t value = metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - napi_value member; - napi_create_int64(env, value, &member); - defineConstantIfMissing(env, global, memberName, member, - (napi_property_attributes)(napi_enumerable | napi_configurable)); - } - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = JS_enumGetter, - .setter = nullptr, - .value = nullptr, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = (void*)((size_t)originalOffset), - }; - napi_define_properties(env, global, 1, &prop); - } -} - -NAPI_FUNCTION(enumGetter) { - void* data; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); - MDSectionOffset offset = (MDSectionOffset)((size_t)data); - MDSectionOffset originalOffset = offset; - auto bridgeState = ObjCBridgeState::InstanceData(env); - - auto cached = bridgeState->mdValueCache[offset]; - if (cached != nullptr) { - return get_ref_value(env, cached); - } - - napi_value result; - napi_create_object(env, &result); - - // enum name - auto enumName = bridgeState->metadata->getString(offset); - std::string enumNameStr = enumName; - offset += sizeof(MDSectionOffset); - - std::string strippedPrefix = stripEnumSuffix(enumNameStr); - - napi_value global; - napi_get_global(env, &global); - - bool next = true; - while (next) { - auto nameOffset = bridgeState->metadata->getOffset(offset); - next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - auto name = bridgeState->metadata->resolveString(nameOffset); - offset += sizeof(MDSectionOffset); - int64_t value = bridgeState->metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - napi_value member; - napi_create_int64(env, value, &member); - - std::string memberName = name; - std::vector aliases; - aliases.push_back(memberName); - - if (!strippedPrefix.empty() && startsWith(memberName, strippedPrefix) && - memberName.size() > strippedPrefix.size()) { - aliases.push_back(memberName.substr(strippedPrefix.size())); - } else if (!strippedPrefix.empty() && !startsWith(memberName, strippedPrefix)) { - aliases.push_back(strippedPrefix + memberName); - } - - if (startsWith(enumNameStr, "NS") && !startsWith(memberName, "NS")) { - aliases.push_back(std::string("NS") + memberName); - } - - if (enumNameStr == "NSStringCompareOptions" && !memberName.ends_with("Search")) { - aliases.push_back(memberName + "Search"); - aliases.push_back(std::string("NS") + memberName + "Search"); - } - - if (!startsWith(memberName, "k")) { - aliases.push_back(std::string("k") + enumNameStr + memberName); - } - - if (isNSComparisonResultOrderingName(enumNameStr, memberName)) { - aliases.push_back(std::string("Ordered") + memberName); - aliases.push_back(std::string("NSOrdered") + memberName); - } - - std::vector uniqueAliases; - uniqueAliases.reserve(aliases.size()); - std::unordered_set seenAliases; - for (const auto& alias : aliases) { - if (seenAliases.insert(alias).second) { - uniqueAliases.push_back(alias); - } - } - - for (const auto& alias : uniqueAliases) { - defineConstantIfMissing(env, result, alias, member); - defineConstantIfMissing(env, global, alias, member, - (napi_property_attributes)(napi_enumerable | napi_configurable)); - } - - std::string reverseCanonical = uniqueAliases.size() > 1 ? uniqueAliases[1] : memberName; - - // reverse mapping enum[value] -> canonical member name - char valueKey[32]; - snprintf(valueKey, sizeof(valueKey), "%lld", (long long)value); - bool hasReverseMapping = false; - napi_has_named_property(env, result, valueKey, &hasReverseMapping); - if (!hasReverseMapping) { - napi_value reverseName; - napi_create_string_utf8(env, reverseCanonical.c_str(), NAPI_AUTO_LENGTH, &reverseName); - napi_property_descriptor reverseProp = { - .utf8name = valueKey, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = reverseName, - .attributes = napi_enumerable, - .data = nullptr, - }; - napi_define_properties(env, result, 1, &reverseProp); - } - } - - bridgeState->mdValueCache[originalOffset] = make_ref(env, result); - - return result; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/ObjCBridge.mm b/NativeScript/ffi/napi/ObjCBridge.mm deleted file mode 100644 index 0c7ebd76a..000000000 --- a/NativeScript/ffi/napi/ObjCBridge.mm +++ /dev/null @@ -1,1142 +0,0 @@ -#include "ObjCBridge.h" -#include "AutoreleasePool.h" -#include "Block.h" -#include "Class.h" -#include "ClassMember.h" -#include "Enum.h" -#include "InlineFunctions.h" -#include "Interop.h" -#include "Metadata.h" -#include "MetadataReader.h" -#include "NativeScript.h" -#include "Object.h" -#include "ObjectRef.h" -#include "Struct.h" -#include "TypeConv.h" -#include "Util.h" -#include "Variable.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef EMBED_METADATA_SIZE -const unsigned char __attribute__((section("__objc_metadata,__objc_metadata"))) -#if defined(__aarch64__) -embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderARM"; -#else -embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderX86"; -#endif -#endif - -namespace nativescript { -namespace { -std::mutex gLiveBridgeStatesMutex; -std::unordered_map gLiveBridgeStates; -std::atomic gNextBridgeStateToken{1}; -constexpr const char* kNativePointerProperty = "__ns_native_ptr"; - -inline void deleteReferenceNow(napi_env env, napi_ref ref, bool unrefFirst) { - if (env == nullptr || ref == nullptr) { - return; - } - - if (unrefFirst) { - uint32_t remaining = 0; - napi_reference_unref(env, ref, &remaining); - } - - napi_delete_reference(env, ref); -} - -inline void deleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, - uint64_t bridgeStateToken, napi_ref ref, - bool unrefFirst) { - if (env == nullptr || ref == nullptr) { - return; - } - - if (bridgeState == nullptr) { - deleteReferenceNow(env, ref, unrefFirst); - return; - } - - if (!IsBridgeStateLive(bridgeState, bridgeStateToken)) { - return; - } - - if (bridgeState->jsThreadId == std::this_thread::get_id()) { -#if !defined(TARGET_ENGINE_QUICKJS) - deleteReferenceNow(env, ref, unrefFirst); - return; -#endif - } - - CFRunLoopRef runLoop = bridgeState->jsRunLoop; - if (runLoop == nullptr) { - runLoop = CFRunLoopGetMain(); - } - - if (runLoop == nullptr) { - if (bridgeState->jsThreadId == std::this_thread::get_id()) { - deleteReferenceNow(env, ref, unrefFirst); - } - return; - } - - CFRetain(runLoop); - CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ - if (IsBridgeStateLive(bridgeState, bridgeStateToken)) { - deleteReferenceNow(env, ref, unrefFirst); - } - CFRelease(runLoop); - }); - CFRunLoopWakeUp(runLoop); -} - -uint64_t RegisterBridgeState(const ObjCBridgeState* bridgeState) { - if (bridgeState == nullptr) { - return 0; - } - - uint64_t token = gNextBridgeStateToken.fetch_add(1, std::memory_order_relaxed); - std::lock_guard lock(gLiveBridgeStatesMutex); - gLiveBridgeStates[bridgeState] = token; - return token; -} - -void UnregisterBridgeState(const ObjCBridgeState* bridgeState) { - if (bridgeState == nullptr) { - return; - } - - std::lock_guard lock(gLiveBridgeStatesMutex); - gLiveBridgeStates.erase(bridgeState); -} -} // namespace - -bool IsBridgeStateLive(const ObjCBridgeState* bridgeState, uint64_t token) noexcept { - if (bridgeState == nullptr || token == 0) { - return false; - } - - std::lock_guard lock(gLiveBridgeStatesMutex); - auto find = gLiveBridgeStates.find(bridgeState); - return find != gLiveBridgeStates.end() && find->second == token; -} - -void DeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, - uint64_t bridgeStateToken, napi_ref ref) { - deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, false); -} - -void ReleaseAndDeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, - uint64_t bridgeStateToken, napi_ref ref) { - deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, true); -} - -bool PostFinalizer(napi_env env, napi_finalize finalize_cb, void* finalize_data, - void* finalize_hint) { - if (env == nullptr || finalize_cb == nullptr) { - return false; - } - - ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && bridgeState->jsThreadId == std::this_thread::get_id()) { -#if !defined(TARGET_ENGINE_QUICKJS) - finalize_cb(env, finalize_data, finalize_hint); - return true; -#endif - } - - CFRunLoopRef runLoop = bridgeState != nullptr ? bridgeState->jsRunLoop : CFRunLoopGetMain(); - if (runLoop == nullptr) { - return false; - } - - if (bridgeState == nullptr && [NSThread isMainThread]) { -#if !defined(TARGET_ENGINE_QUICKJS) - finalize_cb(env, finalize_data, finalize_hint); - return true; -#endif - } - - CFRetain(runLoop); - CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ - finalize_cb(env, finalize_data, finalize_hint); - CFRelease(runLoop); - }); - CFRunLoopWakeUp(runLoop); - return true; -} - -void finalize_bridge_data(napi_env env, void* data, void* hint) { - auto bridgeState = (ObjCBridgeState*)data; - delete bridgeState; -} - -MDMetadataReader* loadMetadataFromFile(const char* metadata_path) { - if (metadata_path == nullptr) { - metadata_path = "metadata.nsmd"; - } - - auto f = fopen(metadata_path == nullptr ? "metadata.nsmd" : metadata_path, "r"); - if (f == nullptr) { - fprintf(stderr, "metadata.nsmd not found\n"); - exit(1); - } - fseek(f, 0, SEEK_END); - auto size = ftell(f); - fseek(f, 0, SEEK_SET); - auto buffer = (uint8_t*)malloc(size); - fread(buffer, 1, size, f); - fclose(f); - return new MDMetadataReader(buffer); -} - -inline bool hasNamedProperty(napi_env env, napi_value object, const char* name) { - bool hasProperty = false; - napi_has_named_property(env, object, name, &hasProperty); - return hasProperty; -} - -inline bool isFunctionValue(napi_env env, napi_value value) { - if (value == nullptr) { - return false; - } - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - if (valueType == napi_function) { - return true; - } - - if (valueType != napi_object) { - return false; - } - - napi_value instance = nullptr; - napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); - if (status == napi_ok) { - return true; - } - - bool hasPendingException = false; - if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { - napi_value exception = nullptr; - napi_get_and_clear_last_exception(env, &exception); - } - return false; -} - -inline void clearPendingException(napi_env env) { - bool hasPendingException = false; - if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { - napi_value exception = nullptr; - napi_get_and_clear_last_exception(env, &exception); - } -} - -inline bool isConstructableValue(napi_env env, napi_value value) { - napi_value instance = nullptr; - napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); - if (status == napi_ok) { - return true; - } - - clearPendingException(env); - return false; -} - -inline bool hasConstructableNamedProperty(napi_env env, napi_value global, const char* name) { - if (!hasNamedProperty(env, global, name)) { - return false; - } - - napi_value value = nullptr; - if (napi_get_named_property(env, global, name, &value) != napi_ok || value == nullptr) { - clearPendingException(env); - return false; - } - - return isConstructableValue(env, value); -} - -inline void defineGlobalValue(napi_env env, napi_value global, const char* name, napi_value value) { - if (name == nullptr || value == nullptr) { - return; - } - - if (napi_set_named_property(env, global, name, value) == napi_ok) { - return; - } - clearPendingException(env); - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = value, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = nullptr, - }; - if (napi_define_properties(env, global, 1, &prop) != napi_ok) { - clearPendingException(env); - } -} - -inline bool defineConstructableGlobalValue(napi_env env, napi_value global, const char* name, - napi_value value) { - if (!isConstructableValue(env, value)) { - return false; - } - - if (napi_set_named_property(env, global, name, value) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - - napi_property_descriptor prop = { - .utf8name = name, - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = value, - .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), - .data = nullptr, - }; - if (napi_define_properties(env, global, 1, &prop) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - - napi_value key = nullptr; - napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &key); - if (key != nullptr) { - bool deleted = false; - if (napi_delete_property(env, global, key, &deleted) == napi_ok && deleted) { - if (napi_define_properties(env, global, 1, &prop) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - if (napi_set_named_property(env, global, name, value) == napi_ok && - hasConstructableNamedProperty(env, global, name)) { - return true; - } - clearPendingException(env); - } else { - clearPendingException(env); - } - } - - return hasConstructableNamedProperty(env, global, name); -} - -inline std::string buildStructEncoding(StructInfo* info) { - if (info == nullptr || info->name == nullptr) { - return ""; - } - - std::string encoding = "{"; - encoding += info->name; - encoding += "="; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - return ""; - } - field.type->encode(&encoding); - } - encoding += "}"; - return encoding; -} - -inline void setTypeEncodingSymbol(napi_env env, napi_value value, const std::string& encoding) { - if (value == nullptr || encoding.empty()) { - return; - } - - napi_value typeSymbol = jsSymbolFor(env, "type"); - napi_value encodedValue = nullptr; - napi_create_string_utf8(env, encoding.c_str(), NAPI_AUTO_LENGTH, &encodedValue); - if (typeSymbol != nullptr && encodedValue != nullptr) { - napi_set_property(env, value, typeSymbol, encodedValue); - } -} - -inline void registerStructAlias(napi_env env, napi_value global, ObjCBridgeState* bridgeState, - const char* aliasName, - std::initializer_list candidates) { - if (bridgeState == nullptr || aliasName == nullptr) { - return; - } - - if (hasNamedProperty(env, global, aliasName)) { - napi_value existing = nullptr; - if (napi_get_named_property(env, global, aliasName, &existing) == napi_ok && - isFunctionValue(env, existing)) { - return; - } - } - - for (const char* candidate : candidates) { - if (candidate == nullptr || candidate[0] == '\0') { - continue; - } - - auto structIt = bridgeState->structOffsets.find(candidate); - if (structIt != bridgeState->structOffsets.end()) { - StructInfo* info = bridgeState->getStructInfo(env, structIt->second); - if (info != nullptr) { - napi_value cls = StructObject::getJSClass(env, info); - if (isFunctionValue(env, cls)) { - setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); - defineGlobalValue(env, global, aliasName, cls); - return; - } - } - } - - if (hasNamedProperty(env, global, candidate)) { - napi_value source = nullptr; - if (napi_get_named_property(env, global, candidate, &source) == napi_ok && - isFunctionValue(env, source)) { - defineGlobalValue(env, global, aliasName, source); - return; - } - } - } -} - -inline void ensureSyntheticCGPoint(napi_env env, napi_value global) { - if (hasConstructableNamedProperty(env, global, "CGPoint")) { - return; - } - - static StructInfo* syntheticInfo = nullptr; - if (syntheticInfo == nullptr) { - syntheticInfo = new StructInfo(); - syntheticInfo->name = strdup("CGPoint"); - syntheticInfo->size = sizeof(double) * 2; - syntheticInfo->jsClass = nullptr; - - const char* doubleEncodingX = "d"; - const char* doubleEncodingY = "d"; - - StructFieldInfo fieldX; - fieldX.name = strdup("x"); - fieldX.offset = 0; - fieldX.type = TypeConv::Make(env, &doubleEncodingX); - syntheticInfo->fields.push_back(fieldX); - - StructFieldInfo fieldY; - fieldY.name = strdup("y"); - fieldY.offset = sizeof(double); - fieldY.type = TypeConv::Make(env, &doubleEncodingY); - syntheticInfo->fields.push_back(fieldY); - } - - napi_value cls = StructObject::getJSClass(env, syntheticInfo); - if (!isFunctionValue(env, cls)) { - return; - } - - setTypeEncodingSymbol(env, cls, "{CGPoint=dd}"); - defineConstructableGlobalValue(env, global, "CGPoint", cls); -} - -inline void ensureConstructableStructAlias(napi_env env, napi_value global, - ObjCBridgeState* bridgeState, const char* aliasName, - std::initializer_list candidates) { - if (bridgeState == nullptr || aliasName == nullptr) { - return; - } - - if (hasConstructableNamedProperty(env, global, aliasName)) { - return; - } - - for (const char* candidate : candidates) { - if (candidate == nullptr || candidate[0] == '\0') { - continue; - } - - if (hasNamedProperty(env, global, candidate)) { - napi_value value = nullptr; - if (napi_get_named_property(env, global, candidate, &value) == napi_ok && - defineConstructableGlobalValue(env, global, aliasName, value)) { - return; - } - clearPendingException(env); - } - - auto structIt = bridgeState->structOffsets.find(candidate); - if (structIt != bridgeState->structOffsets.end()) { - StructInfo* info = bridgeState->getStructInfo(env, structIt->second); - if (info != nullptr) { - napi_value cls = StructObject::getJSClass(env, info); - if (defineConstructableGlobalValue(env, global, aliasName, cls)) { - setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); - return; - } - } - } - } -} - -inline void installMacUIColorCompatShim(napi_env env) { - const char* script = R"( - (function (globalObject) { - if (typeof globalObject.UIColor === "undefined" && - typeof globalObject.NSColor !== "undefined") { - globalObject.UIColor = globalObject.NSColor; - } - - const colorCtor = globalObject.UIColor || globalObject.NSColor; - if (!colorCtor || !colorCtor.prototype) { - return; - } - - if (typeof colorCtor.prototype.initWithRedGreenBlueAlpha === "function") { - return; - } - - colorCtor.prototype.initWithRedGreenBlueAlpha = function (red, green, blue, alpha) { - if (typeof this.initWithSRGBRedGreenBlueAlpha === "function") { - return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof this.initWithCalibratedRedGreenBlueAlpha === "function") { - return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === "function") { - return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === "function") { - return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - return this; - }; - })(globalThis); - )"; - - napi_value shim = nullptr; - napi_create_string_utf8(env, script, NAPI_AUTO_LENGTH, &shim); - if (shim != nullptr) { - napi_value result = nullptr; - napi_run_script(env, shim, &result); - } -} - -inline void* resolveSymbolPointer(ObjCBridgeState* bridgeState, const char* symbolName) { - if (bridgeState == nullptr || symbolName == nullptr || symbolName[0] == '\0') { - return nullptr; - } - - void* symbol = dlsym(bridgeState->self_dl, symbolName); - if (symbol == nullptr) { - symbol = dlsym(RTLD_DEFAULT, symbolName); - } - if (symbol == nullptr) { - std::string underscored = "_"; - underscored += symbolName; - symbol = dlsym(bridgeState->self_dl, underscored.c_str()); - if (symbol == nullptr) { - symbol = dlsym(RTLD_DEFAULT, underscored.c_str()); - } - } - - return symbol; -} - -inline bool unwrapCompatNativeHandle(napi_env env, napi_value value, void** out) { - if (value == nullptr || out == nullptr) { - return false; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *out = ptr != nullptr ? ptr->data : nullptr; - return ptr != nullptr; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *out = ref != nullptr ? ref->data : nullptr; - return ref != nullptr; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - if (valueType == napi_bigint) { - uint64_t raw = 0; - bool lossless = false; - if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { - return false; - } - *out = reinterpret_cast(static_cast(raw)); - return true; - } - - if (valueType == napi_external) { - return napi_get_value_external(env, value, out) == napi_ok; - } - - if (valueType != napi_object && valueType != napi_function) { - return false; - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, value, "__ns_native_ptr", &hasNativePointer) == napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, value, "__ns_native_ptr", &nativePointerValue) == napi_ok && - napi_get_value_external(env, nativePointerValue, out) == napi_ok && *out != nullptr) { - return true; - } - } - - return napi_unwrap(env, value, out) == napi_ok && *out != nullptr; -} - -inline napi_value createCompatDispatchQueueWrapper(napi_env env, dispatch_queue_t queue) { - if (queue == nullptr) { - napi_value nullValue = nullptr; - napi_get_null(env, &nullValue); - return nullValue; - } - - return Pointer::create(env, reinterpret_cast(queue)); -} - -inline napi_value compat_dispatch_get_global_queue(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2] = {nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - int64_t identifier = 0; - if (argc > 0) { - napi_valuetype identifierType = napi_undefined; - if (napi_typeof(env, argv[0], &identifierType) == napi_ok && identifierType == napi_bigint) { - bool lossless = false; - if (napi_get_value_bigint_int64(env, argv[0], &identifier, &lossless) != napi_ok) { - napi_throw_type_error(env, nullptr, - "dispatch_get_global_queue expects a numeric identifier."); - return nullptr; - } - } else { - napi_value coercedIdentifier = nullptr; - if (napi_coerce_to_number(env, argv[0], &coercedIdentifier) != napi_ok || - napi_get_value_int64(env, coercedIdentifier, &identifier) != napi_ok) { - napi_throw_type_error(env, nullptr, - "dispatch_get_global_queue expects a numeric identifier."); - return nullptr; - } - } - } - - uint64_t flags = 0; - if (argc > 1) { - napi_valuetype flagsType = napi_undefined; - if (napi_typeof(env, argv[1], &flagsType) == napi_ok && flagsType == napi_bigint) { - bool lossless = false; - if (napi_get_value_bigint_uint64(env, argv[1], &flags, &lossless) != napi_ok) { - napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); - return nullptr; - } - } else { - napi_value coercedFlags = nullptr; - int64_t signedFlags = 0; - if (napi_coerce_to_number(env, argv[1], &coercedFlags) != napi_ok || - napi_get_value_int64(env, coercedFlags, &signedFlags) != napi_ok) { - napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); - return nullptr; - } - flags = static_cast(signedFlags); - } - } - - return createCompatDispatchQueueWrapper(env, dispatch_get_global_queue(identifier, flags)); -} - -inline napi_value compat_dispatch_get_current_queue(napi_env env, napi_callback_info info) { - (void)info; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return createCompatDispatchQueueWrapper(env, dispatch_get_current_queue()); -#pragma clang diagnostic pop -} - -inline napi_value compat_dispatch_async(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value argv[2] = {nullptr, nullptr}; - napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); - - if (argc < 2) { - napi_throw_type_error(env, nullptr, "dispatch_async expects a queue and callback."); - return nullptr; - } - - void* queueHandle = nullptr; - if (!unwrapCompatNativeHandle(env, argv[0], &queueHandle) || queueHandle == nullptr) { - napi_throw_type_error(env, nullptr, "dispatch_async expects a native queue handle."); - return nullptr; - } - - napi_valuetype callbackType = napi_undefined; - if (napi_typeof(env, argv[1], &callbackType) != napi_ok || callbackType != napi_function) { - napi_throw_type_error(env, nullptr, "dispatch_async expects a function callback."); - return nullptr; - } - - auto closure = new Closure(env, std::string("v"), true); - id block = registerBlock(env, closure, argv[1]); - dispatch_block_t dispatchBlock = (dispatch_block_t)block; - - dispatch_async(reinterpret_cast(queueHandle), dispatchBlock); - [block release]; - - napi_value undefinedValue = nullptr; - napi_get_undefined(env, &undefinedValue); - return undefinedValue; -} - -inline void registerCompatFunctionIfMissing(napi_env env, napi_value global, - ObjCBridgeState* bridgeState, const char* functionName, - const char* encoding) { - if (hasNamedProperty(env, global, functionName)) { - return; - } - - void* fn = resolveSymbolPointer(bridgeState, functionName); - if (fn == nullptr && strcmp(functionName, "CC_SHA256") == 0) { - void* commonCrypto = dlopen("/usr/lib/system/libcommonCrypto.dylib", RTLD_NOW | RTLD_LOCAL); - if (commonCrypto != nullptr) { - fn = dlsym(commonCrypto, functionName); - if (fn == nullptr) { - fn = dlsym(commonCrypto, "_CC_SHA256"); - } - } - } - - if (fn == nullptr) { - return; - } - - napi_value wrapper = FunctionPointer::wrapWithEncoding(env, fn, encoding, false); - if (wrapper != nullptr) { - napi_set_named_property(env, global, functionName, wrapper); - } -} - -inline void registerCompatFunction(napi_env env, napi_value global, const char* functionName, - napi_callback callback) { - napi_value wrapper = nullptr; - napi_create_function(env, functionName, NAPI_AUTO_LENGTH, callback, nullptr, &wrapper); - if (wrapper != nullptr) { - napi_value key = nullptr; - napi_create_string_utf8(env, functionName, NAPI_AUTO_LENGTH, &key); - if (key != nullptr) { - bool deleted = false; - napi_delete_property(env, global, key, &deleted); - clearPendingException(env); - } - defineGlobalValue(env, global, functionName, wrapper); - } -} - -void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeState* bridgeState) { -#if TARGET_OS_OSX - registerStructAlias(env, global, bridgeState, "CGPoint", - {"CGPoint", "_CGPoint", "NSPoint", "_NSPoint"}); - registerStructAlias(env, global, bridgeState, "CGSize", - {"CGSize", "_CGSize", "NSSize", "_NSSize"}); - registerStructAlias(env, global, bridgeState, "CGRect", - {"CGRect", "_CGRect", "NSRect", "_NSRect"}); - ensureSyntheticCGPoint(env, global); - ensureConstructableStructAlias( - env, global, bridgeState, "CGPoint", - {"CGPointStruct", "NSPoint", "NSPointStruct", "_CGPoint", "_NSPoint", "CGPoint"}); - installMacUIColorCompatShim(env); -#endif - - // CommonCrypto compatibility used by historical runtime tests and apps. - registerCompatFunctionIfMissing(env, global, bridgeState, "CC_SHA256", "^C^vQ^C"); - registerCompatFunctionIfMissing(env, global, bridgeState, "CGColorGetComponents", "^d^v"); - - // Force known-good libdispatch globals on macOS. The metadata path can resolve these with an - // incompatible call shape, which crashes when tests dispatch timers from a background queue. - registerCompatFunction(env, global, "dispatch_async", compat_dispatch_async); - registerCompatFunction(env, global, "dispatch_get_current_queue", - compat_dispatch_get_current_queue); - registerCompatFunction(env, global, "dispatch_get_global_queue", - compat_dispatch_get_global_queue); -} - -ObjCBridgeState::ObjCBridgeState(napi_env env, const char* metadata_path, - const void* metadata_ptr) { - this->env = env; - napi_set_instance_data(env, this, finalize_bridge_data, nil); - lifetimeToken = RegisterBridgeState(this); - trackedObjectLiveness = [[NSMutableSet alloc] init]; - - self_dl = dlopen(nullptr, RTLD_NOW); - - if (metadata_ptr && *((const char*)metadata_ptr) != '\0') { -#ifdef EMBED_METADATA_SIZE - // NSLog(@"Ignoring metadata pointer due to embedded metadata"); - metadata = new MDMetadataReader((void*)embedded_metadata); -#else - // NSLog(@"Using metadata from pointer: %p", metadata_ptr); - metadata = new MDMetadataReader((void*)metadata_ptr); -#endif - } else { -#ifdef EMBED_METADATA_SIZE - if (metadata_path != nullptr) { - // NSLog(@"Loading metadata from file: %s", metadata_path); - metadata = loadMetadataFromFile(metadata_path); - } else { - // NSLog(@"Using embedded metadata"); - metadata = new MDMetadataReader((void*)embedded_metadata); - } -#else - unsigned long segmentSize = 0; - auto segmentData = getsegmentdata((const mach_header_64*)_dyld_get_image_header(0), - "__objc_metadata", &segmentSize); - if (segmentData != nullptr) { - metadata = new MDMetadataReader(segmentData); - } else { - metadata = loadMetadataFromFile(metadata_path); - } -#endif - } - - // objc_autoreleasePool = objc_autoreleasePoolPush(); -} - -ObjCBridgeState::~ObjCBridgeState() { - UnregisterBridgeState(this); - - auto deleteRef = [&](napi_ref& ref) { - if (env != nullptr && ref != nullptr) { - napi_delete_reference(env, ref); - ref = nullptr; - } - }; - - for (auto& pair : constructorsByPointer) { - deleteRef(pair.second); - } - constructorsByPointer.clear(); - - for (auto& frame : roundTripCacheFrames) { - for (auto& entry : frame) { - ObjCBridgeState::releaseRoundTripEntry(env, entry.second); - } - } - roundTripCacheFrames.clear(); - - for (auto& entry : recentRoundTripCache) { - ObjCBridgeState::releaseRoundTripEntry(env, entry.second); - } - recentRoundTripCache.clear(); - - for (auto& entry : handleObjectRefs) { - if (entry.second.ownsRef) { - deleteRef(entry.second.ref); - } - } - handleObjectRefs.clear(); - - for (auto& entry : recentObjectWrappers) { - deleteRef(entry.ref); - } - recentObjectWrappers.clear(); - - std::unordered_set classAndProtocolConstructorRefs; - classAndProtocolConstructorRefs.reserve(classes.size() + protocols.size()); - for (const auto& pair : classes) { - if (pair.second != nullptr && pair.second->constructor != nullptr) { - classAndProtocolConstructorRefs.insert(pair.second->constructor); - } - } - for (const auto& pair : protocols) { - if (pair.second != nullptr && pair.second->constructor != nullptr) { - classAndProtocolConstructorRefs.insert(pair.second->constructor); - } - } - for (auto& pair : mdValueCache) { - napi_ref& ref = pair.second; - if (ref != nullptr && - classAndProtocolConstructorRefs.find(ref) == classAndProtocolConstructorRefs.end()) { - deleteRef(ref); - } - } - mdValueCache.clear(); - - deleteRef(pointerClass); - deleteRef(referenceClass); - deleteRef(functionReferenceClass); - deleteRef(createNativeProxy); - deleteRef(createFastEnumeratorIterator); - deleteRef(transferOwnershipToNative); - - // Clean up cached Cif objects - for (auto& pair : cifs) { - delete pair.second; - } - cifs.clear(); - - for (auto& pair : mdMethodSignatureCache) { - delete pair.second; - } - mdMethodSignatureCache.clear(); - - for (auto& pair : mdBlockSignatureCache) { - delete pair.second; - } - mdBlockSignatureCache.clear(); - - // Clean up ObjCClass objects - for (auto& pair : classes) { - delete pair.second; - } - classes.clear(); - - // Clean up ObjCProtocol objects - for (auto& pair : protocols) { - delete pair.second; - } - protocols.clear(); - - // Clean up StructInfo objects - for (auto& pair : structInfoCache) { - delete pair.second; - } - structInfoCache.clear(); - - // Clean up CFunction objects - for (auto& pair : cFunctionCache) { - delete pair.second; - } - cFunctionCache.clear(); - - for (auto& pair : mdFunctionSignatureCache) { - delete pair.second; - } - mdFunctionSignatureCache.clear(); - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - trackedObjectLiveness = nullptr; - [trackedObjectTable release]; - - // if (objc_autoreleasePool != nullptr) - // objc_autoreleasePoolPop(objc_autoreleasePool); - - delete metadata; - dlclose(self_dl); -} - -napi_value ObjCBridgeState::proxyNativeObject(napi_env env, napi_value object, id nativeObject) { - NAPI_PREAMBLE - - napi_value result = object; - const bool nativeIsArray = [nativeObject isKindOfClass:NSArray.class]; - bool shouldProxyArray = nativeIsArray; - if (shouldProxyArray) { - napi_value factory = get_ref_value(env, createNativeProxy); - napi_value transferOwnershipFunc = get_ref_value(env, this->transferOwnershipToNative); - napi_value global; - napi_value args[3] = {object, nullptr, transferOwnershipFunc}; - napi_get_boolean(env, true, &args[1]); - napi_get_global(env, &global); - napi_call_function(env, global, factory, 3, args, &result); - } - - napi_value nativePointer = Pointer::create(env, nativeObject); - if (nativePointer != nullptr) { - napi_set_named_property(env, result, kNativePointerProperty, nativePointer); - } - napi_wrap(env, result, nativeObject, nullptr, nullptr, nullptr); - - napi_ref ref = nullptr; - auto* finalizerContext = new JSObjectFinalizerContext{ - .bridgeState = this, - .bridgeStateToken = lifetimeToken, - .object = nativeObject, - .ref = nullptr, - }; - NAPI_GUARD( - napi_add_finalizer(env, result, finalizerContext, finalize_objc_object, nullptr, &ref)) { - delete finalizerContext; - NAPI_THROW_LAST_ERROR - return nullptr; - } - finalizerContext->ref = ref; - - storeObjectRef(nativeObject, ref); - cacheHandleObjectRef(env, nativeObject, ref); - cacheRecentObjectWrapper(env, nativeObject, result); - attachObjectLifecycleAssociation(env, nativeObject); - trackObject(nativeObject); - - return result; -} - -void ObjCBridgeState::trackObject(id object) noexcept { - if (object == nil) { - return; - } - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - if (trackedObjectTable == nil) { - return; - } - - NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; - std::lock_guard lock(objectRefsMutex); - [trackedObjectTable addObject:objectKey]; -} - -bool ObjCBridgeState::isTrackedObjectAlive(id object) const noexcept { - if (object == nil) { - return false; - } - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - if (trackedObjectTable == nil) { - return false; - } - - NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; - std::lock_guard lock(objectRefsMutex); - return [trackedObjectTable containsObject:objectKey]; -} - -} // namespace nativescript - -using namespace nativescript; - -NAPI_FUNCTION(getArrayBuffer) { - NAPI_CALLBACK_BEGIN(2) - - void* ptr = Pointer::unwrap(env, argv[0])->data; - int64_t length; - napi_get_value_int64(env, argv[1], &length); - - napi_value arrayBuffer; - if (length < 0) { - napi_throw_error(env, nullptr, "Invalid ArrayBuffer length"); - return nullptr; - } - - napi_create_external_arraybuffer(env, ptr, static_cast(length), nullptr, nullptr, - &arrayBuffer); - - return arrayBuffer; -} - -NAPI_FUNCTION(init) { - NAPI_CALLBACK_BEGIN(1) - napi_valuetype type; - napi_typeof(env, argv[0], &type); - const char* metadata_path = nullptr; - if (type == napi_string) { - size_t len; - napi_get_value_string_utf8(env, argv[0], nullptr, 0, &len); - metadata_path = (char*)malloc(len + 1); - napi_get_value_string_utf8(env, argv[0], (char*)metadata_path, len + 1, &len); - } - nativescript_init(env, metadata_path, nullptr); - return nullptr; -} - -NAPI_EXPORT NAPI_MODULE_REGISTER { - const napi_property_descriptor property = NAPI_FUNCTION_DESC(init); - napi_define_properties(env, exports, 1, &property); - return exports; -} - -NAPI_EXPORT void nativescript_init(void* _env, const char* metadata_path, - const void* metadata_ptr) { - napi_env env = (napi_env)_env; - - ObjCBridgeState* bridgeState = new ObjCBridgeState(env, metadata_path, metadata_ptr); - - napi_value objc; - napi_create_object(env, &objc); - - const napi_property_descriptor objcProperties[] = { - NAPI_FUNCTION_DESC(registerClass), NAPI_FUNCTION_DESC(registerBlock), - NAPI_FUNCTION_DESC(import), NAPI_FUNCTION_DESC(autoreleasepool), - NAPI_FUNCTION_DESC(getArrayBuffer), - }; - - napi_define_properties(env, objc, 5, objcProperties); - - napi_value global; - napi_get_global(env, &global); - - const napi_property_descriptor globalProperties[] = {{ - .utf8name = "objc", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = objc, - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "ObjectRef", - .method = nullptr, - .getter = nullptr, - .setter = nullptr, - .value = defineObjectRefClass(env), - .attributes = napi_enumerable, - .data = nullptr, - }, - { - .utf8name = "NativeClass", - .method = JS_registerClass, - .getter = nullptr, - .setter = nullptr, - .value = nullptr, - .attributes = napi_enumerable, - .data = nullptr, - }}; - - napi_define_properties(env, global, 3, globalProperties); - - setupObjCClassDecorator(env); - - initProxyFactory(env, bridgeState); - initFastEnumeratorIteratorFactory(env, bridgeState); - - registerInterop(env, global); - registerInlineFunctions(env); - - bridgeState->registerVarGlobals(env, global); - bridgeState->registerEnumGlobals(env, global); - bridgeState->registerStructGlobals(env, global); - bridgeState->registerUnionGlobals(env, global); - bridgeState->registerFunctionGlobals(env, global); - bridgeState->registerClassGlobals(env, global); - bridgeState->registerProtocolGlobals(env, global); - registerLegacyCompatGlobals(env, global, bridgeState); -} diff --git a/NativeScript/ffi/napi/Object.mm b/NativeScript/ffi/napi/Object.mm deleted file mode 100644 index 5283280ed..000000000 --- a/NativeScript/ffi/napi/Object.mm +++ /dev/null @@ -1,826 +0,0 @@ -#include "Object.h" -#include -#include "Interop.h" -#include "JSObject.h" -#include "ObjCBridge.h" -#include "js_native_api.h" -#include "node_api_util.h" - -#import -#include - -static SEL JSWrapperObjectAssociationKey = @selector(JSWrapperObjectAssociationKey); -static SEL ObjCLifecycleAssociationKey = @selector(ObjCLifecycleAssociationKey); - -@interface JSWrapperObjectAssociation : NSObject - -@property(nonatomic) napi_env env; -@property(nonatomic) napi_ref ref; -@property(nonatomic) nativescript::ObjCBridgeState* bridgeState; -@property(nonatomic) uint64_t bridgeStateToken; - -+ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; - -+ (instancetype)associationFor:(id)object; - -- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref; - -@end - -@interface ObjCLifecycleAssociation : NSObject { - nativescript::ObjCBridgeState* _bridgeState; - uint64_t _bridgeStateToken; - uintptr_t _objectAddress; -} - -- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object; - -@end - -@implementation JSWrapperObjectAssociation - -- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref { - self = [super init]; - if (self) { - self.env = env; - self.ref = ref; - self.bridgeState = nativescript::ObjCBridgeState::InstanceData(env); - self.bridgeStateToken = self.bridgeState != nullptr ? self.bridgeState->lifetimeToken : 0; - } - return self; -} - -+ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object { - napi_ref ref = nativescript::make_ref(env, value); - JSWrapperObjectAssociation* association = [[JSWrapperObjectAssociation alloc] initWithEnv:env - ref:ref]; - objc_setAssociatedObject(object, JSWrapperObjectAssociationKey, association, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [association release]; -} - -+ (instancetype)associationFor:(id)object { - return objc_getAssociatedObject(object, JSWrapperObjectAssociationKey); -} - -- (void)dealloc { - if (self.env != nullptr && self.ref != nullptr && - (self.bridgeState == nullptr || - nativescript::IsBridgeStateLive(self.bridgeState, self.bridgeStateToken))) { - nativescript::DeleteReferenceOnOwningThread(self.env, self.bridgeState, - self.bridgeStateToken, self.ref); - self.ref = nullptr; - } - self.bridgeState = nullptr; - self.bridgeStateToken = 0; - self.env = nullptr; - [super dealloc]; -} - -@end - -@implementation ObjCLifecycleAssociation - -- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object { - self = [super init]; - if (self) { - _bridgeState = bridgeState; - _bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; - _objectAddress = (uintptr_t)object; - } - - return self; -} - -- (void)dealloc { - nativescript::ObjCBridgeState* bridgeState = _bridgeState; - uint64_t bridgeStateToken = _bridgeStateToken; - uintptr_t objectAddress = _objectAddress; - if (nativescript::IsBridgeStateLive(bridgeState, bridgeStateToken)) { - bridgeState->detachObject((id)objectAddress); - } - - [super dealloc]; -} - -@end - -napi_value JS_transferOwnershipToNative(napi_env env, napi_callback_info cbinfo) { - size_t argc = 1; - napi_value arg = nullptr; - if (napi_get_cb_info(env, cbinfo, &argc, &arg, nullptr, nullptr) != napi_ok || argc == 0 || - arg == nullptr) { - return nullptr; - } - - id obj = nil; - if (napi_unwrap(env, arg, (void**)&obj) != napi_ok || obj == nil) { - return nullptr; - } - - [JSWrapperObjectAssociation transferOwnership:env of:arg toNative:obj]; - - return nullptr; -} - -namespace nativescript { - -napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, - Class cls = nil); - -void transferOwnershipToNative(napi_env env, napi_value value, id object) { - if (env == nullptr || value == nullptr || object == nil) { - return; - } - - [JSWrapperObjectAssociation transferOwnership:env of:value toNative:object]; -} - -namespace { -constexpr const char* kNativePointerProperty = "__ns_native_ptr"; - -napi_value findConstructorForClassObject(napi_env env, ObjCBridgeState* bridgeState, Class cls, - napi_value fallback = nullptr) { - if (bridgeState == nullptr || cls == nil) { - return fallback; - } - - auto bridgedClass = bridgeState->classesByPointer.find(cls); - if (bridgedClass != bridgeState->classesByPointer.end() && bridgedClass->second != nullptr) { - return get_ref_value(env, bridgedClass->second->constructor); - } - - auto constructedClass = bridgeState->constructorsByPointer.find(cls); - if (constructedClass != bridgeState->constructorsByPointer.end()) { - napi_value constructor = get_ref_value(env, constructedClass->second); - if (constructor != nullptr) { - return constructor; - } - } - - auto metadataClass = bridgeState->mdClassesByPointer.find(cls); - if (metadataClass != bridgeState->mdClassesByPointer.end()) { - auto bridgedMetadataClass = bridgeState->getClass(env, metadataClass->second); - if (bridgedMetadataClass != nullptr) { - return get_ref_value(env, bridgedMetadataClass->constructor); - } - } - - const char* runtimeName = class_getName(cls); - if (runtimeName != nullptr && runtimeName[0] != '\0') { - napi_value global = nullptr; - napi_value constructor = nullptr; - bool hasGlobal = false; - napi_get_global(env, &global); - if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && - napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && - constructor != nullptr) { - return constructor; - } - } - - napi_value resolved = findConstructorForObject(env, bridgeState, (id)cls, cls); - return resolved != nullptr ? resolved : fallback; -} -} // namespace - -const char* nativeObjectProxySource = R"( - (function (object, isArray, transferOwnershipToNative) { - let isTransfered = false; - const boundMethods = Object.create(null); - let objectAtIndexMethod; - let addObjectMethod; - let removeObjectAtIndexMethod; - let setObjectAtIndexedSubscriptMethod; - - function bindTargetMethod(target, name) { - const cached = boundMethods[name]; - if (cached !== undefined) { - return cached; - } - - const value = target[name]; - if (typeof value !== "function") { - return value; - } - - if (value.__ns_proxy_bound === true) { - boundMethods[name] = value; - return value; - } - - const wrapper = value.bind(target); - Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); - boundMethods[name] = wrapper; - try { - Object.defineProperty(target, name, { - value: wrapper, - configurable: true, - writable: true - }); - } catch (_) { - } - return wrapper; - } - - return new Proxy(object, { - get (target, name, receiver) { - if (name === "superclass" && typeof target.class === "function") { - return target.class().superclass(); - } - - if (isArray && name === "count") { - return target.count; - } - - if (isArray) { - switch (name) { - case "objectAtIndex": - return objectAtIndexMethod !== undefined - ? objectAtIndexMethod - : (objectAtIndexMethod = bindTargetMethod(target, name)); - case "addObject": - return addObjectMethod !== undefined - ? addObjectMethod - : (addObjectMethod = bindTargetMethod(target, name)); - case "removeObjectAtIndex": - return removeObjectAtIndexMethod !== undefined - ? removeObjectAtIndexMethod - : (removeObjectAtIndexMethod = bindTargetMethod(target, name)); - case "setObjectAtIndexedSubscript": - return setObjectAtIndexedSubscriptMethod !== undefined - ? setObjectAtIndexedSubscriptMethod - : (setObjectAtIndexedSubscriptMethod = bindTargetMethod(target, name)); - } - } - - if (typeof name === "string") { - const boundMethod = boundMethods[name]; - if (boundMethod !== undefined) { - return boundMethod; - } - } - - const value = target[name]; - if (value !== undefined || name in target) { - if (typeof value === "function" && name !== "constructor") { - if (value.__ns_proxy_bound === true) { - return value; - } - - let wrapper; - if ((name === "isKindOfClass" || name === "isMemberOfClass")) { - wrapper = function (cls, a1, a2, a3) { - let resolvedClass = cls; - const resolvedClassType = typeof resolvedClass; - if (resolvedClass != null && - (resolvedClassType === "object" || resolvedClassType === "function")) { - try { - const runtimeName = typeof NSStringFromClass === "function" - ? NSStringFromClass(resolvedClass) - : null; - if (typeof runtimeName === "string" && runtimeName.length > 0) { - const registry = globalThis.__nsConstructorsByObjCClassName; - if (registry && registry[runtimeName]) { - resolvedClass = registry[runtimeName]; - } else if (typeof globalThis[runtimeName] !== "undefined") { - resolvedClass = globalThis[runtimeName]; - } - } - } catch (_) { - } - } - - switch (arguments.length) { - case 0: - case 1: - return value.call(target, resolvedClass); - case 2: - return value.call(target, resolvedClass, a1); - case 3: - return value.call(target, resolvedClass, a1, a2); - case 4: - return value.call(target, resolvedClass, a1, a2, a3); - default: { - const args = Array.prototype.slice.call(arguments); - args[0] = resolvedClass; - return Reflect.apply(value, target, args); - } - } - }; - } else { - wrapper = value.bind(target); - } - - Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); - if (typeof name === "string") { - boundMethods[name] = wrapper; - } - try { - Object.defineProperty(target, name, { - value: wrapper, - configurable: true, - writable: true - }); - } catch (_) { - } - return wrapper; - } - return value; - } - - if (typeof name === 'symbol') { - return undefined; - } - - if (isArray) { - const index = Number(name); - if (!isNaN(index)) { - return target.objectAtIndex(index); - } - } - }, - - set (target, name, value) { - const isInternalProperty = typeof name === 'string' && - (name === 'napi_external' || name === 'napi_typetag' || name === '__ns_native_ptr'); - - if (typeof name === 'symbol') { - target[name] = value; - return true; - } - - if (typeof name === "string" && boundMethods[name] !== undefined) { - delete boundMethods[name]; - } - - if (isArray) { - switch (name) { - case "objectAtIndex": - objectAtIndexMethod = undefined; - break; - case "addObject": - addObjectMethod = undefined; - break; - case "removeObjectAtIndex": - removeObjectAtIndexMethod = undefined; - break; - case "setObjectAtIndexedSubscript": - setObjectAtIndexedSubscriptMethod = undefined; - break; - } - } - - if (isArray) { - const index = Number(name); - if (!isNaN(index)) { - target.setObjectAtIndexedSubscript(value, index); - return true; - } - } - - if (!isInternalProperty && !(name in target) && !isTransfered) { - isTransfered = true; - transferOwnershipToNative(target); - } - - target[name] = value; - - return true; - }, - }); - }) -)"; - -void initProxyFactory(napi_env env, ObjCBridgeState* state) { - napi_value script, result; - napi_create_string_utf8(env, nativeObjectProxySource, NAPI_AUTO_LENGTH, &script); - napi_run_script(env, script, &result); - state->createNativeProxy = make_ref(env, result); - - napi_value transferOwnershipToNative; - napi_create_function(env, "transferOwnershipToNative", NAPI_AUTO_LENGTH, - JS_transferOwnershipToNative, nullptr, &transferOwnershipToNative); - state->transferOwnershipToNative = make_ref(env, transferOwnershipToNative); -} - -void attachObjectLifecycleAssociation(napi_env env, id object) { - if (object == nil) { - return; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr) { - return; - } - - if (objc_getAssociatedObject(object, ObjCLifecycleAssociationKey) != nil) { - return; - } - - ObjCLifecycleAssociation* association = - [[ObjCLifecycleAssociation alloc] initWithBridgeState:bridgeState object:object]; - objc_setAssociatedObject(object, ObjCLifecycleAssociationKey, association, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); - [association release]; -} - -namespace { -void finalize_objc_object_now(napi_env /*env*/, void* data, void* hint) { - (void)hint; - JSObjectFinalizerContext* context = static_cast(data); - if (context == nullptr) { - return; - } - - ObjCBridgeState* bridgeState = context->bridgeState; - if (IsBridgeStateLive(bridgeState, context->bridgeStateToken)) { - bridgeState->unregisterObjectIfRefMatches(context->object, context->ref); - } - - delete context; -} -} // namespace - -void finalize_objc_object(napi_env env, void* data, void* hint) { - if (PostFinalizer(env, finalize_objc_object_now, data, hint)) { - return; - } - - finalize_objc_object_now(env, data, hint); -} - -napi_value ObjCBridgeState::getObject(napi_env env, id obj, napi_value constructor, - ObjectOwnership ownership) { - if (obj == nil) { - return nullptr; - } - - NAPI_PREAMBLE - - Class cls = object_getClass(obj); - - if (cls == nullptr) { - return nullptr; - } - - if (class_isMetaClass(cls)) { - return findConstructorForClassObject(env, this, (Class)obj, constructor); - } - - napi_value resolvedConstructor = constructor; - if (resolvedConstructor != nullptr) { - Class hintedClass = nil; - tryResolveBridgedClassConstructor(env, resolvedConstructor, &hintedClass); - } - - napi_value actualConstructor = findConstructorForObject(env, this, obj, cls); - if (actualConstructor != nullptr) { - resolvedConstructor = actualConstructor; - } - - if (napi_value cached = findCachedObjectWrapper(env, obj); cached != nullptr) { - return cached; - } - - napi_value result = nil; - napi_value prototype; - NAPI_GUARD(napi_get_named_property(env, resolvedConstructor, "prototype", &prototype)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - NAPI_GUARD(napi_create_object(env, &result)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - napi_value global; - napi_value objectCtor; - napi_value setPrototypeOf; - napi_value argv[2] = {result, prototype}; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &objectCtor); - napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); - - NAPI_GUARD(napi_call_function(env, objectCtor, setPrototypeOf, 2, argv, nullptr)) { - NAPI_THROW_LAST_ERROR - return nullptr; - } - - napi_wrap(env, result, obj, nullptr, nullptr, nullptr); - napi_value nativePointer = Pointer::create(env, obj); - if (nativePointer != nullptr) { - napi_set_named_property(env, result, kNativePointerProperty, nativePointer); - } - - if (ownership == kUnownedObject) { - [obj retain]; - } - - result = proxyNativeObject(env, result, obj); - - // #if DEBUG - // napi_value global, Error, error, stack; - // napi_get_global(env, &global); - // napi_get_named_property(env, global, "Error", &Error); - // napi_new_instance(env, Error, 0, nullptr, &error); - // napi_get_named_property(env, error, "stack", &stack); - - // size_t stackSize; - // napi_get_value_string_utf8(env, stack, nullptr, 0, &stackSize); - // char *stackStr = new char[stackSize + 1]; - // napi_get_value_string_utf8(env, stack, stackStr, stackSize + 1, nullptr); - - // NSString *str = [NSString stringWithFormat:@"Wrapped object <%s: %p> @ %ld # %s", - // class_getName(cls), obj, [obj retainCount], stackStr]; - // dbglog([str UTF8String]); - - // delete[] stackStr; - // #endif - - return result; -} - -napi_value ObjCBridgeState::findCachedObjectWrapper(napi_env env, id obj) { - if (obj == nil) { - return nullptr; - } - - if (napi_value jsObject = idToJsObject(env, obj); jsObject != nullptr) { - return jsObject; - } - - auto roundTrip = getRoundTripObject(env, obj); - if (roundTrip != nullptr) { - return roundTrip; - } - - if (napi_value recentWrapper = getRecentObjectWrapper(env, obj); recentWrapper != nullptr) { - return recentWrapper; - } - - if (napi_value handleCached = getCachedHandleObject(env, (void*)obj); handleCached != nullptr) { - bool isRawHandleRoundTrip = ownsCachedHandleObjectRef((void*)obj); - void* wrapped = nullptr; - if (napi_unwrap(env, handleCached, &wrapped) == napi_ok && - NormalizeHandleKey(wrapped) == NormalizeHandleKey((void*)obj)) { - return handleCached; - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, handleCached, kNativePointerProperty, &hasNativePointer) == - napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, handleCached, kNativePointerProperty, &nativePointerValue) == - napi_ok && - Pointer::isInstance(env, nativePointerValue)) { - Pointer* pointer = Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && - NormalizeHandleKey(pointer->data) == NormalizeHandleKey((void*)obj)) { - return handleCached; - } - } - } - - if (isRawHandleRoundTrip) { - return handleCached; - } - - removeCachedHandleObject(env, (void*)obj); - } - - if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { - return existing; - } - - JSWrapperObjectAssociation* association = [JSWrapperObjectAssociation associationFor:obj]; - if (association != nil) { - napi_value jsObject = get_ref_value(env, association.ref); - if (jsObject != nullptr) { - bool isArrayBuffer = false; - bool isTypedArray = false; - bool isDataView = false; - if ((napi_is_arraybuffer(env, jsObject, &isArrayBuffer) == napi_ok && isArrayBuffer) || - (napi_is_typedarray(env, jsObject, &isTypedArray) == napi_ok && isTypedArray) || - (napi_is_dataview(env, jsObject, &isDataView) == napi_ok && isDataView)) { - return jsObject; - } - - [obj retain]; - return proxyNativeObject(env, jsObject, obj); - } - } - - return nullptr; -} - -napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, - Class cls) { - if (cls == nil) { - cls = object_getClass(object); - } - - // Look up if there is a custom class for it already - { - auto find = bridgeState->classesByPointer.find(cls); - if (find != bridgeState->classesByPointer.end()) { - return get_ref_value(env, find->second->constructor); - } - } - - // Look up if there is a custom constructor for it already - { - auto find = bridgeState->constructorsByPointer.find(cls); - if (find != bridgeState->constructorsByPointer.end()) { - return get_ref_value(env, find->second); - } - } - - // Look up if there is a metadata-defined class - { - auto find = bridgeState->mdClassesByPointer.find(cls); - if (find != bridgeState->mdClassesByPointer.end()) { - auto cls = bridgeState->getClass(env, find->second); - return get_ref_value(env, cls->constructor); - } - } - - Class superclass = class_getSuperclass(cls); - if (superclass != nullptr) { - napi_value superclassConstructor = - findConstructorForObject(env, bridgeState, object, superclass); - if (superclassConstructor != nullptr) { - return superclassConstructor; - } - } - - // Look up the protocols implemented by this class if no class-based - // constructor could be resolved. For private runtime subclasses we prefer - // inheriting the public superclass surface over exposing a protocol-only - // shell that drops concrete class members. - { - unsigned int count; - auto protocols = class_copyProtocolList(cls, &count); - std::unordered_set impls; - - std::function processProtocolList = [&](Protocol** list, - unsigned int count) { - for (unsigned int i = 0; i < count; i++) { - auto protocol = list[i]; - auto find = bridgeState->mdProtocolsByPointer.find(protocol); - if (find != bridgeState->mdProtocolsByPointer.end()) { - impls.insert(bridgeState->getProtocol(env, find->second)); - } - list = protocol_copyProtocolList(protocol, &count); - processProtocolList(list, count); - } - }; - - processProtocolList(protocols, count); - - if (!impls.empty()) { - napi_value constructor; - napi_define_class(env, class_getName(cls), NAPI_AUTO_LENGTH, ObjCProtocol::jsConstructor, - nullptr, 0, nullptr, &constructor); - for (auto impl : impls) { - ObjCClassMember::defineMembers(env, impl->members, impl->membersOffset, constructor); - } - - bridgeState->constructorsByPointer[cls] = make_ref(env, constructor); - - return constructor; - } - } - - return nullptr; -} - -// Get a napi_value for an Objective-C object, creating it if it doesn't exist. -// Here we also ensure that the native object always points to the same -// JS object, this makes sure that we only ever finalize it once. -// Might want to consider using associated objects instead of a hashtable. -napi_value ObjCBridgeState::getObject(napi_env env, id obj, ObjectOwnership ownership, - MDSectionOffset classOffset, - std::vector* protocolOffsets) { - NAPI_PREAMBLE - - if (obj == nullptr) { - return nullptr; - } - - Class objectClass = object_getClass(obj); - if (objectClass != nil && class_isMetaClass(objectClass)) { - return findConstructorForClassObject(env, this, (Class)obj, nullptr); - } - - auto roundTrip = getRoundTripObject(env, obj); - if (roundTrip != nullptr) { - return roundTrip; - } - - if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { - return existing; - } - - auto findClass = classesByPointer.find(obj); - if (findClass != classesByPointer.end()) { - return get_ref_value(env, findClass->second->constructor); - } - - auto mdFindClassByPointer = mdClassesByPointer.find((Class)obj); - if (mdFindClassByPointer != mdClassesByPointer.end()) { - auto bridgedClass = getClass(env, mdFindClassByPointer->second); - return bridgedClass != nullptr ? get_ref_value(env, bridgedClass->constructor) : nullptr; - } - - auto cls = objectClass; - - auto mdFindByPointer = mdClassesByPointer.find(cls); - if (mdFindByPointer != mdClassesByPointer.end()) { - classOffset = mdFindByPointer->second; - } - - auto findByPointer = classesByPointer.find(cls); - if (findByPointer != classesByPointer.end()) { - return getObject(env, obj, get_ref_value(env, findByPointer->second->constructor), ownership); - } - - napi_value constructor = nullptr; - if (classOffset != 0) { - auto bridgedCls = getClass(env, classOffset); - - if (bridgedCls == nullptr) { - return nullptr; - } - - constructor = get_ref_value(env, bridgedCls->constructor); - } else if (protocolOffsets != nullptr && !protocolOffsets->empty()) { - auto proto = getProtocol(env, protocolOffsets->front()); - - if (proto == nullptr) { - return nullptr; - } - - constructor = get_ref_value(env, proto->constructor); - } else { - constructor = findConstructorForObject(env, this, obj, cls); - } - - if (constructor == nullptr) { - return nullptr; - } - - return getObject(env, obj, constructor, ownership); -} - -void ObjCBridgeState::unregisterObject(id object) noexcept { - // #if DEBUG - // NSString *string = [NSString stringWithFormat: @"Unregistering object <%s: %p> @ %ld # success: - // %d, finalized: %d", - // class_getName(object_getClass(object)), object, [object retainCount], - // (int)objectRefs.contains(object), (int)finalized]; - - // dbglog([string UTF8String]); - // #endif - - if (takeObjectRef(object) != nullptr) { - removeRecentObjectWrapper(env, object); - removeCachedHandleObject(env, (void*)object); - [object release]; - } -} - -bool ObjCBridgeState::unregisterObjectIfRefMatches(id object, napi_ref ref) noexcept { - if (takeObjectRef(object, ref) == nullptr) { - return false; - } - - removeRecentObjectWrapper(env, object); - removeCachedHandleObject(env, (void*)object); - [object release]; - return true; -} - -void ObjCBridgeState::detachObject(id object) noexcept { - takeObjectRef(object); - removeRoundTripObject(object); - removeRecentObjectWrapper(env, object); - removeCachedHandleObject(env, (void*)object); - - if (object == nil) { - return; - } - - NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); - if (trackedObjectTable == nil) { - return; - } - - NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; - std::lock_guard lock(objectRefsMutex); - [trackedObjectTable removeObject:objectKey]; -} - -} // namespace nativescript diff --git a/NativeScript/ffi/napi/SignatureDispatch.h b/NativeScript/ffi/napi/SignatureDispatch.h deleted file mode 100644 index c42a82376..000000000 --- a/NativeScript/ffi/napi/SignatureDispatch.h +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef NS_FFI_NAPI_SIGNATURE_DISPATCH_H -#define NS_FFI_NAPI_SIGNATURE_DISPATCH_H - -#include - -#include -#include -#include - -#include "Cif.h" -#include "js_native_api.h" - -namespace nativescript { - -enum class SignatureCallKind : uint8_t { - ObjCMethod = 1, - CFunction = 2, - BlockInvoke = 3, -}; - -using ObjCPreparedInvoker = void (*)(void* fnptr, void** avalues, void* rvalue); -using CFunctionPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); -using BlockPreparedInvoker = void (*)(void* fnptr, void** avalues, - void* rvalue); -using ObjCNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, id self, - SEL selector, const napi_value* argv, - void* rvalue); -using CFunctionNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, - const napi_value* argv, void* rvalue); - -struct ObjCDispatchEntry { - uint64_t dispatchId; - ObjCPreparedInvoker invoker; -}; - -struct CFunctionDispatchEntry { - uint64_t dispatchId; - CFunctionPreparedInvoker invoker; -}; - -struct BlockDispatchEntry { - uint64_t dispatchId; - BlockPreparedInvoker invoker; -}; - -struct ObjCNapiDispatchEntry { - uint64_t dispatchId; - ObjCNapiInvoker invoker; -}; - -struct CFunctionNapiDispatchEntry { - uint64_t dispatchId; - CFunctionNapiInvoker invoker; -}; - -inline constexpr uint64_t kSignatureHashOffsetBasis = 14695981039346656037ull; -inline constexpr uint64_t kSignatureHashPrime = 1099511628211ull; - -inline uint64_t hashBytesFnv1a(const void* data, size_t size, - uint64_t seed = kSignatureHashOffsetBasis) { - const auto* bytes = static_cast(data); - uint64_t hash = seed; - for (size_t i = 0; i < size; i++) { - hash ^= static_cast(bytes[i]); - hash *= kSignatureHashPrime; - } - return hash; -} - -inline uint64_t composeSignatureDispatchId(uint64_t signatureHash, - SignatureCallKind kind, - uint8_t flags) { - const uint8_t kindByte = static_cast(kind); - uint64_t hash = hashBytesFnv1a(&kindByte, sizeof(kindByte)); - hash = hashBytesFnv1a(&flags, sizeof(flags), hash); - return hashBytesFnv1a(&signatureHash, sizeof(signatureHash), hash); -} - -} // namespace nativescript - -#ifndef NS_GSD_BACKEND_NAPI -#define NS_GSD_BACKEND_NAPI 1 -#endif - -#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH -#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 -#endif - -#ifndef NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH -#define NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH 0 -#endif - -#ifndef NS_GSD_BACKEND_V8 -#define NS_GSD_BACKEND_V8 0 -#endif - -#ifndef NS_GSD_BACKEND_JSC -#define NS_GSD_BACKEND_JSC 0 -#endif - -#ifndef NS_GSD_BACKEND_QUICKJS -#define NS_GSD_BACKEND_QUICKJS 0 -#endif - -#ifndef NS_GSD_BACKEND_HERMES -#define NS_GSD_BACKEND_HERMES 0 -#endif - -#ifndef NS_GSD_BACKEND_ENGINE_DIRECT -#define NS_GSD_BACKEND_ENGINE_DIRECT 0 -#endif - -#if defined(__has_include) -#if __has_include("GeneratedSignatureDispatch.inc") -#include "GeneratedSignatureDispatch.inc" -#endif -#endif - -#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH -namespace nativescript { -inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { - {0, nullptr}}; -inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { - {0, nullptr}}; -inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { - {0, nullptr}}; -} // namespace nativescript -#endif - -#if !NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH -namespace nativescript { -inline constexpr ObjCNapiDispatchEntry kGeneratedObjCNapiDispatchEntries[] = { - {0, nullptr}}; -inline constexpr CFunctionNapiDispatchEntry - kGeneratedCFunctionNapiDispatchEntries[] = {{0, nullptr}}; -} // namespace nativescript -#endif - -namespace nativescript { - -template -inline Invoker lookupDispatchInvoker(const Entry (&entries)[N], - uint64_t dispatchId) { - if (dispatchId == 0 || N <= 1) { - return nullptr; - } - - size_t low = 1; - size_t high = N; - while (low < high) { - const size_t mid = low + ((high - low) >> 1); - const uint64_t midId = entries[mid].dispatchId; - if (midId < dispatchId) { - low = mid + 1; - } else { - high = mid; - } - } - - if (low < N && entries[low].dispatchId == dispatchId) { - return entries[low].invoker; - } - return nullptr; -} - -inline bool isGeneratedDispatchEnabled() { - static const bool enabled = []() { - const char* disableFlag = std::getenv("NS_DISABLE_GSD"); - if (disableFlag == nullptr || disableFlag[0] == '\0') { - return true; - } - return !(disableFlag[0] == '0' && disableFlag[1] == '\0'); - }(); - return enabled; -} - -inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedObjCDispatchEntries, dispatchId); -} - -inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( - uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedCFunctionDispatchEntries, dispatchId); -} - -inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedBlockDispatchEntries, dispatchId); -} - -inline ObjCNapiInvoker lookupObjCNapiInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedObjCNapiDispatchEntries, dispatchId); -} - -inline CFunctionNapiInvoker lookupCFunctionNapiInvoker(uint64_t dispatchId) { - if (!isGeneratedDispatchEnabled()) { - return nullptr; - } - return lookupDispatchInvoker( - kGeneratedCFunctionNapiDispatchEntries, dispatchId); -} - -} // namespace nativescript - -#endif // NS_FFI_NAPI_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/napi/TypeConv.mm b/NativeScript/ffi/napi/TypeConv.mm deleted file mode 100644 index be22ba6c6..000000000 --- a/NativeScript/ffi/napi/TypeConv.mm +++ /dev/null @@ -1,4166 +0,0 @@ -#include "TypeConv.h" -#include "Block.h" -#include "Class.h" -#include "Closure.h" -#include "Interop.h" -#include "JSObject.h" -#include "Metadata.h" -#include "MetadataReader.h" -#include "ObjCBridge.h" -#include "ffi.h" -#include "Struct.h" -#include "js_native_api.h" -#include "js_native_api_types.h" -#include "node_api_util.h" - -#import -#import -#include -#if defined(__has_include) -#if __has_include() -#include -#endif -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -@interface JSWrapperObjectAssociation : NSObject -+ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; -@end - -namespace { - -static napi_value findRegisteredClassConstructor(napi_env env, Class cls) { - if (env == nullptr || cls == nil) { - return nullptr; - } - - const char* runtimeName = class_getName(cls); - if (runtimeName == nullptr || runtimeName[0] == '\0') { - return nullptr; - } - - napi_value global = nullptr; - napi_value classRegistry = nullptr; - bool hasClassRegistry = false; - if (napi_get_global(env, &global) != napi_ok || global == nullptr || - napi_has_named_property(env, global, "__nsConstructorsByObjCClassName", - &hasClassRegistry) != napi_ok || - !hasClassRegistry || - napi_get_named_property(env, global, "__nsConstructorsByObjCClassName", - &classRegistry) != napi_ok || - classRegistry == nullptr) { - return nullptr; - } - - bool hasConstructor = false; - napi_value constructor = nullptr; - if (napi_has_named_property(env, classRegistry, runtimeName, &hasConstructor) == napi_ok && - hasConstructor && - napi_get_named_property(env, classRegistry, runtimeName, &constructor) == napi_ok && - constructor != nullptr) { - return constructor; - } - - return nullptr; -} - -static size_t getBufferElementSize(napi_typedarray_type type) { - switch (type) { - case napi_int8_array: - case napi_uint8_array: - case napi_uint8_clamped_array: - return 1; - case napi_int16_array: - case napi_uint16_array: - return 2; - case napi_int32_array: - case napi_uint32_array: - case napi_float32_array: - return 4; - case napi_float64_array: - case napi_bigint64_array: - case napi_biguint64_array: - return 8; - default: - return 1; - } -} - -static bool getJSBufferData(napi_env env, napi_value value, void** data, size_t* byteLength) { - if (data == nullptr || byteLength == nullptr) { - return false; - } - - bool isArrayBuffer = false; - if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { - return napi_get_arraybuffer_info(env, value, data, byteLength) == napi_ok; - } - - bool isTypedArray = false; - if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { - napi_typedarray_type type; - napi_value arrayBuffer; - size_t byteOffset = 0; - size_t elementLength = 0; - if (napi_get_typedarray_info(env, value, &type, &elementLength, data, &arrayBuffer, - &byteOffset) != napi_ok) { - return false; - } - - *byteLength = elementLength * getBufferElementSize(type); - return true; - } - - bool isDataView = false; - if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { - napi_value arrayBuffer; - size_t byteOffset = 0; - return napi_get_dataview_info(env, value, byteLength, data, &arrayBuffer, &byteOffset) == - napi_ok; - } - - return false; -} - -static uint16_t encodeFloat16(double value) { - if (std::isnan(value)) { - return 0x7e00; - } - - if (std::isinf(value)) { - return std::signbit(value) ? 0xfc00 : 0x7c00; - } - - union { - float f; - uint32_t bits; - } input = {static_cast(value)}; - - const uint32_t sign = (input.bits >> 16) & 0x8000; - uint32_t exponent = (input.bits >> 23) & 0xff; - uint32_t mantissa = input.bits & 0x007fffff; - - if (exponent == 0) { - return static_cast(sign); - } - - int32_t halfExponent = static_cast(exponent) - 127 + 15; - if (halfExponent >= 0x1f) { - return static_cast(sign | 0x7c00); - } - - if (halfExponent <= 0) { - if (halfExponent < -10) { - return static_cast(sign); - } - - mantissa |= 0x00800000; - const uint32_t shift = static_cast(14 - halfExponent); - uint32_t halfMantissa = mantissa >> shift; - if (((mantissa >> (shift - 1)) & 1u) != 0) { - halfMantissa += 1; - } - return static_cast(sign | halfMantissa); - } - - uint32_t halfMantissa = mantissa >> 13; - if ((mantissa & 0x00001000) != 0) { - halfMantissa += 1; - if ((halfMantissa & 0x00000400) != 0) { - halfMantissa = 0; - halfExponent += 1; - if (halfExponent >= 0x1f) { - return static_cast(sign | 0x7c00); - } - } - } - - return static_cast(sign | (static_cast(halfExponent) << 10) | - (halfMantissa & 0x03ff)); -} - -static double decodeFloat16(uint16_t bits) { - const uint32_t sign = (bits & 0x8000u) << 16; - const uint32_t exponent = (bits >> 10) & 0x1fu; - const uint32_t mantissa = bits & 0x03ffu; - - union { - uint32_t bits; - float f; - } output = {0}; - - if (exponent == 0) { - if (mantissa == 0) { - output.bits = sign; - return static_cast(output.f); - } - - uint32_t normalizedMantissa = mantissa; - int32_t normalizedExponent = -14; - while ((normalizedMantissa & 0x0400u) == 0) { - normalizedMantissa <<= 1; - normalizedExponent -= 1; - } - normalizedMantissa &= 0x03ffu; - output.bits = - sign | (static_cast(normalizedExponent + 127) << 23) | (normalizedMantissa << 13); - return static_cast(output.f); - } - - if (exponent == 0x1fu) { - output.bits = sign | 0x7f800000u | (mantissa << 13); - return static_cast(output.f); - } - - output.bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13); - return static_cast(output.f); -} - -static id resolveCachedHandleObject(napi_env env, void* handle) { - if (env == nullptr || handle == nullptr) { - return nil; - } - - auto bridgeState = nativescript::ObjCBridgeState::InstanceData(env); - if (bridgeState == nullptr) { - return nil; - } - - napi_value cachedValue = bridgeState->getCachedHandleObject(env, handle); - if (cachedValue == nullptr) { - return nil; - } - - void* wrapped = nullptr; - if (napi_unwrap(env, cachedValue, &wrapped) == napi_ok && wrapped != nullptr) { - bridgeState->cacheRoundTripObject(env, static_cast(wrapped), cachedValue); - return static_cast(wrapped); - } - - bool hasNativePointer = false; - if (napi_has_named_property(env, cachedValue, "__ns_native_ptr", &hasNativePointer) == napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, cachedValue, "__ns_native_ptr", &nativePointerValue) == - napi_ok) { - if (nativescript::Pointer::isInstance(env, nativePointerValue)) { - nativescript::Pointer* pointer = nativescript::Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && pointer->data != nullptr) { - bridgeState->cacheRoundTripObject(env, static_cast(pointer->data), cachedValue); - return static_cast(pointer->data); - } - } else { - void* nativePointer = nullptr; - if (napi_get_value_external(env, nativePointerValue, &nativePointer) == napi_ok && - nativePointer != nullptr) { - bridgeState->cacheRoundTripObject(env, static_cast(nativePointer), cachedValue); - return static_cast(nativePointer); - } - } - } - } - - return nil; -} - -} // namespace - -namespace nativescript { - -namespace { -constexpr const char* kProtocolSuffix = "Protocol"; - -NSData* createNSDataWrapper(napi_env env, napi_value value, ObjCBridgeState* bridgeState) { - void* data = nullptr; - size_t byteLength = 0; - if (!getJSBufferData(env, value, &data, &byteLength)) { - return nil; - } - - NSData* wrappedData = [NSData dataWithBytes:data length:byteLength]; - if (wrappedData == nil) { - return nil; - } - - if (bridgeState != nullptr && bridgeState->hasRoundTripCacheFrame()) { - bridgeState->cacheRoundTripObject(env, wrappedData, value); - } - - return wrappedData; -} - -inline size_t alignUp(size_t value, size_t alignment) { - if (alignment == 0) { - return value; - } - return ((value + alignment - 1) / alignment) * alignment; -} - -inline uintptr_t normalizeRuntimePointer(uintptr_t ptr) { -#if INTPTR_MAX == INT64_MAX - return ptr & 0x0000FFFFFFFFFFFFULL; -#else - return ptr; -#endif -} - -inline bool isKindOfClassFast(id obj, Class expectedClass) { - if (obj == nil || expectedClass == Nil) { - return false; - } - - return [obj isKindOfClass:expectedClass]; -} - -bool stripProtocolSuffix(const char* name, std::string* out) { - if (name == nullptr || out == nullptr) { - return false; - } - - const size_t nameLen = std::strlen(name); - const size_t suffixLen = std::strlen(kProtocolSuffix); - if (nameLen <= suffixLen) { - return false; - } - - if (std::strcmp(name + (nameLen - suffixLen), kProtocolSuffix) != 0) { - return false; - } - - *out = std::string(name, nameLen - suffixLen); - return !out->empty(); -} - -bool protocolNamesMatch(const char* metadataName, const char* runtimeName) { - if (metadataName == nullptr || runtimeName == nullptr) { - return false; - } - - if (std::strcmp(metadataName, runtimeName) == 0) { - return true; - } - - std::string metadataBase(metadataName); - std::string runtimeBase(runtimeName); - stripProtocolSuffix(metadataName, &metadataBase); - stripProtocolSuffix(runtimeName, &runtimeBase); - - return metadataBase == runtimeBase; -} - -MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const char* protocolName) { - if (metadata == nullptr || protocolName == nullptr) { - return MD_SECTION_OFFSET_NULL; - } - - MDSectionOffset offset = metadata->protocolsOffset; - while (offset < metadata->classesOffset) { - MDSectionOffset originalOffset = offset; - - auto nameOffset = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool next = (nameOffset & mdSectionOffsetNext) != 0; - nameOffset &= ~mdSectionOffsetNext; - - auto name = metadata->resolveString(nameOffset); - if (protocolNamesMatch(name, protocolName)) { - return originalOffset; - } - - while (next) { - auto protocolImpl = metadata->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (protocolImpl & mdSectionOffsetNext) != 0; - } - - next = true; - while (next) { - auto flags = metadata->getMemberFlag(offset); - next = (flags & mdMemberNext) != 0; - offset += sizeof(flags); - - if (flags == mdMemberFlagNull) { - break; - } - - if ((flags & mdMemberProperty) != 0) { - bool readonly = (flags & mdMemberReadonly) != 0; - offset += sizeof(MDSectionOffset); // name - offset += sizeof(MDSectionOffset); // getter selector - offset += sizeof(MDSectionOffset); // getter signature - if (!readonly) { - offset += sizeof(MDSectionOffset); // setter selector - offset += sizeof(MDSectionOffset); // setter signature - } - } else { - offset += sizeof(MDSectionOffset); // selector - offset += sizeof(MDSectionOffset); // signature - } - } - } - - return MD_SECTION_OFFSET_NULL; -} -} // namespace - -// Forward declaration -class StructTypeConv; - -// Thread-local storage for tracking structs currently being processed to detect cycles -thread_local std::unordered_set processingStructs; -thread_local std::unordered_set processingEncodingStructs; - -// Cache for forward-declared struct types that need deferred resolution -thread_local std::unordered_map forwardDeclaredStructs; -thread_local std::unordered_map forwardDeclaredEncodingStructs; - -// Cache for StructTypeConv instances to avoid recreating them and handle recursion -thread_local std::unordered_map> structTypeCache; - -// Cache for encoding-based structs to handle recursion -thread_local std::unordered_map> encodingStructCache; - -ffi_type* typeFromStruct(napi_env env, const char** encoding) { - // Extract struct name for cycle detection - std::string structname; - const char* nameStart = *encoding + 1; // skip '{' - const char* c = nameStart; - while (*c != '\0' && *c != '=') { - structname += *c; - c++; - } - if (*c != '=') { - // Malformed struct encoding. Advance to the end of this token and - // fallback to pointer conversion to avoid reading past the buffer. - while (**encoding != '\0' && **encoding != '}') { - (*encoding)++; - } - if (**encoding == '}') { - (*encoding)++; - } - return &ffi_type_pointer; - } - - // Check if we're already processing this struct (cycle detection) - if (processingEncodingStructs.find(structname) != processingEncodingStructs.end()) { - // Create a forward declaration placeholder - ffi_type* forwardType = new ffi_type; - forwardType->type = FFI_TYPE_STRUCT; - forwardType->size = 0; - forwardType->alignment = 0; - forwardType->elements = nullptr; - - // Cache this forward declaration for later resolution - forwardDeclaredEncodingStructs[structname] = forwardType; - - // Skip the struct encoding - (*encoding)++; // skip '{' - while (**encoding != '}') { - (*encoding)++; - } - (*encoding)++; // skip '}' - - return forwardType; - } - - // Check if we already have a forward declaration for this struct - auto existingForwardIt = forwardDeclaredEncodingStructs.find(structname); - if (existingForwardIt != forwardDeclaredEncodingStructs.end()) { - // Skip the struct encoding - (*encoding)++; // skip '{' - while (**encoding != '\0' && **encoding != '}') { - (*encoding)++; - } - if (**encoding == '}') { - (*encoding)++; // skip '}' - } - - return existingForwardIt->second; - } - - // Mark this struct as being processed - processingEncodingStructs.insert(structname); - - ffi_type* type = new ffi_type; - type->type = FFI_TYPE_STRUCT; - type->size = 0; - type->alignment = 0; - type->elements = nullptr; - - std::vector elements; - - (*encoding)++; // skip '{' - - while (**encoding != '\0' && **encoding != '=') { - (*encoding)++; - } // skip name - if (**encoding == '\0') { - processingEncodingStructs.erase(structname); - delete type; - return &ffi_type_pointer; - } - - (*encoding)++; // skip '=' - - while (**encoding != '\0' && **encoding != '}') { - ffi_type* elementType = TypeConv::Make(env, encoding)->type; - elements.push_back(elementType); - } - - if (**encoding == '}') { - (*encoding)++; // skip '}' - } - - type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); - for (int i = 0; i < elements.size(); i++) { - type->elements[i] = elements[i]; - } - // null-terminate the array - type->elements[elements.size()] = nullptr; - - // If this was a forward declaration, update it with the real layout - auto resolvedForwardIt = forwardDeclaredEncodingStructs.find(structname); - if (resolvedForwardIt != forwardDeclaredEncodingStructs.end()) { - ffi_type* forwardType = resolvedForwardIt->second; - forwardType->type = type->type; - forwardType->size = type->size; - forwardType->alignment = type->alignment; - forwardType->elements = type->elements; - - // Clean up the temporary type and use the forward declaration - delete type; - type = forwardType; - forwardDeclaredEncodingStructs.erase(resolvedForwardIt); - } - - // Remove from processing set - processingEncodingStructs.erase(structname); - - return type; -} - -ffi_type* typeFromStruct(napi_env env, MDMetadataReader* reader, MDSectionOffset structOffset, - bool isUnion) { - // Check if we're already processing this struct (cycle detection) - if (processingStructs.find(structOffset) != processingStructs.end()) { - // Create a forward declaration placeholder - ffi_type* forwardType = new ffi_type; - forwardType->type = FFI_TYPE_STRUCT; - forwardType->size = 0; - forwardType->alignment = 0; - forwardType->elements = nullptr; - - // Cache this forward declaration for later resolution - forwardDeclaredStructs[structOffset] = forwardType; - return forwardType; - } - - // Check if we already have a forward declaration for this struct - auto existingForwardIt = forwardDeclaredStructs.find(structOffset); - if (existingForwardIt != forwardDeclaredStructs.end()) { - return existingForwardIt->second; - } - - // Mark this struct as being processed - processingStructs.insert(structOffset); - - ffi_type* type = new ffi_type; - type->type = FFI_TYPE_STRUCT; - type->size = 0; - type->alignment = 0; - type->elements = nullptr; - - MDSectionOffset nameOffset = reader->getOffset(structOffset); - auto name = reader->resolveString(nameOffset); - bool next = true; - MDSectionOffset currentOffset = structOffset + sizeof(MDSectionOffset); // skip name - currentOffset += sizeof(uint16_t); // skip size - - std::vector elements; - - while (next) { - nameOffset = reader->getOffset(currentOffset); - next = nameOffset & mdSectionOffsetNext; - nameOffset &= ~mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - currentOffset += sizeof(MDSectionOffset); // skip name - if (!isUnion) currentOffset += sizeof(uint16_t); // skip offset - ffi_type* elementType = TypeConv::Make(env, reader, ¤tOffset, 1)->type; - elements.push_back(elementType); - } - - type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); - for (int i = 0; i < elements.size(); i++) { - type->elements[i] = elements[i]; - } - // null-terminate the array - type->elements[elements.size()] = nullptr; - - // If this was a forward declaration, update it with the real layout - auto resolvedForwardIt = forwardDeclaredStructs.find(structOffset); - if (resolvedForwardIt != forwardDeclaredStructs.end()) { - ffi_type* forwardType = resolvedForwardIt->second; - forwardType->type = type->type; - forwardType->size = type->size; - forwardType->alignment = type->alignment; - forwardType->elements = type->elements; - - // Clean up the temporary type and use the forward declaration - delete type; - type = forwardType; - forwardDeclaredStructs.erase(resolvedForwardIt); - } - - // Remove from processing set - processingStructs.erase(structOffset); - - return type; -} - -static inline size_t getTypedArrayUnitLength(napi_typedarray_type type) { - switch (type) { - case napi_int8_array: - case napi_uint8_array: - case napi_uint8_clamped_array: - return 1; - case napi_int16_array: - case napi_uint16_array: - return 2; - case napi_int32_array: - case napi_uint32_array: - case napi_float32_array: - return 4; - case napi_float64_array: - case napi_bigint64_array: - case napi_biguint64_array: - return 8; - default: - return 0; - } -} - -class VoidTypeConv : public TypeConv { - public: - VoidTypeConv() { - type = &ffi_type_void; - kind = mdTypeVoid; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_get_null(env, &result); - return result; - } - - void encode(std::string* encoding) override { *encoding += "v"; } -}; - -static const std::shared_ptr voidTypeConv = std::make_shared(); - -class SCharTypeConv : public TypeConv { - public: - SCharTypeConv() { - type = &ffi_type_schar; - kind = mdTypeChar; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - int8_t raw = *(int8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_int32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - int32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_int32(env, value, &val); - *(int8_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "c"; } -}; - -static const std::shared_ptr scharTypeConv = std::make_shared(); - -class UCharTypeConv : public TypeConv { - public: - UCharTypeConv() { - type = &ffi_type_uchar; - kind = mdTypeUChar; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - uint8_t raw = *(uint8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_uint32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - uint32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint8_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "C"; } -}; - -static const std::shared_ptr ucharTypeConv = std::make_shared(); - -class UInt8TypeConv : public TypeConv { - public: - UInt8TypeConv() { - type = &ffi_type_uint8; - kind = mdTypeUInt8; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - uint8_t raw = *(uint8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_uint32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - uint32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint8_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "C"; } -}; - -static const std::shared_ptr uint8TypeConv = std::make_shared(); - -class SInt16TypeConv : public TypeConv { - public: - SInt16TypeConv() { - type = &ffi_type_sshort; - kind = mdTypeSShort; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_int32(env, *(int16_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - int32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_int32(env, value, &val); - *(int16_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "s"; } -}; - -static const std::shared_ptr sint16TypeConv = std::make_shared(); - -class UInt16TypeConv : public TypeConv { - public: - UInt16TypeConv() { - type = &ffi_type_ushort; - kind = mdTypeUShort; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_uint32(env, *(uint16_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - - if (valueType == napi_string) { - size_t strLen = 0; - napi_get_value_string_utf16(env, value, nullptr, 0, &strLen); - if (strLen != 1) { - napi_throw_type_error(env, nullptr, "Expected a single-character string."); - *(uint16_t*)result = 0; - return; - } - - char16_t chars[2] = {0, 0}; - napi_get_value_string_utf16(env, value, chars, 2, &strLen); - *(uint16_t*)result = static_cast(chars[0]); - return; - } - - uint32_t val = 0; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint16_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "S"; } -}; - -static const std::shared_ptr uint16TypeConv = std::make_shared(); - -// unichar/UniChar (mdTypeUnichar): u16 width, but projected to JS as a -// single-character string for any code unit — not just printable ASCII. -class UnicharTypeConv : public UInt16TypeConv { - public: - UnicharTypeConv() { kind = mdTypeUnichar; } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - char16_t unit = *(char16_t*)value; - napi_value result; - napi_create_string_utf16(env, &unit, 1, &result); - return result; - } -}; - -static const std::shared_ptr unicharTypeConv = std::make_shared(); - -class SInt32TypeConv : public TypeConv { - public: - SInt32TypeConv() { - type = &ffi_type_sint; - kind = mdTypeSInt; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_int32(env, *(int32_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - int32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_int32(env, value, &val); - *(int32_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "i"; } -}; - -static const std::shared_ptr sint32TypeConv = std::make_shared(); - -class UInt32TypeConv : public TypeConv { - public: - UInt32TypeConv() { - type = &ffi_type_uint; - kind = mdTypeUInt; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_uint32(env, *(uint32_t*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - uint32_t val; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint32_t*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "I"; } -}; - -static const std::shared_ptr uint32TypeConv = std::make_shared(); - -class SInt64TypeConv : public TypeConv { - public: - SInt64TypeConv() { - type = &ffi_type_sint64; - kind = mdTypeSInt64; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - int64_t val = *(int64_t*)value; - constexpr int64_t kMaxSafeInteger = 9007199254740991LL; - if (val > kMaxSafeInteger || val < -kMaxSafeInteger) { - napi_create_bigint_int64(env, val, &result); - } else { - napi_create_int64(env, val, &result); - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - switch (valuetype) { - case napi_number: - napi_get_value_int64(env, value, (int64_t*)result); - break; - case napi_bigint: { - bool lossless; - napi_get_value_bigint_int64(env, value, (int64_t*)result, &lossless); - break; - } - case napi_undefined: - case napi_null: - *(int64_t*)result = 0; - break; - case napi_string: - *(int64_t*)result = 0; - break; - default: - napi_throw_type_error(env, nullptr, "Expected a number or bigint"); - break; - } - } - - void encode(std::string* encoding) override { *encoding += "q"; } -}; - -static const std::shared_ptr sint64TypeConv = std::make_shared(); - -class UInt64TypeConv : public TypeConv { - public: - UInt64TypeConv() { - type = &ffi_type_uint64; - kind = mdTypeUInt64; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - uint64_t val = *(uint64_t*)value; - constexpr uint64_t kMaxSafeInteger = 9007199254740991ULL; - if (val > kMaxSafeInteger) { - napi_create_bigint_uint64(env, val, &result); - } else { - napi_create_int64(env, static_cast(val), &result); - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - switch (valuetype) { - case napi_number: - napi_get_value_int64(env, value, (int64_t*)result); - break; - case napi_bigint: { - bool lossless; - napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); - break; - } - case napi_undefined: - case napi_null: - *(int64_t*)result = 0; - break; - default: - napi_throw_type_error(env, nullptr, "Expected a number or bigint"); - break; - } - } - - void encode(std::string* encoding) override { *encoding += "Q"; } -}; - -static const std::shared_ptr uint64TypeConv = std::make_shared(); - -class UInt128TypeConv : public TypeConv { - private: - ffi_type _type = {.size = 0, - .alignment = 0, - .type = FFI_TYPE_STRUCT, - .elements = (ffi_type*[]){ - &ffi_type_uint64, - &ffi_type_uint64, - nullptr, - }}; - - public: - UInt128TypeConv() { - type = &_type; - kind = mdTypeUInt128; - } - - // TODO - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - uint64_t val = *(uint64_t*)value; - napi_create_int64(env, (int64_t)val, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - switch (valuetype) { - case napi_number: - napi_get_value_int64(env, value, (int64_t*)result); - break; - case napi_bigint: { - bool lossless; - napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); - break; - } - default: - napi_throw_type_error(env, nullptr, "Expected a number or bigint"); - break; - } - } -}; - -static const std::shared_ptr uint128TypeConv = std::make_shared(); - -class Float32TypeConv : public TypeConv { - public: - Float32TypeConv() { - type = &ffi_type_float; - kind = mdTypeFloat; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_double(env, *(float*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - double val; - napi_coerce_to_number(env, value, &value); - napi_get_value_double(env, value, &val); - *(float*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "f"; } -}; - -static const std::shared_ptr float32TypeConv = std::make_shared(); - -class Float16TypeConv : public TypeConv { - public: - Float16TypeConv() { - type = &ffi_type_uint16; - kind = mdTypeF16; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_double(env, decodeFloat16(*(uint16_t*)value), &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - double val = 0; - napi_coerce_to_number(env, value, &value); - napi_get_value_double(env, value, &val); - *(uint16_t*)result = encodeFloat16(val); - } - - void encode(std::string* encoding) override { *encoding += "H"; } -}; - -static const std::shared_ptr float16TypeConv = std::make_shared(); - -class Float64TypeConv : public TypeConv { - public: - Float64TypeConv() { - type = &ffi_type_double; - kind = mdTypeDouble; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_double(env, *(double*)value, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - double val; - napi_coerce_to_number(env, value, &value); - napi_get_value_double(env, value, &val); - if (std::isnan(val) || std::isinf(val)) { - val = 0.0; - } - *(double*)result = val; - } - - void encode(std::string* encoding) override { *encoding += "d"; } -}; - -static const std::shared_ptr float64TypeConv = std::make_shared(); - -class BoolTypeConv : public TypeConv { - public: - BoolTypeConv() { - type = &ffi_type_uint8; - kind = mdTypeBool; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - uint8_t raw = *(uint8_t*)value; - if (raw == 0 || raw == 1) { - napi_value result; - napi_get_boolean(env, raw == 1, &result); - return result; - } - - napi_value result; - napi_create_uint32(env, raw, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - - if (valueType == napi_number) { - uint32_t val = 0; - napi_coerce_to_number(env, value, &value); - napi_get_value_uint32(env, value, &val); - *(uint8_t*)result = static_cast(val); - return; - } - - if (valueType == napi_bigint) { - uint64_t val = 0; - bool lossless = false; - napi_get_value_bigint_uint64(env, value, &val, &lossless); - *(uint8_t*)result = static_cast(val); - return; - } - - bool val = false; - napi_coerce_to_bool(env, value, &value); - napi_get_value_bool(env, value, &val); - *(uint8_t*)result = static_cast(val ? 1 : 0); - } - - void encode(std::string* encoding) override { *encoding += "B"; } -}; - -static const std::shared_ptr boolTypeConv = std::make_shared(); - -class PointerTypeConv : public TypeConv { - public: - std::shared_ptr pointeeType = nullptr; - - PointerTypeConv() { - type = &ffi_type_pointer; - kind = mdTypePointer; - } - - PointerTypeConv(std::shared_ptr pointeeType) : pointeeType(pointeeType) { - type = &ffi_type_pointer; - kind = mdTypePointer; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* raw = *((void**)value); - if (raw == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - - auto normalizePtr = [](void* ptr) -> uintptr_t { -#if INTPTR_MAX == INT64_MAX - // Objective-C pointers may carry auth/tag bits on some runtimes. - // Compare using canonical lower bits for stable lookups. - return reinterpret_cast(ptr) & 0x0000FFFFFFFFFFFFULL; -#else - return reinterpret_cast(ptr); -#endif - }; - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - auto classIt = bridgeState->mdClassesByPointer.find((Class)raw); - if (classIt != bridgeState->mdClassesByPointer.end()) { - auto cls = bridgeState->getClass(env, classIt->second); - if (cls != nullptr) { - return get_ref_value(env, cls->constructor); - } - } else { - const uintptr_t rawNormalized = normalizePtr(raw); - for (const auto& entry : bridgeState->mdClassesByPointer) { - if (normalizePtr((void*)entry.first) != rawNormalized) { - continue; - } - - auto cls = bridgeState->getClass(env, entry.second); - if (cls != nullptr) { - return get_ref_value(env, cls->constructor); - } - } - } - - auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)raw); - if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { - auto proto = bridgeState->getProtocol(env, protocolIt->second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } else { - const uintptr_t rawNormalized = normalizePtr(raw); - for (const auto& entry : bridgeState->mdProtocolsByPointer) { - if (normalizePtr((void*)entry.first) != rawNormalized) { - continue; - } - - auto proto = bridgeState->getProtocol(env, entry.second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } - - // Some protocol pointers come from compile-time @protocol() references - // and don't always match objc_getProtocol() pointer identity. - // Resolve them by scanning runtime protocol list and matching by address. - unsigned int protocolCount = 0; - Protocol** protocols = objc_copyProtocolList(&protocolCount); - if (protocols != nullptr) { - for (unsigned int i = 0; i < protocolCount; i++) { - Protocol* runtimeProto = protocols[i]; - if (normalizePtr((void*)runtimeProto) != rawNormalized) { - continue; - } - - const char* runtimeName = protocol_getName(runtimeProto); - MDSectionOffset metadataOffset = - findProtocolMetadataOffset(bridgeState->metadata, runtimeName); - if (metadataOffset != MD_SECTION_OFFSET_NULL) { - bridgeState->registerProtocolMetadata(runtimeProto, metadataOffset); - auto proto = bridgeState->getProtocol(env, metadataOffset); - bridgeState->registerRuntimeProtocol(proto, runtimeProto); - if (proto != nullptr) { - ::free(protocols); - return get_ref_value(env, proto->constructor); - } - } - - break; - } - ::free(protocols); - } - } - } - - if (pointeeType != nullptr && pointeeType->kind != mdTypeVoid) { - napi_value referenceValue = Reference::create(env, pointeeType, raw, false); - if (referenceValue != nullptr) { - return referenceValue; - } - } - - return Pointer::create(env, raw); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - void** res = (void**)result; - - auto unwrapKnownNativeHandle = [&](napi_value input, void** out) -> bool { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - napi_valuetype inputType = napi_undefined; - if (napi_typeof(env, input, &inputType) == napi_ok && - (inputType == napi_function || inputType == napi_object)) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, input, &bridgedType) && - bridgedType != nil) { - *out = (void*)bridgedType; - return true; - } - } - } - - void* wrapped = nullptr; - napi_status unwrapStatus = napi_unwrap(env, input, &wrapped); - if (unwrapStatus != napi_ok) { - bool hasNativePointer = false; - if (napi_has_named_property(env, input, "__ns_native_ptr", &hasNativePointer) == - napi_ok && - hasNativePointer) { - napi_value nativePointerValue = nullptr; - if (napi_get_named_property(env, input, "__ns_native_ptr", &nativePointerValue) == - napi_ok && - Pointer::isInstance(env, nativePointerValue)) { - Pointer* pointer = Pointer::unwrap(env, nativePointerValue); - if (pointer != nullptr && pointer->data != nullptr) { - *out = pointer->data; - return true; - } - } - } - return false; - } - - if (bridgeState != nullptr) { - for (const auto& entry : bridgeState->classes) { - auto bridgedClass = entry.second; - if (bridgedClass == wrapped) { - *out = (void*)bridgedClass->nativeClass; - return true; - } - } - - for (const auto& entry : bridgeState->protocols) { - auto bridgedProtocol = entry.second; - if (bridgedProtocol == wrapped) { - *out = (void*)objc_getProtocol(bridgedProtocol->name.c_str()); - return true; - } - } - } - - *out = wrapped; - return true; - }; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nullptr; - return; - - case napi_bigint: { - uint64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - *res = (void*)val; - return; - } - - case napi_string: { - size_t len = 0; - NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - char* str = (char*)malloc(len + 1); - - NAPI_GUARD(napi_get_value_string_utf8(env, value, str, len + 1, &len)) { - NAPI_THROW_LAST_ERROR - ::free(str); - return; - } - - str[len] = '\0'; - - bool shouldCreateCFString = - pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || - pointeeType->kind == mdTypeNSMutableStringObject); - - if (shouldCreateCFString) { - CFStringRef cfStr = - CFStringCreateWithCString(kCFAllocatorDefault, str, kCFStringEncodingUTF8); - ::free(str); - *res = (void*)cfStr; - *shouldFree = true; - *shouldFreeAny = true; - } else { - *res = (void*)str; - *shouldFree = true; - *shouldFreeAny = true; - } - return; - } - - case napi_external: { - NAPI_GUARD(napi_get_value_external(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_object: { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *res = (void*)bridgedType; - return; - } - } - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *res = ptr->data; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid Reference"); - *res = nullptr; - return; - } - if (ref->data == nullptr) { - std::shared_ptr resolvedType = pointeeType; - if (resolvedType == nullptr) { - resolvedType = ref->type; - } - - napi_value pendingInitValue = Reference::getInitValue(env, value, ref); - napi_valuetype pendingInitType = napi_undefined; - if (pendingInitValue != nullptr) { - napi_typeof(env, pendingInitValue, &pendingInitType); - } - if (resolvedType == nullptr && pendingInitValue != nullptr) { - if (pendingInitValue != nullptr) { - napi_valuetype initType = napi_undefined; - if (napi_typeof(env, pendingInitValue, &initType) == napi_ok) { - auto makeStructType = [&](StructInfo* info) -> std::shared_ptr { - if (info == nullptr || info->name == nullptr) { - return nullptr; - } - - std::string encoding = "{"; - encoding += info->name; - encoding += "="; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - return nullptr; - } - field.type->encode(&encoding); - } - encoding += "}"; - - const char* encodingPtr = encoding.c_str(); - return TypeConv::Make(env, &encodingPtr); - }; - - if (initType == napi_object) { - if (StructObject::isInstance(env, pendingInitValue)) { - StructObject* structObj = StructObject::unwrap(env, pendingInitValue); - if (structObj != nullptr) { - resolvedType = makeStructType(structObj->info); - } - } - - if (resolvedType == nullptr) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr) { - bool isArray = false; - bool isTypedArray = false; - bool isArrayBuffer = false; - bool isDataView = false; - napi_is_array(env, pendingInitValue, &isArray); - napi_is_typedarray(env, pendingInitValue, &isTypedArray); - napi_is_arraybuffer(env, pendingInitValue, &isArrayBuffer); - napi_is_dataview(env, pendingInitValue, &isDataView); - if (!isArray && !isTypedArray && !isArrayBuffer && !isDataView) { - napi_value propertyNames = nullptr; - if (napi_get_property_names(env, pendingInitValue, &propertyNames) == - napi_ok && - propertyNames != nullptr) { - uint32_t propertyCount = 0; - napi_get_array_length(env, propertyNames, &propertyCount); - std::unordered_set keys; - std::unordered_map keyIsInteger; - keys.reserve(propertyCount); - for (uint32_t i = 0; i < propertyCount; i++) { - napi_value keyValue = nullptr; - if (napi_get_element(env, propertyNames, i, &keyValue) != napi_ok || - keyValue == nullptr) { - continue; - } - napi_valuetype keyType = napi_undefined; - if (napi_typeof(env, keyValue, &keyType) != napi_ok || - keyType != napi_string) { - continue; - } - size_t keyLength = 0; - if (napi_get_value_string_utf8(env, keyValue, nullptr, 0, - &keyLength) != napi_ok) { - continue; - } - std::vector keyBuffer(keyLength + 1, '\0'); - if (napi_get_value_string_utf8(env, keyValue, keyBuffer.data(), - keyBuffer.size(), - &keyLength) != napi_ok) { - continue; - } - std::string key(keyBuffer.data(), keyLength); - keys.insert(key); - - napi_value propertyValue = nullptr; - if (napi_get_property(env, pendingInitValue, keyValue, - &propertyValue) == napi_ok && - propertyValue != nullptr) { - napi_valuetype propertyType = napi_undefined; - if (napi_typeof(env, propertyValue, &propertyType) == napi_ok) { - bool isInteger = false; - if (propertyType == napi_bigint) { - isInteger = true; - } else if (propertyType == napi_number) { - double numericValue = 0; - if (napi_get_value_double(env, propertyValue, &numericValue) == - napi_ok) { - int64_t truncated = static_cast(numericValue); - isInteger = static_cast(truncated) == numericValue; - } - } - keyIsInteger[key] = isInteger; - } - } - } - - if (!keys.empty()) { - auto isIntegerKind = [](MDTypeKind kind) -> bool { - switch (kind) { - case mdTypeChar: - case mdTypeSInt: - case mdTypeSShort: - case mdTypeSLong: - case mdTypeSInt64: - case mdTypeUChar: - case mdTypeUInt: - case mdTypeUShort: - case mdTypeUnichar: - case mdTypeULong: - case mdTypeUInt64: - case mdTypeUInt8: - case mdTypeBool: - return true; - default: - return false; - } - }; - - auto isFloatingKind = [](MDTypeKind kind) -> bool { - return kind == mdTypeFloat || kind == mdTypeDouble || - kind == mdTypeLongDouble || kind == mdTypeF16; - }; - - StructInfo* bestMatch = nullptr; - int bestScore = std::numeric_limits::min(); - uint16_t bestSize = std::numeric_limits::max(); - - for (const auto& entry : bridgeState->structOffsets) { - StructInfo* info = bridgeState->getStructInfo(env, entry.second); - if (info == nullptr || info->fields.size() != keys.size()) { - continue; - } - - bool match = true; - for (const auto& field : info->fields) { - if (field.name == nullptr || - keys.find(field.name) == keys.end()) { - match = false; - break; - } - } - if (!match) { - continue; - } - - int score = 0; - bool hasOnlyNumericFields = true; - for (const auto& field : info->fields) { - if (field.type == nullptr) { - hasOnlyNumericFields = false; - break; - } - - MDTypeKind fieldKind = field.type->kind; - if (isIntegerKind(fieldKind)) { - auto integerEntry = - keyIsInteger.find(field.name != nullptr ? field.name : ""); - score += - (integerEntry != keyIsInteger.end() && integerEntry->second) - ? 3 - : 1; - } else if (isFloatingKind(fieldKind)) { - score += 2; - } else { - hasOnlyNumericFields = false; - break; - } - } - - if (!hasOnlyNumericFields) { - continue; - } - - if (score > bestScore || - (score == bestScore && info->size < bestSize)) { - bestScore = score; - bestSize = info->size; - bestMatch = info; - } - } - - if (bestMatch != nullptr) { - resolvedType = makeStructType(bestMatch); - } - } - } - } - } - } - } - - if (resolvedType == nullptr) { - const char* inferredEncoding = "@"; - if (initType == napi_number || initType == napi_bigint) { - inferredEncoding = "q"; - } else if (initType == napi_boolean) { - inferredEncoding = "B"; - } - resolvedType = TypeConv::Make(env, &inferredEncoding); - } - } - } - } - - if (resolvedType == nullptr) { - const char* defaultEncoding = "@"; - resolvedType = TypeConv::Make(env, &defaultEncoding); - } - - ref->type = resolvedType; - size_t pointeeSize = sizeof(void*); - if (resolvedType != nullptr && resolvedType->type != nullptr && - resolvedType->type->size > 0) { - pointeeSize = resolvedType->type->size; - } - ref->data = calloc(1, pointeeSize); - if (ref->data == nullptr) { - napi_throw_error(env, nullptr, "Out of memory while allocating out parameter"); - *res = nullptr; - return; - } - ref->ownsData = true; - napi_value initValue = Reference::getInitValue(env, value, ref); - if (initValue != nullptr) { - bool shouldFree; - ref->type->toNative(env, initValue, ref->data, &shouldFree, &shouldFree); - Reference::clearInitValue(env, value, ref); - } - } - *res = ref->data; - return; - } - - if (StructObject::isInstance(env, value)) { - StructObject* structObj = StructObject::unwrap(env, value); - if (structObj != nullptr) { - *res = structObj->data; - } else - *res = nullptr; - return; - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - if (isTypedArray) { - void* data; - size_t length = 0; - napi_typedarray_type type; - NAPI_GUARD( - napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - - *res = data; - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - *res = data; - return; - } - break; - } - - case napi_function: { - if (unwrapKnownNativeHandle(value, res)) { - return; - } - break; - } - - default: - napi_throw_error(env, nullptr, "Invalid pointer type"); - *res = nullptr; - return; - } - - napi_throw_error(env, nullptr, "Invalid pointer type"); - *res = nullptr; - } - - void free(napi_env env, void* value) override { - if (value == nullptr) { - return; - } - - bool isCFString = pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || - pointeeType->kind == mdTypeNSMutableStringObject); - - if (isCFString) { - CFRelease((CFStringRef)value); - } else { - ::free(value); - } - } - - void encode(std::string* encoding) override { *encoding += "^v"; } -}; - -static const std::shared_ptr pointerTypeConv = std::make_shared(); - -class BlockTypeConv : public TypeConv { - public: - MDSectionOffset signatureOffset; - - BlockTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { - type = &ffi_type_pointer; - kind = mdTypeBlock; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* fn = *((void**)value); - if (fn == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - return FunctionPointer::wrap(env, fn, signatureOffset, true); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - void** res = (void**)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nullptr; - return; - - case napi_bigint: { - uint64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - *res = (void*)val; - return; - } - - case napi_external: { - NAPI_GUARD(napi_get_value_external(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_object: { - NAPI_GUARD(napi_unwrap(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_function: { - if (FunctionReference::isInstance(env, value)) { - FunctionReference* ref = FunctionReference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid FunctionReference"); - *res = nullptr; - return; - } - *res = ref->getFunctionPointer(signatureOffset, true); - return; - } - - void* wrapped; - status = napi_unwrap(env, value, &wrapped); - if (status == napi_ok) { - *res = wrapped; - return; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto closure = new Closure(env, bridgeState->metadata, signatureOffset, true); - id block = registerBlock(env, closure, value); - *res = (void*)block; - *shouldFree = true; - *shouldFreeAny = true; - return; - } - - default: - napi_throw_error(env, nullptr, "Invalid block pointer type"); - *res = nullptr; - return; - } - } - - void free(napi_env env, void* value) override { - if (value != nullptr) { - [(id)value release]; - } - } - - void encode(std::string* encoding) override { *encoding += "^v"; } -}; - -namespace { -void function_pointer_finalize_now(napi_env env, void* finalize_data, void* finalize_hint) { - Closure* closure = static_cast(finalize_hint); - if (closure != nullptr) { - Closure::destroyOnOwningThread(closure); - } -} -} // namespace - -void function_pointer_finalize(napi_env env, void* finalize_data, void* finalize_hint) { - if (PostFinalizer(env, function_pointer_finalize_now, finalize_data, finalize_hint)) { - return; - } - - function_pointer_finalize_now(env, finalize_data, finalize_hint); -} - -class FunctionPointerTypeConv : public TypeConv { - public: - MDSectionOffset signatureOffset; - - FunctionPointerTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { - type = &ffi_type_pointer; - kind = mdTypeFunctionPointer; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* fn = *((void**)value); - if (fn == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - return FunctionPointer::wrap(env, fn, signatureOffset, false); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - void** res = (void**)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nullptr; - return; - - case napi_bigint: { - uint64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - *res = (void*)val; - return; - } - - case napi_external: { - NAPI_GUARD(napi_get_value_external(env, value, res)) { - NAPI_THROW_LAST_ERROR - *res = nullptr; - return; - } - return; - } - - case napi_object: { - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *res = ptr->data; - } else if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *res = ref->data; - } else if (FunctionReference::isInstance(env, value)) { - FunctionReference* ref = FunctionReference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid FunctionReference"); - *res = nullptr; - return; - } - *res = ref->getFunctionPointer(signatureOffset, false); - } else { - napi_throw_error(env, nullptr, "Invalid function pointer object"); - *res = nullptr; - } - return; - } - - case napi_function: { - if (FunctionReference::isInstance(env, value)) { - FunctionReference* ref = FunctionReference::unwrap(env, value); - if (ref == nullptr) { - napi_throw_error(env, nullptr, "Invalid FunctionReference"); - *res = nullptr; - return; - } - *res = ref->getFunctionPointer(signatureOffset, false); - return; - } - - void* wrapped; - status = napi_unwrap(env, value, &wrapped); - if (status == napi_ok) { - *res = wrapped; - return; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto closure = new Closure(env, bridgeState->metadata, signatureOffset, false); - closure->func = make_ref(env, value); - napi_remove_wrap(env, value, nullptr); - napi_ref ref; - napi_wrap(env, value, closure->fnptr, function_pointer_finalize, closure, &ref); - *res = (void*)closure->fnptr; - return; - } - - default: - napi_throw_error(env, nullptr, "Invalid block pointer type"); - *res = nullptr; - return; - } - } - - void encode(std::string* encoding) override { *encoding += "^v"; } -}; - -class StringTypeConv : public TypeConv { - public: - StringTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeString; - } - - napi_value toJS(napi_env env, void* cont, uint32_t flags) override { - void* value = *((void**)cont); - if (value == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - if ((flags & kCStringAsReference) != 0) { - return Reference::create(env, scharTypeConv, value, false); - } - - napi_value result; - napi_create_string_utf8(env, (char*)value, NAPI_AUTO_LENGTH, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - napi_valuetype valuetype; - napi_typeof(env, value, &valuetype); - - if (valuetype == napi_null || valuetype == napi_undefined) { - *(char**)result = nullptr; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - if (valuetype == napi_object) { - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *(char**)result = (char*)ptr->data; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *(char**)result = (char*)ref->data; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - if (isTypedArray) { - void* data = nullptr; - size_t length = 0; - napi_typedarray_type typedArrayType; - napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); - *(char**)result = static_cast(data); - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - *(char**)result = static_cast(data); - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - bool isDataView = false; - napi_is_dataview(env, value, &isDataView); - if (isDataView) { - void* data = nullptr; - size_t byteLength = 0; - napi_value arrayBuffer = nullptr; - size_t byteOffset = 0; - napi_get_dataview_info(env, value, &byteLength, &data, &arrayBuffer, &byteOffset); - *(char**)result = static_cast(data); - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - *(char**)result = nullptr; - *shouldFree = false; - *shouldFreeAny = false; - return; - } - - char** res = (char**)result; - - *res = nullptr; - size_t len = 0; - - NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - *res = (char*)malloc(len + 1); - - NAPI_GUARD(napi_get_value_string_utf8(env, value, *res, len + 1, &len)) { - NAPI_THROW_LAST_ERROR - ::free(*res); - return; - } - - (*res)[len] = '\0'; - - *shouldFree = true; - *shouldFreeAny = true; - } - - void free(napi_env env, void* value) override { ::free(value); } - - void encode(std::string* encoding) override { *encoding += "*"; } -}; - -static const std::shared_ptr stringTypeConv = std::make_shared(); - -class ObjCObjectTypeConv : public TypeConv { - public: - MDSectionOffset classOffset = 0; - std::vector protocolOffsets; - - ObjCObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeAnyObject; - } - - ObjCObjectTypeConv(MDSectionOffset classOffset, std::vector protocolOffsets) - : classOffset(classOffset), protocolOffsets(protocolOffsets) { - type = &ffi_type_pointer; - if (classOffset != 0) { - kind = mdTypeClassObject; - } else { - kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; - } - } - - ObjCObjectTypeConv(std::vector protocolOffsets) - : protocolOffsets(protocolOffsets) { - type = &ffi_type_pointer; - kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - void* rawPtr = *((void**)value); - id obj = (__bridge id)rawPtr; - - if (obj == nil) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - - if (bridgeState != nullptr) { - if (object_isClass(obj)) { - if (napi_value constructor = findRegisteredClassConstructor(env, (Class)obj); - constructor != nullptr) { - return constructor; - } - } - - auto normalizePtr = [](void* ptr) -> uintptr_t { - return normalizeRuntimePointer(reinterpret_cast(ptr)); - }; - - auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)obj); - if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { - auto proto = bridgeState->getProtocol(env, protocolIt->second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } else { - const uintptr_t objNormalized = normalizePtr((void*)obj); - for (const auto& entry : bridgeState->mdProtocolsByPointer) { - if (normalizePtr((void*)entry.first) != objNormalized) { - continue; - } - - auto proto = bridgeState->getProtocol(env, entry.second); - if (proto != nullptr) { - return get_ref_value(env, proto->constructor); - } - } - } - } - - // Always unbox NSNull and CFBoolean/NSNumber values (except NSDecimalNumber), - // so primitive round-trips match historical runtime behavior. - if (isKindOfClassFast(obj, [NSNull class])) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - if (isKindOfClassFast(obj, [NSNumber class]) && - !isKindOfClassFast(obj, [NSDecimalNumber class])) { - if (CFGetTypeID((CFTypeRef)obj) == CFBooleanGetTypeID()) { - napi_value result; - napi_get_boolean(env, [obj boolValue], &result); - return result; - } - - napi_value result; - napi_create_double(env, [obj doubleValue], &result); - return result; - } - - // Untyped id values that are actually Objective-C blocks should be - // callable from JS. Preserve callback identity when we already have one. - if (isObjCBlockObject(obj)) { - napi_value cached = getCachedBlockCallback(env, (void*)obj); - if (cached != nullptr) { - return cached; - } - - const char* signature = getObjCBlockSignature((void*)obj); - if (signature != nullptr) { - return FunctionPointer::wrapWithEncoding(env, (void*)obj, signature, true); - } - } - - // Auto-unbox plain id string values. - const bool isUntypedObject = classOffset == 0 && protocolOffsets.empty(); - if (isUntypedObject && isKindOfClassFast(obj, [NSString class])) { - NSUInteger length = [obj length]; - std::vector chars(length > 0 ? length : 1); - if (length > 0) { - [((NSString*)obj) getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; - } - napi_value result; - napi_create_string_utf16(env, length > 0 ? chars.data() : nullptr, length, &result); - return result; - } - - if (bridgeState == nullptr) { - return Pointer::create(env, (void*)obj); - } - - if (napi_value existing = bridgeState->findCachedObjectWrapper(env, obj); existing != nullptr) { - return existing; - } - - ObjectOwnership ownership; - if ((flags & kReturnOwned) != 0) { - ownership = kOwnedObject; - } else { - ownership = kUnownedObject; - } - - auto object = bridgeState->getObject(env, obj, ownership, classOffset, &protocolOffsets); - if (object == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - return object; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - id* res = (id*)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_null: - case napi_undefined: - *res = nil; - return; - - case napi_string: { - size_t len = 0; - NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, 0, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - std::vector chars(len + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, value, chars.data(), len + 1, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - *res = [[[NSString alloc] initWithBytes:chars.data() - length:len - encoding:NSUTF8StringEncoding] autorelease]; - if (*res == nil) { - *res = [NSString string]; - } - break; - } - - case napi_number: { - double val = 0; - NAPI_GUARD(napi_get_value_double(env, value, &val)) { - NAPI_THROW_LAST_ERROR - return; - } - *res = [NSNumber numberWithDouble:val]; - break; - } - - case napi_boolean: { - bool val = false; - NAPI_GUARD(napi_get_value_bool(env, value, &val)) { - NAPI_THROW_LAST_ERROR - return; - } - *res = [NSNumber numberWithBool:val]; - break; - } - - case napi_bigint: { - int64_t val = 0; - bool lossless = false; - NAPI_GUARD(napi_get_value_bigint_int64(env, value, &val, &lossless)) { - NAPI_THROW_LAST_ERROR - return; - } - *res = [NSNumber numberWithLongLong:val]; - break; - } - - case napi_external: - NAPI_GUARD(napi_get_value_external(env, value, (void**)res)) { - NAPI_THROW_LAST_ERROR - *res = nil; - return; - } - break; - - case napi_object: - case napi_function: { - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto cacheRoundTrip = [&](id nativeObj) { - if (nativeObj == nil || bridgeState == nullptr || - !bridgeState->hasRoundTripCacheFrame()) { - return; - } - - bridgeState->cacheRoundTripObject(env, nativeObj, value); - }; - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - void* pointerData = ptr != nullptr ? ptr->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { - *res = cachedObject; - return; - } - *res = (id)pointerData; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - void* referenceData = ref != nullptr ? ref->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, referenceData); - cachedObject != nil) { - *res = cachedObject; - return; - } - *res = (id)referenceData; - return; - } - - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *res = bridgedType; - return; - } - } - - void* wrapped = nullptr; - status = napi_unwrap(env, value, &wrapped); - - if (status != napi_ok) { - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - *res = createNSDataWrapper(env, value, bridgeState); - if (*res != nil) { - cacheRoundTrip(*res); - return; - } - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - if (isTypedArray) { - *res = createNSDataWrapper(env, value, bridgeState); - if (*res != nil) { - cacheRoundTrip(*res); - return; - } - } - - bool isDataView = false; - napi_is_dataview(env, value, &isDataView); - if (isDataView) { - *res = createNSDataWrapper(env, value, bridgeState); - if (*res != nil) { - cacheRoundTrip(*res); - return; - } - } - - bool isArray = false; - napi_is_array(env, value, &isArray); - if (isArray) { - uint32_t len = 0; - napi_get_array_length(env, value, &len); - *res = [NSMutableArray arrayWithCapacity:len]; - - for (uint32_t i = 0; i < len; i++) { - napi_value elem; - napi_get_element(env, value, i, &elem); - id obj = nil; - toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); - [(*res) addObject:obj != nil ? obj : [NSNull null]]; - } - - cacheRoundTrip(*res); - return; - } else { - napi_value global, jsObject, valueConstructor, DateConstructor, MapConstructor; - napi_value StringConstructor, NumberConstructor, BooleanConstructor; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Object", &jsObject); - napi_get_named_property(env, global, "Date", &DateConstructor); - napi_get_named_property(env, global, "Map", &MapConstructor); - napi_get_named_property(env, global, "String", &StringConstructor); - napi_get_named_property(env, global, "Number", &NumberConstructor); - napi_get_named_property(env, global, "Boolean", &BooleanConstructor); - napi_get_named_property(env, value, "constructor", &valueConstructor); - bool isEqual; - napi_strict_equals(env, jsObject, valueConstructor, &isEqual); - bool isDate; - napi_strict_equals(env, DateConstructor, valueConstructor, &isDate); - bool isMap; - napi_strict_equals(env, MapConstructor, valueConstructor, &isMap); - bool isStringObject = false; - bool isNumberObject = false; - bool isBooleanObject = false; - napi_strict_equals(env, StringConstructor, valueConstructor, &isStringObject); - napi_strict_equals(env, NumberConstructor, valueConstructor, &isNumberObject); - napi_strict_equals(env, BooleanConstructor, valueConstructor, &isBooleanObject); - - if (isStringObject || isNumberObject || isBooleanObject) { - napi_value valueOfMethod; - napi_get_named_property(env, value, "valueOf", &valueOfMethod); - napi_value primitiveValue; - napi_call_function(env, value, valueOfMethod, 0, nullptr, &primitiveValue); - toNative(env, primitiveValue, result, shouldFree, shouldFreeAny); - return; - } - - if (isDate) { - // Get the timestamp from the JavaScript Date object - napi_value getTimeMethod; - napi_get_named_property(env, value, "getTime", &getTimeMethod); - napi_value timestamp; - napi_call_function(env, value, getTimeMethod, 0, nullptr, ×tamp); - - double timeInMilliseconds; - napi_get_value_double(env, timestamp, &timeInMilliseconds); - - // Convert milliseconds to seconds for NSDate - NSTimeInterval timeInSeconds = timeInMilliseconds / 1000.0; - *res = [NSDate dateWithTimeIntervalSince1970:timeInSeconds]; - cacheRoundTrip(*res); - return; - } - - if (isMap) { - *res = [NSMutableDictionary dictionary]; - - napi_value entriesMethod; - napi_get_named_property(env, value, "entries", &entriesMethod); - napi_value iterator; - napi_call_function(env, value, entriesMethod, 0, nullptr, &iterator); - - napi_value nextMethod; - napi_get_named_property(env, iterator, "next", &nextMethod); - - while (true) { - napi_value step; - napi_call_function(env, iterator, nextMethod, 0, nullptr, &step); - - napi_value doneValue; - napi_get_named_property(env, step, "done", &doneValue); - bool done = false; - napi_get_value_bool(env, doneValue, &done); - if (done) { - break; - } - - napi_value tuple; - napi_get_named_property(env, step, "value", &tuple); - napi_value keyValue; - napi_value elementValue; - napi_get_element(env, tuple, 0, &keyValue); - napi_get_element(env, tuple, 1, &elementValue); - - id keyObject = nil; - id valueObject = nil; - toNative(env, keyValue, (void*)&keyObject, shouldFree, shouldFreeAny); - toNative(env, elementValue, (void*)&valueObject, shouldFree, shouldFreeAny); - - if (keyObject != nil && valueObject != nil) { - [(*res) setObject:valueObject forKey:keyObject]; - } - } - - cacheRoundTrip(*res); - return; - } - - if (!isEqual) { - *res = jsObjectToId(env, value); - return; - } - - bool hasLength = false; - napi_has_named_property(env, value, "length", &hasLength); - if (hasLength) { - napi_value lengthValue; - napi_get_named_property(env, value, "length", &lengthValue); - napi_valuetype lengthType = napi_undefined; - napi_typeof(env, lengthValue, &lengthType); - if (lengthType == napi_number) { - uint32_t len = 0; - napi_get_value_uint32(env, lengthValue, &len); - *res = [NSMutableArray arrayWithCapacity:len]; - for (uint32_t i = 0; i < len; i++) { - bool hasElement = false; - napi_has_element(env, value, i, &hasElement); - if (!hasElement) { - [(*res) addObject:[NSNull null]]; - continue; - } - napi_value elem; - napi_get_element(env, value, i, &elem); - id obj = nil; - toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); - [(*res) addObject:obj != nil ? obj : [NSNull null]]; - } - cacheRoundTrip(*res); - return; - } - } - - *res = [NSMutableDictionary dictionary]; - napi_value objectKeysMethod = nullptr; - napi_get_named_property(env, jsObject, "keys", &objectKeysMethod); - napi_value keys = nullptr; - napi_call_function(env, jsObject, objectKeysMethod, 1, &value, &keys); - uint32_t len = 0; - napi_get_array_length(env, keys, &len); - - for (uint32_t i = 0; i < len; i++) { - napi_value key = nullptr; - napi_get_element(env, keys, i, &key); - - if (key == nullptr) { - continue; - } - - napi_value keyString = key; - napi_valuetype keyType = napi_undefined; - if (napi_typeof(env, key, &keyType) != napi_ok) { - continue; - } - - if (keyType == napi_symbol) { - continue; - } - - if (keyType != napi_string) { - if (napi_coerce_to_string(env, key, &keyString) != napi_ok || - keyString == nullptr) { - continue; - } - } - - size_t keyLength = 0; - if (napi_get_value_string_utf8(env, keyString, nullptr, 0, &keyLength) != napi_ok) { - continue; - } - - std::vector keyBuffer(keyLength + 1, '\0'); - if (napi_get_value_string_utf8(env, keyString, keyBuffer.data(), keyBuffer.size(), - &keyLength) != napi_ok) { - continue; - } - - NSString* nsKey = [NSString stringWithUTF8String:keyBuffer.data()]; - if (nsKey == nil) { - continue; - } - - id obj = nil; - napi_value elem = nullptr; - if (napi_get_property(env, value, key, &elem) != napi_ok) { - continue; - } - toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); - if (obj != nil) { - [(*res) setObject:obj forKey:nsKey]; - } - } - - cacheRoundTrip(*res); - return; - } - } - - if (bridgeState != nullptr && wrapped != nullptr) { - for (const auto& entry : bridgeState->classes) { - auto bridgedClass = entry.second; - if (bridgedClass == wrapped) { - *res = (id)bridgedClass->nativeClass; - return; - } - } - - for (const auto& entry : bridgeState->protocols) { - auto bridgedProtocol = entry.second; - if (bridgedProtocol != wrapped) { - continue; - } - - Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); - if (runtimeProtocol == nil) { - std::string baseName; - if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { - runtimeProtocol = objc_getProtocol(baseName.c_str()); - } - } - - if (runtimeProtocol != nil) { - *res = (id)runtimeProtocol; - return; - } - } - } - - *res = (id)wrapped; - cacheRoundTrip(*res); - return; - - break; - } - - default: - napi_throw_error(env, nullptr, "Invalid object type"); - *res = nil; - break; - } - } - - void free(napi_env env, void* value) override { - id obj = *((id*)value); - auto bridgeState = ObjCBridgeState::InstanceData(env); - bridgeState->unregisterObject(obj); - } - - void encode(std::string* encoding) override { *encoding += "@"; } -}; - -static const std::shared_ptr objcObjectTypeConv = - std::make_shared(); - -class ObjCInstanceObjectTypeConv : public ObjCObjectTypeConv { - public: - ObjCInstanceObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeInstanceObject; - } -}; - -static const auto objcInstanceObjectTypeConv = std::make_shared(); - -class ObjCNSStringObjectTypeConv : public TypeConv { - public: - ObjCNSStringObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeNSStringObject; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - NSString* str = *((NSString**)value); - - if (str == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - NSUInteger length = [str length]; - std::vector chars(length > 0 ? length : 1); - if (length > 0) { - [str getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; - } - napi_value result; - napi_create_string_utf16(env, chars.data(), length, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - ObjCObjectTypeConv typeConv; - typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); - } - - void encode(std::string* encoding) override { *encoding += "@"; } -}; - -static const std::shared_ptr objcNSStringObjectTypeConv = - std::make_shared(); - -class ObjCNSMutableStringObjectTypeConv : public TypeConv { - public: - ObjCNSMutableStringObjectTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeNSMutableStringObject; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - NSMutableString* str = *((NSMutableString**)value); - - if (str == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (napi_value existing = bridgeState->findCachedObjectWrapper(env, str); existing != nullptr) { - return existing; - } - - ObjectOwnership ownership = (flags & kReturnOwned) != 0 ? kOwnedObject : kUnownedObject; - return bridgeState->getObject(env, str, ownership); - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - napi_valuetype type; - napi_typeof(env, value, &type); - if (type == napi_string) { - NSMutableString** res = (NSMutableString**)result; - - size_t len = 0; - NAPI_GUARD(napi_get_value_string_utf16(env, value, nullptr, len, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - std::vector chars(len + 1); - - NAPI_GUARD(napi_get_value_string_utf16(env, value, chars.data(), len + 1, &len)) { - NAPI_THROW_LAST_ERROR - return; - } - - *res = [[NSMutableString alloc] initWithCharacters:(unichar*)chars.data() length:len]; - return; - } - - ObjCObjectTypeConv typeConv; - typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); - } - - void encode(std::string* encoding) override { *encoding += "@"; } -}; - -static const std::shared_ptr objcNSMutableStringObjectTypeConv = - std::make_shared(); - -class ObjCClassTypeConv : public TypeConv { - public: - ObjCClassTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeClass; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - Class cls = *((Class*)value); - - if (cls == nullptr) { - napi_value null; - napi_get_null(env, &null); - return null; - } - - if (napi_value constructor = findRegisteredClassConstructor(env, cls); - constructor != nullptr) { - return constructor; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - return bridgeState != nullptr ? bridgeState->getObject(env, (id)cls, kUnownedObject, 0, nullptr) - : nullptr; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - ObjCObjectTypeConv typeConv; - typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); - } - - void encode(std::string* encoding) override { *encoding += "#"; } -}; - -static const std::shared_ptr objcClassTypeConv = - std::make_shared(); - -char selector_name_buf[256]; - -class SelectorTypeConv : public TypeConv { - public: - SelectorTypeConv() { - type = &ffi_type_pointer; - kind = mdTypeSelector; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - SEL val = *((SEL*)value); - napi_create_string_utf8(env, sel_getName(val), NAPI_AUTO_LENGTH, &result); - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - SEL* res = (SEL*)result; - - napi_valuetype type; - napi_typeof(env, value, &type); - - switch (type) { - case napi_string: - NAPI_GUARD(napi_get_value_string_utf8(env, value, selector_name_buf, 256, NULL)) { - NAPI_THROW_LAST_ERROR - *res = NULL; - return; - } - *res = sel_registerName(selector_name_buf); - break; - - case napi_undefined: - case napi_null: - *res = NULL; - return; - - default: - napi_throw_error(env, nullptr, "Invalid selector type"); - *res = NULL; - return; - } - } - - void encode(std::string* encoding) override { *encoding += ":"; } -}; - -static const std::shared_ptr selectorTypeConv = - std::make_shared(); - -class StructTypeConv : public TypeConv { - public: - MDSectionOffset structOffset; - StructInfo* info = nullptr; - bool structInfoSearched = false; - - StructTypeConv(MDSectionOffset structOffset, ffi_type* type) : structOffset(structOffset) { - this->type = type; - kind = mdTypeStruct; - } - - // ~StructTypeConv() { delete type; } - - inline StructInfo* getInfo(napi_env env) { - if (!structInfoSearched) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - info = bridgeState->getStructInfo(env, structOffset); - structInfoSearched = true; - } - - return info; - } - - inline size_t getStructSize(napi_env env) { - if (this->type != nullptr && this->type->size > 0) { - return this->type->size; - } - - auto info = getInfo(env); - return info != nullptr ? info->size : 0; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - auto info = getInfo(env); - - if (info == nullptr) { - napi_value result; - void* data; - napi_create_arraybuffer(env, type->size, &data, &result); - memcpy(data, value, type->size); - return result; - } else { - return StructObject::fromNative(env, info, value, (flags & kStructZeroCopy) == 0); - } - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - const size_t structSize = getStructSize(env); - if (structSize == 0) { - napi_throw_type_error(env, "TypeError", "Invalid struct size"); - return; - } - - bool isTypedArray = false; - napi_is_typedarray(env, value, &isTypedArray); - - if (isTypedArray) { - void* data; - size_t length = 0; - napi_typedarray_type type; - NAPI_GUARD(napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { - NAPI_THROW_LAST_ERROR - return; - } - const size_t unitLength = getTypedArrayUnitLength(type); - const size_t byteLength = length * unitLength; - memset(result, 0, structSize); - memcpy(result, data, std::min(byteLength, structSize)); - - return; - } - - napi_valuetype type; - napi_typeof(env, value, &type); - - if (type == napi_null || type == napi_undefined) { - auto info = getInfo(env); - - if (info == nullptr) { - napi_throw_type_error(env, "TypeError", - "Invalid struct type, must be Struct Object, " - "Struct Object Descriptor or TypedArray"); - return; - } - - memset(result, 0, info->size); - return; - } else if (type != napi_object) { - napi_throw_type_error(env, "TypeError", - "Invalid struct type, must be Struct Object, " - "Struct Object Descriptor or TypedArray"); - return; - } - - auto structObject = StructObject::unwrap(env, value); - if (structObject != nullptr) { - const size_t copySize = std::min(static_cast(structObject->info->size), structSize); - memset(result, 0, structSize); - memcpy(result, structObject->data, copySize); - return; - } - - auto info = getInfo(env); - - if (info == nullptr) { - napi_throw_type_error(env, "TypeError", - "Invalid struct type, must be Struct Object or TypedArray"); - return; - } - - if (structSize < info->size) { - std::vector storage(info->size, 0); - StructObject(env, info, value, storage.data()); - memcpy(result, storage.data(), structSize); - return; - } - - // Serialize directly to previously allocated memory. - StructObject(env, info, value, result); - } -}; - -class ArrayTypeConv : public TypeConv { - public: - int arraySize; - std::shared_ptr elementType; - bool decayToPointerForArguments = false; - - ArrayTypeConv(int arraySize, std::shared_ptr elementType) - : arraySize(arraySize), elementType(elementType) { - auto arrayType = new ffi_type(); - arrayType->type = FFI_TYPE_STRUCT; - arrayType->size = 0; - arrayType->alignment = 0; - arrayType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (arraySize + 1)); - for (int i = 0; i < arraySize; i++) { - arrayType->elements[i] = elementType->type; - } - arrayType->elements[arraySize] = nullptr; - type = arrayType; - kind = mdTypeArray; - } - - ffi_type* ffiTypeForArgument() override { - decayToPointerForArguments = true; - return &ffi_type_pointer; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - if (decayToPointerForArguments) { - void* raw = *((void**)value); - if (raw == nullptr) { - napi_value nullValue; - napi_get_null(env, &nullValue); - return nullValue; - } - return Pointer::create(env, raw); - } - - napi_value result; - napi_create_array_with_length(env, arraySize, &result); - - size_t elementSize = getElementSize(); - - auto base = static_cast(value); - for (int i = 0; i < arraySize; i++) { - void* slot = base + (i * elementSize); - napi_value elementValue = elementType->toJS(env, slot, flags); - napi_valuetype elementValueType = napi_undefined; - napi_typeof(env, elementValue, &elementValueType); - if (elementValueType == napi_boolean) { - bool boolValue = false; - napi_get_value_bool(env, elementValue, &boolValue); - napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); - } - napi_set_element(env, result, i, elementValue); - if (StructObject::isInstance(env, elementValue)) { - napi_set_named_property(env, elementValue, "__ns_parent_struct_array", result); - } - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - if (decayToPointerForArguments) { - void** pointerResult = static_cast(result); - *pointerResult = nullptr; - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - return; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - *pointerResult = ptr != nullptr ? ptr->data : nullptr; - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - *pointerResult = ref != nullptr ? ref->data : nullptr; - return; - } - - if (StructObject::isInstance(env, value)) { - StructObject* structObject = StructObject::unwrap(env, value); - *pointerResult = structObject != nullptr ? structObject->data : nullptr; - return; - } - - size_t arrayByteSize = getArrayByteSize(); - if (arrayByteSize == 0) { - return; - } - void* copiedBuffer = malloc(arrayByteSize); - if (copiedBuffer == nullptr) { - napi_throw_error(env, nullptr, "Out of memory while converting C array argument"); - return; - } - - copyToInlineArrayStorage(env, value, copiedBuffer, shouldFree, shouldFreeAny); - - bool hasPendingException = false; - napi_is_exception_pending(env, &hasPendingException); - if (hasPendingException) { - ::free(copiedBuffer); - return; - } - - *pointerResult = copiedBuffer; - if (shouldFree != nullptr) { - *shouldFree = true; - } - if (shouldFreeAny != nullptr) { - *shouldFreeAny = true; - } - return; - } - - copyToInlineArrayStorage(env, value, result, shouldFree, shouldFreeAny); - } - - void free(napi_env env, void* value) override { - if (value != nullptr) { - ::free(value); - } - } - - void encode(std::string* encoding) override { - *encoding += "["; - *encoding += std::to_string(arraySize); - elementType->encode(encoding); - *encoding += "]"; - } - - private: - size_t getElementSize() const { - size_t elementSize = - elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; - if (elementSize == 0 && type != nullptr && arraySize > 0 && type->size >= (size_t)arraySize) { - elementSize = type->size / static_cast(arraySize); - } - if (elementSize == 0) { - elementSize = sizeof(void*); - } - return elementSize; - } - - size_t getArrayByteSize() const { return getElementSize() * static_cast(arraySize); } - - void copyToInlineArrayStorage(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) { - size_t elementSize = getElementSize(); - size_t arrayByteSize = getArrayByteSize(); - memset(result, 0, arrayByteSize); - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - return; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - if (ptr == nullptr || ptr->data == nullptr) { - return; - } - memcpy(result, ptr->data, arrayByteSize); - return; - } - - bool isArray = false; - napi_is_array(env, value, &isArray); - if (isArray) { - for (int i = 0; i < arraySize; i++) { - bool hasElement = false; - napi_has_element(env, value, i, &hasElement); - if (!hasElement) { - continue; - } - - napi_value elementValue; - napi_get_element(env, value, i, &elementValue); - void* slot = static_cast(result) + (i * elementSize); - elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); - } - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - memcpy(result, data, std::min(byteLength, arrayByteSize)); - return; - } - - void* data; - size_t length = 0; - napi_typedarray_type typedArrayType; - napi_status typedArrayStatus = - napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); - if (typedArrayStatus != napi_ok) { - NAPI_THROW_LAST_ERROR - return; - } - - size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); - memcpy(result, data, std::min(copyLength, arrayByteSize)); - } -}; - -class VectorTypeConv : public TypeConv { - public: - uint16_t vectorSize; - std::shared_ptr elementType; - MDTypeKind vectorKind; - - VectorTypeConv(MDTypeKind vectorKind, uint16_t vectorSize, std::shared_ptr elementType) - : vectorSize(vectorSize), elementType(elementType), vectorKind(vectorKind) { - auto vectorType = new ffi_type(); -#if defined(FFI_TYPE_EXT_VECTOR) - vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; -#else - vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; -#endif - size_t lanes = std::max(vectorSize, 1); - // 3-lane vectors are ABI-lowered to 4-lane storage on Apple platforms. - size_t abiLanes = lanes == 3 ? 4 : lanes; - vectorType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (abiLanes + 1)); - - ffi_type* elementFfiType = elementType != nullptr && elementType->type != nullptr - ? elementType->type - : &ffi_type_float; - const size_t elementSize = std::max(elementFfiType->size, sizeof(float)); - const size_t elementAlignment = - std::max(elementFfiType->alignment, static_cast(1)); - - for (size_t i = 0; i < abiLanes; i++) { - vectorType->elements[i] = elementFfiType; - } - vectorType->elements[abiLanes] = nullptr; - - size_t vectorAlignment = elementAlignment; - if (vectorKind != mdTypeComplex) { - size_t packedSize = abiLanes * elementSize; - size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; - vectorAlignment = std::max(vectorAlignment, preferredAlignment); - } - vectorAlignment = std::min(vectorAlignment, 16); - vectorType->alignment = static_cast(vectorAlignment); - vectorType->size = alignUp(abiLanes * elementSize, vectorAlignment); - - type = vectorType; - kind = vectorKind; - } - - napi_value toJS(napi_env env, void* value, uint32_t flags) override { - napi_value result; - napi_create_array_with_length(env, vectorSize, &result); - - size_t elementSize = getElementSize(); - auto base = static_cast(value); - for (uint16_t i = 0; i < vectorSize; i++) { - void* slot = base + (static_cast(i) * elementSize); - napi_value elementValue = elementType->toJS(env, slot, flags); - napi_valuetype elementValueType = napi_undefined; - napi_typeof(env, elementValue, &elementValueType); - if (elementValueType == napi_boolean) { - bool boolValue = false; - napi_get_value_bool(env, elementValue, &boolValue); - napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); - } - napi_set_element(env, result, i, elementValue); - } - return result; - } - - void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) override { - NAPI_PREAMBLE - - memset(result, 0, getVectorByteSize()); - - napi_valuetype valueType = napi_undefined; - napi_typeof(env, value, &valueType); - if (valueType == napi_null || valueType == napi_undefined) { - return; - } - - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - if (ptr != nullptr && ptr->data != nullptr) { - copyFromContiguousBuffer(ptr->data, getVectorByteSize(), result); - } - return; - } - - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - if (ref != nullptr && ref->data != nullptr) { - copyFromContiguousBuffer(ref->data, getVectorByteSize(), result); - } - return; - } - - if (StructObject::isInstance(env, value)) { - StructObject* structObject = StructObject::unwrap(env, value); - if (structObject != nullptr && structObject->data != nullptr) { - copyFromContiguousBuffer(structObject->data, structObject->info->size, result); - } - return; - } - - bool isArray = false; - napi_is_array(env, value, &isArray); - if (isArray) { - writeFromArrayElements(env, value, result, shouldFree, shouldFreeAny); - return; - } - - bool isArrayBuffer = false; - napi_is_arraybuffer(env, value, &isArrayBuffer); - if (isArrayBuffer) { - void* data = nullptr; - size_t byteLength = 0; - napi_get_arraybuffer_info(env, value, &data, &byteLength); - copyFromContiguousBuffer(data, byteLength, result); - return; - } - - void* data = nullptr; - size_t length = 0; - napi_typedarray_type typedArrayType = napi_int8_array; - napi_status typedArrayStatus = - napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); - if (typedArrayStatus == napi_ok) { - size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); - copyFromContiguousBuffer(data, copyLength, result); - return; - } - - napi_throw_type_error( - env, "TypeError", - "Invalid vector type, expected array, typed array, array buffer, pointer or reference."); - } - - void encode(std::string* encoding) override { - *encoding += "V"; - *encoding += std::to_string(vectorSize); - if (elementType != nullptr) { - elementType->encode(encoding); - } - } - - private: - inline size_t getElementSize() const { - size_t elementSize = - elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; - if (elementSize == 0) { - elementSize = sizeof(float); - } - return elementSize; - } - - inline size_t getVectorByteSize() const { - size_t expectedSize = static_cast(vectorSize) * getElementSize(); - if (type != nullptr && type->size > expectedSize) { - expectedSize = type->size; - } - return expectedSize; - } - - inline void copyFromContiguousBuffer(void* source, size_t sourceLength, void* destination) const { - memcpy(destination, source, std::min(sourceLength, getVectorByteSize())); - } - - void writeFromArrayElements(napi_env env, napi_value value, void* result, bool* shouldFree, - bool* shouldFreeAny) { - size_t elementSize = getElementSize(); - auto base = static_cast(result); - - for (uint16_t i = 0; i < vectorSize; i++) { - bool hasElement = false; - napi_has_element(env, value, i, &hasElement); - if (!hasElement) { - continue; - } - - napi_value elementValue; - napi_get_element(env, value, i, &elementValue); - void* slot = base + (static_cast(i) * elementSize); - elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); - } - } -}; - -std::shared_ptr TypeConv::Make(napi_env env, const char** encoding) { - char first = **encoding; - bool readonly = false; - if (first == 'r') { - readonly = true; - first = *(++(*encoding)); - } - - switch (first) { - case 'c': - (*encoding)++; - return scharTypeConv; - case 'i': - (*encoding)++; - return sint32TypeConv; - case 's': - (*encoding)++; - return sint16TypeConv; - case 'l': - case 'q': - (*encoding)++; - return sint64TypeConv; - case 'C': - (*encoding)++; - return uint8TypeConv; - case 'I': - (*encoding)++; - return uint32TypeConv; - case 'S': - (*encoding)++; - return uint16TypeConv; - case 'L': - case 'Q': - (*encoding)++; - return uint64TypeConv; - case 'f': - (*encoding)++; - return float32TypeConv; - case 'd': - (*encoding)++; - return float64TypeConv; - case 'B': - (*encoding)++; - return boolTypeConv; - case 'v': - (*encoding)++; - return voidTypeConv; - case '*': - (*encoding)++; - return stringTypeConv; - case '@': - (*encoding)++; - return objcObjectTypeConv; - case '#': - (*encoding)++; - return objcClassTypeConv; - case ':': - (*encoding)++; - return selectorTypeConv; - case '[': { - char c = **encoding; - std::string num; - while ((c = **encoding) >= '0' && c <= '9') { - num += c; - (*encoding)++; - } - auto arraySize = std::stoi(num); - auto elementType = TypeConv::Make(env, encoding); - while (**encoding != ']') { - (*encoding)++; - } // skip array type - (*encoding)++; // skip ']' - return std::make_shared(ArrayTypeConv(arraySize, elementType)); - } - case '{': { - std::string structname; - const char* c = *encoding + 1; - while (*c != '\0' && *c != '=') { - structname += *c; - c++; - } - if (*c != '=') { - while (**encoding != '\0' && **encoding != '}') { - (*encoding)++; - } - if (**encoding == '}') { - (*encoding)++; - } - return pointerTypeConv; - } - - // Check if we already have a cached StructTypeConv for this encoding-based struct - auto cacheIt = encodingStructCache.find(structname); - if (cacheIt != encodingStructCache.end()) { - return cacheIt->second; - } - - auto bridgeState = ObjCBridgeState::InstanceData(env); - MDSectionOffset structOffset = MD_SECTION_OFFSET_NULL; - if (bridgeState != nullptr) { - auto structOffsetIt = bridgeState->structOffsets.find(structname); - if (structOffsetIt != bridgeState->structOffsets.end()) { - structOffset = structOffsetIt->second; - } - } - auto type = typeFromStruct(env, encoding); - auto structTypeConv = std::make_shared(StructTypeConv(structOffset, type)); - - // Cache the StructTypeConv - encodingStructCache[structname] = structTypeConv; - - return structTypeConv; - } - case 'b': { - (*encoding)++; - char c = **encoding; - while ((c = **encoding) >= '0' && c <= '9') { - (*encoding)++; - } // skip bits - return uint64TypeConv; - } - case '^': - (*encoding)++; - TypeConv::Make(env, encoding); - return pointerTypeConv; - case '?': - // unknown type - return pointerTypeConv; - default: - std::cout << "getTypeInfo unknown encoding: " << *encoding << std::endl; - return pointerTypeConv; - } -} - -std::shared_ptr TypeConv::Make(napi_env env, MDMetadataReader* reader, - MDSectionOffset* offset, uint8_t opaquePointers) { - auto kind = reader->getTypeKind(*offset); - bool next = (MDTypeFlag)kind & mdTypeFlagNext; - kind = (MDTypeKind)((kind & ~mdTypeFlagNext) & ~mdTypeFlagVariadic); - *offset += sizeof(MDTypeKind); - - switch (kind) { - case mdTypeChar: { - return scharTypeConv; - } - - case mdTypeSInt: { - return sint32TypeConv; - } - - case mdTypeSShort: { - return sint16TypeConv; - } - - case mdTypeSLong: - case mdTypeSInt64: { - return sint64TypeConv; - } - - case mdTypeUInt8: { - return uint8TypeConv; - } - - case mdTypeUChar: { - return ucharTypeConv; - } - - case mdTypeUInt: { - return uint32TypeConv; - } - - case mdTypeUShort: { - return uint16TypeConv; - } - - case mdTypeUnichar: { - return unicharTypeConv; - } - - case mdTypeULong: - case mdTypeUInt64: { - return uint64TypeConv; - } - - case mdTypeFloat: { - return float32TypeConv; - } - - case mdTypeF16: { - return float16TypeConv; - } - - case mdTypeDouble: { - return float64TypeConv; - } - - case mdTypeBool: { - return boolTypeConv; - } - - case mdTypeVoid: { - return voidTypeConv; - } - - case mdTypeString: { - return stringTypeConv; - } - - case mdTypeAnyObject: { - return objcObjectTypeConv; - } - - case mdTypeInstanceObject: { - return objcInstanceObjectTypeConv; - } - - case mdTypeClassObject: { - auto classOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool next = (classOffset & mdSectionOffsetNext) != 0; - classOffset &= ~mdSectionOffsetNext; - if (classOffset == MD_SECTION_OFFSET_NULL) { - classOffset = 0; - } else { - classOffset += reader->classesOffset; - } - std::vector protocolOffsets; - while (next) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & mdSectionOffsetNext) != 0; - protocolOffset &= ~mdSectionOffsetNext; - if (protocolOffset == MD_SECTION_OFFSET_NULL) { - protocolOffset = 0; - } else { - protocolOffset += reader->protocolsOffset; - protocolOffsets.push_back(protocolOffset); - } - } - return std::make_shared(classOffset, protocolOffsets); - } - - case mdTypeProtocolObject: { - std::vector protocolOffsets; - bool next = true; - while (next) { - auto protocolOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & mdSectionOffsetNext) != 0; - protocolOffset &= ~mdSectionOffsetNext; - if (protocolOffset == MD_SECTION_OFFSET_NULL) { - protocolOffset = 0; - } else { - protocolOffset += reader->protocolsOffset; - protocolOffsets.push_back(protocolOffset); - } - } - return std::make_shared(protocolOffsets); - } - - case mdTypeNSStringObject: { - return objcNSStringObjectTypeConv; - } - - case mdTypeNSMutableStringObject: { - return objcNSMutableStringObjectTypeConv; - } - - case mdTypeClass: { - return objcClassTypeConv; - } - - case mdTypeSelector: { - return selectorTypeConv; - } - - case mdTypeArray: { - auto arraySize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset); - return std::make_shared(ArrayTypeConv(arraySize, elementType)); - } - - case mdTypeStruct: { - auto structOffset = reader->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - auto isUnion = (structOffset & mdSectionOffsetNext) != 0; - structOffset &= ~mdSectionOffsetNext; - if (structOffset == MD_SECTION_OFFSET_NULL) { - return pointerTypeConv; - } - structOffset += isUnion ? reader->unionsOffset : reader->structsOffset; - auto structName = reader->getString(structOffset); - - // Check if we already have a cached StructTypeConv for this struct - auto cacheIt = structTypeCache.find(structOffset); - if (cacheIt != structTypeCache.end()) { - return cacheIt->second; - } - - // Check if we're currently processing this struct (recursion detection) - bool isRecursive = processingStructs.find(structOffset) != processingStructs.end(); - - ffi_type* type = nullptr; - if (opaquePointers != 2 && !isRecursive) { - type = typeFromStruct(env, reader, structOffset, isUnion); - } - - auto structTypeConv = std::make_shared(structOffset, type); - - // Cache the StructTypeConv to handle recursion and avoid duplicates - structTypeCache[structOffset] = structTypeConv; - - return structTypeConv; - } - - case mdTypePointer: { - auto pointeeType = TypeConv::Make(env, reader, offset, opaquePointers == 1 ? 2 : 0); - return std::make_shared(pointeeType); - } - - case mdTypeOpaquePointer: { - return pointerTypeConv; - } - - case mdTypeVector: { - auto vectorSize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); - return std::make_shared(kind, vectorSize, elementType); - } - - case mdTypeBlock: { - auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; - *offset += sizeof(MDSectionOffset); - return std::make_shared(blockSignature); - } - - case mdTypeFunctionPointer: { - auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; - *offset += sizeof(MDSectionOffset); - return std::make_shared(blockSignature); - } - - case mdTypeUInt128: { - return uint128TypeConv; - } - - case mdTypeExtVector: { - auto vectorSize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); - return std::make_shared(kind, vectorSize, elementType); - } - - case mdTypeComplex: { - auto vectorSize = reader->getArraySize(*offset); - *offset += sizeof(uint16_t); - auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); - return std::make_shared(kind, vectorSize, elementType); - } - - default: - return pointerTypeConv; - } -} - -namespace { - -bool tryFastConvertStringToNSString(napi_env env, napi_value value, id* out, bool mutableString) { - if (out == nullptr) { - return false; - } - - if (mutableString) { - constexpr size_t kStackUtf16Capacity = 128; - char16_t utf16Stack[kStackUtf16Capacity]; - char16_t* utf16Buffer = utf16Stack; - size_t utf16Capacity = kStackUtf16Capacity; - size_t utf16Length = 0; - if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != - napi_ok) { - return false; - } - - std::vector utf16Heap; - if (utf16Length + 1 >= utf16Capacity) { - if (napi_get_value_string_utf16(env, value, nullptr, 0, &utf16Length) != napi_ok) { - return false; - } - utf16Heap.resize(utf16Length + 1, 0); - utf16Buffer = utf16Heap.data(); - utf16Capacity = utf16Heap.size(); - if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != - napi_ok) { - return false; - } - } - - *out = [[NSMutableString alloc] initWithCharacters:reinterpret_cast(utf16Buffer) - length:utf16Length]; - return true; - } - - constexpr size_t kStackUtf8Capacity = 256; - char utf8Stack[kStackUtf8Capacity]; - char* utf8Buffer = utf8Stack; - size_t utf8Capacity = kStackUtf8Capacity; - size_t utf8Length = 0; - if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { - return false; - } - - std::vector utf8Heap; - if (utf8Length + 1 >= utf8Capacity) { - if (napi_get_value_string_utf8(env, value, nullptr, 0, &utf8Length) != napi_ok) { - return false; - } - utf8Heap.resize(utf8Length + 1, '\0'); - utf8Buffer = utf8Heap.data(); - utf8Capacity = utf8Heap.size(); - if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { - return false; - } - } - - id stringValue = [[[NSString alloc] initWithBytes:utf8Buffer - length:utf8Length - encoding:NSUTF8StringEncoding] autorelease]; - *out = stringValue != nil ? stringValue : [NSString string]; - return true; -} - -bool tryFastConvertObjCObjectValue(napi_env env, napi_value value, napi_valuetype valueType, - MDTypeKind kind, id* out) { - if (out == nullptr) { - return false; - } - - switch (valueType) { - case napi_null: - case napi_undefined: - *out = nil; - return true; - - case napi_external: { - void* external = nullptr; - if (napi_get_value_external(env, value, &external) != napi_ok) { - return false; - } - *out = static_cast(external); - return true; - } - - case napi_number: { - double numericValue = 0; - if (napi_get_value_double(env, value, &numericValue) != napi_ok) { - return false; - } - *out = [NSNumber numberWithDouble:numericValue]; - return true; - } - - case napi_boolean: { - bool boolValue = false; - if (napi_get_value_bool(env, value, &boolValue) != napi_ok) { - return false; - } - *out = [NSNumber numberWithBool:boolValue]; - return true; - } - - case napi_bigint: { - int64_t bigintValue = 0; - bool lossless = false; - if (napi_get_value_bigint_int64(env, value, &bigintValue, &lossless) != napi_ok) { - return false; - } - *out = [NSNumber numberWithLongLong:bigintValue]; - return true; - } - - case napi_string: - return tryFastConvertStringToNSString(env, value, out, kind == mdTypeNSMutableStringObject); - - case napi_object: - case napi_function: { - auto bridgeState = ObjCBridgeState::InstanceData(env); - auto cacheRoundTrip = [&](id nativeObj) { - if (nativeObj == nil || bridgeState == nullptr || !bridgeState->hasRoundTripCacheFrame()) { - return; - } - - bridgeState->cacheRoundTripObject(env, nativeObj, value); - }; - - if (valueType == napi_object) { - if (Pointer::isInstance(env, value)) { - Pointer* ptr = Pointer::unwrap(env, value); - void* pointerData = ptr != nullptr ? ptr->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { - *out = cachedObject; - return true; - } - *out = (id)pointerData; - return true; - } - if (Reference::isInstance(env, value)) { - Reference* ref = Reference::unwrap(env, value); - void* referenceData = ref != nullptr ? ref->data : nullptr; - if (id cachedObject = resolveCachedHandleObject(env, referenceData); - cachedObject != nil) { - *out = cachedObject; - return true; - } - *out = (id)referenceData; - return true; - } - } - - if (bridgeState != nullptr) { - id bridgedType = nil; - if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && - bridgedType != nil) { - *out = bridgedType; - return true; - } - } - - void* wrapped = nullptr; - if (napi_unwrap(env, value, &wrapped) == napi_ok) { - if (valueType == napi_function || valueType == napi_object) { - auto bridgeState = ObjCBridgeState::InstanceData(env); - if (bridgeState != nullptr && wrapped != nullptr) { - for (const auto& entry : bridgeState->classes) { - auto bridgedClass = entry.second; - if (bridgedClass == wrapped) { - *out = (id)bridgedClass->nativeClass; - return true; - } - } - - for (const auto& entry : bridgeState->protocols) { - auto bridgedProtocol = entry.second; - if (bridgedProtocol == wrapped) { - Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); - if (runtimeProtocol == nil) { - std::string baseName; - if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { - runtimeProtocol = objc_getProtocol(baseName.c_str()); - } - } - if (runtimeProtocol != nil) { - *out = (id)runtimeProtocol; - return true; - } - } - } - } - } - - *out = (id)wrapped; - cacheRoundTrip(*out); - return true; - } - - bool isTypedArray = false; - if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { - *out = createNSDataWrapper(env, value, bridgeState); - if (*out != nil) { - cacheRoundTrip(*out); - return true; - } - return false; - } - - bool isArrayBuffer = false; - if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { - *out = createNSDataWrapper(env, value, bridgeState); - if (*out != nil) { - cacheRoundTrip(*out); - return true; - } - return false; - } - - bool isDataView = false; - if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { - *out = createNSDataWrapper(env, value, bridgeState); - if (*out != nil) { - cacheRoundTrip(*out); - return true; - } - return false; - } - - return false; - } - - default: - return false; - } -} - -} // namespace - -bool TryFastConvertNapiArgument(napi_env env, MDTypeKind kind, napi_value value, void* result) { - if (result == nullptr || value == nullptr) { - return false; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return tryFastConvertObjCObjectValue(env, value, valueType, static_cast(kind), - reinterpret_cast(result)); - - case mdTypeSelector: { - SEL* selector = reinterpret_cast(result); - switch (valueType) { - case napi_null: - case napi_undefined: - *selector = nullptr; - return true; - - case napi_string: { - constexpr size_t kStackSelectorCapacity = 128; - char selectorStack[kStackSelectorCapacity]; - size_t selectorLength = 0; - if (napi_get_value_string_utf8(env, value, selectorStack, kStackSelectorCapacity, - &selectorLength) != napi_ok) { - return false; - } - const char* selectorName = selectorStack; - std::vector selectorHeap; - if (selectorLength + 1 >= kStackSelectorCapacity) { - if (napi_get_value_string_utf8(env, value, nullptr, 0, &selectorLength) != napi_ok) { - return false; - } - selectorHeap.resize(selectorLength + 1, '\0'); - if (napi_get_value_string_utf8(env, value, selectorHeap.data(), selectorHeap.size(), - &selectorLength) != napi_ok) { - return false; - } - selectorName = selectorHeap.data(); - } - *selector = sel_registerName(selectorName); - return true; - } - - default: - return false; - } - } - - default: - return false; - } -} - -bool TryFastConvertNapiUInt16Argument(napi_env env, napi_value value, uint16_t* result) { - if (result == nullptr || value == nullptr) { - return false; - } - - napi_valuetype valueType = napi_undefined; - if (napi_typeof(env, value, &valueType) != napi_ok) { - return false; - } - - if (valueType == napi_string) { - size_t strLen = 0; - if (napi_get_value_string_utf16(env, value, nullptr, 0, &strLen) != napi_ok) { - return false; - } - if (strLen != 1) { - napi_throw_type_error(env, nullptr, "Expected a single-character string."); - *result = 0; - return false; - } - - char16_t chars[2] = {0, 0}; - if (napi_get_value_string_utf16(env, value, chars, 2, &strLen) != napi_ok) { - return false; - } - - *result = static_cast(chars[0]); - return true; - } - - napi_value coerced = value; - if (napi_coerce_to_number(env, value, &coerced) != napi_ok) { - return false; - } - - uint32_t converted = 0; - if (napi_get_value_uint32(env, coerced, &converted) != napi_ok) { - return false; - } - - *result = static_cast(converted); - return true; -} - -// Cleanup function to clear thread-local caches -void clearStructTypeCaches() { - processingStructs.clear(); - processingEncodingStructs.clear(); - forwardDeclaredStructs.clear(); - forwardDeclaredEncodingStructs.clear(); - structTypeCache.clear(); - encodingStructCache.clear(); -} - -} // namespace nativescript diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.h b/NativeScript/ffi/objc/hermes/NativeApiJsi.h new file mode 100644 index 000000000..b37401f30 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.h @@ -0,0 +1,13 @@ +#ifndef NATIVE_API_JSI_H_FFI_FORWARD +#define NATIVE_API_JSI_H_FFI_FORWARD + +// Moved to jsi/hermes/NativeApiJsi.h. Hermes is the one backend that exposes +// the real facebook::jsi API rather than reimplementing the nativescript +// engine shape, and this declaration is already platform-neutral, so Android's +// Hermes backend can share it verbatim. +// +// This forwarding header keeps the Apple include paths unchanged. + +#include "jsi/hermes/NativeApiJsi.h" + +#endif // NATIVE_API_JSI_H_FFI_FORWARD diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsi.mm b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm new file mode 100644 index 000000000..5f3e76e38 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm @@ -0,0 +1,285 @@ +#include "NativeApiJsi.h" + +#ifdef TARGET_ENGINE_HERMES + +#import +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" +#include "NativeApiJsiSignatureDispatch.h" +#ifndef NATIVESCRIPT_REACT_NATIVE +// js_jsr_for_runtime: maps the bridge's unsafe jsi::Runtime back to the +// ThreadSafeRuntime that guards it (see NativeApiRuntimeScope below). Only the +// standalone runtime owns a JSR; @nativescript/react-native ships this +// translation unit without napi/, so the include must not be reached there. +#include "napi/hermes/jsr.h" +#endif + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace { + +using facebook::jsi::Array; +using facebook::jsi::ArrayBuffer; +using facebook::jsi::BigInt; +using facebook::jsi::Function; +using facebook::jsi::HostObject; +using facebook::jsi::MutableBuffer; +using facebook::jsi::Object; +using facebook::jsi::PropNameID; +using facebook::jsi::Runtime; +using facebook::jsi::String; +using facebook::jsi::StringBuffer; +using facebook::jsi::Value; +using facebook::jsi::JSError; + +using NativeApiConfig = NativeApiJsiConfig; +using NativeApiScheduler = NativeApiJsiScheduler; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function setPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "setPrototypeOf"); + setPrototypeOf.call(runtime, Value(runtime, object), Value(runtime, prototype)); +} + +// Entering the VM has to take the ThreadSafeRuntime lock, because native +// callbacks and host-object accessors run on whatever thread the platform +// hands them to -- an NSOperationQueue worker, a URLSession delegate queue -- +// and the bridge only holds getUnsafeRuntime(). Without this, Callbacks.mm +// falls back to its empty default scope and lets two threads into the +// interpreter at once, which corrupts the handle stack and GC roots. V8 +// supplies the same thing as a v8::Locker (see NativeApiV8RuntimeSupport.mm). +// HermesMutex is a recursive_mutex, so nesting these is safe. +// +// Not under @nativescript/react-native: there the bridge is installed into a +// jsi::Runtime React Native created and owns, so no JSR exists to look up and +// serialization is React Native's job (its JS thread and CallInvoker). That +// build falls through to the empty default scope in Callbacks.mm, which is what +// it has always used. The guard is negative on purpose -- a build that forgets +// to define the macro keeps the lock rather than silently losing it. +#ifndef NATIVESCRIPT_REACT_NATIVE +#define NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE 1 + +class NativeApiRuntimeScope final { + public: + explicit NativeApiRuntimeScope(Runtime& runtime) + : jsr_(js_jsr_for_runtime(&runtime)) { + if (jsr_ != nullptr) { + jsr_->lock(); + } + } + + ~NativeApiRuntimeScope() { + if (jsr_ != nullptr) { + jsr_->unlock(); + } + } + + NativeApiRuntimeScope(const NativeApiRuntimeScope&) = delete; + NativeApiRuntimeScope& operator=(const NativeApiRuntimeScope&) = delete; + + private: + JSR* jsr_; +}; +#endif // NATIVESCRIPT_REACT_NATIVE + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_RUNTIME_NAME "jsi" +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "hermes" +#define NATIVESCRIPT_NATIVE_API_HOST_SET_VOID 1 +#define NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 +#include "../shared/bridge/ObjCBridge.mm" +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + +#include "NativeApiJsiGsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || + !prepared.gsdEngineCallable || dispatchSuperClass != Nil || + count != prepared.gsdEngineArgumentCount) { + return false; + } + + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, args, + prepared.signature.returnType}; + if (!invoker(ctx)) { + return false; + } + *result = std::move(ctx.result); + return true; +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + NativeApiSelectorGroupState state( + std::move(bridge), lookupClass, receiverIsClass, std::move(selectors), + std::move(preparedInvocations), std::move(boundReceiver), + std::move(boundReceiverState)); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__nativeSelectorGroup"), 0, + [state = std::move(state)]( + Runtime& runtime, const Value& thisValue, const Value* args, + size_t count) mutable -> Value { + NativeApiRoundTripCacheFrameGuard roundTripFrame(state.bridge); + std::shared_ptr receiverHostObject; + auto resolveReceiverHost = [&]() { + if (receiverHostObject) { + return receiverHostObject; + } + if (state.boundReceiverState != nullptr) { + receiverHostObject = state.boundReceiver.lock(); + } + // Fall through rather than else-if: the bound receiver is weak, so a + // collected wrapper leaves it empty, and the call still has a perfectly + // good receiver in thisValue. Binding must not turn a live call into + // "requires a native receiver". SelectorGroupCall.h already resolves it + // this way; this lambda was the one place that did not. + if (!receiverHostObject && thisValue.isObject()) { + Object receiverObject = thisValue.asObject(runtime); + if (receiverObject.isHostObject( + runtime)) { + receiverHostObject = + receiverObject.getHostObject( + runtime); + } + } + return receiverHostObject; + }; + auto call = resolveNativeApiSelectorGroupCall( + runtime, state, count, + [&]() -> id { + auto host = resolveReceiverHost(); + return host != nullptr ? host->object() : nil; + }, + resolveReceiverHost, + [](uint64_t dispatchId) { + return lookupObjCGsdInvoker(dispatchId); + }); + if (call.hasImmediateResult) { + return std::move(call.immediateResult); + } + + // GSD fast path: read jsi args directly, call objc_msgSend with a + // typed cast, produce the jsi return value — bypassing all generic + // marshalling. Only engages for plain calls (no super dispatch, init + // disown handling, or implicit NSError-out argument). + if (call.prepared->gsdEngineCallable && call.dispatchClass == Nil && + count == call.prepared->gsdEngineArgumentCount && + !(!state.receiverIsClass && call.prepared->isInitMethod)) { + auto invoker = + reinterpret_cast(call.prepared->engineInvoker); + GsdObjCContext ctx{runtime, state.bridge, call.receiver, + call.prepared->selector, args, + call.prepared->signature.returnType}; + if (invoker(ctx)) { + return std::move(ctx.result); + } + } + + if (state.receiverIsClass) { + return callPreparedObjCSelector(runtime, state.bridge, call.receiver, + true, *call.prepared, args, count, + Nil); + } + if (!receiverHostObject) { + receiverHostObject = resolveReceiverHost(); + } + if (!receiverHostObject) { + throw JSError(runtime, + "Objective-C selector requires a native receiver."); + } + return receiverHostObject->callPreparedObjectSelector( + runtime, *call.prepared, args, count, call.dispatchClass); + }); +} + +} // namespace + +#include "../shared/bridge/Install.mm" + +Object CreateNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { + return CreateNativeApi(runtime, config); +} + +void InstallNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { + InstallNativeApi(runtime, config); +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApiJSI(facebook::jsi::Runtime* runtime, + const char* metadataPath) { + if (runtime == nullptr) { + return; + } + nativescript::NativeApiJsiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApiJSI(*runtime, config); +} + +#endif // TARGET_ENGINE_HERMES diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsiGsd.mm b/NativeScript/ffi/objc/hermes/NativeApiJsiGsd.mm new file mode 100644 index 000000000..43ba137f9 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsiGsd.mm @@ -0,0 +1,165 @@ +// --- GSD (Generated Signature Dispatch) for Hermes/JSI --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads jsi::Value arguments and writes the jsi::Value return value using +// the shared engine-neutral conversion helpers (which already operate on the +// jsi value type). Readers require the fast representation; anything else +// makes a reader return false so the invoker falls back to the generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + const Value* arguments; + const NativeApiType& returnType; + Value result = Value::undefined(); + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + bool readNumber(size_t i, double* out) { + const Value& v = arguments[i]; + if (!v.isNumber()) return false; + *out = v.asNumber(); + return true; + } + bool readBool(size_t i, uint8_t* out) { + const Value& v = arguments[i]; + if (!v.isBool()) return false; + *out = v.getBool() ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + template + bool readUnsigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { return readNumber(i, out); } + bool readSelector(size_t i, SEL* out) { + return readFastEngineSelectorArgument(runtime, arguments[i], out); + } + bool readClass(size_t i, Class* out) { + Class cls = classFromEngineValue(runtime, arguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + const Value& v = arguments[i]; + if (v.isNull() || v.isUndefined()) { + *out = nil; + return true; + } + if (!v.isObject()) return false; + Object o = v.getObject(runtime); + if (o.isHostObject(runtime)) { + *out = o.getHostObject(runtime)->object(); + return true; + } + if (o.isHostObject(runtime)) { + *out = static_cast( + o.getHostObject(runtime)->nativeClass()); + return true; + } + Class cls = classFromEngineValue(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (o.isHostObject(runtime)) { + *out = static_cast( + o.getHostObject(runtime) + ->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() { result = Value::undefined(); } + void setBool(bool v) { result = Value(v); } + void setInt32(int32_t v) { result = Value(static_cast(v)); } + void setUInt32(uint32_t v) { result = Value(static_cast(v)); } + void setUInt16(uint16_t v) { + result = Value(static_cast(v)); + } + void setInt64(int64_t v) { result = signedInteger64ToEngineValue(runtime, v); } + void setUInt64(uint64_t v) { + result = unsignedInteger64ToEngineValue(runtime, v); + } + void setDouble(double v) { result = Value(v); } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + result = name != nullptr ? makeString(runtime, name) : Value::null(); + } + void setClass(Class v) { + if (v == nil) { + result = Value::null(); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + } + void setObject(id obj) { + result = convertNativeReturnValue(runtime, bridge, returnType, &obj); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript; GsdObjCContext/ObjCGsdDispatchEntry stay reachable +// via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("GeneratedGsdSignatureDispatch.inc") +#include "GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/hermes/jsi/NativeApiJsiReactNative.h b/NativeScript/ffi/objc/hermes/NativeApiJsiReactNative.h similarity index 100% rename from NativeScript/ffi/hermes/jsi/NativeApiJsiReactNative.h rename to NativeScript/ffi/objc/hermes/NativeApiJsiReactNative.h diff --git a/NativeScript/ffi/objc/hermes/NativeApiJsiSignatureDispatch.h b/NativeScript/ffi/objc/hermes/NativeApiJsiSignatureDispatch.h new file mode 100644 index 000000000..997e99ece --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsiSignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NS_FFI_HERMES_NATIVE_API_JSI_SIGNATURE_DISPATCH_H +#define NS_FFI_HERMES_NATIVE_API_JSI_SIGNATURE_DISPATCH_H + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +#if defined(__has_include) +#if __has_include("GeneratedSignatureDispatch.inc") +#include "GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NS_FFI_HERMES_NATIVE_API_JSI_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/objc/hermes/README.md b/NativeScript/ffi/objc/hermes/README.md new file mode 100644 index 000000000..35c04d97a --- /dev/null +++ b/NativeScript/ffi/objc/hermes/README.md @@ -0,0 +1,23 @@ +# Native API Hermes JSI backend + +This directory owns the Hermes-facing Native API entrypoint: + +- `NativeApiJsi.h` exposes the public JSI install/create API. +- `NativeApiJsi.mm` binds Hermes JSI types to the Hermes-owned bridge + implementation files in this directory. +- `NativeApiJsiReactNative.h` adapts React Native `CallInvoker`s to the JSI + scheduler config used by the TurboModule. +- `NativeApiJsiSignatureDispatch.h` wires Hermes generated signature dispatch + tables into native invocation. + +Hermes is the only backend that exposes the real `facebook::jsi` API. V8, JSC, +and QuickJS own their bridge implementations in their respective engine +directories. + +React Native integrations should include `NativeApiJsiReactNative.h` from a +TurboModule implementation and pass the module's JS/UI `CallInvoker`s: + +```cpp +nativescript::InstallReactNativeNativeApiJSI( + runtime, jsInvoker, uiInvoker, metadataPath, metadataPtr); +``` diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSC.h b/NativeScript/ffi/objc/jsc/NativeApiJSC.h new file mode 100644 index 000000000..cd03fd630 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSC.h @@ -0,0 +1,20 @@ +#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H +#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H + +#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include + +namespace nativescript { + +using NativeApiScheduler = NativeApiBackendScheduler; +using NativeApiConfig = NativeApiBackendConfig; + +void InstallNativeApi(JSGlobalContextRef context, + const NativeApiConfig& config = NativeApiConfig{}); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSGlobalContextRef context, + const char* metadataPath); + +#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_H diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSC.mm b/NativeScript/ffi/objc/jsc/NativeApiJSC.mm new file mode 100644 index 000000000..227af4c60 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSC.mm @@ -0,0 +1,71 @@ +#include "NativeApiJSC.h" + +#ifdef TARGET_ENGINE_JSC + +#include "NativeApiJSCRuntime.h" +#include "SignatureDispatch.h" + +namespace nativescript { + +namespace { + +using nativescript::engine::Array; +using nativescript::engine::ArrayBuffer; +using nativescript::engine::BigInt; +using nativescript::engine::Function; +using nativescript::engine::HostObject; +using nativescript::engine::MutableBuffer; +using nativescript::engine::Object; +using nativescript::engine::PropNameID; +using nativescript::engine::Runtime; +using nativescript::engine::String; +using nativescript::engine::StringBuffer; +using nativescript::engine::Value; +using nativescript::engine::JSError; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "jsc" +#include "../shared/bridge/ObjCBridge.mm" +// clang-format on +#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 + +#include "NativeApiJSCRuntimeSupport.mm" + +// clang-format off +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + +#include "NativeApiJSCSelectorGroups.mm" + +} // namespace + +#include "../shared/bridge/Install.mm" + +void InstallNativeApi(JSGlobalContextRef context, const NativeApiConfig& config) { + if (context == nullptr) { + return; + } + Runtime runtime(context); + InstallNativeApi(runtime, config); +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSGlobalContextRef context, + const char* metadataPath) { + nativescript::NativeApiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApi(context, config); +} + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCGsd.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCGsd.mm new file mode 100644 index 000000000..78c91cab2 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCGsd.mm @@ -0,0 +1,305 @@ +// --- GSD (Generated Signature Dispatch) for JSC --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads JS arguments and writes the JS return value via the JSC API. The +// readers/setters mirror JSC's generic conversions exactly; any value not in +// the fast representation makes a reader return false so the invoker falls +// back to the fully correct generic path. Number readers require an actual +// JS number so coercion edge cases (numeric strings, single-char unichar +// arguments) defer to the generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + JSContextRef context; + const JSValueRef* arguments; + const NativeApiType& returnType; + JSValueRef result = nullptr; + const Value* valueArguments = nullptr; + bool materializeValueResult = false; + Value valueResult = Value::undefined(); + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + bool readNumber(size_t i, double* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isNumber()) return false; + *out = v.getNumber(); + return true; + } + JSValueRef v = arguments[i]; + if (!JSValueIsNumber(context, v)) return false; + JSValueRef exception = nullptr; + double converted = JSValueToNumber(context, v, &exception); + if (exception != nullptr) return false; + *out = converted; + return true; + } + bool readBool(size_t i, uint8_t* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isBool()) return false; + *out = v.getBool() ? 1 : 0; + return true; + } + JSValueRef v = arguments[i]; + if (!JSValueIsBoolean(context, v)) return false; + *out = JSValueToBoolean(context, v) ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + template + bool readUnsigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { return readNumber(i, out); } + bool readSelector(size_t i, SEL* out) { + if (valueArguments != nullptr) { + return readFastEngineSelectorArgument(runtime, valueArguments[i], out); + } + return readJSCEngineSelectorArgument(runtime, arguments[i], out); + } + bool readClass(size_t i, Class* out) { + if (valueArguments != nullptr) { + Class cls = classFromEngineValue(runtime, valueArguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + if (auto* c = jscHostObjectRaw( + runtime, arguments[i])) { + *out = c->nativeClass(); + return true; + } + Class cls = jscNativeClassArgument(runtime, arguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (v.isNull() || v.isUndefined()) { + *out = nil; + return true; + } + if (!v.isObject()) return false; + Object object = v.asObject(runtime); + if (object.isHostObject(runtime)) { + *out = object.getHostObject(runtime)->object(); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime)->nativeClass()); + return true; + } + Class cls = classFromEngineValue(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime) + ->nativeProtocol()); + return true; + } + return false; + } + JSValueRef v = arguments[i]; + if (v == nullptr || JSValueIsNull(context, v) || + JSValueIsUndefined(context, v)) { + *out = nil; + return true; + } + if (auto* h = jscHostObjectRaw(runtime, v)) { + *out = h->object(); + return true; + } + if (auto* c = jscHostObjectRaw(runtime, v)) { + *out = static_cast(c->nativeClass()); + return true; + } + if (JSValueIsObject(context, v)) { + Class cls = jscNativeClassArgument(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + } + if (auto* p = jscHostObjectRaw(runtime, v)) { + *out = static_cast(p->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() { + if (materializeValueResult) { + valueResult = Value::undefined(); + return; + } + result = JSValueMakeUndefined(context); + } + void setBool(bool v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JSValueMakeBoolean(context, v); + } + void setInt32(int32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JSValueMakeNumber(context, v); + } + void setUInt32(uint32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JSValueMakeNumber(context, v); + } + void setUInt16(uint16_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JSValueMakeNumber(context, v); + } + void setInt64(int64_t v) { + if (materializeValueResult) { + valueResult = signedInteger64ToEngineValue(runtime, v); + return; + } + result = jscInteger64Value(runtime, v); + } + void setUInt64(uint64_t v) { + if (materializeValueResult) { + valueResult = unsignedInteger64ToEngineValue(runtime, v); + return; + } + result = jscUnsignedInteger64Value(runtime, v); + } + void setDouble(double v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JSValueMakeNumber(context, v); + } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + if (materializeValueResult) { + valueResult = name != nullptr ? makeString(runtime, name) : Value::null(); + return; + } + if (name == nullptr) { + result = JSValueMakeNull(context); + return; + } + JSStringRef string = engine::jscengine::makeJSString(name); + result = JSValueMakeString(context, string); + JSStringRelease(string); + } + void setClass(Class v) { + if (materializeValueResult) { + if (v == nil) { + valueResult = Value::null(); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + valueResult = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return; + } + if (v == nil) { + result = JSValueMakeNull(context); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value classValue = makeNativeClassValue(runtime, bridge, std::move(symbol)); + result = classValue.local(runtime); + } + void setObject(id obj) { + if (materializeValueResult) { + valueResult = convertNativeReturnValue(runtime, bridge, returnType, &obj); + return; + } + result = setJSCEngineObjectReturn(runtime, bridge, returnType, obj); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript. GsdObjCContext/ObjCGsdDispatchEntry remain reachable +// via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("GeneratedGsdSignatureDispatch.inc") +#include "GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCMarshalling.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCMarshalling.mm new file mode 100644 index 000000000..327d90236 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCMarshalling.mm @@ -0,0 +1,478 @@ +// Included by NativeApiJSCSelectorGroups.mm inside the NativeScript anonymous namespace. + +std::string jscValueToUtf8(Runtime& runtime, JSValueRef value) { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), value, &exception); + if (string == nullptr || exception != nullptr) { + if (string != nullptr) { + JSStringRelease(string); + } + return {}; + } + std::string result = engine::jscengine::stringToUtf8(string); + JSStringRelease(string); + return result; +} + +bool jscNumberValue(Runtime& runtime, JSValueRef value, double* result) { + if (result == nullptr) { + return false; + } + JSValueRef exception = nullptr; + double converted = JSValueToNumber(runtime.context(), value, &exception); + if (exception != nullptr) { + return false; + } + *result = converted; + return true; +} + +template +std::shared_ptr jscHostObject(Runtime& runtime, JSValueRef value) { + if (value == nullptr || !JSValueIsObject(runtime.context(), value)) { + return nullptr; + } + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception != nullptr || object == nullptr) { + return nullptr; + } + auto* holder = static_cast( + JSObjectGetPrivate(object)); + if (holder == nullptr || + holder->typeToken != engine::jscengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); +} + +template +T* jscHostObjectRaw(Runtime& runtime, JSValueRef value) { + if (value == nullptr || !JSValueIsObject(runtime.context(), value)) { + return nullptr; + } + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception != nullptr || object == nullptr) { + return nullptr; + } + auto* holder = static_cast( + JSObjectGetPrivate(object)); + if (holder == nullptr || + holder->typeToken != engine::jscengine::hostObjectTypeToken()) { + return nullptr; + } + return static_cast(holder->hostObject.get()); +} + +id jscNativeObjectArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, JSValueRef value, + NativeApiArgumentFrame& frame) { + if (value == nullptr || JSValueIsNull(runtime.context(), value) || + JSValueIsUndefined(runtime.context(), value)) { + return nil; + } + if (JSValueIsString(runtime.context(), value)) { + std::string utf8 = jscValueToUtf8(runtime, value); + id string = type.kind == metagen::mdTypeNSMutableStringObject + ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + if (string != nil) { + frame.addObject(string); + } + return string; + } + if (JSValueIsBoolean(runtime.context(), value)) { + return [NSNumber numberWithBool:JSValueToBoolean(runtime.context(), value)]; + } + if (JSValueIsNumber(runtime.context(), value)) { + double converted = 0; + if (jscNumberValue(runtime, value, &converted)) { + return [NSNumber numberWithDouble:converted]; + } + } + if (!JSValueIsObject(runtime.context(), value)) { + return nil; + } + if (auto objectHost = jscHostObject(runtime, value)) { + return objectHost->object(); + } + if (auto classHost = jscHostObject(runtime, value)) { + return static_cast(classHost->nativeClass()); + } + if (auto protocolHost = + jscHostObject(runtime, value)) { + return static_cast(protocolHost->nativeProtocol()); + } + if (auto pointerHost = + jscHostObject(runtime, value)) { + return static_cast(pointerHost->pointer()); + } + if (auto referenceHost = + jscHostObject(runtime, value)) { + return static_cast(referenceHost->data()); + } + if (auto structHost = + jscHostObject(runtime, value)) { + return static_cast(structHost->data()); + } + + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception == nullptr && object != nullptr) { + JSStringRef property = engine::jscengine::makeJSString("__nativeApiClass"); + JSValueRef wrappedClassValue = + JSObjectGetProperty(runtime.context(), object, property, nullptr); + JSStringRelease(property); + if (auto classHost = + jscHostObject(runtime, + wrappedClassValue)) { + return static_cast(classHost->nativeClass()); + } + } + + Value wrapped = Value::borrowed(runtime, value); + return objectFromEngineValue(runtime, bridge, wrapped, frame, + type.kind == + metagen::mdTypeNSMutableStringObject); +} + +Class jscNativeClassArgument(Runtime& runtime, JSValueRef value) { + if (value == nullptr || JSValueIsNull(runtime.context(), value) || + JSValueIsUndefined(runtime.context(), value)) { + return Nil; + } + if (auto classHost = jscHostObject(runtime, value)) { + return classHost->nativeClass(); + } + if (JSValueIsObject(runtime.context(), value)) { + JSValueRef exception = nullptr; + JSObjectRef object = JSValueToObject(runtime.context(), value, &exception); + if (exception == nullptr && object != nullptr) { + JSStringRef property = engine::jscengine::makeJSString("__nativeApiClass"); + JSValueRef wrappedClassValue = + JSObjectGetProperty(runtime.context(), object, property, nullptr); + JSStringRelease(property); + if (auto classHost = + jscHostObject(runtime, + wrappedClassValue)) { + return classHost->nativeClass(); + } + } + } + Value wrapped = Value::borrowed(runtime, value); + return classFromEngineValue(runtime, wrapped); +} + +bool readJSCEngineSelectorArgument(Runtime& runtime, JSValueRef value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (value == nullptr || JSValueIsNull(runtime.context(), value) || + JSValueIsUndefined(runtime.context(), value)) { + *result = nullptr; + return true; + } + if (!JSValueIsString(runtime.context(), value)) { + return false; + } + std::string selectorName = jscValueToUtf8(runtime, value); + *result = sel_registerName(selectorName.c_str()); + return true; +} + +template +bool writeJSCNumber(Runtime& runtime, JSValueRef value, void* target) { + double converted = 0; + if (!jscNumberValue(runtime, value, &converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; +} + +bool prepareJSCEngineArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, JSValueRef value, + NativeApiArgumentFrame& frame, size_t index) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + + switch (type.kind) { + case metagen::mdTypeBool: + if (!JSValueIsBoolean(runtime.context(), value)) { + return false; + } + *static_cast(target) = + JSValueToBoolean(runtime.context(), value) ? 1 : 0; + return true; + case metagen::mdTypeChar: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSShort: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + if (JSValueIsString(runtime.context(), value)) { + std::string text = jscValueToUtf8(runtime, value); + if (text.size() != 1) { + return false; + } + *static_cast(target) = + static_cast(static_cast(text[0])); + return true; + } + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSInt: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeUInt: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeFloat: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeDouble: + return writeJSCNumber(runtime, value, target); + case metagen::mdTypeSelector: + return readJSCEngineSelectorArgument(runtime, value, + static_cast(target)); + case metagen::mdTypeClass: { + Class cls = jscNativeClassArgument(runtime, value); + if (cls == Nil) { + return false; + } + *static_cast(target) = cls; + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + *static_cast(target) = + jscNativeObjectArgument(runtime, bridge, type, value, frame); + return true; + default: + break; + } + + Value wrapped = Value::borrowed(runtime, value); + convertEngineFfiArgument(runtime, bridge, type, wrapped, target, frame); + return true; +} + +JSValueRef jscInteger64Value(Runtime& runtime, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return JSValueMakeNumber(runtime.context(), static_cast(value)); + } + Value bigint = BigInt::fromInt64(runtime, value); + return bigint.local(runtime); +} + +JSValueRef jscUnsignedInteger64Value(Runtime& runtime, uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return JSValueMakeNumber(runtime.context(), static_cast(value)); + } + Value bigint = BigInt::fromUint64(runtime, value); + return bigint.local(runtime); +} + +JSValueRef setJSCEngineObjectReturn( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, id object) { + if (object == nil) { + return JSValueMakeNull(runtime.context()); + } + Value roundTrip = + findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + JSValueRef result = roundTrip.local(runtime); + if (type.returnOwned) { + [object release]; + } + return result; + } + if (nativeObjectReturnMayCoerceToString(type) && + nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + JSStringRef string = engine::jscengine::makeJSString(utf8); + JSValueRef result = JSValueMakeString(runtime.context(), string); + JSStringRelease(string); + return result; + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + return JSValueMakeNull(runtime.context()); + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == + CFBooleanGetTypeID() || + (objCType != nullptr && + std::strcmp(objCType, @encode(BOOL)) == 0); + JSValueRef result = + isBool ? JSValueMakeBoolean(runtime.context(), [number boolValue]) + : JSValueMakeNumber(runtime.context(), [number doubleValue]); + if (type.returnOwned) { + [object release]; + } + return result; + } + + if (const NativeApiSymbol* classSymbol = + bridge->findClassForRuntimePointer((void*)object)) { + Value result = makeNativeClassValue(runtime, bridge, *classSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + Value result = makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + Value result = makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + return result.local(runtime); +} + +JSValueRef setJSCEngineReturnValue( + Runtime& runtime, const std::shared_ptr& bridge, + NativeApiType type, void* value, const std::string& selectorName) { + switch (type.kind) { + case metagen::mdTypeVoid: + return JSValueMakeUndefined(runtime.context()); + case metagen::mdTypeBool: + return JSValueMakeBoolean(runtime.context(), + *static_cast(value) != 0); + case metagen::mdTypeChar: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeSShort: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeUShort: + return JSValueMakeNumber(runtime.context(), *static_cast(value)); + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + JSStringRef string = engine::jscengine::makeJSString(std::string(buffer, length)); + JSValueRef result = JSValueMakeString(runtime.context(), string); + JSStringRelease(string); + return result; + } + case metagen::mdTypeSInt: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeUInt: + return JSValueMakeNumber(runtime.context(), + *static_cast(value)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return jscInteger64Value(runtime, *static_cast(value)); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return jscUnsignedInteger64Value(runtime, + *static_cast(value)); + case metagen::mdTypeFloat: + return JSValueMakeNumber(runtime.context(), *static_cast(value)); + case metagen::mdTypeDouble: + return JSValueMakeNumber(runtime.context(), *static_cast(value)); + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + return JSValueMakeNull(runtime.context()); + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return result.local(runtime); + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + if ((selectorName == "valueForKey:" || + selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(type)) { + type.kind = metagen::mdTypeAnyObject; + } + return setJSCEngineObjectReturn(runtime, bridge, type, + *static_cast(value)); + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorNameValue = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorNameValue == nullptr) { + return JSValueMakeNull(runtime.context()); + } + JSStringRef string = engine::jscengine::makeJSString(selectorNameValue); + JSValueRef result = JSValueMakeString(runtime.context(), string); + JSStringRelease(string); + return result; + } + default: + break; + } + Value result = convertNativeReturnValue(runtime, bridge, type, value); + return result.local(runtime); +} diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h new file mode 100644 index 000000000..4cca8bb61 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h @@ -0,0 +1,40 @@ +#ifndef NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H +#define NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H + +// Apple-side entry point for the JavaScriptCore engine layer. +// +// The engine layer itself -- the `nativescript::engine` classes wrapping JSC +// -- moved to jsi/jsc/JSCRuntime.h so the Android runtime can compile it too. +// What stays here is exactly what the Apple ObjC bridge needs on top of it and +// Android has no use for: Foundation, the Objective-C runtime, the Mach-O +// metadata section lookup, and the metadata reader. +// +// Apple sources include this path, unchanged, and get both halves. + +#ifdef TARGET_ENGINE_JSC + +#import +#include +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" + +#include "jsi/jsc/JSCRuntime.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +#endif // TARGET_ENGINE_JSC + +#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCRuntimeSupport.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntimeSupport.mm new file mode 100644 index 000000000..8d1849d19 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntimeSupport.mm @@ -0,0 +1,12 @@ +// Included by NativeApiJSC.mm inside the NativeScript anonymous namespace. + +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::make_shared(runtime.state()); +} + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + JSObjectSetPrototype(runtime.context(), object.local(runtime), + prototype.local(runtime)); +} + diff --git a/NativeScript/ffi/objc/jsc/NativeApiJSCSelectorGroups.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCSelectorGroups.mm new file mode 100644 index 000000000..1020e8cf2 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCSelectorGroups.mm @@ -0,0 +1,286 @@ +// Included by NativeApiJSC.mm inside the NativeScript anonymous namespace. + +#include "../shared/bridge/SelectorGroupData.h" + +#include "NativeApiJSCMarshalling.mm" + +#include "NativeApiJSCGsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || + !prepared.gsdEngineCallable || dispatchSuperClass != Nil || + count != prepared.gsdEngineArgumentCount) { + return false; + } + + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), nullptr, prepared.signature.returnType}; + ctx.valueArguments = args; + ctx.materializeValueResult = true; + if (!invoker(ctx)) { + return false; + } + *result = std::move(ctx.valueResult); + return true; +} + +JSValueRef setJSCEnginePreparedObjCResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const std::shared_ptr& receiverHostObject, + const std::optional& initializerClassWrapper, + size_t providedCount, const JSValueRef arguments[], + Class dispatchSuperClass) { + const NativeApiSignature& signature = prepared.signature; + if (receiver == nil || signature.variadic || + unsupportedEngineType(signature.returnType)) { + throw JSError(runtime, + "Objective-C selector is not supported by JSC engine: " + + prepared.selectorName); + } + + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (providedCount > expected || providedCount + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } else if (providedCount != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + // GSD fast path: the generated invoker reads args directly from the JSC + // arguments, calls objc_msgSend with a typed cast, and produces the JS + // return value — bypassing all generic marshalling. + if (prepared.gsdEngineCallable && dispatchSuperClass == Nil && + providedCount == prepared.gsdEngineArgumentCount && + !initializerClassWrapper && !isNSErrorOutMethod) { + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), arguments, signature.returnType}; + if (invoker(ctx)) { + return ctx.result; + } + } + + if (dispatchSuperClass == Nil && !initializerClassWrapper && + providedCount <= 2) { + Value fastArgs[2]; + for (size_t i = 0; i < providedCount; i++) { + fastArgs[i] = Value::borrowed(runtime, arguments[i]); + } + Value fastResult; + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, + fastArgs, providedCount, Nil, + &fastResult)) { + return fastResult.local(runtime); + } + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + for (size_t i = 0; i < providedCount; i++) { + if (!prepareJSCEngineArgument(runtime, bridge, signature.argumentTypes[i], + arguments[i], frame, i)) { + throw JSError(runtime, + "Objective-C argument is not supported by JSC engine: " + + prepared.selectorName); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && providedCount + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + if (initializerClassWrapper) { + id resultObject = nil; + if (isObjectiveCObjectType(returnType)) { + resultObject = *static_cast(returnStorage.data()); + } + if (receiverHostObject != nullptr && resultObject != receiver) { + receiverHostObject->disownObject(receiver); + } + if (resultObject != nil) { + bridge->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *initializerClassWrapper)); + } + } + return setJSCEngineReturnValue(runtime, bridge, returnType, + returnStorage.data(), prepared.selectorName); +} + +JSValueRef NativeApiSelectorGroupCall( + JSContextRef context, JSObjectRef function, JSObjectRef thisObject, + size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + auto* data = + static_cast(JSObjectGetPrivate(function)); + if (data == nullptr || data->selectors == nullptr || + data->preparedInvocations == nullptr) { + return JSValueMakeUndefined(context); + } + + Runtime& runtime = data->runtime; + try { + NativeApiRoundTripCacheFrameGuard roundTripFrame(data->bridge); + auto resolveObjectHost = [&]() + -> engine::jscengine::HostObjectHolder* { + if (thisObject == nullptr) { + return nullptr; + } + auto* holder = static_cast( + JSObjectGetPrivate(thisObject)); + return holder != nullptr && + holder->typeToken == + engine::jscengine::hostObjectTypeToken< + NativeApiObjectHostObject>() + ? holder + : nullptr; + }; + auto call = resolveNativeApiSelectorGroupCall( + runtime, *data, argumentCount, + [&]() -> id { + auto* holder = resolveObjectHost(); + return holder != nullptr + ? static_cast( + holder->hostObject.get())->object() + : nil; + }, + [&]() -> std::shared_ptr { + if (data->boundReceiverState != nullptr) { + return nullptr; + } + auto* holder = resolveObjectHost(); + return holder != nullptr + ? std::static_pointer_cast( + holder->hostObject) + : nullptr; + }, + [](uint64_t dispatchId) { return lookupObjCGsdInvoker(dispatchId); }); + if (call.hasImmediateResult) { + return call.immediateResult.local(runtime); + } + return setJSCEnginePreparedObjCResult( + runtime, data->bridge, call.receiver, *call.prepared, + call.receiverHostObject, call.initializerClassWrapper, argumentCount, + arguments, call.dispatchClass); + } catch (const std::exception& error) { + engine::jscengine::setException(context, exception, error); + return JSValueMakeUndefined(context); + } +} + +void NativeApiSelectorGroupFinalize(JSObjectRef function) { + delete static_cast( + JSObjectGetPrivate(function)); +} + +JSClassRef NativeApiSelectorGroupFunctionClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->selectorGroupFunctionClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineSelectorGroupFunction"; + definition.callAsFunction = NativeApiSelectorGroupCall; + definition.finalize = NativeApiSelectorGroupFinalize; + state->selectorGroupFunctionClass = JSClassCreate(&definition); + } + return state->selectorGroupFunctionClass; +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + auto* data = new NativeApiSelectorGroupData( + runtime.state(), std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)); + JSObjectRef function = + JSObjectMake(runtime.context(), + NativeApiSelectorGroupFunctionClass(runtime), data); + engine::jscengine::setFunctionPrototype(runtime.context(), function); + + JSStringRef property = engine::jscengine::makeJSString("name"); + JSStringRef functionName = + engine::jscengine::makeJSString("__nativeSelectorGroup"); + JSValueRef value = JSValueMakeString(runtime.context(), functionName); + JSObjectSetProperty(runtime.context(), function, property, value, + kJSPropertyAttributeReadOnly, nullptr); + JSStringRelease(functionName); + JSStringRelease(property); + + Value functionValue(runtime, function); + return functionValue.asObject(runtime).asFunction(runtime); +} diff --git a/NativeScript/ffi/objc/jsc/SignatureDispatch.h b/NativeScript/ffi/objc/jsc/SignatureDispatch.h new file mode 100644 index 000000000..02ac6e805 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/SignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NATIVESCRIPT_FFI_JSC_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_JSC_SIGNATURE_DISPATCH_H + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +#if defined(__has_include) +#if __has_include("GeneratedSignatureDispatch.inc") +#include "GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_JSC_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/napi/AutoreleasePool.h b/NativeScript/ffi/objc/napi/AutoreleasePool.h similarity index 100% rename from NativeScript/ffi/napi/AutoreleasePool.h rename to NativeScript/ffi/objc/napi/AutoreleasePool.h diff --git a/NativeScript/ffi/napi/AutoreleasePool.mm b/NativeScript/ffi/objc/napi/AutoreleasePool.mm similarity index 100% rename from NativeScript/ffi/napi/AutoreleasePool.mm rename to NativeScript/ffi/objc/napi/AutoreleasePool.mm diff --git a/NativeScript/ffi/napi/Block.h b/NativeScript/ffi/objc/napi/Block.h similarity index 100% rename from NativeScript/ffi/napi/Block.h rename to NativeScript/ffi/objc/napi/Block.h diff --git a/NativeScript/ffi/napi/Block.mm b/NativeScript/ffi/objc/napi/Block.mm similarity index 99% rename from NativeScript/ffi/napi/Block.mm rename to NativeScript/ffi/objc/napi/Block.mm index 3b8697afc..a99189a6a 100644 --- a/NativeScript/ffi/napi/Block.mm +++ b/NativeScript/ffi/objc/napi/Block.mm @@ -9,7 +9,7 @@ #include #include #include "Interop.h" -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "ObjCBridge.h" #include "SignatureDispatch.h" #include "TypeConv.h" diff --git a/NativeScript/ffi/napi/CFunction.h b/NativeScript/ffi/objc/napi/CFunction.h similarity index 100% rename from NativeScript/ffi/napi/CFunction.h rename to NativeScript/ffi/objc/napi/CFunction.h diff --git a/NativeScript/ffi/napi/CFunction.mm b/NativeScript/ffi/objc/napi/CFunction.mm similarity index 99% rename from NativeScript/ffi/napi/CFunction.mm rename to NativeScript/ffi/objc/napi/CFunction.mm index 3f9d26714..e23a2b673 100644 --- a/NativeScript/ffi/napi/CFunction.mm +++ b/NativeScript/ffi/objc/napi/CFunction.mm @@ -12,8 +12,8 @@ #include "Interop.h" #include "ObjCBridge.h" #include "SignatureDispatch.h" -#include "runtime/NativeScriptException.h" -#include "Tasks.h" +#include "runtime/apple/NativeScriptException.h" +#include "ffi/objc/shared/Tasks.h" #ifdef ENABLE_JS_RUNTIME #include "jsr.h" #endif diff --git a/NativeScript/ffi/napi/CallbackThreading.h b/NativeScript/ffi/objc/napi/CallbackThreading.h similarity index 89% rename from NativeScript/ffi/napi/CallbackThreading.h rename to NativeScript/ffi/objc/napi/CallbackThreading.h index 63c17990f..5993ecc07 100644 --- a/NativeScript/ffi/napi/CallbackThreading.h +++ b/NativeScript/ffi/objc/napi/CallbackThreading.h @@ -4,6 +4,7 @@ #include "js_native_api.h" #include +#include #include #if defined(ENABLE_JS_RUNTIME) @@ -55,19 +56,19 @@ class NativeCallRuntimeUnlockScope final { return; } - auto it = JSR::env_to_jsr_cache.find(env); - if (it == JSR::env_to_jsr_cache.end() || it->second == nullptr) { + jsr_ = JSR::FromEnv(env); + if (jsr_ == nullptr) { return; } - jsr_ = it->second; unlockedDepth_ = js_current_env_lock_depth(env); for (int i = 0; i < unlockedDepth_; i++) { jsr_->unlock(); } if (unlockedDepth_ == 0 && jsr_->runtime != nullptr) { - runtime_ = jsr_->runtime.get(); - runtime_->unlock(); + auto* runtime = jsr_->runtime.get(); + runtime->unlock(); + relockRuntime_ = [runtime]() { runtime->lock(); }; unlockedRuntime_ = true; } if (unlockedDepth_ > 0 || unlockedRuntime_) { @@ -91,8 +92,8 @@ class NativeCallRuntimeUnlockScope final { jsr_->lock(); } } - if (unlockedRuntime_ && runtime_ != nullptr) { - runtime_->lock(); + if (unlockedRuntime_ && relockRuntime_) { + relockRuntime_(); } #endif } @@ -104,7 +105,7 @@ class NativeCallRuntimeUnlockScope final { private: #if defined(ENABLE_JS_RUNTIME) && defined(TARGET_ENGINE_HERMES) JSR* jsr_ = nullptr; - facebook::jsi::ThreadSafeRuntime* runtime_ = nullptr; + std::function relockRuntime_; #endif int unlockedDepth_ = 0; bool unlockedRuntime_ = false; @@ -121,9 +122,8 @@ class NativeCallbackScope final { return; } - auto it = JSR::env_to_jsr_cache.find(env_); - if (it != JSR::env_to_jsr_cache.end() && it->second != nullptr) { - jsr_ = it->second; + if (JSR* jsr = JSR::FromEnv(env_)) { + jsr_ = jsr; jsr_->lock(); detail::native_caller_thread_callback_depth += 1; napi_open_handle_scope(env_, &napiHandleScope_); diff --git a/NativeScript/ffi/napi/Cif.h b/NativeScript/ffi/objc/napi/Cif.h similarity index 100% rename from NativeScript/ffi/napi/Cif.h rename to NativeScript/ffi/objc/napi/Cif.h diff --git a/NativeScript/ffi/objc/napi/Cif.mm b/NativeScript/ffi/objc/napi/Cif.mm new file mode 100644 index 000000000..4e1fe2004 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Cif.mm @@ -0,0 +1,360 @@ +#include "Cif.h" +#include +#include +#include +#include +#include +#include +#include +#include "Metadata.h" +#include "MetadataReader.h" +#include "ObjCBridge.h" +#include "ffi/objc/shared/SignatureDispatchCore.h" +#include "TypeConv.h" +#include "Util.h" + +namespace nativescript { +namespace { + +inline bool typeRequiresSlowGeneratedNapiDispatch(const std::shared_ptr& type) { + if (type == nullptr) { + return false; + } + + switch (type->kind) { + case mdTypeUChar: + case mdTypeUInt8: + case mdTypeString: + case mdTypePointer: + case mdTypeStruct: + case mdTypeArray: + case mdTypeBlock: + case mdTypeFunctionPointer: + case mdTypeVector: + case mdTypeExtVector: + case mdTypeComplex: + return true; + default: + return false; + } +} + +inline bool typeKindMayUseRoundTripCache(MDTypeKind kind) { + switch (kind) { + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +inline void updateGeneratedNapiDispatchCompatibility(Cif* cif) { + if (cif == nullptr) { + return; + } + + cif->skipGeneratedNapiDispatch = false; + cif->generatedDispatchHasRoundTripCacheArgument = false; + cif->generatedDispatchUsesObjectReturnStorage = false; + + if (cif->returnType != nullptr) { + cif->generatedDispatchUsesObjectReturnStorage = + typeKindMayUseRoundTripCache(cif->returnType->kind); + } + + cif->skipGeneratedNapiDispatch = typeRequiresSlowGeneratedNapiDispatch(cif->returnType); + if (cif->skipGeneratedNapiDispatch) { + return; + } + + for (const auto& argType : cif->argTypes) { + if (argType != nullptr && typeKindMayUseRoundTripCache(argType->kind)) { + cif->generatedDispatchHasRoundTripCacheArgument = true; + } + if (typeRequiresSlowGeneratedNapiDispatch(argType)) { + cif->skipGeneratedNapiDispatch = true; + return; + } + } +} + +} // namespace + +// Essentially, we cache libffi structures per unique method signature, +// this helps us avoid the overhead of creating them on the fly for each +// invocation. +Cif* ObjCBridgeState::getMethodCif(napi_env env, Method method) { + auto encoding = std::string(method_getTypeEncoding(method)); + auto find = this->cifs[encoding]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, method); + this->cifs[encoding] = cif; + + return cif; +} + +Cif* ObjCBridgeState::getMethodCif(napi_env env, MDSectionOffset offset) { + auto find = this->mdMethodSignatureCache[offset]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, metadata, offset, true, false); + this->mdMethodSignatureCache[offset] = cif; + + return cif; +} + +Cif* ObjCBridgeState::getBlockCif(napi_env env, MDSectionOffset offset) { + auto find = this->mdBlockSignatureCache[offset]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, metadata, offset, false, true); + this->mdBlockSignatureCache[offset] = cif; + + return cif; +} + +Cif* ObjCBridgeState::getCFunctionCif(napi_env env, MDSectionOffset offset) { + auto find = this->mdFunctionSignatureCache[offset]; + if (find != nullptr) { + return find; + } + + auto cif = new Cif(env, metadata, offset, false, false); + this->mdFunctionSignatureCache[offset] = cif; + + return cif; +} + +Cif::Cif(napi_env env, std::string encoding, unsigned int implicitArgc) { + auto signature = [NSMethodSignature signatureWithObjCTypes:encoding.c_str()]; + unsigned long numberOfArguments = signature.numberOfArguments; + unsigned long skippedArgs = std::min(numberOfArguments, implicitArgc); + this->argc = (int)(numberOfArguments - skippedArgs); + this->argv = (napi_value*)malloc(sizeof(napi_value) * this->argc); + + unsigned int totalArgc = (unsigned int)numberOfArguments; + + const char* returnType = signature.methodReturnType; + this->returnType = TypeConv::Make(env, &returnType); + + ffi_type* rtype = this->returnType->type; + this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); + + unsigned long methodReturnLength = signature.methodReturnLength; + unsigned long frameLength = signature.frameLength; + + this->rvalue = malloc(methodReturnLength); + this->rvalueLength = methodReturnLength; + this->frameLength = frameLength; + + this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; + if (this->avalues != nullptr) { + memset(this->avalues, 0, sizeof(void*) * this->argc); + } + this->shouldFree = (bool*)malloc(sizeof(bool) * this->argc); + memset(this->shouldFree, false, sizeof(bool) * this->argc); + this->shouldFreeAny = false; + this->avaluesAllocStart = 0; + this->avaluesAllocCount = 0; + + for (int i = 0; i < numberOfArguments; i++) { + const char* argenc = [signature getArgumentTypeAtIndex:i]; + + auto argTypeInfo = TypeConv::Make(env, &argenc); + this->atypes[i] = argTypeInfo->ffiTypeForArgument(); + + if (i >= skippedArgs) { + this->argTypes.push_back(argTypeInfo); + } + } + + ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); + + if (status != FFI_OK) { + std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; + return; + } + + for (unsigned int i = 0; i < this->argc; i++) { + this->avalues[i] = malloc(cif.arg_types[i + skippedArgs]->size); + this->avaluesAllocCount++; + } + + updateGeneratedNapiDispatchCompatibility(this); +} + +Cif::Cif(napi_env env, Method method) { + const unsigned int totalArgc = method_getNumberOfArguments(method); + this->argc = totalArgc >= 2 ? totalArgc - 2 : 0; + this->argv = this->argc > 0 ? (napi_value*)malloc(sizeof(napi_value) * this->argc) : nullptr; + + char* returnTypeEnc = method_copyReturnType(method); + const char* returnTypePtr = returnTypeEnc; + this->returnType = TypeConv::Make(env, &returnTypePtr); + if (returnTypeEnc != nullptr) { + free(returnTypeEnc); + } + + ffi_type* rtype = this->returnType->type; + this->atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); + + this->rvalueLength = std::max(1, rtype->size); + this->rvalue = malloc(this->rvalueLength); + this->frameLength = 0; + + this->avalues = this->argc > 0 ? (void**)malloc(sizeof(void*) * this->argc) : nullptr; + if (this->avalues != nullptr) { + memset(this->avalues, 0, sizeof(void*) * this->argc); + } + + this->shouldFree = this->argc > 0 ? (bool*)malloc(sizeof(bool) * this->argc) : nullptr; + if (this->shouldFree != nullptr) { + memset(this->shouldFree, false, sizeof(bool) * this->argc); + } + this->shouldFreeAny = false; + this->avaluesAllocStart = 0; + this->avaluesAllocCount = 0; + + for (unsigned int i = 0; i < totalArgc; i++) { + char* argEnc = method_copyArgumentType(method, i); + const char* argEncPtr = argEnc; + auto argTypeInfo = TypeConv::Make(env, &argEncPtr); + if (argEnc != nullptr) { + free(argEnc); + } + + this->atypes[i] = argTypeInfo->ffiTypeForArgument(); + if (i >= 2) { + this->argTypes.push_back(argTypeInfo); + } + } + + ffi_status status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, totalArgc, rtype, this->atypes); + if (status != FFI_OK) { + std::cout << "Failed to prepare CIF, libffi returned error:" << status << std::endl; + return; + } + + for (unsigned int i = 0; i < this->argc; i++) { + this->avalues[i] = malloc(cif.arg_types[i + 2]->size); + this->avaluesAllocCount++; + } + + updateGeneratedNapiDispatchCompatibility(this); +} + +Cif::Cif(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, bool isMethod, + bool isBlock) { + MDSectionOffset signatureStart = offset; + auto returnTypeKind = reader->getTypeKind(offset); + bool next = ((MDTypeFlag)returnTypeKind & mdTypeFlagNext) != 0; + isVariadic = ((MDTypeFlag)returnTypeKind & mdTypeFlagVariadic) != 0; + + returnType = TypeConv::Make(env, reader, &offset); + + auto implicitArgs = isMethod ? 2 : isBlock ? 1 : 0; + + shouldFreeAny = false; + atypes = nullptr; + avaluesAllocStart = 0; + avaluesAllocCount = 0; + + if (next || isMethod || isBlock) { + while (next) { + auto argTypeKind = reader->getTypeKind(offset); + next = ((MDTypeFlag)argTypeKind & mdTypeFlagNext) != 0; + auto argTypeInfo = TypeConv::Make(env, reader, &offset); + std::string enc; + argTypeInfo->encode(&enc); + argTypes.push_back(argTypeInfo); + } + + argc = (int)argTypes.size(); + + auto totalArgc = argc + implicitArgs; + + argv = (napi_value*)malloc(sizeof(napi_value) * argc); + shouldFree = (bool*)malloc(sizeof(bool) * argc); + + atypes = (ffi_type**)malloc(sizeof(ffi_type*) * totalArgc); + avalues = (void**)malloc(sizeof(void*) * argc); + memset(avalues, 0, sizeof(void*) * argc); + + if (isMethod) { + atypes[0] = &ffi_type_pointer; + atypes[1] = &ffi_type_pointer; + } + + if (isBlock) { + atypes[0] = &ffi_type_pointer; + } + + for (int i = 0; i < argc; i++) { + atypes[i + implicitArgs] = argTypes[i]->ffiTypeForArgument(); + shouldFree[i] = false; + } + } else { + argc = 0; + argv = nullptr; + avalues = nullptr; + shouldFree = nullptr; + } + + ffi_status status = + ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argc + implicitArgs, returnType->type, atypes); + + if (status != FFI_OK) { + std::cout << "Failed to prepare CIF, libffi returned error: " << status << std::endl; + return; + } + + for (int i = 0; i < argc; i++) { + avalues[i] = malloc(cif.arg_types[i + implicitArgs]->size); + avaluesAllocCount++; + } + + rvalue = malloc(cif.rtype->size); + rvalueLength = cif.rtype->size; + + signatureHash = metadataSignatureHash(reader, signatureStart); + + updateGeneratedNapiDispatchCompatibility(this); +} + +Cif::~Cif() { + if (rvalue != nullptr) { + free(rvalue); + } + if (argv != nullptr) { + free(argv); + } + if (avalues != nullptr) { + for (unsigned int i = 0; i < avaluesAllocCount; i++) { + auto index = avaluesAllocStart + i; + if (avalues[index] != nullptr) { + free(avalues[index]); + } + } + free(avalues); + } + if (atypes != nullptr) { + free(atypes); + } + if (shouldFree != nullptr) { + free(shouldFree); + } +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/Class.h b/NativeScript/ffi/objc/napi/Class.h similarity index 100% rename from NativeScript/ffi/napi/Class.h rename to NativeScript/ffi/objc/napi/Class.h diff --git a/NativeScript/ffi/objc/napi/Class.mm b/NativeScript/ffi/objc/napi/Class.mm new file mode 100644 index 000000000..46676d3f5 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Class.mm @@ -0,0 +1,1075 @@ +#include "Class.h" +#include "ClassBuilder.h" +#include "ClassMember.h" +#include "Interop.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "ObjCBridge.h" +#include "Protocol.h" +#include "Util.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import +#include +#include + +namespace nativescript { + +napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo); + +namespace { + +std::atomic loadedImageGeneration{0}; + +// Image load functions cannot be unregistered and run on the loading thread +// with the ObjC runtime lock held, so this only touches process-wide state. +void onImageLoaded(const struct mach_header* /*header*/) { + loadedImageGeneration.fetch_add(1, std::memory_order_release); +} + +} // namespace + +inline bool tryGetInteropPointerArg(napi_env env, napi_value value, void** out) { + if (out == nullptr || value == nullptr) { + return false; + } + + *out = nullptr; + + napi_valuetype argType = napi_undefined; + napi_typeof(env, value, &argType); + + if (argType == napi_external) { + return napi_get_value_external(env, value, out) == napi_ok && *out != nullptr; + } + + if (argType == napi_bigint) { + uint64_t raw = 0; + bool lossless = false; + if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { + return false; + } + *out = reinterpret_cast(raw); + return *out != nullptr; + } + + if (argType != napi_object) { + return false; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *out = ptr != nullptr ? ptr->data : nullptr; + return *out != nullptr; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *out = ref != nullptr ? ref->data : nullptr; + return *out != nullptr; + } + + return false; +} + +void ObjCBridgeState::registerClassGlobals(napi_env env, napi_value global) { + static std::once_flag imageLoadObserver; + std::call_once(imageLoadObserver, [] { objc_addLoadImageFunc(onImageLoaded); }); + pendingMdClassesImageGeneration = loadedImageGeneration.load(std::memory_order_acquire); + + MDSectionOffset offset = metadata->classesOffset; + while (offset < metadata->structsOffset) { + MDSectionOffset originalOffset = offset; + auto nameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + auto runtimeNameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + auto name = metadata->resolveString(nameOffset); + auto runtimeName = name; + if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { + runtimeName = metadata->resolveString(runtimeNameOffset); + } + while (hasProtocols) { + auto protocolOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; + } + + auto superclass = metadata->getOffset(offset); + offset += sizeof(superclass); + + bool next = (superclass & mdSectionOffsetNext) != 0; + + while (next) { + auto flags = metadata->getMemberFlag(offset); + next = (flags & mdMemberNext) != 0; + offset += sizeof(flags); + + if ((flags & mdMemberProperty) != 0) { + bool readonly = (flags & mdMemberReadonly) != 0; + offset += sizeof(MDSectionOffset); // name + offset += sizeof(MDSectionOffset); // getterSelector + offset += sizeof(MDSectionOffset); // getterSignature + if (!readonly) { + offset += sizeof(MDSectionOffset); // setterSelector + offset += sizeof(MDSectionOffset); // setterSignature + } + } else { + offset += sizeof(MDSectionOffset); // selector + offset += sizeof(MDSectionOffset); // signature + } + } + + auto nativeClass = objc_getClass(runtimeName); + if (nativeClass != nil) { + mdClassesByPointer[nativeClass] = originalOffset; + } else { + pendingMdClasses.emplace_back(runtimeName, originalOffset); + } + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = JS_classGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + + napi_define_properties(env, global, 1, &prop); + } +} + +bool ObjCBridgeState::resolvePendingMetadataClasses() { + if (pendingMdClasses.empty()) { + return false; + } + + uint64_t generation = loadedImageGeneration.load(std::memory_order_acquire); + if (generation == pendingMdClassesImageGeneration) { + return false; + } + pendingMdClassesImageGeneration = generation; + + size_t pendingCount = pendingMdClasses.size(); + std::erase_if(pendingMdClasses, [this](const auto& pending) { + Class nativeClass = objc_getClass(pending.first); + if (nativeClass == nil) { + return false; + } + mdClassesByPointer.emplace(nativeClass, pending.second); + return true; + }); + return pendingMdClasses.size() != pendingCount; +} + +NAPI_FUNCTION(registerClass) { + NAPI_CALLBACK_BEGIN(1) + + // In case no arguments are passed, we just return the NativeObject class. + // This is to support both @NativeClass and @NativeClass() syntaxes. + // Maybe we should support more in future like + // @NativeClass(NSApplicationDelegate). + if (argc == 0) { + napi_value func; + napi_create_function(env, "NativeClass", NAPI_AUTO_LENGTH, JS_registerClass, nullptr, &func); + return func; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + // bridgeState->registerClass(env, argv[0]); + + ClassBuilder* builder = new ClassBuilder(env, argv[0]); + // It gets lazily built when a static method is called. + // builder->build(); + bridgeState->registerRuntimeClass(builder, builder->nativeClass); + + return nullptr; +} + +const char* ObjCClassDecorator = R"( +globalThis.ObjCClass = function ObjCClass(...protocols) { + return function (target) { + if (target.ObjCProtocols) { + target.ObjCProtocols.push(...protocols); + } else { + target.ObjCProtocols = protocols; + } + + return globalThis.NativeClass(target); + }; +}; +)"; + +void setupObjCClassDecorator(napi_env env) { + napi_value global, script; + napi_get_global(env, &global); + napi_create_string_utf8(env, ObjCClassDecorator, NAPI_AUTO_LENGTH, &script); + napi_run_script(env, script, &global); +} + +char class_name[256]; + +// Get a Bridged Class by metadata offset, creating it if it doesn't exist. +// This is used to cache ObjCClass instances. +ObjCClass* ObjCBridgeState::getClass(napi_env env, MDSectionOffset offset) { + auto find = this->classes[offset]; + if (find != nullptr) { + return find; + } + + auto cls = new ObjCClass(env, offset); + this->classes[offset] = cls; + + return cls; +} + +NAPI_FUNCTION(import) { + NAPI_CALLBACK_BEGIN(1) + + NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], class_name, 256, nullptr)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + std::string name = class_name; + + if (!name.starts_with("/")) { + name = "/System/Library/Frameworks/" + name + ".framework"; + } + + NSBundle* bundle = [NSBundle bundleWithPath:[NSString stringWithUTF8String:name.c_str()]]; + if (bundle == nil) { + NSLog(@"Could not find bundle: %s", name.c_str()); + return nullptr; + } + + bool loaded = [bundle load]; + + if (!loaded) { + NSLog(@"Could not load bundle: %s", name.c_str()); + return nullptr; + } + + return nullptr; +} + +NAPI_FUNCTION(classGetter) { + void* data; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); + MDSectionOffset offset = (MDSectionOffset)((size_t)data); + auto bridgeState = ObjCBridgeState::InstanceData(env); + + auto cached = bridgeState->mdValueCache[offset]; + if (cached != nullptr) { + return get_ref_value(env, cached); + } + + // std::string name = bridgeState->metadata->getString(offset); + auto cls = bridgeState->getClass(env, offset); + + if (cls != nullptr) { + bridgeState->mdValueCache[offset] = cls->constructor; + } else { + return nullptr; + } + + return get_ref_value(env, cls->constructor); +} + +NAPI_FUNCTION(classSuperclassFallback) { + napi_value jsThis; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, nullptr); + + Class currentClass = nil; + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && jsThis != nullptr) { + bridgeState->tryResolveBridgedClassConstructor(env, jsThis, ¤tClass); + } + if (currentClass == nil) { + napi_unwrap(env, jsThis, (void**)¤tClass); + } + if (currentClass == nil) { + return nullptr; + } + + Class superClass = class_getSuperclass(currentClass); + if (superClass == nil) { + return nullptr; + } + + auto find = bridgeState->classesByPointer.find(superClass); + if (find != bridgeState->classesByPointer.end()) { + return get_ref_value(env, find->second->constructor); + } + + MDSectionOffset superClassOffset = MD_SECTION_OFFSET_NULL; + if (bridgeState->findMetadataClassOffset(superClass, &superClassOffset)) { + auto bridgedClass = bridgeState->getClass(env, superClassOffset); + return get_ref_value(env, bridgedClass->constructor); + } + + const char* runtimeName = class_getName(superClass); + if (runtimeName != nullptr && runtimeName[0] != '\0') { + napi_value global = nullptr; + napi_value constructor = nullptr; + bool hasGlobal = false; + napi_get_global(env, &global); + if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && + napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && + constructor != nullptr) { + return constructor; + } + } + + return bridgeState->getObject(env, (id)superClass, kUnownedObject, 0, nullptr); +} + +NAPI_FUNCTION(classHasInstance) { + size_t argc = 1; + napi_value argv[1] = {nullptr}; + napi_value jsThis = nullptr; + napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, nullptr); + + Class expectedClass = nil; + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && jsThis != nullptr) { + bridgeState->tryResolveBridgedClassConstructor(env, jsThis, &expectedClass); + } + if (expectedClass == nil) { + napi_unwrap(env, jsThis, (void**)&expectedClass); + } + + bool isInstance = false; + if (expectedClass != nil && argc > 0 && argv[0] != nullptr) { + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, argv[0], &valueType) == napi_ok && + (valueType == napi_object || valueType == napi_function)) { + id instance = nil; + napi_status unwrapStatus = napi_invalid_arg; + if (Pointer::isInstance(env, argv[0])) { + Pointer* pointer = Pointer::unwrap(env, argv[0]); + instance = pointer != nullptr ? static_cast(pointer->data) : nil; + unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; + } else if (Reference::isInstance(env, argv[0])) { + Reference* reference = Reference::unwrap(env, argv[0]); + instance = reference != nullptr ? static_cast(reference->data) : nil; + unwrapStatus = instance != nil ? napi_ok : napi_invalid_arg; + } else { + unwrapStatus = napi_unwrap(env, argv[0], (void**)&instance); + } + + if (unwrapStatus != napi_ok || instance == nil) { + napi_value nativePointer = nullptr; + if (napi_get_named_property(env, argv[0], "__ns_native_ptr", &nativePointer) == napi_ok && + Pointer::isInstance(env, nativePointer)) { + Pointer* pointer = Pointer::unwrap(env, nativePointer); + instance = pointer != nullptr ? static_cast(pointer->data) : nil; + } + } + + if (instance != nil) { + Class currentClass = object_getClass(instance); + while (currentClass != nil) { + if (currentClass == expectedClass) { + isInstance = true; + break; + } + currentClass = class_getSuperclass(currentClass); + } + } + } + } + + napi_value result = nullptr; + napi_get_boolean(env, isInstance, &result); + return result; +} + +NAPI_FUNCTION(BridgedConstructor) { + NAPI_CALLBACK_BEGIN(16) + + napi_value newTarget = nullptr; + napi_get_new_target(env, cbinfo, &newTarget); + + napi_valuetype thisType = napi_undefined; + if (jsThis == nullptr || napi_typeof(env, jsThis, &thisType) != napi_ok || + (thisType != napi_object && thisType != napi_function)) { + napi_create_object(env, &jsThis); + + napi_value prototypeOwner = newTarget; + if (prototypeOwner == nullptr || napi_typeof(env, prototypeOwner, &thisType) != napi_ok || + (thisType != napi_function && thisType != napi_object)) { + prototypeOwner = nullptr; + } + + if (prototypeOwner != nullptr) { + napi_value prototype = nullptr; + if (napi_get_named_property(env, prototypeOwner, "prototype", &prototype) == napi_ok && + prototype != nullptr) { + napi_value global = nullptr; + napi_value objectCtor = nullptr; + napi_value setPrototypeOf = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + napi_value setPrototypeArgs[2] = {jsThis, prototype}; + napi_call_function(env, objectCtor, setPrototypeOf, 2, setPrototypeArgs, nullptr); + } + } + } + + napi_valuetype jsType = napi_undefined; + if (argc > 0) { + napi_typeof(env, argv[0], &jsType); + } + + id object = nil; + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + auto ensureWrappedThis = [&](id nativeObject) { + if (jsThis == nullptr || nativeObject == nil) { + return; + } + + if (newTarget != nullptr) { + // Fresh constructor receivers should not need an unwrap probe here. + // On V8, reentrant constructor calls from ObjC->JS block callbacks can + // stall inside napi_unwrap() on that receiver. + napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); + return; + } + + void* existingWrapped = nullptr; + napi_status unwrapStatus = napi_unwrap(env, jsThis, &existingWrapped); + if (unwrapStatus == napi_ok && existingWrapped != nullptr) { + return; + } + + napi_wrap(env, jsThis, nativeObject, nullptr, nullptr, nullptr); + }; + + Class cls = (Class)data; + + napi_value constructor = newTarget; + if (constructor == nullptr && jsThis != nullptr) { + napi_get_named_property(env, jsThis, "constructor", &constructor); + } + + if (constructor != nullptr) { + Class newTargetCls = nil; + if (!(bridgeState != nullptr && + bridgeState->tryResolveBridgedClassConstructor(env, constructor, &newTargetCls))) { + napi_unwrap(env, constructor, (void**)&newTargetCls); + } + + if (newTargetCls != nil) { + cls = newTargetCls; + } + } + + if (jsType == napi_external) { + return jsThis; + } else { + // Backward compatibility: allow `new Class(pointer)` to wrap an existing + // native instance instead of running initializer resolution. + if (argc == 1 && argv[0] != nullptr) { + void* rawPointer = nullptr; + if (tryGetInteropPointerArg(env, argv[0], &rawPointer) && rawPointer != nullptr) { + if (napi_value cached = bridgeState->getCachedHandleObject(env, rawPointer); + cached != nullptr) { + return cached; + } + + object = (id)rawPointer; + if (napi_value existing = bridgeState->findCachedObjectWrapper(env, object); + existing != nullptr) { + return existing; + } + + ensureWrappedThis(object); + jsThis = bridgeState->proxyNativeObject(env, jsThis, object); + napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); + return jsThis; + } + } + + bool supercall = class_conformsToProtocol(cls, @protocol(ObjCBridgeClassBuilderProtocol)); + + if (supercall) { + ClassBuilder* builder = (ClassBuilder*)bridgeState->classesByPointer[cls]; + if (!builder->isFinal) builder->build(); + } + + // Allocate first, then run initializer resolution through the bridged + // JS "init" method so constructor arguments participate in selector + // matching (including Swift-style token objects). + object = [cls alloc]; + ensureWrappedThis(object); + jsThis = bridgeState->proxyNativeObject(env, jsThis, object); + } + + napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr); + + napi_value initMethod; + napi_status initStatus = napi_get_named_property(env, jsThis, "init", &initMethod); + if (initStatus != napi_ok || initMethod == nullptr) { + return jsThis; + } + + napi_value initResult; + initStatus = napi_call_function(env, jsThis, initMethod, argc, argv, &initResult); + if (initStatus != napi_ok) { + return nullptr; + } + + return initResult != nullptr ? initResult : jsThis; +} + +// Used to display the description of a native object in console.log. +// It's just implementation of nodejs.util.inspect.custom. +NAPI_FUNCTION(CustomInspect) { + napi_value jsThis; + void* data; + size_t argc = 0; + + napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); + + id self = nil; + napi_unwrap(env, jsThis, (void**)&self); + + if (self == nil) { + napi_value result; + napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); + return result; + } + + auto description = [self description]; + auto descriptionString = [description UTF8String]; + + napi_value result; + napi_create_string_utf8(env, descriptionString, NAPI_AUTO_LENGTH, &result); + + return result; +} + +// Used for Symbol.dispose (using statement support). +NAPI_FUNCTION(releaseObject) { + napi_value jsThis; + void* data; + napi_get_cb_info(env, cbinfo, nil, nil, &jsThis, &data); + id self; + napi_unwrap(env, jsThis, (void**)&self); + auto bridgeState = ObjCBridgeState::InstanceData(env); + bridgeState->unregisterObject(self); + return nullptr; +} + +// Implemented in JS to minimize calls into native. Fast enumeration +// makes use of buffers, so we only fill up the buffer once via +// native call and only do it again if needed via _fillStack. +static const char* FastEnumerationIteratorFactorySource = R"( + (function () { + return { + stack: new Array(16), + stacklen: -1, + stackptr: -1, + done: false, + + next() { + if (this.stackptr < 0 && !this.done) { + this.stacklen = this._fillStack(this.stack); + if (this.stacklen == 0) { + this.done = true; + this.stackptr = -1; + } else { + this.stackptr = 0; + } + } + + if (this.done) { + return { done: true }; + } + + const result = { value: this.stack[this.stackptr++], done: false }; + if (this.stackptr >= this.stacklen) { + this.stackptr = -1; + } + + return result; + }, + }; + }) +)"; + +void initFastEnumeratorIteratorFactory(napi_env env, ObjCBridgeState* bridgeState) { + napi_value result, script; + napi_create_string_utf8(env, FastEnumerationIteratorFactorySource, NAPI_AUTO_LENGTH, &script); + napi_run_script(env, script, &result); + bridgeState->createFastEnumeratorIterator = make_ref(env, result); +} + +class FastEnumerationIterator { + public: + FastEnumerationIterator(id collection) : collection(collection) {} + + static void finalize(napi_env env, void* data, void* hint) { + FastEnumerationIterator* iterator = (FastEnumerationIterator*)data; + delete iterator; + } + + static napi_value fillStack(napi_env env, napi_callback_info cbinfo) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + + napi_value jsThis; + void* data; + size_t argc = 1; + napi_value stackArray; + + napi_get_cb_info(env, cbinfo, &argc, &stackArray, &jsThis, &data); + + FastEnumerationIterator* self = nil; + napi_unwrap(env, jsThis, (void**)&self); + + NSUInteger count = [self->collection countByEnumeratingWithState:&self->state + objects:self->stackbuf + count:16]; + + for (uint32_t index = 0; index < static_cast(count); index++) { + id obj = self->state.itemsPtr[index]; + napi_value jsObj = bridgeState->getObject(env, obj); + napi_set_element(env, stackArray, index, jsObj); + } + + napi_value result; + napi_create_uint32(env, static_cast(count), &result); + + return result; + } + + napi_value toJS(napi_env env) { + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + + napi_value createIterator = get_ref_value(env, bridgeState->createFastEnumeratorIterator); + + napi_value result; + napi_call_function(env, createIterator, createIterator, 0, nullptr, &result); + + napi_property_descriptor fillStack = { + .utf8name = "_fillStack", + .name = nil, + .method = FastEnumerationIterator::fillStack, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }; + + napi_define_properties(env, result, 1, &fillStack); + + napi_ref ref; + napi_wrap(env, result, this, FastEnumerationIterator::finalize, nullptr, &ref); + + return result; + } + + id collection; + NSFastEnumerationState state = {0}; + id stackbuf[16]; + BOOL firstLoop = YES; + long mutationsPtrValue; +}; + +NAPI_FUNCTION(fastEnumeration) { + napi_value jsThis; + void* data; + size_t argc = 0; + + napi_get_cb_info(env, cbinfo, &argc, nil, &jsThis, &data); + + id self = nil; + napi_unwrap(env, jsThis, (void**)&self); + + if (self == nil) { + napi_value result; + napi_create_string_utf8(env, "(nil)", NAPI_AUTO_LENGTH, &result); + return result; + } + + if (![self conformsToProtocol:@protocol(NSFastEnumeration)]) { + napi_throw_error(env, nil, "Object does not conform to NSFastEnumeration"); + return nullptr; + } + + auto iterator = new FastEnumerationIterator((id)self); + return iterator->toJS(env); +} + +std::string NativeObjectName = "NativeObject"; + +// Bridge an Objective-C class to JavaScript on the fly. Runtime introspection +// is used to determine the class's properties and methods. +// In an overview, we define two versions of same class. One is the "normal" +// one, and the other is the "supercall" one. The supercall one is used to call +// superclasses' methods. The normal one is used to call the class's own +// methods. The supercall one is used automatically when the normal one is +// extended by a JS class. +// Every Bridged Class extends the NativeObject class. + +void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value constructor, + ObjCProtocol* protocol, ObjCClass* cls) { + ObjCClassMember::defineMembers(env, members, protocol->membersOffset, constructor, cls); + for (auto protocol : protocol->protocols) { + defineProtocolMembers(env, members, constructor, protocol, cls); + } +} + +ObjCClass::ObjCClass(napi_env env, MDSectionOffset offset) { + NAPI_PREAMBLE + + this->env = env; + + metadataOffset = offset; + + bridgeState = ObjCBridgeState::InstanceData(env); + bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; + + bool isNativeObject = offset == MD_SECTION_OFFSET_NULL; + + std::vector protocolOffsets; + + MDSectionOffset superClassOffset = MD_SECTION_OFFSET_NULL; + bool hasMembers = false; + std::string jsConstructorName; + if (isNativeObject) { + name = NativeObjectName; + jsConstructorName = name; + nativeClass = nil; + } else { + auto nameOffset = bridgeState->metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + auto runtimeNameOffset = bridgeState->metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + name = bridgeState->metadata->resolveString(nameOffset); + const char* runtimeName = name.c_str(); + if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { + runtimeName = bridgeState->metadata->resolveString(runtimeNameOffset); + } + jsConstructorName = runtimeName; + if (jsConstructorName.empty()) { + jsConstructorName = name; + } + nativeClass = objc_getClass(runtimeName); + while (hasProtocols) { + auto protocolOffset = bridgeState->metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & mdSectionOffsetNext) != 0; + protocolOffset &= ~mdSectionOffsetNext; + if (protocolOffset != MD_SECTION_OFFSET_NULL) protocolOffsets.push_back(protocolOffset); + } + superClassOffset = bridgeState->metadata->getOffset(offset); + hasMembers = (superClassOffset & mdSectionOffsetNext) != 0; + superClassOffset &= ~mdSectionOffsetNext; + offset += sizeof(MDSectionOffset); + } + + napi_value constructor, prototype; + + napi_define_class(env, jsConstructorName.c_str(), jsConstructorName.length(), + JS_BridgedConstructor, (void*)nativeClass, 0, nil, &constructor); + + if (nativeClass != nil) { + napi_wrap(env, constructor, (void*)nativeClass, nil, nil, nil); + bridgeState->registerRuntimeClass(this, nativeClass); + } + + napi_get_named_property(env, constructor, "prototype", &prototype); + + napi_value superConstructor = nil, superPrototype = nil; + + // If the class requested isn't NativeObject - the class which every bridged + // class extends - we need to find the super class and inherit from it, if it + // exists. + if (!isNativeObject) { + if (superClassOffset != MD_SECTION_OFFSET_NULL) { + superClassOffset += bridgeState->metadata->classesOffset; + } + + // If class offset is 0, it means that the class doesn't have a super class. + // But we just inherit NativeObject class in that case. + superclass = bridgeState->getClass(env, superClassOffset); + if (superclass != nil) { + superConstructor = get_ref_value(env, superclass->constructor); + superPrototype = get_ref_value(env, superclass->prototype); + napi_inherits(env, constructor, superConstructor); + } + + for (auto protocolOffset : protocolOffsets) { + auto protocol = + bridgeState->getProtocol(env, protocolOffset + bridgeState->metadata->protocolsOffset); + if (protocol == nil) continue; + defineProtocolMembers(env, members, constructor, protocol, this); + } + } else { + superclass = nullptr; + } + + napi_value constructorNameValue = nullptr; + napi_create_string_utf8(env, jsConstructorName.c_str(), jsConstructorName.length(), + &constructorNameValue); + napi_property_descriptor constructorNameProp = { + .utf8name = "name", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = constructorNameValue, + .attributes = napi_default, + .data = nullptr, + }; + napi_define_properties(env, constructor, 1, &constructorNameProp); + + this->constructor = make_ref(env, constructor); + this->prototype = make_ref(env, prototype); + + if (isNativeObject) { + napi_value global, Symbol, SymbolDispose, SymbolIterator; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &Symbol); + napi_get_named_property(env, Symbol, "iterator", &SymbolIterator); + napi_get_named_property(env, Symbol, "dispose", &SymbolDispose); + napi_valuetype type; + napi_typeof(env, SymbolDispose, &type); + + napi_value sizeofValue; + napi_create_int32(env, sizeof(id), &sizeofValue); + + napi_property_descriptor properties[] = { + { + .utf8name = nil, + .name = jsSymbolFor(env, "nodejs.util.inspect.custom"), + .method = JS_CustomInspect, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }, + { + .utf8name = "toString", + .name = nil, + .method = JS_CustomInspect, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }, + { + .utf8name = nil, + .name = jsSymbolFor(env, "sizeof"), + .method = nil, + .getter = nil, + .setter = nil, + .value = sizeofValue, + .attributes = napi_enumerable, + .data = nil, + }, + { + .utf8name = nil, + .name = SymbolIterator, + .method = JS_fastEnumeration, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = napi_enumerable, + .data = nil, + }}; + + napi_define_properties(env, prototype, 4, properties); + + napi_define_properties(env, constructor, 1, &properties[2]); + + bool hasSuperclass = false; + napi_value superclassKey = nullptr; + napi_create_string_utf8(env, "superclass", NAPI_AUTO_LENGTH, &superclassKey); + napi_has_own_property(env, constructor, superclassKey, &hasSuperclass); + if (!hasSuperclass) { + napi_property_descriptor superclassProperty = { + .utf8name = "superclass", + .name = nil, + .method = JS_classSuperclassFallback, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = + (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &superclassProperty); + } + + if (type == napi_symbol) { + properties[0].name = SymbolDispose; + properties[0].method = JS_releaseObject; + + napi_define_properties(env, prototype, 1, properties); + } + + return; + } + + // Add the 'extend' static method to all native classes (not NativeObject) + if (!isNativeObject) { + napi_value extendMethod; + napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr, + &extendMethod); + napi_set_named_property(env, constructor, "extend", extendMethod); + } + + if (!hasMembers) return; + + ObjCClassMember::defineMembers(env, members, offset, constructor, this); + + auto hasOwnNamedProperty = [&](napi_value object, const char* propertyName) { + napi_value key = nullptr; + napi_create_string_utf8(env, propertyName, NAPI_AUTO_LENGTH, &key); + bool hasOwn = false; + napi_has_own_property(env, object, key, &hasOwn); + return hasOwn; + }; + + if (!hasOwnNamedProperty(constructor, "name")) { + napi_value classNameValue = nullptr; + napi_create_string_utf8(env, jsConstructorName.c_str(), NAPI_AUTO_LENGTH, &classNameValue); + napi_property_descriptor nameProperty = { + .utf8name = "name", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = classNameValue, + .attributes = (napi_property_attributes)(napi_configurable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &nameProperty); + } + + if (!hasOwnNamedProperty(constructor, "length")) { + napi_value zeroValue = nullptr; + napi_create_int32(env, 0, &zeroValue); + napi_property_descriptor lengthProperty = { + .utf8name = "length", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = zeroValue, + .attributes = (napi_property_attributes)(napi_configurable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &lengthProperty); + } + + if (!hasOwnNamedProperty(constructor, "alloc")) { + napi_property_descriptor allocProperty = { + .utf8name = "alloc", + .name = nil, + .method = JS_NSObject_alloc, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = + (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &allocProperty); + } + + if (!hasOwnNamedProperty(constructor, "arguments") || + !hasOwnNamedProperty(constructor, "caller")) { + napi_value undefinedValue = nullptr; + napi_get_undefined(env, &undefinedValue); + napi_property_descriptor slots[] = { + { + .utf8name = "arguments", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = undefinedValue, + .attributes = (napi_property_attributes)(napi_configurable | napi_writable), + .data = nil, + }, + { + .utf8name = "caller", + .name = nil, + .method = nil, + .getter = nil, + .setter = nil, + .value = undefinedValue, + .attributes = (napi_property_attributes)(napi_configurable | napi_writable), + .data = nil, + }, + }; + napi_define_properties(env, constructor, 2, slots); + } + + napi_value global = nullptr; + napi_value symbolCtor = nullptr; + napi_value hasInstanceSymbol = nullptr; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &symbolCtor); + napi_get_named_property(env, symbolCtor, "hasInstance", &hasInstanceSymbol); + bool hasOwnHasInstance = false; + napi_has_own_property(env, constructor, hasInstanceSymbol, &hasOwnHasInstance); + if (!hasOwnHasInstance) { + napi_property_descriptor hasInstanceProperty = { + .utf8name = nil, + .name = hasInstanceSymbol, + .method = JS_classHasInstance, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = (napi_property_attributes)(napi_configurable), + .data = nil, + }; + napi_define_properties(env, constructor, 1, &hasInstanceProperty); + } + + if (!hasOwnNamedProperty(prototype, "toString")) { + napi_property_descriptor toStringProperty = { + .utf8name = "toString", + .name = nil, + .method = JS_CustomInspect, + .getter = nil, + .setter = nil, + .value = nil, + .attributes = + (napi_property_attributes)(napi_configurable | napi_writable | napi_enumerable), + .data = nil, + }; + napi_define_properties(env, prototype, 1, &toStringProperty); + } +} + +ObjCClass::~ObjCClass() { + if (env != nullptr && + (bridgeState == nullptr || IsBridgeStateLive(bridgeState, bridgeStateToken))) { + DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, constructor); + DeleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, prototype); + } + constructor = nullptr; + prototype = nullptr; + bridgeState = nullptr; + bridgeStateToken = 0; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/ClassBuilder.h b/NativeScript/ffi/objc/napi/ClassBuilder.h similarity index 100% rename from NativeScript/ffi/napi/ClassBuilder.h rename to NativeScript/ffi/objc/napi/ClassBuilder.h diff --git a/NativeScript/ffi/napi/ClassBuilder.mm b/NativeScript/ffi/objc/napi/ClassBuilder.mm similarity index 100% rename from NativeScript/ffi/napi/ClassBuilder.mm rename to NativeScript/ffi/objc/napi/ClassBuilder.mm diff --git a/NativeScript/ffi/napi/ClassMember.h b/NativeScript/ffi/objc/napi/ClassMember.h similarity index 100% rename from NativeScript/ffi/napi/ClassMember.h rename to NativeScript/ffi/objc/napi/ClassMember.h diff --git a/NativeScript/ffi/napi/ClassMember.mm b/NativeScript/ffi/objc/napi/ClassMember.mm similarity index 98% rename from NativeScript/ffi/napi/ClassMember.mm rename to NativeScript/ffi/objc/napi/ClassMember.mm index 772901273..c2b8a9633 100644 --- a/NativeScript/ffi/napi/ClassMember.mm +++ b/NativeScript/ffi/objc/napi/ClassMember.mm @@ -22,10 +22,10 @@ #include "SignatureDispatch.h" #include "TypeConv.h" #include "Util.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api.h" #include "js_native_api_types.h" #include "node_api_util.h" -#include "runtime/NativeScriptException.h" namespace nativescript { @@ -410,8 +410,13 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, bool classMethod, bool isStret = cif->returnType->type->size > 16 && cif->returnType->type->type == FFI_TYPE_STRUCT; #endif + NapiNativeCallbackExceptionCapture callbackException; + ScopedNapiNativeCallbackExceptionCapture callbackExceptionCapture( + &callbackException); + @try { if (!supercall) { + bool preparedInvoked = false; if (cif != nullptr && cif->signatureHash != 0) { if (descriptor != nullptr && (!descriptor->dispatchLookupCached || @@ -435,22 +440,24 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, bool classMethod, if (invoker != nullptr) { NativeCallRuntimeUnlockScope unlockRuntime(env); invoker((void*)objc_msgSend, avalues, rvalue); - return true; + preparedInvoked = true; } } + if (!preparedInvoked) { #if defined(__x86_64__) - if (isStret) { - NativeCallRuntimeUnlockScope unlockRuntime(env); - ffi_call(&cif->cif, FFI_FN(objc_msgSend_stret), rvalue, avalues); - } else { + if (isStret) { + NativeCallRuntimeUnlockScope unlockRuntime(env); + ffi_call(&cif->cif, FFI_FN(objc_msgSend_stret), rvalue, avalues); + } else { + NativeCallRuntimeUnlockScope unlockRuntime(env); + ffi_call(&cif->cif, FFI_FN(objc_msgSend), rvalue, avalues); + } +#else NativeCallRuntimeUnlockScope unlockRuntime(env); ffi_call(&cif->cif, FFI_FN(objc_msgSend), rvalue, avalues); - } -#else - NativeCallRuntimeUnlockScope unlockRuntime(env); - ffi_call(&cif->cif, FFI_FN(objc_msgSend), rvalue, avalues); #endif + } } else { Class superClass = classMethod ? class_getSuperclass(object_getClass((id)receiverClass)) : class_getSuperclass(receiverClass); @@ -478,6 +485,10 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, bool classMethod, return false; } + if (rethrowNapiNativeCallbackException(env, callbackException)) { + return false; + } + return true; } diff --git a/NativeScript/ffi/objc/napi/Closure.h b/NativeScript/ffi/objc/napi/Closure.h new file mode 100644 index 000000000..d81a43f4d --- /dev/null +++ b/NativeScript/ffi/objc/napi/Closure.h @@ -0,0 +1,88 @@ +#ifndef CLOSURE_H +#define CLOSURE_H + +#include + +#include +#include +#include + +#include "MetadataReader.h" +#include "TypeConv.h" +#include "ffi.h" +#include "node_api_util.h" +#include "objc/runtime.h" + +namespace nativescript { + +class ObjCBridgeState; + +struct NapiNativeCallbackExceptionCapture { + napi_env env = nullptr; + napi_ref errorRef = nullptr; + + ~NapiNativeCallbackExceptionCapture(); + void clear(); +}; + +class ScopedNapiNativeCallbackExceptionCapture { + public: + explicit ScopedNapiNativeCallbackExceptionCapture( + NapiNativeCallbackExceptionCapture* capture); + ~ScopedNapiNativeCallbackExceptionCapture(); + + ScopedNapiNativeCallbackExceptionCapture( + const ScopedNapiNativeCallbackExceptionCapture&) = delete; + ScopedNapiNativeCallbackExceptionCapture& operator=( + const ScopedNapiNativeCallbackExceptionCapture&) = delete; + + private: + NapiNativeCallbackExceptionCapture* capture_ = nullptr; +}; + +bool recordNapiNativeCallbackException(napi_env env, napi_value error); +bool rethrowNapiNativeCallbackException( + napi_env env, NapiNativeCallbackExceptionCapture& capture); + +class Closure { + public: + static void callBlockFromMainThread(napi_env env, napi_value js_cb, + void* context, void* data); + static void destroyOnOwningThread(Closure* closure); + + Closure(napi_env env, std::string typeEncoding, bool isBlock, bool isMethod = false); + Closure(napi_env env, MDMetadataReader* reader, MDSectionOffset offset, + bool isBlock = false, std::string* encoding = nullptr, + bool isMethod = false, bool isGetter = false, bool isSetter = false); + + ~Closure(); + void retain(); + void release(); + + napi_env env = nullptr; + ObjCBridgeState* bridgeState = nullptr; + uint64_t bridgeStateToken = 0; + napi_ref thisConstructor; + napi_ref func = nullptr; + bool isGetter = false; + bool isSetter = false; + std::string propertyName; + SEL selector = nullptr; + napi_threadsafe_function tsfn = nullptr; + + std::thread::id jsThreadId = std::this_thread::get_id(); + CFRunLoopRef jsRunLoop = CFRunLoopGetCurrent(); + std::atomic retainCount{1}; + + ffi_cif cif; + ffi_closure* closure; + void* fnptr; + ffi_type** atypes = nullptr; // Track malloc'd atypes array + + std::shared_ptr returnType; + std::vector> argTypes; +}; + +} // namespace nativescript + +#endif /* CLOSURE_H */ diff --git a/NativeScript/ffi/napi/Closure.mm b/NativeScript/ffi/objc/napi/Closure.mm similarity index 86% rename from NativeScript/ffi/napi/Closure.mm rename to NativeScript/ffi/objc/napi/Closure.mm index ab90659ca..1e5be935b 100644 --- a/NativeScript/ffi/napi/Closure.mm +++ b/NativeScript/ffi/objc/napi/Closure.mm @@ -6,7 +6,7 @@ #include "ObjCBridge.h" #include "TypeConv.h" #include "Util.h" -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api.h" #include "js_native_api_types.h" #ifdef ENABLE_JS_RUNTIME @@ -28,6 +28,9 @@ namespace { +thread_local std::vector + gNativeCallbackExceptionCaptureStack; + inline void deleteClosureOnOwningThread(Closure* closure) { if (closure == nullptr) { return; @@ -61,6 +64,73 @@ inline void deleteClosureOnOwningThread(Closure* closure) { } // namespace +NapiNativeCallbackExceptionCapture::~NapiNativeCallbackExceptionCapture() { + clear(); +} + +void NapiNativeCallbackExceptionCapture::clear() { + if (env != nullptr && errorRef != nullptr) { + napi_delete_reference(env, errorRef); + } + env = nullptr; + errorRef = nullptr; +} + +ScopedNapiNativeCallbackExceptionCapture:: + ScopedNapiNativeCallbackExceptionCapture( + NapiNativeCallbackExceptionCapture* capture) + : capture_(capture) { + gNativeCallbackExceptionCaptureStack.push_back(capture_); +} + +ScopedNapiNativeCallbackExceptionCapture:: + ~ScopedNapiNativeCallbackExceptionCapture() { + if (!gNativeCallbackExceptionCaptureStack.empty() && + gNativeCallbackExceptionCaptureStack.back() == capture_) { + gNativeCallbackExceptionCaptureStack.pop_back(); + } +} + +bool recordNapiNativeCallbackException(napi_env env, napi_value error) { + if (env == nullptr || error == nullptr || + gNativeCallbackExceptionCaptureStack.empty()) { + return false; + } + + auto* capture = gNativeCallbackExceptionCaptureStack.back(); + if (capture == nullptr || capture->errorRef != nullptr) { + return capture != nullptr; + } + + capture->env = env; + return napi_create_reference(env, error, 1, &capture->errorRef) == napi_ok; +} + +bool rethrowNapiNativeCallbackException( + napi_env env, NapiNativeCallbackExceptionCapture& capture) { + if (env == nullptr || capture.errorRef == nullptr) { + return false; + } + + napi_value error = nullptr; + napi_ref errorRef = capture.errorRef; + capture.errorRef = nullptr; + napi_get_reference_value(env, errorRef, &error); + napi_delete_reference(env, errorRef); + capture.env = nullptr; + + if (error != nullptr) { + NativeScriptException nativeScriptException( + env, error, "JS implemented closure threw an exception"); + nativeScriptException.ReThrowToJS(env); + } else { + NativeScriptException nativeScriptException( + "Unable to obtain the error thrown by the JS implemented closure"); + nativeScriptException.ReThrowToJS(env); + } + return true; +} + void Closure::destroyOnOwningThread(Closure* closure) { deleteClosureOnOwningThread(closure); } inline bool selectorEndsWithErrorParam(SEL selector) { @@ -135,7 +205,11 @@ inline void JSCallbackInner(Closure* closure, napi_value func, napi_value thisAr napi_create_error(env, code, msg, &result); } - NativeScriptException::OnUncaughtError(env, result); + if (recordNapiNativeCallbackException(env, result)) { + napi_get_undefined(env, &result); + } else { + NativeScriptException::OnUncaughtError(env, result); + } } // Even if call was failed and result is just undefined, let's still try to @@ -251,7 +325,11 @@ void JSMethodCallback(ffi_cif* cif, void* ret, void* args[], void* data) { napi_create_error(env, code, msg, &result); } - NativeScriptException::OnUncaughtError(env, result); + if (recordNapiNativeCallbackException(env, result)) { + napi_get_undefined(env, &result); + } else { + NativeScriptException::OnUncaughtError(env, result); + } } bool shouldFree; diff --git a/NativeScript/ffi/napi/Enum.h b/NativeScript/ffi/objc/napi/Enum.h similarity index 100% rename from NativeScript/ffi/napi/Enum.h rename to NativeScript/ffi/objc/napi/Enum.h diff --git a/NativeScript/ffi/objc/napi/Enum.mm b/NativeScript/ffi/objc/napi/Enum.mm new file mode 100644 index 000000000..c66c80a09 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Enum.mm @@ -0,0 +1,142 @@ +#include "Enum.h" +#import +#include +#include +#include +#include +#include "ObjCBridge.h" +#include "ffi/objc/shared/EnumMemberAliases.h" + +namespace nativescript { + +namespace { +inline void defineConstantIfMissing(napi_env env, napi_value object, const std::string& name, + napi_value value, + napi_property_attributes attributes = napi_enumerable) { + bool hasProperty = false; + napi_has_named_property(env, object, name.c_str(), &hasProperty); + if (hasProperty) { + return; + } + + napi_property_descriptor prop = { + .utf8name = name.c_str(), + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = value, + .attributes = attributes, + .data = nullptr, + }; + napi_define_properties(env, object, 1, &prop); +} + +inline std::string stripEnumSuffix(const std::string& enumName) { + static const std::vector suffixes = { + "Options", "Option", "Enums", "Enum", "Result", "Direction", "Orientation", + "Style", "Mask", "Type", "Status", "Modes", "Mode", "s"}; + + for (const auto& suffix : suffixes) { + if (enumName.size() > suffix.size() && + enumName.compare(enumName.size() - suffix.size(), suffix.size(), suffix) == 0) { + return enumName.substr(0, enumName.size() - suffix.size()); + } + } + + return enumName; +} +} // namespace + +void ObjCBridgeState::registerEnumGlobals(napi_env env, napi_value global) { + MDSectionOffset offset = metadata->enumsOffset; + while (offset < metadata->signaturesOffset) { + MDSectionOffset originalOffset = offset; + auto name = metadata->getString(offset); + + offset = metadata->forEachEnumMember( + originalOffset, [&](const char*, const std::string& cName, int64_t value) { + if (cName.empty()) { + return; + } + napi_value member; + napi_create_int64(env, value, &member); + defineConstantIfMissing(env, global, cName, member, + (napi_property_attributes)(napi_enumerable | napi_configurable)); + }); + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = JS_enumGetter, + .setter = nullptr, + .value = nullptr, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = (void*)((size_t)originalOffset), + }; + napi_define_properties(env, global, 1, &prop); + } +} + +NAPI_FUNCTION(enumGetter) { + void* data; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, nullptr, &data); + MDSectionOffset offset = (MDSectionOffset)((size_t)data); + MDSectionOffset originalOffset = offset; + auto bridgeState = ObjCBridgeState::InstanceData(env); + + auto cached = bridgeState->mdValueCache[offset]; + if (cached != nullptr) { + return get_ref_value(env, cached); + } + + napi_value result; + napi_create_object(env, &result); + + // enum name + auto enumName = bridgeState->metadata->getString(offset); + std::string enumNameStr = enumName; + + std::string strippedPrefix = stripEnumSuffix(enumNameStr); + + bridgeState->metadata->forEachEnumMember(originalOffset, [&](const char* name, + const std::string& cName, + int64_t value) { + napi_value member; + napi_create_int64(env, value, &member); + + std::string memberName = name; + auto aliases = enum_members::aliasesFor(enumNameStr, strippedPrefix, memberName, cName); + + for (const auto& alias : aliases) { + defineConstantIfMissing(env, result, alias.name, member); + } + + const std::string& reverseCanonical = aliases.size() > 1 ? aliases[1].name : memberName; + + // reverse mapping enum[value] -> canonical member name + char valueKey[32]; + snprintf(valueKey, sizeof(valueKey), "%lld", (long long)value); + bool hasReverseMapping = false; + napi_has_named_property(env, result, valueKey, &hasReverseMapping); + if (!hasReverseMapping) { + napi_value reverseName; + napi_create_string_utf8(env, reverseCanonical.c_str(), NAPI_AUTO_LENGTH, &reverseName); + napi_property_descriptor reverseProp = { + .utf8name = valueKey, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = reverseName, + .attributes = napi_enumerable, + .data = nullptr, + }; + napi_define_properties(env, result, 1, &reverseProp); + } + }); + + bridgeState->mdValueCache[originalOffset] = make_ref(env, result); + + return result; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/InlineFunctions.h b/NativeScript/ffi/objc/napi/InlineFunctions.h similarity index 100% rename from NativeScript/ffi/napi/InlineFunctions.h rename to NativeScript/ffi/objc/napi/InlineFunctions.h diff --git a/NativeScript/ffi/napi/InlineFunctions.mm b/NativeScript/ffi/objc/napi/InlineFunctions.mm similarity index 100% rename from NativeScript/ffi/napi/InlineFunctions.mm rename to NativeScript/ffi/objc/napi/InlineFunctions.mm diff --git a/NativeScript/ffi/napi/Interop.h b/NativeScript/ffi/objc/napi/Interop.h similarity index 100% rename from NativeScript/ffi/napi/Interop.h rename to NativeScript/ffi/objc/napi/Interop.h diff --git a/NativeScript/ffi/napi/Interop.mm b/NativeScript/ffi/objc/napi/Interop.mm similarity index 100% rename from NativeScript/ffi/napi/Interop.mm rename to NativeScript/ffi/objc/napi/Interop.mm diff --git a/NativeScript/ffi/napi/JSObject.h b/NativeScript/ffi/objc/napi/JSObject.h similarity index 100% rename from NativeScript/ffi/napi/JSObject.h rename to NativeScript/ffi/objc/napi/JSObject.h diff --git a/NativeScript/ffi/napi/JSObject.mm b/NativeScript/ffi/objc/napi/JSObject.mm similarity index 100% rename from NativeScript/ffi/napi/JSObject.mm rename to NativeScript/ffi/objc/napi/JSObject.mm diff --git a/NativeScript/ffi/napi/ObjCBridge.h b/NativeScript/ffi/objc/napi/ObjCBridge.h similarity index 96% rename from NativeScript/ffi/napi/ObjCBridge.h rename to NativeScript/ffi/objc/napi/ObjCBridge.h index 6e7eb19a0..565966e3e 100644 --- a/NativeScript/ffi/napi/ObjCBridge.h +++ b/NativeScript/ffi/objc/napi/ObjCBridge.h @@ -50,7 +50,7 @@ struct HandleObjectRef { struct RecentObjectWrapperRef { uintptr_t objectKey = 0; uintptr_t objectClassKey = 0; - napi_ref ref = nullptr; + napi_ref borrowedRef = nullptr; }; void finalize_objc_object(napi_env /*env*/, void* data, void* hint); @@ -161,6 +161,26 @@ class ObjCBridgeState { ObjCClass* getClass(napi_env env, MDSectionOffset offset); + inline bool findMetadataClassOffset(Class cls, MDSectionOffset* offset) { + auto find = mdClassesByPointer.find(cls); + if (find == mdClassesByPointer.end()) { + if (!resolvePendingMetadataClasses()) { + return false; + } + find = mdClassesByPointer.find(cls); + if (find == mdClassesByPointer.end()) { + return false; + } + } + *offset = find->second; + return true; + } + + // Maps metadata classes whose image was not loaded when the class globals + // were registered. Only does work after another image has loaded since the + // last call. Returns whether any class was mapped. + bool resolvePendingMetadataClasses(); + ObjCProtocol* getProtocol(napi_env env, MDSectionOffset offset); Cif* getMethodCif(napi_env env, Method method); @@ -330,16 +350,9 @@ class ObjCBridgeState { handleObjectRefs.erase(it); bumpHandleObjectRefsGeneration(); } - inline void deleteRecentObjectWrapperRef(napi_env env, - RecentObjectWrapperRef& entry) { - if (env != nullptr && entry.ref != nullptr) { - napi_delete_reference(env, entry.ref); - } - entry = {}; - } inline void cacheRecentObjectWrapper(napi_env env, id object, - napi_value value) { - if (env == nullptr || object == nil || value == nullptr) { + napi_value value, napi_ref ref) { + if (env == nullptr || object == nil || value == nullptr || ref == nullptr) { return; } @@ -350,7 +363,7 @@ class ObjCBridgeState { continue; } - napi_value existing = get_ref_value(env, entry.ref); + napi_value existing = get_ref_value(env, entry.borrowedRef); if (existing != nullptr) { bool isSameValue = false; if (napi_strict_equals(env, existing, value, &isSameValue) == napi_ok && @@ -359,8 +372,7 @@ class ObjCBridgeState { } } - deleteRecentObjectWrapperRef(env, entry); - napi_create_reference(env, value, 1, &entry.ref); + entry.borrowedRef = ref; entry.objectKey = objectKey; entry.objectClassKey = objectClassKey; return; @@ -369,9 +381,8 @@ class ObjCBridgeState { RecentObjectWrapperRef entry{ .objectKey = objectKey, .objectClassKey = objectClassKey, - .ref = nullptr, + .borrowedRef = ref, }; - napi_create_reference(env, value, 1, &entry.ref); static constexpr size_t kRecentObjectWrapperLimit = 16; if (recentObjectWrappers.size() < kRecentObjectWrapperLimit) { @@ -381,7 +392,6 @@ class ObjCBridgeState { RecentObjectWrapperRef& replaced = recentObjectWrappers[nextRecentObjectWrapperSlot++ % kRecentObjectWrapperLimit]; - deleteRecentObjectWrapperRef(env, replaced); replaced = entry; } inline napi_value getRecentObjectWrapper(napi_env env, id object) { @@ -397,12 +407,11 @@ class ObjCBridgeState { continue; } - napi_value value = get_ref_value(env, it->ref); + napi_value value = get_ref_value(env, it->borrowedRef); if (value != nullptr) { return value; } - deleteRecentObjectWrapperRef(env, *it); it = recentObjectWrappers.erase(it); } @@ -417,7 +426,6 @@ class ObjCBridgeState { const uintptr_t objectClassKey = NormalizeHandleKey((void*)object_getClass(object)); for (auto it = recentObjectWrappers.begin(); it != recentObjectWrappers.end();) { if (it->objectKey == objectKey && it->objectClassKey == objectClassKey) { - deleteRecentObjectWrapperRef(env, *it); it = recentObjectWrappers.erase(it); } else { ++it; @@ -955,6 +963,8 @@ class ObjCBridgeState { std::unordered_map protocols; std::unordered_map classesByPointer; std::unordered_map mdClassesByPointer; + std::vector> pendingMdClasses; + uint64_t pendingMdClassesImageGeneration = 0; std::unordered_map mdProtocolsByPointer; std::unordered_map nativeObjectsByBridgeWrapper; std::unordered_map constructorsByPointer; diff --git a/NativeScript/ffi/objc/napi/ObjCBridge.mm b/NativeScript/ffi/objc/napi/ObjCBridge.mm new file mode 100644 index 000000000..9dd865fd2 --- /dev/null +++ b/NativeScript/ffi/objc/napi/ObjCBridge.mm @@ -0,0 +1,1152 @@ +#include "ObjCBridge.h" +#include "AutoreleasePool.h" +#include "Block.h" +#include "Class.h" +#include "ClassMember.h" +#include "Enum.h" +#include "InlineFunctions.h" +#include "Interop.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "NativeScript.h" +#include "Object.h" +#include "ObjectRef.h" +#include "Struct.h" +#include "TypeConv.h" +#include "Util.h" +#include "Variable.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EMBED_METADATA_SIZE +const unsigned char __attribute__((section("__objc_metadata,__objc_metadata"))) +#if defined(__aarch64__) +embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderARM"; +#else +embedded_metadata[EMBED_METADATA_SIZE] = "NSMDSectionHeaderX86"; +#endif +#endif + +namespace nativescript { +namespace { +std::mutex gLiveBridgeStatesMutex; +std::unordered_map gLiveBridgeStates; +std::atomic gNextBridgeStateToken{1}; +constexpr const char* kNativePointerProperty = "__ns_native_ptr"; + +bool envFlagEnabled(const char* name) { + const char* value = std::getenv(name); + if (value == nullptr || value[0] == '\0') { + return false; + } + + return std::strcmp(value, "0") != 0 && std::strcmp(value, "false") != 0 && + std::strcmp(value, "FALSE") != 0 && std::strcmp(value, "False") != 0 && + std::strcmp(value, "off") != 0 && std::strcmp(value, "OFF") != 0 && + std::strcmp(value, "no") != 0 && std::strcmp(value, "NO") != 0; +} + +inline void deleteReferenceNow(napi_env env, napi_ref ref, bool unrefFirst) { + if (env == nullptr || ref == nullptr) { + return; + } + + if (unrefFirst) { + uint32_t remaining = 0; + napi_reference_unref(env, ref, &remaining); + } + + napi_delete_reference(env, ref); +} + +inline void deleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, + uint64_t bridgeStateToken, napi_ref ref, + bool unrefFirst) { + if (env == nullptr || ref == nullptr) { + return; + } + + if (bridgeState == nullptr) { + deleteReferenceNow(env, ref, unrefFirst); + return; + } + + if (!IsBridgeStateLive(bridgeState, bridgeStateToken)) { + return; + } + + if (bridgeState->jsThreadId == std::this_thread::get_id()) { +#if !defined(TARGET_ENGINE_QUICKJS) + deleteReferenceNow(env, ref, unrefFirst); + return; +#endif + } + + CFRunLoopRef runLoop = bridgeState->jsRunLoop; + if (runLoop == nullptr) { + runLoop = CFRunLoopGetMain(); + } + + if (runLoop == nullptr) { + if (bridgeState->jsThreadId == std::this_thread::get_id()) { + deleteReferenceNow(env, ref, unrefFirst); + } + return; + } + + CFRetain(runLoop); + CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ + if (IsBridgeStateLive(bridgeState, bridgeStateToken)) { + deleteReferenceNow(env, ref, unrefFirst); + } + CFRelease(runLoop); + }); + CFRunLoopWakeUp(runLoop); +} + +uint64_t RegisterBridgeState(const ObjCBridgeState* bridgeState) { + if (bridgeState == nullptr) { + return 0; + } + + uint64_t token = gNextBridgeStateToken.fetch_add(1, std::memory_order_relaxed); + std::lock_guard lock(gLiveBridgeStatesMutex); + gLiveBridgeStates[bridgeState] = token; + return token; +} + +void UnregisterBridgeState(const ObjCBridgeState* bridgeState) { + if (bridgeState == nullptr) { + return; + } + + std::lock_guard lock(gLiveBridgeStatesMutex); + gLiveBridgeStates.erase(bridgeState); +} +} // namespace + +bool IsBridgeStateLive(const ObjCBridgeState* bridgeState, uint64_t token) noexcept { + if (bridgeState == nullptr || token == 0) { + return false; + } + + std::lock_guard lock(gLiveBridgeStatesMutex); + auto find = gLiveBridgeStates.find(bridgeState); + return find != gLiveBridgeStates.end() && find->second == token; +} + +void DeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, + uint64_t bridgeStateToken, napi_ref ref) { + deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, false); +} + +void ReleaseAndDeleteReferenceOnOwningThread(napi_env env, ObjCBridgeState* bridgeState, + uint64_t bridgeStateToken, napi_ref ref) { + deleteReferenceOnOwningThread(env, bridgeState, bridgeStateToken, ref, true); +} + +bool PostFinalizer(napi_env env, napi_finalize finalize_cb, void* finalize_data, + void* finalize_hint) { + if (env == nullptr || finalize_cb == nullptr) { + return false; + } + + ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && bridgeState->jsThreadId == std::this_thread::get_id()) { +#if !defined(TARGET_ENGINE_QUICKJS) + finalize_cb(env, finalize_data, finalize_hint); + return true; +#endif + } + + CFRunLoopRef runLoop = bridgeState != nullptr ? bridgeState->jsRunLoop : CFRunLoopGetMain(); + if (runLoop == nullptr) { + return false; + } + + if (bridgeState == nullptr && [NSThread isMainThread]) { +#if !defined(TARGET_ENGINE_QUICKJS) + finalize_cb(env, finalize_data, finalize_hint); + return true; +#endif + } + + CFRetain(runLoop); + CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ + finalize_cb(env, finalize_data, finalize_hint); + CFRelease(runLoop); + }); + CFRunLoopWakeUp(runLoop); + return true; +} + +void finalize_bridge_data(napi_env env, void* data, void* hint) { + auto bridgeState = (ObjCBridgeState*)data; + delete bridgeState; +} + +MDMetadataReader* loadMetadataFromFile(const char* metadata_path) { + if (metadata_path == nullptr) { + metadata_path = "metadata.nsmd"; + } + + auto f = fopen(metadata_path == nullptr ? "metadata.nsmd" : metadata_path, "r"); + if (f == nullptr) { + fprintf(stderr, "metadata.nsmd not found\n"); + exit(1); + } + fseek(f, 0, SEEK_END); + auto size = ftell(f); + fseek(f, 0, SEEK_SET); + auto buffer = (uint8_t*)malloc(size); + fread(buffer, 1, size, f); + fclose(f); + return new MDMetadataReader(buffer); +} + +inline bool hasNamedProperty(napi_env env, napi_value object, const char* name) { + bool hasProperty = false; + napi_has_named_property(env, object, name, &hasProperty); + return hasProperty; +} + +inline bool isFunctionValue(napi_env env, napi_value value) { + if (value == nullptr) { + return false; + } + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + if (valueType == napi_function) { + return true; + } + + if (valueType != napi_object) { + return false; + } + + napi_value instance = nullptr; + napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); + if (status == napi_ok) { + return true; + } + + bool hasPendingException = false; + if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + } + return false; +} + +inline void clearPendingException(napi_env env) { + bool hasPendingException = false; + if (napi_is_exception_pending(env, &hasPendingException) == napi_ok && hasPendingException) { + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + } +} + +inline bool isConstructableValue(napi_env env, napi_value value) { + napi_value instance = nullptr; + napi_status status = napi_new_instance(env, value, 0, nullptr, &instance); + if (status == napi_ok) { + return true; + } + + clearPendingException(env); + return false; +} + +inline bool hasConstructableNamedProperty(napi_env env, napi_value global, const char* name) { + if (!hasNamedProperty(env, global, name)) { + return false; + } + + napi_value value = nullptr; + if (napi_get_named_property(env, global, name, &value) != napi_ok || value == nullptr) { + clearPendingException(env); + return false; + } + + return isConstructableValue(env, value); +} + +inline void defineGlobalValue(napi_env env, napi_value global, const char* name, napi_value value) { + if (name == nullptr || value == nullptr) { + return; + } + + if (napi_set_named_property(env, global, name, value) == napi_ok) { + return; + } + clearPendingException(env); + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = value, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = nullptr, + }; + if (napi_define_properties(env, global, 1, &prop) != napi_ok) { + clearPendingException(env); + } +} + +inline bool defineConstructableGlobalValue(napi_env env, napi_value global, const char* name, + napi_value value) { + if (!isConstructableValue(env, value)) { + return false; + } + + if (napi_set_named_property(env, global, name, value) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + + napi_property_descriptor prop = { + .utf8name = name, + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = value, + .attributes = (napi_property_attributes)(napi_enumerable | napi_configurable), + .data = nullptr, + }; + if (napi_define_properties(env, global, 1, &prop) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + + napi_value key = nullptr; + napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &key); + if (key != nullptr) { + bool deleted = false; + if (napi_delete_property(env, global, key, &deleted) == napi_ok && deleted) { + if (napi_define_properties(env, global, 1, &prop) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + if (napi_set_named_property(env, global, name, value) == napi_ok && + hasConstructableNamedProperty(env, global, name)) { + return true; + } + clearPendingException(env); + } else { + clearPendingException(env); + } + } + + return hasConstructableNamedProperty(env, global, name); +} + +inline std::string buildStructEncoding(StructInfo* info) { + if (info == nullptr || info->name == nullptr) { + return ""; + } + + std::string encoding = "{"; + encoding += info->name; + encoding += "="; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + return ""; + } + field.type->encode(&encoding); + } + encoding += "}"; + return encoding; +} + +inline void setTypeEncodingSymbol(napi_env env, napi_value value, const std::string& encoding) { + if (value == nullptr || encoding.empty()) { + return; + } + + napi_value typeSymbol = jsSymbolFor(env, "type"); + napi_value encodedValue = nullptr; + napi_create_string_utf8(env, encoding.c_str(), NAPI_AUTO_LENGTH, &encodedValue); + if (typeSymbol != nullptr && encodedValue != nullptr) { + napi_set_property(env, value, typeSymbol, encodedValue); + } +} + +inline void registerStructAlias(napi_env env, napi_value global, ObjCBridgeState* bridgeState, + const char* aliasName, + std::initializer_list candidates) { + if (bridgeState == nullptr || aliasName == nullptr) { + return; + } + + if (hasNamedProperty(env, global, aliasName)) { + napi_value existing = nullptr; + if (napi_get_named_property(env, global, aliasName, &existing) == napi_ok && + isFunctionValue(env, existing)) { + return; + } + } + + for (const char* candidate : candidates) { + if (candidate == nullptr || candidate[0] == '\0') { + continue; + } + + auto structIt = bridgeState->structOffsets.find(candidate); + if (structIt != bridgeState->structOffsets.end()) { + StructInfo* info = bridgeState->getStructInfo(env, structIt->second); + if (info != nullptr) { + napi_value cls = StructObject::getJSClass(env, info); + if (isFunctionValue(env, cls)) { + setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); + defineGlobalValue(env, global, aliasName, cls); + return; + } + } + } + + if (hasNamedProperty(env, global, candidate)) { + napi_value source = nullptr; + if (napi_get_named_property(env, global, candidate, &source) == napi_ok && + isFunctionValue(env, source)) { + defineGlobalValue(env, global, aliasName, source); + return; + } + } + } +} + +inline void ensureSyntheticCGPoint(napi_env env, napi_value global) { + if (hasConstructableNamedProperty(env, global, "CGPoint")) { + return; + } + + static StructInfo* syntheticInfo = nullptr; + if (syntheticInfo == nullptr) { + syntheticInfo = new StructInfo(); + syntheticInfo->name = strdup("CGPoint"); + syntheticInfo->size = sizeof(double) * 2; + syntheticInfo->jsClass = nullptr; + + const char* doubleEncodingX = "d"; + const char* doubleEncodingY = "d"; + + StructFieldInfo fieldX; + fieldX.name = strdup("x"); + fieldX.offset = 0; + fieldX.type = TypeConv::Make(env, &doubleEncodingX); + syntheticInfo->fields.push_back(fieldX); + + StructFieldInfo fieldY; + fieldY.name = strdup("y"); + fieldY.offset = sizeof(double); + fieldY.type = TypeConv::Make(env, &doubleEncodingY); + syntheticInfo->fields.push_back(fieldY); + } + + napi_value cls = StructObject::getJSClass(env, syntheticInfo); + if (!isFunctionValue(env, cls)) { + return; + } + + setTypeEncodingSymbol(env, cls, "{CGPoint=dd}"); + defineConstructableGlobalValue(env, global, "CGPoint", cls); +} + +inline void ensureConstructableStructAlias(napi_env env, napi_value global, + ObjCBridgeState* bridgeState, const char* aliasName, + std::initializer_list candidates) { + if (bridgeState == nullptr || aliasName == nullptr) { + return; + } + + if (hasConstructableNamedProperty(env, global, aliasName)) { + return; + } + + for (const char* candidate : candidates) { + if (candidate == nullptr || candidate[0] == '\0') { + continue; + } + + if (hasNamedProperty(env, global, candidate)) { + napi_value value = nullptr; + if (napi_get_named_property(env, global, candidate, &value) == napi_ok && + defineConstructableGlobalValue(env, global, aliasName, value)) { + return; + } + clearPendingException(env); + } + + auto structIt = bridgeState->structOffsets.find(candidate); + if (structIt != bridgeState->structOffsets.end()) { + StructInfo* info = bridgeState->getStructInfo(env, structIt->second); + if (info != nullptr) { + napi_value cls = StructObject::getJSClass(env, info); + if (defineConstructableGlobalValue(env, global, aliasName, cls)) { + setTypeEncodingSymbol(env, cls, buildStructEncoding(info)); + return; + } + } + } + } +} + +inline void installMacUIColorCompatShim(napi_env env) { + const char* script = R"( + (function (globalObject) { + if (typeof globalObject.UIColor === "undefined" && + typeof globalObject.NSColor !== "undefined") { + globalObject.UIColor = globalObject.NSColor; + } + + const colorCtor = globalObject.UIColor || globalObject.NSColor; + if (!colorCtor || !colorCtor.prototype) { + return; + } + + if (typeof colorCtor.prototype.initWithRedGreenBlueAlpha === "function") { + return; + } + + colorCtor.prototype.initWithRedGreenBlueAlpha = function (red, green, blue, alpha) { + if (typeof this.initWithSRGBRedGreenBlueAlpha === "function") { + return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof this.initWithCalibratedRedGreenBlueAlpha === "function") { + return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === "function") { + return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === "function") { + return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + return this; + }; + })(globalThis); + )"; + + napi_value shim = nullptr; + napi_create_string_utf8(env, script, NAPI_AUTO_LENGTH, &shim); + if (shim != nullptr) { + napi_value result = nullptr; + napi_run_script(env, shim, &result); + } +} + +inline void* resolveSymbolPointer(ObjCBridgeState* bridgeState, const char* symbolName) { + if (bridgeState == nullptr || symbolName == nullptr || symbolName[0] == '\0') { + return nullptr; + } + + void* symbol = dlsym(bridgeState->self_dl, symbolName); + if (symbol == nullptr) { + symbol = dlsym(RTLD_DEFAULT, symbolName); + } + if (symbol == nullptr) { + std::string underscored = "_"; + underscored += symbolName; + symbol = dlsym(bridgeState->self_dl, underscored.c_str()); + if (symbol == nullptr) { + symbol = dlsym(RTLD_DEFAULT, underscored.c_str()); + } + } + + return symbol; +} + +inline bool unwrapCompatNativeHandle(napi_env env, napi_value value, void** out) { + if (value == nullptr || out == nullptr) { + return false; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *out = ptr != nullptr ? ptr->data : nullptr; + return ptr != nullptr; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *out = ref != nullptr ? ref->data : nullptr; + return ref != nullptr; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + + if (valueType == napi_bigint) { + uint64_t raw = 0; + bool lossless = false; + if (napi_get_value_bigint_uint64(env, value, &raw, &lossless) != napi_ok) { + return false; + } + *out = reinterpret_cast(static_cast(raw)); + return true; + } + + if (valueType == napi_external) { + return napi_get_value_external(env, value, out) == napi_ok; + } + + if (valueType != napi_object && valueType != napi_function) { + return false; + } + + bool hasNativePointer = false; + if (napi_has_named_property(env, value, "__ns_native_ptr", &hasNativePointer) == napi_ok && + hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, value, "__ns_native_ptr", &nativePointerValue) == napi_ok && + napi_get_value_external(env, nativePointerValue, out) == napi_ok && *out != nullptr) { + return true; + } + } + + return napi_unwrap(env, value, out) == napi_ok && *out != nullptr; +} + +inline napi_value createCompatDispatchQueueWrapper(napi_env env, dispatch_queue_t queue) { + if (queue == nullptr) { + napi_value nullValue = nullptr; + napi_get_null(env, &nullValue); + return nullValue; + } + + return Pointer::create(env, reinterpret_cast(queue)); +} + +inline napi_value compat_dispatch_get_global_queue(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2] = {nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + int64_t identifier = 0; + if (argc > 0) { + napi_valuetype identifierType = napi_undefined; + if (napi_typeof(env, argv[0], &identifierType) == napi_ok && identifierType == napi_bigint) { + bool lossless = false; + if (napi_get_value_bigint_int64(env, argv[0], &identifier, &lossless) != napi_ok) { + napi_throw_type_error(env, nullptr, + "dispatch_get_global_queue expects a numeric identifier."); + return nullptr; + } + } else { + napi_value coercedIdentifier = nullptr; + if (napi_coerce_to_number(env, argv[0], &coercedIdentifier) != napi_ok || + napi_get_value_int64(env, coercedIdentifier, &identifier) != napi_ok) { + napi_throw_type_error(env, nullptr, + "dispatch_get_global_queue expects a numeric identifier."); + return nullptr; + } + } + } + + uint64_t flags = 0; + if (argc > 1) { + napi_valuetype flagsType = napi_undefined; + if (napi_typeof(env, argv[1], &flagsType) == napi_ok && flagsType == napi_bigint) { + bool lossless = false; + if (napi_get_value_bigint_uint64(env, argv[1], &flags, &lossless) != napi_ok) { + napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); + return nullptr; + } + } else { + napi_value coercedFlags = nullptr; + int64_t signedFlags = 0; + if (napi_coerce_to_number(env, argv[1], &coercedFlags) != napi_ok || + napi_get_value_int64(env, coercedFlags, &signedFlags) != napi_ok) { + napi_throw_type_error(env, nullptr, "dispatch_get_global_queue expects numeric flags."); + return nullptr; + } + flags = static_cast(signedFlags); + } + } + + return createCompatDispatchQueueWrapper(env, dispatch_get_global_queue(identifier, flags)); +} + +inline napi_value compat_dispatch_get_current_queue(napi_env env, napi_callback_info info) { + (void)info; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return createCompatDispatchQueueWrapper(env, dispatch_get_current_queue()); +#pragma clang diagnostic pop +} + +inline napi_value compat_dispatch_async(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value argv[2] = {nullptr, nullptr}; + napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); + + if (argc < 2) { + napi_throw_type_error(env, nullptr, "dispatch_async expects a queue and callback."); + return nullptr; + } + + void* queueHandle = nullptr; + if (!unwrapCompatNativeHandle(env, argv[0], &queueHandle) || queueHandle == nullptr) { + napi_throw_type_error(env, nullptr, "dispatch_async expects a native queue handle."); + return nullptr; + } + + napi_valuetype callbackType = napi_undefined; + if (napi_typeof(env, argv[1], &callbackType) != napi_ok || callbackType != napi_function) { + napi_throw_type_error(env, nullptr, "dispatch_async expects a function callback."); + return nullptr; + } + + auto closure = new Closure(env, std::string("v"), true); + id block = registerBlock(env, closure, argv[1]); + dispatch_block_t dispatchBlock = (dispatch_block_t)block; + + dispatch_async(reinterpret_cast(queueHandle), dispatchBlock); + [block release]; + + napi_value undefinedValue = nullptr; + napi_get_undefined(env, &undefinedValue); + return undefinedValue; +} + +inline void registerCompatFunctionIfMissing(napi_env env, napi_value global, + ObjCBridgeState* bridgeState, const char* functionName, + const char* encoding) { + if (hasNamedProperty(env, global, functionName)) { + return; + } + + void* fn = resolveSymbolPointer(bridgeState, functionName); + if (fn == nullptr && strcmp(functionName, "CC_SHA256") == 0) { + void* commonCrypto = dlopen("/usr/lib/system/libcommonCrypto.dylib", RTLD_NOW | RTLD_LOCAL); + if (commonCrypto != nullptr) { + fn = dlsym(commonCrypto, functionName); + if (fn == nullptr) { + fn = dlsym(commonCrypto, "_CC_SHA256"); + } + } + } + + if (fn == nullptr) { + return; + } + + napi_value wrapper = FunctionPointer::wrapWithEncoding(env, fn, encoding, false); + if (wrapper != nullptr) { + napi_set_named_property(env, global, functionName, wrapper); + } +} + +inline void registerCompatFunction(napi_env env, napi_value global, const char* functionName, + napi_callback callback) { + napi_value wrapper = nullptr; + napi_create_function(env, functionName, NAPI_AUTO_LENGTH, callback, nullptr, &wrapper); + if (wrapper != nullptr) { + napi_value key = nullptr; + napi_create_string_utf8(env, functionName, NAPI_AUTO_LENGTH, &key); + if (key != nullptr) { + bool deleted = false; + napi_delete_property(env, global, key, &deleted); + clearPendingException(env); + } + defineGlobalValue(env, global, functionName, wrapper); + } +} + +void registerLegacyCompatGlobals(napi_env env, napi_value global, ObjCBridgeState* bridgeState) { +#if TARGET_OS_OSX + registerStructAlias(env, global, bridgeState, "CGPoint", + {"CGPoint", "_CGPoint", "NSPoint", "_NSPoint"}); + registerStructAlias(env, global, bridgeState, "CGSize", + {"CGSize", "_CGSize", "NSSize", "_NSSize"}); + registerStructAlias(env, global, bridgeState, "CGRect", + {"CGRect", "_CGRect", "NSRect", "_NSRect"}); + ensureSyntheticCGPoint(env, global); + ensureConstructableStructAlias( + env, global, bridgeState, "CGPoint", + {"CGPointStruct", "NSPoint", "NSPointStruct", "_CGPoint", "_NSPoint", "CGPoint"}); + installMacUIColorCompatShim(env); +#endif + + // CommonCrypto compatibility used by historical runtime tests and apps. + registerCompatFunctionIfMissing(env, global, bridgeState, "CC_SHA256", "^C^vQ^C"); + registerCompatFunctionIfMissing(env, global, bridgeState, "CGColorGetComponents", "^d^v"); + + // Force known-good libdispatch globals on macOS. The metadata path can resolve these with an + // incompatible call shape, which crashes when tests dispatch timers from a background queue. + registerCompatFunction(env, global, "dispatch_async", compat_dispatch_async); + registerCompatFunction(env, global, "dispatch_get_current_queue", + compat_dispatch_get_current_queue); + registerCompatFunction(env, global, "dispatch_get_global_queue", + compat_dispatch_get_global_queue); +} + +ObjCBridgeState::ObjCBridgeState(napi_env env, const char* metadata_path, + const void* metadata_ptr) { + this->env = env; + napi_set_instance_data(env, this, finalize_bridge_data, nil); + lifetimeToken = RegisterBridgeState(this); + trackedObjectLiveness = [[NSMutableSet alloc] init]; + + self_dl = dlopen(nullptr, RTLD_NOW); + + if (metadata_ptr && *((const char*)metadata_ptr) != '\0') { +#ifdef EMBED_METADATA_SIZE + // NSLog(@"Ignoring metadata pointer due to embedded metadata"); + metadata = new MDMetadataReader((void*)embedded_metadata); +#else + // NSLog(@"Using metadata from pointer: %p", metadata_ptr); + metadata = new MDMetadataReader((void*)metadata_ptr); +#endif + } else { +#ifdef EMBED_METADATA_SIZE + if (metadata_path != nullptr) { + // NSLog(@"Loading metadata from file: %s", metadata_path); + metadata = loadMetadataFromFile(metadata_path); + } else { + // NSLog(@"Using embedded metadata"); + metadata = new MDMetadataReader((void*)embedded_metadata); + } +#else + unsigned long segmentSize = 0; + auto segmentData = getsegmentdata((const mach_header_64*)_dyld_get_image_header(0), + "__objc_metadata", &segmentSize); + if (segmentData != nullptr) { + metadata = new MDMetadataReader(segmentData); + } else { + metadata = loadMetadataFromFile(metadata_path); + } +#endif + } + + // objc_autoreleasePool = objc_autoreleasePoolPush(); +} + +ObjCBridgeState::~ObjCBridgeState() { + UnregisterBridgeState(this); + + auto deleteRef = [&](napi_ref& ref) { + if (env != nullptr && ref != nullptr) { + napi_delete_reference(env, ref); + ref = nullptr; + } + }; + + for (auto& pair : constructorsByPointer) { + deleteRef(pair.second); + } + constructorsByPointer.clear(); + + for (auto& frame : roundTripCacheFrames) { + for (auto& entry : frame) { + ObjCBridgeState::releaseRoundTripEntry(env, entry.second); + } + } + roundTripCacheFrames.clear(); + + for (auto& entry : recentRoundTripCache) { + ObjCBridgeState::releaseRoundTripEntry(env, entry.second); + } + recentRoundTripCache.clear(); + + for (auto& entry : handleObjectRefs) { + if (entry.second.ownsRef) { + deleteRef(entry.second.ref); + } + } + handleObjectRefs.clear(); + + recentObjectWrappers.clear(); + + std::unordered_set classAndProtocolConstructorRefs; + classAndProtocolConstructorRefs.reserve(classes.size() + protocols.size()); + for (const auto& pair : classes) { + if (pair.second != nullptr && pair.second->constructor != nullptr) { + classAndProtocolConstructorRefs.insert(pair.second->constructor); + } + } + for (const auto& pair : protocols) { + if (pair.second != nullptr && pair.second->constructor != nullptr) { + classAndProtocolConstructorRefs.insert(pair.second->constructor); + } + } + for (auto& pair : mdValueCache) { + napi_ref& ref = pair.second; + if (ref != nullptr && + classAndProtocolConstructorRefs.find(ref) == classAndProtocolConstructorRefs.end()) { + deleteRef(ref); + } + } + mdValueCache.clear(); + + deleteRef(pointerClass); + deleteRef(referenceClass); + deleteRef(functionReferenceClass); + deleteRef(createNativeProxy); + deleteRef(createFastEnumeratorIterator); + deleteRef(transferOwnershipToNative); + + // Clean up cached Cif objects + for (auto& pair : cifs) { + delete pair.second; + } + cifs.clear(); + + for (auto& pair : mdMethodSignatureCache) { + delete pair.second; + } + mdMethodSignatureCache.clear(); + + for (auto& pair : mdBlockSignatureCache) { + delete pair.second; + } + mdBlockSignatureCache.clear(); + + // Clean up ObjCClass objects + for (auto& pair : classes) { + delete pair.second; + } + classes.clear(); + + // Clean up ObjCProtocol objects + for (auto& pair : protocols) { + delete pair.second; + } + protocols.clear(); + + // Clean up StructInfo objects + for (auto& pair : structInfoCache) { + delete pair.second; + } + structInfoCache.clear(); + + // Clean up CFunction objects + for (auto& pair : cFunctionCache) { + delete pair.second; + } + cFunctionCache.clear(); + + for (auto& pair : mdFunctionSignatureCache) { + delete pair.second; + } + mdFunctionSignatureCache.clear(); + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + trackedObjectLiveness = nullptr; + [trackedObjectTable release]; + + // if (objc_autoreleasePool != nullptr) + // objc_autoreleasePoolPop(objc_autoreleasePool); + + delete metadata; + dlclose(self_dl); +} + +napi_value ObjCBridgeState::proxyNativeObject(napi_env env, napi_value object, id nativeObject) { + NAPI_PREAMBLE + + napi_value result = object; + const bool nativeIsArray = [nativeObject isKindOfClass:NSArray.class]; + bool shouldProxyArray = nativeIsArray && !envFlagEnabled("NS_DISABLE_NAPI_ARRAY_PROXY"); + if (shouldProxyArray) { + napi_value factory = get_ref_value(env, createNativeProxy); + napi_value transferOwnershipFunc = get_ref_value(env, this->transferOwnershipToNative); + napi_value global; + napi_value args[3] = {object, nullptr, transferOwnershipFunc}; + napi_get_boolean(env, true, &args[1]); + napi_get_global(env, &global); + napi_call_function(env, global, factory, 3, args, &result); + } + + napi_value nativePointer = Pointer::create(env, nativeObject); + if (nativePointer != nullptr) { + napi_set_named_property(env, result, kNativePointerProperty, nativePointer); + } + napi_wrap(env, result, nativeObject, nullptr, nullptr, nullptr); + + napi_ref ref = nullptr; + auto* finalizerContext = new JSObjectFinalizerContext{ + .bridgeState = this, + .bridgeStateToken = lifetimeToken, + .object = nativeObject, + .ref = nullptr, + }; + NAPI_GUARD( + napi_add_finalizer(env, result, finalizerContext, finalize_objc_object, nullptr, &ref)) { + delete finalizerContext; + NAPI_THROW_LAST_ERROR + return nullptr; + } + finalizerContext->ref = ref; + + storeObjectRef(nativeObject, ref); + cacheHandleObjectRef(env, nativeObject, ref); + cacheRecentObjectWrapper(env, nativeObject, result, ref); + attachObjectLifecycleAssociation(env, nativeObject); + trackObject(nativeObject); + + return result; +} + +void ObjCBridgeState::trackObject(id object) noexcept { + if (object == nil) { + return; + } + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + if (trackedObjectTable == nil) { + return; + } + + NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; + std::lock_guard lock(objectRefsMutex); + [trackedObjectTable addObject:objectKey]; +} + +bool ObjCBridgeState::isTrackedObjectAlive(id object) const noexcept { + if (object == nil) { + return false; + } + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + if (trackedObjectTable == nil) { + return false; + } + + NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; + std::lock_guard lock(objectRefsMutex); + return [trackedObjectTable containsObject:objectKey]; +} + +} // namespace nativescript + +using namespace nativescript; + +NAPI_FUNCTION(getArrayBuffer) { + NAPI_CALLBACK_BEGIN(2) + + void* ptr = Pointer::unwrap(env, argv[0])->data; + int64_t length; + napi_get_value_int64(env, argv[1], &length); + + napi_value arrayBuffer; + if (length < 0) { + napi_throw_error(env, nullptr, "Invalid ArrayBuffer length"); + return nullptr; + } + + napi_create_external_arraybuffer(env, ptr, static_cast(length), nullptr, nullptr, + &arrayBuffer); + + return arrayBuffer; +} + +NAPI_FUNCTION(init) { + NAPI_CALLBACK_BEGIN(1) + napi_valuetype type; + napi_typeof(env, argv[0], &type); + const char* metadata_path = nullptr; + if (type == napi_string) { + size_t len; + napi_get_value_string_utf8(env, argv[0], nullptr, 0, &len); + metadata_path = (char*)malloc(len + 1); + napi_get_value_string_utf8(env, argv[0], (char*)metadata_path, len + 1, &len); + } + nativescript_init(env, metadata_path, nullptr); + return nullptr; +} + +NAPI_EXPORT NAPI_MODULE_REGISTER { + const napi_property_descriptor property = NAPI_FUNCTION_DESC(init); + napi_define_properties(env, exports, 1, &property); + return exports; +} + +NAPI_EXPORT void nativescript_init(void* _env, const char* metadata_path, + const void* metadata_ptr) { + napi_env env = (napi_env)_env; + + ObjCBridgeState* bridgeState = new ObjCBridgeState(env, metadata_path, metadata_ptr); + + napi_value objc; + napi_create_object(env, &objc); + + const napi_property_descriptor objcProperties[] = { + NAPI_FUNCTION_DESC(registerClass), NAPI_FUNCTION_DESC(registerBlock), + NAPI_FUNCTION_DESC(import), NAPI_FUNCTION_DESC(autoreleasepool), + NAPI_FUNCTION_DESC(getArrayBuffer), + }; + + napi_define_properties(env, objc, 5, objcProperties); + + napi_value global; + napi_get_global(env, &global); + + const napi_property_descriptor globalProperties[] = {{ + .utf8name = "objc", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = objc, + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "ObjectRef", + .method = nullptr, + .getter = nullptr, + .setter = nullptr, + .value = defineObjectRefClass(env), + .attributes = napi_enumerable, + .data = nullptr, + }, + { + .utf8name = "NativeClass", + .method = JS_registerClass, + .getter = nullptr, + .setter = nullptr, + .value = nullptr, + .attributes = napi_enumerable, + .data = nullptr, + }}; + + napi_define_properties(env, global, 3, globalProperties); + + setupObjCClassDecorator(env); + + initProxyFactory(env, bridgeState); + initFastEnumeratorIteratorFactory(env, bridgeState); + + registerInterop(env, global); + registerInlineFunctions(env); + + bridgeState->registerVarGlobals(env, global); + bridgeState->registerEnumGlobals(env, global); + bridgeState->registerStructGlobals(env, global); + bridgeState->registerUnionGlobals(env, global); + bridgeState->registerFunctionGlobals(env, global); + bridgeState->registerClassGlobals(env, global); + bridgeState->registerProtocolGlobals(env, global); + registerLegacyCompatGlobals(env, global, bridgeState); +} diff --git a/NativeScript/ffi/napi/Object.h b/NativeScript/ffi/objc/napi/Object.h similarity index 100% rename from NativeScript/ffi/napi/Object.h rename to NativeScript/ffi/objc/napi/Object.h diff --git a/NativeScript/ffi/objc/napi/Object.mm b/NativeScript/ffi/objc/napi/Object.mm new file mode 100644 index 000000000..6fcd9ee17 --- /dev/null +++ b/NativeScript/ffi/objc/napi/Object.mm @@ -0,0 +1,826 @@ +#include "Object.h" +#include +#include "Interop.h" +#include "JSObject.h" +#include "ObjCBridge.h" +#include "js_native_api.h" +#include "node_api_util.h" + +#import +#include + +static SEL JSWrapperObjectAssociationKey = @selector(JSWrapperObjectAssociationKey); +static SEL ObjCLifecycleAssociationKey = @selector(ObjCLifecycleAssociationKey); + +@interface JSWrapperObjectAssociation : NSObject + +@property(nonatomic) napi_env env; +@property(nonatomic) napi_ref ref; +@property(nonatomic) nativescript::ObjCBridgeState* bridgeState; +@property(nonatomic) uint64_t bridgeStateToken; + ++ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; + ++ (instancetype)associationFor:(id)object; + +- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref; + +@end + +@interface ObjCLifecycleAssociation : NSObject { + nativescript::ObjCBridgeState* _bridgeState; + uint64_t _bridgeStateToken; + uintptr_t _objectAddress; +} + +- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object; + +@end + +@implementation JSWrapperObjectAssociation + +- (instancetype)initWithEnv:(napi_env)env ref:(napi_ref)ref { + self = [super init]; + if (self) { + self.env = env; + self.ref = ref; + self.bridgeState = nativescript::ObjCBridgeState::InstanceData(env); + self.bridgeStateToken = self.bridgeState != nullptr ? self.bridgeState->lifetimeToken : 0; + } + return self; +} + ++ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object { + napi_ref ref = nativescript::make_ref(env, value); + JSWrapperObjectAssociation* association = [[JSWrapperObjectAssociation alloc] initWithEnv:env + ref:ref]; + objc_setAssociatedObject(object, JSWrapperObjectAssociationKey, association, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [association release]; +} + ++ (instancetype)associationFor:(id)object { + return objc_getAssociatedObject(object, JSWrapperObjectAssociationKey); +} + +- (void)dealloc { + if (self.env != nullptr && self.ref != nullptr && + (self.bridgeState == nullptr || + nativescript::IsBridgeStateLive(self.bridgeState, self.bridgeStateToken))) { + nativescript::DeleteReferenceOnOwningThread(self.env, self.bridgeState, + self.bridgeStateToken, self.ref); + self.ref = nullptr; + } + self.bridgeState = nullptr; + self.bridgeStateToken = 0; + self.env = nullptr; + [super dealloc]; +} + +@end + +@implementation ObjCLifecycleAssociation + +- (instancetype)initWithBridgeState:(nativescript::ObjCBridgeState*)bridgeState object:(id)object { + self = [super init]; + if (self) { + _bridgeState = bridgeState; + _bridgeStateToken = bridgeState != nullptr ? bridgeState->lifetimeToken : 0; + _objectAddress = (uintptr_t)object; + } + + return self; +} + +- (void)dealloc { + nativescript::ObjCBridgeState* bridgeState = _bridgeState; + uint64_t bridgeStateToken = _bridgeStateToken; + uintptr_t objectAddress = _objectAddress; + if (nativescript::IsBridgeStateLive(bridgeState, bridgeStateToken)) { + bridgeState->detachObject((id)objectAddress); + } + + [super dealloc]; +} + +@end + +napi_value JS_transferOwnershipToNative(napi_env env, napi_callback_info cbinfo) { + size_t argc = 1; + napi_value arg = nullptr; + if (napi_get_cb_info(env, cbinfo, &argc, &arg, nullptr, nullptr) != napi_ok || argc == 0 || + arg == nullptr) { + return nullptr; + } + + id obj = nil; + if (napi_unwrap(env, arg, (void**)&obj) != napi_ok || obj == nil) { + return nullptr; + } + + [JSWrapperObjectAssociation transferOwnership:env of:arg toNative:obj]; + + return nullptr; +} + +namespace nativescript { + +napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, + Class cls = nil); + +void transferOwnershipToNative(napi_env env, napi_value value, id object) { + if (env == nullptr || value == nullptr || object == nil) { + return; + } + + [JSWrapperObjectAssociation transferOwnership:env of:value toNative:object]; +} + +namespace { +constexpr const char* kNativePointerProperty = "__ns_native_ptr"; + +napi_value findConstructorForClassObject(napi_env env, ObjCBridgeState* bridgeState, Class cls, + napi_value fallback = nullptr) { + if (bridgeState == nullptr || cls == nil) { + return fallback; + } + + auto bridgedClass = bridgeState->classesByPointer.find(cls); + if (bridgedClass != bridgeState->classesByPointer.end() && bridgedClass->second != nullptr) { + return get_ref_value(env, bridgedClass->second->constructor); + } + + auto constructedClass = bridgeState->constructorsByPointer.find(cls); + if (constructedClass != bridgeState->constructorsByPointer.end()) { + napi_value constructor = get_ref_value(env, constructedClass->second); + if (constructor != nullptr) { + return constructor; + } + } + + MDSectionOffset metadataOffset = MD_SECTION_OFFSET_NULL; + if (bridgeState->findMetadataClassOffset(cls, &metadataOffset)) { + auto bridgedMetadataClass = bridgeState->getClass(env, metadataOffset); + if (bridgedMetadataClass != nullptr) { + return get_ref_value(env, bridgedMetadataClass->constructor); + } + } + + const char* runtimeName = class_getName(cls); + if (runtimeName != nullptr && runtimeName[0] != '\0') { + napi_value global = nullptr; + napi_value constructor = nullptr; + bool hasGlobal = false; + napi_get_global(env, &global); + if (napi_has_named_property(env, global, runtimeName, &hasGlobal) == napi_ok && hasGlobal && + napi_get_named_property(env, global, runtimeName, &constructor) == napi_ok && + constructor != nullptr) { + return constructor; + } + } + + napi_value resolved = findConstructorForObject(env, bridgeState, (id)cls, cls); + return resolved != nullptr ? resolved : fallback; +} +} // namespace + +const char* nativeObjectProxySource = R"( + (function (object, isArray, transferOwnershipToNative) { + let isTransfered = false; + const boundMethods = Object.create(null); + let objectAtIndexMethod; + let addObjectMethod; + let removeObjectAtIndexMethod; + let setObjectAtIndexedSubscriptMethod; + + function bindTargetMethod(target, name) { + const cached = boundMethods[name]; + if (cached !== undefined) { + return cached; + } + + const value = target[name]; + if (typeof value !== "function") { + return value; + } + + if (value.__ns_proxy_bound === true) { + boundMethods[name] = value; + return value; + } + + const wrapper = value.bind(target); + Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); + boundMethods[name] = wrapper; + try { + Object.defineProperty(target, name, { + value: wrapper, + configurable: true, + writable: true + }); + } catch (_) { + } + return wrapper; + } + + return new Proxy(object, { + get (target, name, receiver) { + if (name === "superclass" && typeof target.class === "function") { + return target.class().superclass(); + } + + if (isArray && name === "count") { + return target.count; + } + + if (isArray) { + switch (name) { + case "objectAtIndex": + return objectAtIndexMethod !== undefined + ? objectAtIndexMethod + : (objectAtIndexMethod = bindTargetMethod(target, name)); + case "addObject": + return addObjectMethod !== undefined + ? addObjectMethod + : (addObjectMethod = bindTargetMethod(target, name)); + case "removeObjectAtIndex": + return removeObjectAtIndexMethod !== undefined + ? removeObjectAtIndexMethod + : (removeObjectAtIndexMethod = bindTargetMethod(target, name)); + case "setObjectAtIndexedSubscript": + return setObjectAtIndexedSubscriptMethod !== undefined + ? setObjectAtIndexedSubscriptMethod + : (setObjectAtIndexedSubscriptMethod = bindTargetMethod(target, name)); + } + } + + if (typeof name === "string") { + const boundMethod = boundMethods[name]; + if (boundMethod !== undefined) { + return boundMethod; + } + } + + const value = target[name]; + if (value !== undefined || name in target) { + if (typeof value === "function" && name !== "constructor") { + if (value.__ns_proxy_bound === true) { + return value; + } + + let wrapper; + if ((name === "isKindOfClass" || name === "isMemberOfClass")) { + wrapper = function (cls, a1, a2, a3) { + let resolvedClass = cls; + const resolvedClassType = typeof resolvedClass; + if (resolvedClass != null && + (resolvedClassType === "object" || resolvedClassType === "function")) { + try { + const runtimeName = typeof NSStringFromClass === "function" + ? NSStringFromClass(resolvedClass) + : null; + if (typeof runtimeName === "string" && runtimeName.length > 0) { + const registry = globalThis.__nsConstructorsByObjCClassName; + if (registry && registry[runtimeName]) { + resolvedClass = registry[runtimeName]; + } else if (typeof globalThis[runtimeName] !== "undefined") { + resolvedClass = globalThis[runtimeName]; + } + } + } catch (_) { + } + } + + switch (arguments.length) { + case 0: + case 1: + return value.call(target, resolvedClass); + case 2: + return value.call(target, resolvedClass, a1); + case 3: + return value.call(target, resolvedClass, a1, a2); + case 4: + return value.call(target, resolvedClass, a1, a2, a3); + default: { + const args = Array.prototype.slice.call(arguments); + args[0] = resolvedClass; + return Reflect.apply(value, target, args); + } + } + }; + } else { + wrapper = value.bind(target); + } + + Object.defineProperty(wrapper, "__ns_proxy_bound", { value: true }); + if (typeof name === "string") { + boundMethods[name] = wrapper; + } + try { + Object.defineProperty(target, name, { + value: wrapper, + configurable: true, + writable: true + }); + } catch (_) { + } + return wrapper; + } + return value; + } + + if (typeof name === 'symbol') { + return undefined; + } + + if (isArray) { + const index = Number(name); + if (!isNaN(index)) { + return target.objectAtIndex(index); + } + } + }, + + set (target, name, value) { + const isInternalProperty = typeof name === 'string' && + (name === 'napi_external' || name === 'napi_typetag' || name === '__ns_native_ptr'); + + if (typeof name === 'symbol') { + target[name] = value; + return true; + } + + if (typeof name === "string" && boundMethods[name] !== undefined) { + delete boundMethods[name]; + } + + if (isArray) { + switch (name) { + case "objectAtIndex": + objectAtIndexMethod = undefined; + break; + case "addObject": + addObjectMethod = undefined; + break; + case "removeObjectAtIndex": + removeObjectAtIndexMethod = undefined; + break; + case "setObjectAtIndexedSubscript": + setObjectAtIndexedSubscriptMethod = undefined; + break; + } + } + + if (isArray) { + const index = Number(name); + if (!isNaN(index)) { + target.setObjectAtIndexedSubscript(value, index); + return true; + } + } + + if (!isInternalProperty && !(name in target) && !isTransfered) { + isTransfered = true; + transferOwnershipToNative(target); + } + + target[name] = value; + + return true; + }, + }); + }) +)"; + +void initProxyFactory(napi_env env, ObjCBridgeState* state) { + napi_value script, result; + napi_create_string_utf8(env, nativeObjectProxySource, NAPI_AUTO_LENGTH, &script); + napi_run_script(env, script, &result); + state->createNativeProxy = make_ref(env, result); + + napi_value transferOwnershipToNative; + napi_create_function(env, "transferOwnershipToNative", NAPI_AUTO_LENGTH, + JS_transferOwnershipToNative, nullptr, &transferOwnershipToNative); + state->transferOwnershipToNative = make_ref(env, transferOwnershipToNative); +} + +void attachObjectLifecycleAssociation(napi_env env, id object) { + if (object == nil) { + return; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return; + } + + if (objc_getAssociatedObject(object, ObjCLifecycleAssociationKey) != nil) { + return; + } + + ObjCLifecycleAssociation* association = + [[ObjCLifecycleAssociation alloc] initWithBridgeState:bridgeState object:object]; + objc_setAssociatedObject(object, ObjCLifecycleAssociationKey, association, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + [association release]; +} + +namespace { +void finalize_objc_object_now(napi_env /*env*/, void* data, void* hint) { + (void)hint; + JSObjectFinalizerContext* context = static_cast(data); + if (context == nullptr) { + return; + } + + ObjCBridgeState* bridgeState = context->bridgeState; + if (IsBridgeStateLive(bridgeState, context->bridgeStateToken)) { + bridgeState->unregisterObjectIfRefMatches(context->object, context->ref); + } + + delete context; +} +} // namespace + +void finalize_objc_object(napi_env env, void* data, void* hint) { + if (PostFinalizer(env, finalize_objc_object_now, data, hint)) { + return; + } + + finalize_objc_object_now(env, data, hint); +} + +napi_value ObjCBridgeState::getObject(napi_env env, id obj, napi_value constructor, + ObjectOwnership ownership) { + if (obj == nil) { + return nullptr; + } + + NAPI_PREAMBLE + + Class cls = object_getClass(obj); + + if (cls == nullptr) { + return nullptr; + } + + if (class_isMetaClass(cls)) { + return findConstructorForClassObject(env, this, (Class)obj, constructor); + } + + napi_value resolvedConstructor = constructor; + if (resolvedConstructor != nullptr) { + Class hintedClass = nil; + tryResolveBridgedClassConstructor(env, resolvedConstructor, &hintedClass); + } + + napi_value actualConstructor = findConstructorForObject(env, this, obj, cls); + if (actualConstructor != nullptr) { + resolvedConstructor = actualConstructor; + } + + if (napi_value cached = findCachedObjectWrapper(env, obj); cached != nullptr) { + return cached; + } + + napi_value result = nil; + napi_value prototype; + NAPI_GUARD(napi_get_named_property(env, resolvedConstructor, "prototype", &prototype)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + NAPI_GUARD(napi_create_object(env, &result)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + napi_value global; + napi_value objectCtor; + napi_value setPrototypeOf; + napi_value argv[2] = {result, prototype}; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &objectCtor); + napi_get_named_property(env, objectCtor, "setPrototypeOf", &setPrototypeOf); + + NAPI_GUARD(napi_call_function(env, objectCtor, setPrototypeOf, 2, argv, nullptr)) { + NAPI_THROW_LAST_ERROR + return nullptr; + } + + napi_wrap(env, result, obj, nullptr, nullptr, nullptr); + napi_value nativePointer = Pointer::create(env, obj); + if (nativePointer != nullptr) { + napi_set_named_property(env, result, kNativePointerProperty, nativePointer); + } + + if (ownership == kUnownedObject) { + [obj retain]; + } + + result = proxyNativeObject(env, result, obj); + + // #if DEBUG + // napi_value global, Error, error, stack; + // napi_get_global(env, &global); + // napi_get_named_property(env, global, "Error", &Error); + // napi_new_instance(env, Error, 0, nullptr, &error); + // napi_get_named_property(env, error, "stack", &stack); + + // size_t stackSize; + // napi_get_value_string_utf8(env, stack, nullptr, 0, &stackSize); + // char *stackStr = new char[stackSize + 1]; + // napi_get_value_string_utf8(env, stack, stackStr, stackSize + 1, nullptr); + + // NSString *str = [NSString stringWithFormat:@"Wrapped object <%s: %p> @ %ld # %s", + // class_getName(cls), obj, [obj retainCount], stackStr]; + // dbglog([str UTF8String]); + + // delete[] stackStr; + // #endif + + return result; +} + +napi_value ObjCBridgeState::findCachedObjectWrapper(napi_env env, id obj) { + if (obj == nil) { + return nullptr; + } + + if (napi_value jsObject = idToJsObject(env, obj); jsObject != nullptr) { + return jsObject; + } + + auto roundTrip = getRoundTripObject(env, obj); + if (roundTrip != nullptr) { + return roundTrip; + } + + if (napi_value recentWrapper = getRecentObjectWrapper(env, obj); recentWrapper != nullptr) { + return recentWrapper; + } + + if (napi_value handleCached = getCachedHandleObject(env, (void*)obj); handleCached != nullptr) { + bool isRawHandleRoundTrip = ownsCachedHandleObjectRef((void*)obj); + void* wrapped = nullptr; + if (napi_unwrap(env, handleCached, &wrapped) == napi_ok && + NormalizeHandleKey(wrapped) == NormalizeHandleKey((void*)obj)) { + return handleCached; + } + + bool hasNativePointer = false; + if (napi_has_named_property(env, handleCached, kNativePointerProperty, &hasNativePointer) == + napi_ok && + hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, handleCached, kNativePointerProperty, &nativePointerValue) == + napi_ok && + Pointer::isInstance(env, nativePointerValue)) { + Pointer* pointer = Pointer::unwrap(env, nativePointerValue); + if (pointer != nullptr && + NormalizeHandleKey(pointer->data) == NormalizeHandleKey((void*)obj)) { + return handleCached; + } + } + } + + if (isRawHandleRoundTrip) { + return handleCached; + } + + removeCachedHandleObject(env, (void*)obj); + } + + if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { + return existing; + } + + JSWrapperObjectAssociation* association = [JSWrapperObjectAssociation associationFor:obj]; + if (association != nil) { + napi_value jsObject = get_ref_value(env, association.ref); + if (jsObject != nullptr) { + bool isArrayBuffer = false; + bool isTypedArray = false; + bool isDataView = false; + if ((napi_is_arraybuffer(env, jsObject, &isArrayBuffer) == napi_ok && isArrayBuffer) || + (napi_is_typedarray(env, jsObject, &isTypedArray) == napi_ok && isTypedArray) || + (napi_is_dataview(env, jsObject, &isDataView) == napi_ok && isDataView)) { + return jsObject; + } + + [obj retain]; + return proxyNativeObject(env, jsObject, obj); + } + } + + return nullptr; +} + +napi_value findConstructorForObject(napi_env env, ObjCBridgeState* bridgeState, id object, + Class cls) { + if (cls == nil) { + cls = object_getClass(object); + } + + // Look up if there is a custom class for it already + { + auto find = bridgeState->classesByPointer.find(cls); + if (find != bridgeState->classesByPointer.end()) { + return get_ref_value(env, find->second->constructor); + } + } + + // Look up if there is a custom constructor for it already + { + auto find = bridgeState->constructorsByPointer.find(cls); + if (find != bridgeState->constructorsByPointer.end()) { + return get_ref_value(env, find->second); + } + } + + // Look up if there is a metadata-defined class + { + MDSectionOffset metadataOffset = MD_SECTION_OFFSET_NULL; + if (bridgeState->findMetadataClassOffset(cls, &metadataOffset)) { + auto cls = bridgeState->getClass(env, metadataOffset); + return get_ref_value(env, cls->constructor); + } + } + + Class superclass = class_getSuperclass(cls); + if (superclass != nullptr) { + napi_value superclassConstructor = + findConstructorForObject(env, bridgeState, object, superclass); + if (superclassConstructor != nullptr) { + return superclassConstructor; + } + } + + // Look up the protocols implemented by this class if no class-based + // constructor could be resolved. For private runtime subclasses we prefer + // inheriting the public superclass surface over exposing a protocol-only + // shell that drops concrete class members. + { + unsigned int count; + auto protocols = class_copyProtocolList(cls, &count); + std::unordered_set impls; + + std::function processProtocolList = [&](Protocol** list, + unsigned int count) { + for (unsigned int i = 0; i < count; i++) { + auto protocol = list[i]; + auto find = bridgeState->mdProtocolsByPointer.find(protocol); + if (find != bridgeState->mdProtocolsByPointer.end()) { + impls.insert(bridgeState->getProtocol(env, find->second)); + } + list = protocol_copyProtocolList(protocol, &count); + processProtocolList(list, count); + } + }; + + processProtocolList(protocols, count); + + if (!impls.empty()) { + napi_value constructor; + napi_define_class(env, class_getName(cls), NAPI_AUTO_LENGTH, ObjCProtocol::jsConstructor, + nullptr, 0, nullptr, &constructor); + for (auto impl : impls) { + ObjCClassMember::defineMembers(env, impl->members, impl->membersOffset, constructor); + } + + bridgeState->constructorsByPointer[cls] = make_ref(env, constructor); + + return constructor; + } + } + + return nullptr; +} + +// Get a napi_value for an Objective-C object, creating it if it doesn't exist. +// Here we also ensure that the native object always points to the same +// JS object, this makes sure that we only ever finalize it once. +// Might want to consider using associated objects instead of a hashtable. +napi_value ObjCBridgeState::getObject(napi_env env, id obj, ObjectOwnership ownership, + MDSectionOffset classOffset, + std::vector* protocolOffsets) { + NAPI_PREAMBLE + + if (obj == nullptr) { + return nullptr; + } + + Class objectClass = object_getClass(obj); + if (objectClass != nil && class_isMetaClass(objectClass)) { + return findConstructorForClassObject(env, this, (Class)obj, nullptr); + } + + auto roundTrip = getRoundTripObject(env, obj); + if (roundTrip != nullptr) { + return roundTrip; + } + + if (napi_value existing = getNormalizedObjectRef(env, obj); existing != nullptr) { + return existing; + } + + auto findClass = classesByPointer.find(obj); + if (findClass != classesByPointer.end()) { + return get_ref_value(env, findClass->second->constructor); + } + + auto mdFindClassByPointer = mdClassesByPointer.find((Class)obj); + if (mdFindClassByPointer != mdClassesByPointer.end()) { + auto bridgedClass = getClass(env, mdFindClassByPointer->second); + return bridgedClass != nullptr ? get_ref_value(env, bridgedClass->constructor) : nullptr; + } + + auto cls = objectClass; + + MDSectionOffset metadataOffset = MD_SECTION_OFFSET_NULL; + if (findMetadataClassOffset(cls, &metadataOffset)) { + classOffset = metadataOffset; + } + + auto findByPointer = classesByPointer.find(cls); + if (findByPointer != classesByPointer.end()) { + return getObject(env, obj, get_ref_value(env, findByPointer->second->constructor), ownership); + } + + napi_value constructor = nullptr; + if (classOffset != 0) { + auto bridgedCls = getClass(env, classOffset); + + if (bridgedCls == nullptr) { + return nullptr; + } + + constructor = get_ref_value(env, bridgedCls->constructor); + } else if (protocolOffsets != nullptr && !protocolOffsets->empty()) { + auto proto = getProtocol(env, protocolOffsets->front()); + + if (proto == nullptr) { + return nullptr; + } + + constructor = get_ref_value(env, proto->constructor); + } else { + constructor = findConstructorForObject(env, this, obj, cls); + } + + if (constructor == nullptr) { + return nullptr; + } + + return getObject(env, obj, constructor, ownership); +} + +void ObjCBridgeState::unregisterObject(id object) noexcept { + // #if DEBUG + // NSString *string = [NSString stringWithFormat: @"Unregistering object <%s: %p> @ %ld # success: + // %d, finalized: %d", + // class_getName(object_getClass(object)), object, [object retainCount], + // (int)objectRefs.contains(object), (int)finalized]; + + // dbglog([string UTF8String]); + // #endif + + if (takeObjectRef(object) != nullptr) { + removeRecentObjectWrapper(env, object); + removeCachedHandleObject(env, (void*)object); + [object release]; + } +} + +bool ObjCBridgeState::unregisterObjectIfRefMatches(id object, napi_ref ref) noexcept { + if (takeObjectRef(object, ref) == nullptr) { + return false; + } + + removeRecentObjectWrapper(env, object); + removeCachedHandleObject(env, (void*)object); + [object release]; + return true; +} + +void ObjCBridgeState::detachObject(id object) noexcept { + takeObjectRef(object); + removeRoundTripObject(object); + removeRecentObjectWrapper(env, object); + removeCachedHandleObject(env, (void*)object); + + if (object == nil) { + return; + } + + NSMutableSet* trackedObjectTable = static_cast(trackedObjectLiveness); + if (trackedObjectTable == nil) { + return; + } + + NSNumber* objectKey = [NSNumber numberWithUnsignedLongLong:NormalizeHandleKey((void*)object)]; + std::lock_guard lock(objectRefsMutex); + [trackedObjectTable removeObject:objectKey]; +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/ObjectRef.h b/NativeScript/ffi/objc/napi/ObjectRef.h similarity index 100% rename from NativeScript/ffi/napi/ObjectRef.h rename to NativeScript/ffi/objc/napi/ObjectRef.h diff --git a/NativeScript/ffi/napi/ObjectRef.mm b/NativeScript/ffi/objc/napi/ObjectRef.mm similarity index 100% rename from NativeScript/ffi/napi/ObjectRef.mm rename to NativeScript/ffi/objc/napi/ObjectRef.mm diff --git a/NativeScript/ffi/napi/Protocol.h b/NativeScript/ffi/objc/napi/Protocol.h similarity index 100% rename from NativeScript/ffi/napi/Protocol.h rename to NativeScript/ffi/objc/napi/Protocol.h diff --git a/NativeScript/ffi/napi/Protocol.mm b/NativeScript/ffi/objc/napi/Protocol.mm similarity index 100% rename from NativeScript/ffi/napi/Protocol.mm rename to NativeScript/ffi/objc/napi/Protocol.mm diff --git a/NativeScript/ffi/objc/napi/SignatureDispatch.h b/NativeScript/ffi/objc/napi/SignatureDispatch.h new file mode 100644 index 000000000..5d4df5093 --- /dev/null +++ b/NativeScript/ffi/objc/napi/SignatureDispatch.h @@ -0,0 +1,138 @@ +#ifndef NS_FFI_NAPI_SIGNATURE_DISPATCH_H +#define NS_FFI_NAPI_SIGNATURE_DISPATCH_H + +#include + +#include "Cif.h" +#include "ffi/objc/shared/SignatureDispatchCore.h" +#include "js_native_api.h" + +namespace nativescript { + +using ObjCNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, id self, + SEL selector, const napi_value* argv, + void* rvalue); +using CFunctionNapiInvoker = bool (*)(napi_env env, Cif* cif, void* fnptr, + const napi_value* argv, void* rvalue); + +struct ObjCNapiDispatchEntry { + uint64_t dispatchId; + ObjCNapiInvoker invoker; +}; + +struct CFunctionNapiDispatchEntry { + uint64_t dispatchId; + CFunctionNapiInvoker invoker; +}; + +} // namespace nativescript + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 1 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 0 +#endif + +#define NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH \ + (NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_NAPI || NS_GSD_BACKEND_PREPARED) + +#if defined(__has_include) +#if __has_include("GeneratedSignatureDispatch.inc") +#include "GeneratedSignatureDispatch.inc" +#elif NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc is required when generated signature dispatch is enabled. +#endif +#elif NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH +#error __has_include is required to validate GeneratedSignatureDispatch.inc. +#endif + +#if NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH && !NS_HAS_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable this generated signature dispatch backend. +#endif + +#if NS_GSD_BACKEND_NAPI && !NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable Node-API generated signature dispatch. +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH +namespace nativescript { +inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { + {0, nullptr}}; +inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { + {0, nullptr}}; +} // namespace nativescript +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH +namespace nativescript { +inline constexpr ObjCNapiDispatchEntry kGeneratedObjCNapiDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionNapiDispatchEntry + kGeneratedCFunctionNapiDispatchEntries[] = {{0, nullptr}}; +} // namespace nativescript +#endif + +namespace nativescript { + +inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCDispatchEntries, dispatchId); +} + +inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( + uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionDispatchEntries, dispatchId); +} + +inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedBlockDispatchEntries, dispatchId); +} + +inline ObjCNapiInvoker lookupObjCNapiInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCNapiDispatchEntries, dispatchId); +} + +inline CFunctionNapiInvoker lookupCFunctionNapiInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionNapiDispatchEntries, dispatchId); +} + +} // namespace nativescript + +#endif // NS_FFI_NAPI_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/napi/Struct.h b/NativeScript/ffi/objc/napi/Struct.h similarity index 100% rename from NativeScript/ffi/napi/Struct.h rename to NativeScript/ffi/objc/napi/Struct.h diff --git a/NativeScript/ffi/napi/Struct.mm b/NativeScript/ffi/objc/napi/Struct.mm similarity index 100% rename from NativeScript/ffi/napi/Struct.mm rename to NativeScript/ffi/objc/napi/Struct.mm diff --git a/NativeScript/ffi/napi/TypeConv.h b/NativeScript/ffi/objc/napi/TypeConv.h similarity index 100% rename from NativeScript/ffi/napi/TypeConv.h rename to NativeScript/ffi/objc/napi/TypeConv.h diff --git a/NativeScript/ffi/objc/napi/TypeConv.mm b/NativeScript/ffi/objc/napi/TypeConv.mm new file mode 100644 index 000000000..7ac512969 --- /dev/null +++ b/NativeScript/ffi/objc/napi/TypeConv.mm @@ -0,0 +1,4252 @@ +#include "TypeConv.h" +#include "Block.h" +#include "Class.h" +#include "Closure.h" +#include "Interop.h" +#include "JSObject.h" +#include "Metadata.h" +#include "MetadataReader.h" +#include "ObjCBridge.h" +#include "ffi.h" +#include "Struct.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "node_api_util.h" + +#import +#import +#include +#if defined(__has_include) +#if __has_include() +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +@interface JSWrapperObjectAssociation : NSObject ++ (void)transferOwnership:(napi_env)env of:(napi_value)value toNative:(id)object; +@end + +namespace { + +static napi_value findRegisteredClassConstructor(napi_env env, Class cls) { + if (env == nullptr || cls == nil) { + return nullptr; + } + + const char* runtimeName = class_getName(cls); + if (runtimeName == nullptr || runtimeName[0] == '\0') { + return nullptr; + } + + napi_value global = nullptr; + napi_value classRegistry = nullptr; + bool hasClassRegistry = false; + if (napi_get_global(env, &global) != napi_ok || global == nullptr || + napi_has_named_property(env, global, "__nsConstructorsByObjCClassName", + &hasClassRegistry) != napi_ok || + !hasClassRegistry || + napi_get_named_property(env, global, "__nsConstructorsByObjCClassName", + &classRegistry) != napi_ok || + classRegistry == nullptr) { + return nullptr; + } + + bool hasConstructor = false; + napi_value constructor = nullptr; + if (napi_has_named_property(env, classRegistry, runtimeName, &hasConstructor) == napi_ok && + hasConstructor && + napi_get_named_property(env, classRegistry, runtimeName, &constructor) == napi_ok && + constructor != nullptr) { + return constructor; + } + + return nullptr; +} + +static size_t getBufferElementSize(napi_typedarray_type type) { + switch (type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: + case napi_bigint64_array: + case napi_biguint64_array: + return 8; + default: + return 1; + } +} + +static bool getJSBufferData(napi_env env, napi_value value, void** data, size_t* byteLength) { + if (data == nullptr || byteLength == nullptr) { + return false; + } + + bool isArrayBuffer = false; + if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { + return napi_get_arraybuffer_info(env, value, data, byteLength) == napi_ok; + } + + bool isTypedArray = false; + if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { + napi_typedarray_type type; + napi_value arrayBuffer; + size_t byteOffset = 0; + size_t elementLength = 0; + if (napi_get_typedarray_info(env, value, &type, &elementLength, data, &arrayBuffer, + &byteOffset) != napi_ok) { + return false; + } + + *byteLength = elementLength * getBufferElementSize(type); + return true; + } + + bool isDataView = false; + if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { + napi_value arrayBuffer; + size_t byteOffset = 0; + return napi_get_dataview_info(env, value, byteLength, data, &arrayBuffer, &byteOffset) == + napi_ok; + } + + return false; +} + +struct ActiveObjectConversion { + napi_env env; + napi_value value; +}; + +thread_local std::vector activeObjectConversions; + +class ScopedObjectConversion { + public: + ScopedObjectConversion(napi_env env, napi_value value) { + for (const auto& active : activeObjectConversions) { + if (active.env != env) { + continue; + } + + bool isSameObject = false; + status_ = napi_strict_equals(env, active.value, value, &isSameObject); + if (status_ != napi_ok) { + return; + } + + if (isSameObject) { + napi_throw_error( + env, nullptr, + "Circular JavaScript object graphs cannot be converted to Objective-C collections."); + return; + } + } + + activeObjectConversions.push_back({env, value}); + entered_ = true; + } + + ~ScopedObjectConversion() { + if (entered_) { + activeObjectConversions.pop_back(); + } + } + + bool entered() const { return entered_; } + napi_status status() const { return status_; } + + private: + bool entered_ = false; + napi_status status_ = napi_ok; +}; + +static bool hasPendingException(napi_env env) { + bool pending = false; + return napi_is_exception_pending(env, &pending) == napi_ok && pending; +} + +static uint16_t encodeFloat16(double value) { + if (std::isnan(value)) { + return 0x7e00; + } + + if (std::isinf(value)) { + return std::signbit(value) ? 0xfc00 : 0x7c00; + } + + union { + float f; + uint32_t bits; + } input = {static_cast(value)}; + + const uint32_t sign = (input.bits >> 16) & 0x8000; + uint32_t exponent = (input.bits >> 23) & 0xff; + uint32_t mantissa = input.bits & 0x007fffff; + + if (exponent == 0) { + return static_cast(sign); + } + + int32_t halfExponent = static_cast(exponent) - 127 + 15; + if (halfExponent >= 0x1f) { + return static_cast(sign | 0x7c00); + } + + if (halfExponent <= 0) { + if (halfExponent < -10) { + return static_cast(sign); + } + + mantissa |= 0x00800000; + const uint32_t shift = static_cast(14 - halfExponent); + uint32_t halfMantissa = mantissa >> shift; + if (((mantissa >> (shift - 1)) & 1u) != 0) { + halfMantissa += 1; + } + return static_cast(sign | halfMantissa); + } + + uint32_t halfMantissa = mantissa >> 13; + if ((mantissa & 0x00001000) != 0) { + halfMantissa += 1; + if ((halfMantissa & 0x00000400) != 0) { + halfMantissa = 0; + halfExponent += 1; + if (halfExponent >= 0x1f) { + return static_cast(sign | 0x7c00); + } + } + } + + return static_cast(sign | (static_cast(halfExponent) << 10) | + (halfMantissa & 0x03ff)); +} + +static double decodeFloat16(uint16_t bits) { + const uint32_t sign = (bits & 0x8000u) << 16; + const uint32_t exponent = (bits >> 10) & 0x1fu; + const uint32_t mantissa = bits & 0x03ffu; + + union { + uint32_t bits; + float f; + } output = {0}; + + if (exponent == 0) { + if (mantissa == 0) { + output.bits = sign; + return static_cast(output.f); + } + + uint32_t normalizedMantissa = mantissa; + int32_t normalizedExponent = -14; + while ((normalizedMantissa & 0x0400u) == 0) { + normalizedMantissa <<= 1; + normalizedExponent -= 1; + } + normalizedMantissa &= 0x03ffu; + output.bits = + sign | (static_cast(normalizedExponent + 127) << 23) | (normalizedMantissa << 13); + return static_cast(output.f); + } + + if (exponent == 0x1fu) { + output.bits = sign | 0x7f800000u | (mantissa << 13); + return static_cast(output.f); + } + + output.bits = sign | ((exponent - 15 + 127) << 23) | (mantissa << 13); + return static_cast(output.f); +} + +static id resolveCachedHandleObject(napi_env env, void* handle) { + if (env == nullptr || handle == nullptr) { + return nil; + } + + auto bridgeState = nativescript::ObjCBridgeState::InstanceData(env); + if (bridgeState == nullptr) { + return nil; + } + + napi_value cachedValue = bridgeState->getCachedHandleObject(env, handle); + if (cachedValue == nullptr) { + return nil; + } + + void* wrapped = nullptr; + if (napi_unwrap(env, cachedValue, &wrapped) == napi_ok && wrapped != nullptr) { + bridgeState->cacheRoundTripObject(env, static_cast(wrapped), cachedValue); + return static_cast(wrapped); + } + + bool hasNativePointer = false; + if (napi_has_named_property(env, cachedValue, "__ns_native_ptr", &hasNativePointer) == napi_ok && + hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, cachedValue, "__ns_native_ptr", &nativePointerValue) == + napi_ok) { + if (nativescript::Pointer::isInstance(env, nativePointerValue)) { + nativescript::Pointer* pointer = nativescript::Pointer::unwrap(env, nativePointerValue); + if (pointer != nullptr && pointer->data != nullptr) { + bridgeState->cacheRoundTripObject(env, static_cast(pointer->data), cachedValue); + return static_cast(pointer->data); + } + } else { + void* nativePointer = nullptr; + if (napi_get_value_external(env, nativePointerValue, &nativePointer) == napi_ok && + nativePointer != nullptr) { + bridgeState->cacheRoundTripObject(env, static_cast(nativePointer), cachedValue); + return static_cast(nativePointer); + } + } + } + } + + return nil; +} + +} // namespace + +namespace nativescript { + +namespace { +constexpr const char* kProtocolSuffix = "Protocol"; + +NSData* createNSDataWrapper(napi_env env, napi_value value, ObjCBridgeState* bridgeState) { + void* data = nullptr; + size_t byteLength = 0; + if (!getJSBufferData(env, value, &data, &byteLength)) { + return nil; + } + + NSData* wrappedData = [NSData dataWithBytes:data length:byteLength]; + if (wrappedData == nil) { + return nil; + } + + if (bridgeState != nullptr && bridgeState->hasRoundTripCacheFrame()) { + bridgeState->cacheRoundTripObject(env, wrappedData, value); + } + + return wrappedData; +} + +inline size_t alignUp(size_t value, size_t alignment) { + if (alignment == 0) { + return value; + } + return ((value + alignment - 1) / alignment) * alignment; +} + +inline uintptr_t normalizeRuntimePointer(uintptr_t ptr) { +#if INTPTR_MAX == INT64_MAX + return ptr & 0x0000FFFFFFFFFFFFULL; +#else + return ptr; +#endif +} + +inline bool isKindOfClassFast(id obj, Class expectedClass) { + if (obj == nil || expectedClass == Nil) { + return false; + } + + return [obj isKindOfClass:expectedClass]; +} + +bool stripProtocolSuffix(const char* name, std::string* out) { + if (name == nullptr || out == nullptr) { + return false; + } + + const size_t nameLen = std::strlen(name); + const size_t suffixLen = std::strlen(kProtocolSuffix); + if (nameLen <= suffixLen) { + return false; + } + + if (std::strcmp(name + (nameLen - suffixLen), kProtocolSuffix) != 0) { + return false; + } + + *out = std::string(name, nameLen - suffixLen); + return !out->empty(); +} + +bool protocolNamesMatch(const char* metadataName, const char* runtimeName) { + if (metadataName == nullptr || runtimeName == nullptr) { + return false; + } + + if (std::strcmp(metadataName, runtimeName) == 0) { + return true; + } + + std::string metadataBase(metadataName); + std::string runtimeBase(runtimeName); + stripProtocolSuffix(metadataName, &metadataBase); + stripProtocolSuffix(runtimeName, &runtimeBase); + + return metadataBase == runtimeBase; +} + +MDSectionOffset findProtocolMetadataOffset(MDMetadataReader* metadata, const char* protocolName) { + if (metadata == nullptr || protocolName == nullptr) { + return MD_SECTION_OFFSET_NULL; + } + + MDSectionOffset offset = metadata->protocolsOffset; + while (offset < metadata->classesOffset) { + MDSectionOffset originalOffset = offset; + + auto nameOffset = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool next = (nameOffset & mdSectionOffsetNext) != 0; + nameOffset &= ~mdSectionOffsetNext; + + auto name = metadata->resolveString(nameOffset); + if (protocolNamesMatch(name, protocolName)) { + return originalOffset; + } + + while (next) { + auto protocolImpl = metadata->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (protocolImpl & mdSectionOffsetNext) != 0; + } + + next = true; + while (next) { + auto flags = metadata->getMemberFlag(offset); + next = (flags & mdMemberNext) != 0; + offset += sizeof(flags); + + if (flags == mdMemberFlagNull) { + break; + } + + if ((flags & mdMemberProperty) != 0) { + bool readonly = (flags & mdMemberReadonly) != 0; + offset += sizeof(MDSectionOffset); // name + offset += sizeof(MDSectionOffset); // getter selector + offset += sizeof(MDSectionOffset); // getter signature + if (!readonly) { + offset += sizeof(MDSectionOffset); // setter selector + offset += sizeof(MDSectionOffset); // setter signature + } + } else { + offset += sizeof(MDSectionOffset); // selector + offset += sizeof(MDSectionOffset); // signature + } + } + } + + return MD_SECTION_OFFSET_NULL; +} +} // namespace + +// Forward declaration +class StructTypeConv; + +// Thread-local storage for tracking structs currently being processed to detect cycles +thread_local std::unordered_set processingStructs; +thread_local std::unordered_set processingEncodingStructs; + +// Cache for forward-declared struct types that need deferred resolution +thread_local std::unordered_map forwardDeclaredStructs; +thread_local std::unordered_map forwardDeclaredEncodingStructs; + +// Cache for StructTypeConv instances to avoid recreating them and handle recursion +thread_local std::unordered_map> structTypeCache; + +// Cache for encoding-based structs to handle recursion +thread_local std::unordered_map> encodingStructCache; + +ffi_type* typeFromStruct(napi_env env, const char** encoding) { + // Extract struct name for cycle detection + std::string structname; + const char* nameStart = *encoding + 1; // skip '{' + const char* c = nameStart; + while (*c != '\0' && *c != '=') { + structname += *c; + c++; + } + if (*c != '=') { + // Malformed struct encoding. Advance to the end of this token and + // fallback to pointer conversion to avoid reading past the buffer. + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; + } + return &ffi_type_pointer; + } + + // Check if we're already processing this struct (cycle detection) + if (processingEncodingStructs.find(structname) != processingEncodingStructs.end()) { + // Create a forward declaration placeholder + ffi_type* forwardType = new ffi_type; + forwardType->type = FFI_TYPE_STRUCT; + forwardType->size = 0; + forwardType->alignment = 0; + forwardType->elements = nullptr; + + // Cache this forward declaration for later resolution + forwardDeclaredEncodingStructs[structname] = forwardType; + + // Skip the struct encoding + (*encoding)++; // skip '{' + while (**encoding != '}') { + (*encoding)++; + } + (*encoding)++; // skip '}' + + return forwardType; + } + + // Check if we already have a forward declaration for this struct + auto existingForwardIt = forwardDeclaredEncodingStructs.find(structname); + if (existingForwardIt != forwardDeclaredEncodingStructs.end()) { + // Skip the struct encoding + (*encoding)++; // skip '{' + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; // skip '}' + } + + return existingForwardIt->second; + } + + // Mark this struct as being processed + processingEncodingStructs.insert(structname); + + ffi_type* type = new ffi_type; + type->type = FFI_TYPE_STRUCT; + type->size = 0; + type->alignment = 0; + type->elements = nullptr; + + std::vector elements; + + (*encoding)++; // skip '{' + + while (**encoding != '\0' && **encoding != '=') { + (*encoding)++; + } // skip name + if (**encoding == '\0') { + processingEncodingStructs.erase(structname); + delete type; + return &ffi_type_pointer; + } + + (*encoding)++; // skip '=' + + while (**encoding != '\0' && **encoding != '}') { + ffi_type* elementType = TypeConv::Make(env, encoding)->type; + elements.push_back(elementType); + } + + if (**encoding == '}') { + (*encoding)++; // skip '}' + } + + type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); + for (int i = 0; i < elements.size(); i++) { + type->elements[i] = elements[i]; + } + // null-terminate the array + type->elements[elements.size()] = nullptr; + + // If this was a forward declaration, update it with the real layout + auto resolvedForwardIt = forwardDeclaredEncodingStructs.find(structname); + if (resolvedForwardIt != forwardDeclaredEncodingStructs.end()) { + ffi_type* forwardType = resolvedForwardIt->second; + forwardType->type = type->type; + forwardType->size = type->size; + forwardType->alignment = type->alignment; + forwardType->elements = type->elements; + + // Clean up the temporary type and use the forward declaration + delete type; + type = forwardType; + forwardDeclaredEncodingStructs.erase(resolvedForwardIt); + } + + // Remove from processing set + processingEncodingStructs.erase(structname); + + return type; +} + +ffi_type* typeFromStruct(napi_env env, MDMetadataReader* reader, MDSectionOffset structOffset, + bool isUnion) { + // Check if we're already processing this struct (cycle detection) + if (processingStructs.find(structOffset) != processingStructs.end()) { + // Create a forward declaration placeholder + ffi_type* forwardType = new ffi_type; + forwardType->type = FFI_TYPE_STRUCT; + forwardType->size = 0; + forwardType->alignment = 0; + forwardType->elements = nullptr; + + // Cache this forward declaration for later resolution + forwardDeclaredStructs[structOffset] = forwardType; + return forwardType; + } + + // Check if we already have a forward declaration for this struct + auto existingForwardIt = forwardDeclaredStructs.find(structOffset); + if (existingForwardIt != forwardDeclaredStructs.end()) { + return existingForwardIt->second; + } + + // Mark this struct as being processed + processingStructs.insert(structOffset); + + ffi_type* type = new ffi_type; + type->type = FFI_TYPE_STRUCT; + type->size = 0; + type->alignment = 0; + type->elements = nullptr; + + MDSectionOffset nameOffset = reader->getOffset(structOffset); + auto name = reader->resolveString(nameOffset); + bool next = true; + MDSectionOffset currentOffset = structOffset + sizeof(MDSectionOffset); // skip name + currentOffset += sizeof(uint16_t); // skip size + + std::vector elements; + + while (next) { + nameOffset = reader->getOffset(currentOffset); + next = nameOffset & mdSectionOffsetNext; + nameOffset &= ~mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + currentOffset += sizeof(MDSectionOffset); // skip name + if (!isUnion) currentOffset += sizeof(uint16_t); // skip offset + ffi_type* elementType = TypeConv::Make(env, reader, ¤tOffset, 1)->type; + elements.push_back(elementType); + } + + type->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (elements.size() + 1)); + for (int i = 0; i < elements.size(); i++) { + type->elements[i] = elements[i]; + } + // null-terminate the array + type->elements[elements.size()] = nullptr; + + // If this was a forward declaration, update it with the real layout + auto resolvedForwardIt = forwardDeclaredStructs.find(structOffset); + if (resolvedForwardIt != forwardDeclaredStructs.end()) { + ffi_type* forwardType = resolvedForwardIt->second; + forwardType->type = type->type; + forwardType->size = type->size; + forwardType->alignment = type->alignment; + forwardType->elements = type->elements; + + // Clean up the temporary type and use the forward declaration + delete type; + type = forwardType; + forwardDeclaredStructs.erase(resolvedForwardIt); + } + + // Remove from processing set + processingStructs.erase(structOffset); + + return type; +} + +static inline size_t getTypedArrayUnitLength(napi_typedarray_type type) { + switch (type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: + case napi_bigint64_array: + case napi_biguint64_array: + return 8; + default: + return 0; + } +} + +class VoidTypeConv : public TypeConv { + public: + VoidTypeConv() { + type = &ffi_type_void; + kind = mdTypeVoid; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_get_null(env, &result); + return result; + } + + void encode(std::string* encoding) override { *encoding += "v"; } +}; + +static const std::shared_ptr voidTypeConv = std::make_shared(); + +class SCharTypeConv : public TypeConv { + public: + SCharTypeConv() { + type = &ffi_type_schar; + kind = mdTypeChar; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + int8_t raw = *(int8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_int32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + int32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_int32(env, value, &val); + *(int8_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "c"; } +}; + +static const std::shared_ptr scharTypeConv = std::make_shared(); + +class UCharTypeConv : public TypeConv { + public: + UCharTypeConv() { + type = &ffi_type_uchar; + kind = mdTypeUChar; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + uint8_t raw = *(uint8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_uint32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + uint32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint8_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "C"; } +}; + +static const std::shared_ptr ucharTypeConv = std::make_shared(); + +class UInt8TypeConv : public TypeConv { + public: + UInt8TypeConv() { + type = &ffi_type_uint8; + kind = mdTypeUInt8; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + uint8_t raw = *(uint8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_uint32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + uint32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint8_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "C"; } +}; + +static const std::shared_ptr uint8TypeConv = std::make_shared(); + +class SInt16TypeConv : public TypeConv { + public: + SInt16TypeConv() { + type = &ffi_type_sshort; + kind = mdTypeSShort; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_int32(env, *(int16_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + int32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_int32(env, value, &val); + *(int16_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "s"; } +}; + +static const std::shared_ptr sint16TypeConv = std::make_shared(); + +class UInt16TypeConv : public TypeConv { + public: + UInt16TypeConv() { + type = &ffi_type_ushort; + kind = mdTypeUShort; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_uint32(env, *(uint16_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + + if (valueType == napi_string) { + size_t strLen = 0; + napi_get_value_string_utf16(env, value, nullptr, 0, &strLen); + if (strLen != 1) { + napi_throw_type_error(env, nullptr, "Expected a single-character string."); + *(uint16_t*)result = 0; + return; + } + + char16_t chars[2] = {0, 0}; + napi_get_value_string_utf16(env, value, chars, 2, &strLen); + *(uint16_t*)result = static_cast(chars[0]); + return; + } + + uint32_t val = 0; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint16_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "S"; } +}; + +static const std::shared_ptr uint16TypeConv = std::make_shared(); + +// unichar/UniChar (mdTypeUnichar): u16 width, but projected to JS as a +// single-character string for any code unit — not just printable ASCII. +class UnicharTypeConv : public UInt16TypeConv { + public: + UnicharTypeConv() { kind = mdTypeUnichar; } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + char16_t unit = *(char16_t*)value; + napi_value result; + napi_create_string_utf16(env, &unit, 1, &result); + return result; + } +}; + +static const std::shared_ptr unicharTypeConv = std::make_shared(); + +class SInt32TypeConv : public TypeConv { + public: + SInt32TypeConv() { + type = &ffi_type_sint; + kind = mdTypeSInt; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_int32(env, *(int32_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + int32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_int32(env, value, &val); + *(int32_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "i"; } +}; + +static const std::shared_ptr sint32TypeConv = std::make_shared(); + +class UInt32TypeConv : public TypeConv { + public: + UInt32TypeConv() { + type = &ffi_type_uint; + kind = mdTypeUInt; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_uint32(env, *(uint32_t*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + uint32_t val; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint32_t*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "I"; } +}; + +static const std::shared_ptr uint32TypeConv = std::make_shared(); + +class SInt64TypeConv : public TypeConv { + public: + SInt64TypeConv() { + type = &ffi_type_sint64; + kind = mdTypeSInt64; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + int64_t val = *(int64_t*)value; + constexpr int64_t kMaxSafeInteger = 9007199254740991LL; + if (val > kMaxSafeInteger || val < -kMaxSafeInteger) { + napi_create_bigint_int64(env, val, &result); + } else { + napi_create_int64(env, val, &result); + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + switch (valuetype) { + case napi_number: + napi_get_value_int64(env, value, (int64_t*)result); + break; + case napi_bigint: { + bool lossless; + napi_get_value_bigint_int64(env, value, (int64_t*)result, &lossless); + break; + } + case napi_undefined: + case napi_null: + *(int64_t*)result = 0; + break; + case napi_string: + *(int64_t*)result = 0; + break; + default: + napi_throw_type_error(env, nullptr, "Expected a number or bigint"); + break; + } + } + + void encode(std::string* encoding) override { *encoding += "q"; } +}; + +static const std::shared_ptr sint64TypeConv = std::make_shared(); + +class UInt64TypeConv : public TypeConv { + public: + UInt64TypeConv() { + type = &ffi_type_uint64; + kind = mdTypeUInt64; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + uint64_t val = *(uint64_t*)value; + constexpr uint64_t kMaxSafeInteger = 9007199254740991ULL; + if (val > kMaxSafeInteger) { + napi_create_bigint_uint64(env, val, &result); + } else { + napi_create_int64(env, static_cast(val), &result); + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + switch (valuetype) { + case napi_number: + napi_get_value_int64(env, value, (int64_t*)result); + break; + case napi_bigint: { + bool lossless; + napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); + break; + } + case napi_undefined: + case napi_null: + *(int64_t*)result = 0; + break; + default: + napi_throw_type_error(env, nullptr, "Expected a number or bigint"); + break; + } + } + + void encode(std::string* encoding) override { *encoding += "Q"; } +}; + +static const std::shared_ptr uint64TypeConv = std::make_shared(); + +class UInt128TypeConv : public TypeConv { + private: + ffi_type _type = {.size = 0, + .alignment = 0, + .type = FFI_TYPE_STRUCT, + .elements = (ffi_type*[]){ + &ffi_type_uint64, + &ffi_type_uint64, + nullptr, + }}; + + public: + UInt128TypeConv() { + type = &_type; + kind = mdTypeUInt128; + } + + // TODO + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + uint64_t val = *(uint64_t*)value; + napi_create_int64(env, (int64_t)val, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + switch (valuetype) { + case napi_number: + napi_get_value_int64(env, value, (int64_t*)result); + break; + case napi_bigint: { + bool lossless; + napi_get_value_bigint_uint64(env, value, (uint64_t*)result, &lossless); + break; + } + default: + napi_throw_type_error(env, nullptr, "Expected a number or bigint"); + break; + } + } +}; + +static const std::shared_ptr uint128TypeConv = std::make_shared(); + +class Float32TypeConv : public TypeConv { + public: + Float32TypeConv() { + type = &ffi_type_float; + kind = mdTypeFloat; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_double(env, *(float*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + double val; + napi_coerce_to_number(env, value, &value); + napi_get_value_double(env, value, &val); + *(float*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "f"; } +}; + +static const std::shared_ptr float32TypeConv = std::make_shared(); + +class Float16TypeConv : public TypeConv { + public: + Float16TypeConv() { + type = &ffi_type_uint16; + kind = mdTypeF16; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_double(env, decodeFloat16(*(uint16_t*)value), &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + double val = 0; + napi_coerce_to_number(env, value, &value); + napi_get_value_double(env, value, &val); + *(uint16_t*)result = encodeFloat16(val); + } + + void encode(std::string* encoding) override { *encoding += "H"; } +}; + +static const std::shared_ptr float16TypeConv = std::make_shared(); + +class Float64TypeConv : public TypeConv { + public: + Float64TypeConv() { + type = &ffi_type_double; + kind = mdTypeDouble; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_double(env, *(double*)value, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + double val; + napi_coerce_to_number(env, value, &value); + napi_get_value_double(env, value, &val); + if (std::isnan(val) || std::isinf(val)) { + val = 0.0; + } + *(double*)result = val; + } + + void encode(std::string* encoding) override { *encoding += "d"; } +}; + +static const std::shared_ptr float64TypeConv = std::make_shared(); + +class BoolTypeConv : public TypeConv { + public: + BoolTypeConv() { + type = &ffi_type_uint8; + kind = mdTypeBool; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + uint8_t raw = *(uint8_t*)value; + if (raw == 0 || raw == 1) { + napi_value result; + napi_get_boolean(env, raw == 1, &result); + return result; + } + + napi_value result; + napi_create_uint32(env, raw, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + + if (valueType == napi_number) { + uint32_t val = 0; + napi_coerce_to_number(env, value, &value); + napi_get_value_uint32(env, value, &val); + *(uint8_t*)result = static_cast(val); + return; + } + + if (valueType == napi_bigint) { + uint64_t val = 0; + bool lossless = false; + napi_get_value_bigint_uint64(env, value, &val, &lossless); + *(uint8_t*)result = static_cast(val); + return; + } + + bool val = false; + napi_coerce_to_bool(env, value, &value); + napi_get_value_bool(env, value, &val); + *(uint8_t*)result = static_cast(val ? 1 : 0); + } + + void encode(std::string* encoding) override { *encoding += "B"; } +}; + +static const std::shared_ptr boolTypeConv = std::make_shared(); + +class PointerTypeConv : public TypeConv { + public: + std::shared_ptr pointeeType = nullptr; + + PointerTypeConv() { + type = &ffi_type_pointer; + kind = mdTypePointer; + } + + PointerTypeConv(std::shared_ptr pointeeType) : pointeeType(pointeeType) { + type = &ffi_type_pointer; + kind = mdTypePointer; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* raw = *((void**)value); + if (raw == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + + auto normalizePtr = [](void* ptr) -> uintptr_t { +#if INTPTR_MAX == INT64_MAX + // Objective-C pointers may carry auth/tag bits on some runtimes. + // Compare using canonical lower bits for stable lookups. + return reinterpret_cast(ptr) & 0x0000FFFFFFFFFFFFULL; +#else + return reinterpret_cast(ptr); +#endif + }; + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + auto classIt = bridgeState->mdClassesByPointer.find((Class)raw); + if (classIt != bridgeState->mdClassesByPointer.end()) { + auto cls = bridgeState->getClass(env, classIt->second); + if (cls != nullptr) { + return get_ref_value(env, cls->constructor); + } + } else { + const uintptr_t rawNormalized = normalizePtr(raw); + for (const auto& entry : bridgeState->mdClassesByPointer) { + if (normalizePtr((void*)entry.first) != rawNormalized) { + continue; + } + + auto cls = bridgeState->getClass(env, entry.second); + if (cls != nullptr) { + return get_ref_value(env, cls->constructor); + } + } + } + + auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)raw); + if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { + auto proto = bridgeState->getProtocol(env, protocolIt->second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } else { + const uintptr_t rawNormalized = normalizePtr(raw); + for (const auto& entry : bridgeState->mdProtocolsByPointer) { + if (normalizePtr((void*)entry.first) != rawNormalized) { + continue; + } + + auto proto = bridgeState->getProtocol(env, entry.second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } + + // Some protocol pointers come from compile-time @protocol() references + // and don't always match objc_getProtocol() pointer identity. + // Resolve them by scanning runtime protocol list and matching by address. + unsigned int protocolCount = 0; + Protocol** protocols = objc_copyProtocolList(&protocolCount); + if (protocols != nullptr) { + for (unsigned int i = 0; i < protocolCount; i++) { + Protocol* runtimeProto = protocols[i]; + if (normalizePtr((void*)runtimeProto) != rawNormalized) { + continue; + } + + const char* runtimeName = protocol_getName(runtimeProto); + MDSectionOffset metadataOffset = + findProtocolMetadataOffset(bridgeState->metadata, runtimeName); + if (metadataOffset != MD_SECTION_OFFSET_NULL) { + bridgeState->registerProtocolMetadata(runtimeProto, metadataOffset); + auto proto = bridgeState->getProtocol(env, metadataOffset); + bridgeState->registerRuntimeProtocol(proto, runtimeProto); + if (proto != nullptr) { + ::free(protocols); + return get_ref_value(env, proto->constructor); + } + } + + break; + } + ::free(protocols); + } + } + } + + if (pointeeType != nullptr && pointeeType->kind != mdTypeVoid) { + napi_value referenceValue = Reference::create(env, pointeeType, raw, false); + if (referenceValue != nullptr) { + return referenceValue; + } + } + + return Pointer::create(env, raw); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + void** res = (void**)result; + + auto unwrapKnownNativeHandle = [&](napi_value input, void** out) -> bool { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + napi_valuetype inputType = napi_undefined; + if (napi_typeof(env, input, &inputType) == napi_ok && + (inputType == napi_function || inputType == napi_object)) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, input, &bridgedType) && + bridgedType != nil) { + *out = (void*)bridgedType; + return true; + } + } + } + + void* wrapped = nullptr; + napi_status unwrapStatus = napi_unwrap(env, input, &wrapped); + if (unwrapStatus != napi_ok) { + bool hasNativePointer = false; + if (napi_has_named_property(env, input, "__ns_native_ptr", &hasNativePointer) == + napi_ok && + hasNativePointer) { + napi_value nativePointerValue = nullptr; + if (napi_get_named_property(env, input, "__ns_native_ptr", &nativePointerValue) == + napi_ok && + Pointer::isInstance(env, nativePointerValue)) { + Pointer* pointer = Pointer::unwrap(env, nativePointerValue); + if (pointer != nullptr && pointer->data != nullptr) { + *out = pointer->data; + return true; + } + } + } + return false; + } + + if (bridgeState != nullptr) { + for (const auto& entry : bridgeState->classes) { + auto bridgedClass = entry.second; + if (bridgedClass == wrapped) { + *out = (void*)bridgedClass->nativeClass; + return true; + } + } + + for (const auto& entry : bridgeState->protocols) { + auto bridgedProtocol = entry.second; + if (bridgedProtocol == wrapped) { + *out = (void*)objc_getProtocol(bridgedProtocol->name.c_str()); + return true; + } + } + } + + *out = wrapped; + return true; + }; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nullptr; + return; + + case napi_bigint: { + uint64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + *res = (void*)val; + return; + } + + case napi_string: { + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + char* str = (char*)malloc(len + 1); + + NAPI_GUARD(napi_get_value_string_utf8(env, value, str, len + 1, &len)) { + NAPI_THROW_LAST_ERROR + ::free(str); + return; + } + + str[len] = '\0'; + + bool shouldCreateCFString = + pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || + pointeeType->kind == mdTypeNSMutableStringObject); + + if (shouldCreateCFString) { + CFStringRef cfStr = + CFStringCreateWithCString(kCFAllocatorDefault, str, kCFStringEncodingUTF8); + ::free(str); + *res = (void*)cfStr; + *shouldFree = true; + *shouldFreeAny = true; + } else { + *res = (void*)str; + *shouldFree = true; + *shouldFreeAny = true; + } + return; + } + + case napi_external: { + NAPI_GUARD(napi_get_value_external(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_object: { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && + bridgedType != nil) { + *res = (void*)bridgedType; + return; + } + } + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *res = ptr->data; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid Reference"); + *res = nullptr; + return; + } + if (ref->data == nullptr) { + std::shared_ptr resolvedType = pointeeType; + if (resolvedType == nullptr) { + resolvedType = ref->type; + } + + napi_value pendingInitValue = Reference::getInitValue(env, value, ref); + napi_valuetype pendingInitType = napi_undefined; + if (pendingInitValue != nullptr) { + napi_typeof(env, pendingInitValue, &pendingInitType); + } + if (resolvedType == nullptr && pendingInitValue != nullptr) { + if (pendingInitValue != nullptr) { + napi_valuetype initType = napi_undefined; + if (napi_typeof(env, pendingInitValue, &initType) == napi_ok) { + auto makeStructType = [&](StructInfo* info) -> std::shared_ptr { + if (info == nullptr || info->name == nullptr) { + return nullptr; + } + + std::string encoding = "{"; + encoding += info->name; + encoding += "="; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + return nullptr; + } + field.type->encode(&encoding); + } + encoding += "}"; + + const char* encodingPtr = encoding.c_str(); + return TypeConv::Make(env, &encodingPtr); + }; + + if (initType == napi_object) { + if (StructObject::isInstance(env, pendingInitValue)) { + StructObject* structObj = StructObject::unwrap(env, pendingInitValue); + if (structObj != nullptr) { + resolvedType = makeStructType(structObj->info); + } + } + + if (resolvedType == nullptr) { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr) { + bool isArray = false; + bool isTypedArray = false; + bool isArrayBuffer = false; + bool isDataView = false; + napi_is_array(env, pendingInitValue, &isArray); + napi_is_typedarray(env, pendingInitValue, &isTypedArray); + napi_is_arraybuffer(env, pendingInitValue, &isArrayBuffer); + napi_is_dataview(env, pendingInitValue, &isDataView); + if (!isArray && !isTypedArray && !isArrayBuffer && !isDataView) { + napi_value propertyNames = nullptr; + if (napi_get_property_names(env, pendingInitValue, &propertyNames) == + napi_ok && + propertyNames != nullptr) { + uint32_t propertyCount = 0; + napi_get_array_length(env, propertyNames, &propertyCount); + std::unordered_set keys; + std::unordered_map keyIsInteger; + keys.reserve(propertyCount); + for (uint32_t i = 0; i < propertyCount; i++) { + napi_value keyValue = nullptr; + if (napi_get_element(env, propertyNames, i, &keyValue) != napi_ok || + keyValue == nullptr) { + continue; + } + napi_valuetype keyType = napi_undefined; + if (napi_typeof(env, keyValue, &keyType) != napi_ok || + keyType != napi_string) { + continue; + } + size_t keyLength = 0; + if (napi_get_value_string_utf8(env, keyValue, nullptr, 0, + &keyLength) != napi_ok) { + continue; + } + std::vector keyBuffer(keyLength + 1, '\0'); + if (napi_get_value_string_utf8(env, keyValue, keyBuffer.data(), + keyBuffer.size(), + &keyLength) != napi_ok) { + continue; + } + std::string key(keyBuffer.data(), keyLength); + keys.insert(key); + + napi_value propertyValue = nullptr; + if (napi_get_property(env, pendingInitValue, keyValue, + &propertyValue) == napi_ok && + propertyValue != nullptr) { + napi_valuetype propertyType = napi_undefined; + if (napi_typeof(env, propertyValue, &propertyType) == napi_ok) { + bool isInteger = false; + if (propertyType == napi_bigint) { + isInteger = true; + } else if (propertyType == napi_number) { + double numericValue = 0; + if (napi_get_value_double(env, propertyValue, &numericValue) == + napi_ok) { + int64_t truncated = static_cast(numericValue); + isInteger = static_cast(truncated) == numericValue; + } + } + keyIsInteger[key] = isInteger; + } + } + } + + if (!keys.empty()) { + auto isIntegerKind = [](MDTypeKind kind) -> bool { + switch (kind) { + case mdTypeChar: + case mdTypeSInt: + case mdTypeSShort: + case mdTypeSLong: + case mdTypeSInt64: + case mdTypeUChar: + case mdTypeUInt: + case mdTypeUShort: + case mdTypeUnichar: + case mdTypeULong: + case mdTypeUInt64: + case mdTypeUInt8: + case mdTypeBool: + return true; + default: + return false; + } + }; + + auto isFloatingKind = [](MDTypeKind kind) -> bool { + return kind == mdTypeFloat || kind == mdTypeDouble || + kind == mdTypeLongDouble || kind == mdTypeF16; + }; + + StructInfo* bestMatch = nullptr; + int bestScore = std::numeric_limits::min(); + uint16_t bestSize = std::numeric_limits::max(); + + for (const auto& entry : bridgeState->structOffsets) { + StructInfo* info = bridgeState->getStructInfo(env, entry.second); + if (info == nullptr || info->fields.size() != keys.size()) { + continue; + } + + bool match = true; + for (const auto& field : info->fields) { + if (field.name == nullptr || + keys.find(field.name) == keys.end()) { + match = false; + break; + } + } + if (!match) { + continue; + } + + int score = 0; + bool hasOnlyNumericFields = true; + for (const auto& field : info->fields) { + if (field.type == nullptr) { + hasOnlyNumericFields = false; + break; + } + + MDTypeKind fieldKind = field.type->kind; + if (isIntegerKind(fieldKind)) { + auto integerEntry = + keyIsInteger.find(field.name != nullptr ? field.name : ""); + score += + (integerEntry != keyIsInteger.end() && integerEntry->second) + ? 3 + : 1; + } else if (isFloatingKind(fieldKind)) { + score += 2; + } else { + hasOnlyNumericFields = false; + break; + } + } + + if (!hasOnlyNumericFields) { + continue; + } + + if (score > bestScore || + (score == bestScore && info->size < bestSize)) { + bestScore = score; + bestSize = info->size; + bestMatch = info; + } + } + + if (bestMatch != nullptr) { + resolvedType = makeStructType(bestMatch); + } + } + } + } + } + } + } + + if (resolvedType == nullptr) { + const char* inferredEncoding = "@"; + if (initType == napi_number || initType == napi_bigint) { + inferredEncoding = "q"; + } else if (initType == napi_boolean) { + inferredEncoding = "B"; + } + resolvedType = TypeConv::Make(env, &inferredEncoding); + } + } + } + } + + if (resolvedType == nullptr) { + const char* defaultEncoding = "@"; + resolvedType = TypeConv::Make(env, &defaultEncoding); + } + + ref->type = resolvedType; + size_t pointeeSize = sizeof(void*); + if (resolvedType != nullptr && resolvedType->type != nullptr && + resolvedType->type->size > 0) { + pointeeSize = resolvedType->type->size; + } + ref->data = calloc(1, pointeeSize); + if (ref->data == nullptr) { + napi_throw_error(env, nullptr, "Out of memory while allocating out parameter"); + *res = nullptr; + return; + } + ref->ownsData = true; + napi_value initValue = Reference::getInitValue(env, value, ref); + if (initValue != nullptr) { + bool shouldFree; + ref->type->toNative(env, initValue, ref->data, &shouldFree, &shouldFree); + Reference::clearInitValue(env, value, ref); + } + } + *res = ref->data; + return; + } + + if (StructObject::isInstance(env, value)) { + StructObject* structObj = StructObject::unwrap(env, value); + if (structObj != nullptr) { + *res = structObj->data; + } else + *res = nullptr; + return; + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + if (isTypedArray) { + void* data; + size_t length = 0; + napi_typedarray_type type; + NAPI_GUARD( + napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + + *res = data; + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + *res = data; + return; + } + break; + } + + case napi_function: { + if (unwrapKnownNativeHandle(value, res)) { + return; + } + break; + } + + default: + napi_throw_error(env, nullptr, "Invalid pointer type"); + *res = nullptr; + return; + } + + napi_throw_error(env, nullptr, "Invalid pointer type"); + *res = nullptr; + } + + void free(napi_env env, void* value) override { + if (value == nullptr) { + return; + } + + bool isCFString = pointeeType != nullptr && (pointeeType->kind == mdTypeNSStringObject || + pointeeType->kind == mdTypeNSMutableStringObject); + + if (isCFString) { + CFRelease((CFStringRef)value); + } else { + ::free(value); + } + } + + void encode(std::string* encoding) override { *encoding += "^v"; } +}; + +static const std::shared_ptr pointerTypeConv = std::make_shared(); + +class BlockTypeConv : public TypeConv { + public: + MDSectionOffset signatureOffset; + + BlockTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { + type = &ffi_type_pointer; + kind = mdTypeBlock; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* fn = *((void**)value); + if (fn == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + return FunctionPointer::wrap(env, fn, signatureOffset, true); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + void** res = (void**)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nullptr; + return; + + case napi_bigint: { + uint64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + *res = (void*)val; + return; + } + + case napi_external: { + NAPI_GUARD(napi_get_value_external(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_object: { + NAPI_GUARD(napi_unwrap(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_function: { + if (FunctionReference::isInstance(env, value)) { + FunctionReference* ref = FunctionReference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid FunctionReference"); + *res = nullptr; + return; + } + *res = ref->getFunctionPointer(signatureOffset, true); + return; + } + + void* wrapped; + status = napi_unwrap(env, value, &wrapped); + if (status == napi_ok) { + *res = wrapped; + return; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto closure = new Closure(env, bridgeState->metadata, signatureOffset, true); + id block = registerBlock(env, closure, value); + *res = (void*)block; + *shouldFree = true; + *shouldFreeAny = true; + return; + } + + default: + napi_throw_error(env, nullptr, "Invalid block pointer type"); + *res = nullptr; + return; + } + } + + void free(napi_env env, void* value) override { + if (value != nullptr) { + [(id)value release]; + } + } + + void encode(std::string* encoding) override { *encoding += "^v"; } +}; + +namespace { +void function_pointer_finalize_now(napi_env env, void* finalize_data, void* finalize_hint) { + Closure* closure = static_cast(finalize_hint); + if (closure != nullptr) { + Closure::destroyOnOwningThread(closure); + } +} +} // namespace + +void function_pointer_finalize(napi_env env, void* finalize_data, void* finalize_hint) { + if (PostFinalizer(env, function_pointer_finalize_now, finalize_data, finalize_hint)) { + return; + } + + function_pointer_finalize_now(env, finalize_data, finalize_hint); +} + +class FunctionPointerTypeConv : public TypeConv { + public: + MDSectionOffset signatureOffset; + + FunctionPointerTypeConv(MDSectionOffset signatureOffset) : signatureOffset(signatureOffset) { + type = &ffi_type_pointer; + kind = mdTypeFunctionPointer; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* fn = *((void**)value); + if (fn == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + return FunctionPointer::wrap(env, fn, signatureOffset, false); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + void** res = (void**)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nullptr; + return; + + case napi_bigint: { + uint64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_uint64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + *res = (void*)val; + return; + } + + case napi_external: { + NAPI_GUARD(napi_get_value_external(env, value, res)) { + NAPI_THROW_LAST_ERROR + *res = nullptr; + return; + } + return; + } + + case napi_object: { + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *res = ptr->data; + } else if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *res = ref->data; + } else if (FunctionReference::isInstance(env, value)) { + FunctionReference* ref = FunctionReference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid FunctionReference"); + *res = nullptr; + return; + } + *res = ref->getFunctionPointer(signatureOffset, false); + } else { + napi_throw_error(env, nullptr, "Invalid function pointer object"); + *res = nullptr; + } + return; + } + + case napi_function: { + if (FunctionReference::isInstance(env, value)) { + FunctionReference* ref = FunctionReference::unwrap(env, value); + if (ref == nullptr) { + napi_throw_error(env, nullptr, "Invalid FunctionReference"); + *res = nullptr; + return; + } + *res = ref->getFunctionPointer(signatureOffset, false); + return; + } + + void* wrapped; + status = napi_unwrap(env, value, &wrapped); + if (status == napi_ok) { + *res = wrapped; + return; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto closure = new Closure(env, bridgeState->metadata, signatureOffset, false); + closure->func = make_ref(env, value); + napi_remove_wrap(env, value, nullptr); + napi_ref ref; + napi_wrap(env, value, closure->fnptr, function_pointer_finalize, closure, &ref); + *res = (void*)closure->fnptr; + return; + } + + default: + napi_throw_error(env, nullptr, "Invalid block pointer type"); + *res = nullptr; + return; + } + } + + void encode(std::string* encoding) override { *encoding += "^v"; } +}; + +class StringTypeConv : public TypeConv { + public: + StringTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeString; + } + + napi_value toJS(napi_env env, void* cont, uint32_t flags) override { + void* value = *((void**)cont); + if (value == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + if ((flags & kCStringAsReference) != 0) { + return Reference::create(env, scharTypeConv, value, false); + } + + napi_value result; + napi_create_string_utf8(env, (char*)value, NAPI_AUTO_LENGTH, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + napi_valuetype valuetype; + napi_typeof(env, value, &valuetype); + + if (valuetype == napi_null || valuetype == napi_undefined) { + *(char**)result = nullptr; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + if (valuetype == napi_object) { + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *(char**)result = (char*)ptr->data; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *(char**)result = (char*)ref->data; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + if (isTypedArray) { + void* data = nullptr; + size_t length = 0; + napi_typedarray_type typedArrayType; + napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); + *(char**)result = static_cast(data); + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + *(char**)result = static_cast(data); + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + bool isDataView = false; + napi_is_dataview(env, value, &isDataView); + if (isDataView) { + void* data = nullptr; + size_t byteLength = 0; + napi_value arrayBuffer = nullptr; + size_t byteOffset = 0; + napi_get_dataview_info(env, value, &byteLength, &data, &arrayBuffer, &byteOffset); + *(char**)result = static_cast(data); + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + *(char**)result = nullptr; + *shouldFree = false; + *shouldFreeAny = false; + return; + } + + char** res = (char**)result; + + *res = nullptr; + size_t len = 0; + + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, len, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + *res = (char*)malloc(len + 1); + + NAPI_GUARD(napi_get_value_string_utf8(env, value, *res, len + 1, &len)) { + NAPI_THROW_LAST_ERROR + ::free(*res); + return; + } + + (*res)[len] = '\0'; + + *shouldFree = true; + *shouldFreeAny = true; + } + + void free(napi_env env, void* value) override { ::free(value); } + + void encode(std::string* encoding) override { *encoding += "*"; } +}; + +static const std::shared_ptr stringTypeConv = std::make_shared(); + +class ObjCObjectTypeConv : public TypeConv { + public: + MDSectionOffset classOffset = 0; + std::vector protocolOffsets; + + ObjCObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeAnyObject; + } + + ObjCObjectTypeConv(MDSectionOffset classOffset, std::vector protocolOffsets) + : classOffset(classOffset), protocolOffsets(protocolOffsets) { + type = &ffi_type_pointer; + if (classOffset != 0) { + kind = mdTypeClassObject; + } else { + kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; + } + } + + ObjCObjectTypeConv(std::vector protocolOffsets) + : protocolOffsets(protocolOffsets) { + type = &ffi_type_pointer; + kind = protocolOffsets.empty() ? mdTypeAnyObject : mdTypeProtocolObject; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + void* rawPtr = *((void**)value); + id obj = (__bridge id)rawPtr; + + if (obj == nil) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + + if (bridgeState != nullptr) { + if (object_isClass(obj)) { + if (napi_value constructor = findRegisteredClassConstructor(env, (Class)obj); + constructor != nullptr) { + return constructor; + } + } + + auto normalizePtr = [](void* ptr) -> uintptr_t { + return normalizeRuntimePointer(reinterpret_cast(ptr)); + }; + + auto protocolIt = bridgeState->mdProtocolsByPointer.find((Protocol*)obj); + if (protocolIt != bridgeState->mdProtocolsByPointer.end()) { + auto proto = bridgeState->getProtocol(env, protocolIt->second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } else { + const uintptr_t objNormalized = normalizePtr((void*)obj); + for (const auto& entry : bridgeState->mdProtocolsByPointer) { + if (normalizePtr((void*)entry.first) != objNormalized) { + continue; + } + + auto proto = bridgeState->getProtocol(env, entry.second); + if (proto != nullptr) { + return get_ref_value(env, proto->constructor); + } + } + } + } + + // Always unbox NSNull and CFBoolean/NSNumber values (except NSDecimalNumber), + // so primitive round-trips match historical runtime behavior. + if (isKindOfClassFast(obj, [NSNull class])) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + if (isKindOfClassFast(obj, [NSNumber class]) && + !isKindOfClassFast(obj, [NSDecimalNumber class])) { + if (CFGetTypeID((CFTypeRef)obj) == CFBooleanGetTypeID()) { + napi_value result; + napi_get_boolean(env, [obj boolValue], &result); + return result; + } + + napi_value result; + napi_create_double(env, [obj doubleValue], &result); + return result; + } + + // Untyped id values that are actually Objective-C blocks should be + // callable from JS. Preserve callback identity when we already have one. + if (isObjCBlockObject(obj)) { + napi_value cached = getCachedBlockCallback(env, (void*)obj); + if (cached != nullptr) { + return cached; + } + + const char* signature = getObjCBlockSignature((void*)obj); + if (signature != nullptr) { + return FunctionPointer::wrapWithEncoding(env, (void*)obj, signature, true); + } + } + + // Auto-unbox plain id string values. + const bool isUntypedObject = classOffset == 0 && protocolOffsets.empty(); + if (isUntypedObject && isKindOfClassFast(obj, [NSString class])) { + NSUInteger length = [obj length]; + std::vector chars(length > 0 ? length : 1); + if (length > 0) { + [((NSString*)obj) getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; + } + napi_value result; + napi_create_string_utf16(env, length > 0 ? chars.data() : nullptr, length, &result); + return result; + } + + if (bridgeState == nullptr) { + return Pointer::create(env, (void*)obj); + } + + if (napi_value existing = bridgeState->findCachedObjectWrapper(env, obj); existing != nullptr) { + return existing; + } + + ObjectOwnership ownership; + if ((flags & kReturnOwned) != 0) { + ownership = kOwnedObject; + } else { + ownership = kUnownedObject; + } + + auto object = bridgeState->getObject(env, obj, ownership, classOffset, &protocolOffsets); + if (object == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + return object; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + id* res = (id*)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_null: + case napi_undefined: + *res = nil; + return; + + case napi_string: { + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf8(env, value, nullptr, 0, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + std::vector chars(len + 1); + NAPI_GUARD(napi_get_value_string_utf8(env, value, chars.data(), len + 1, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + *res = [[[NSString alloc] initWithBytes:chars.data() + length:len + encoding:NSUTF8StringEncoding] autorelease]; + if (*res == nil) { + *res = [NSString string]; + } + break; + } + + case napi_number: { + double val = 0; + NAPI_GUARD(napi_get_value_double(env, value, &val)) { + NAPI_THROW_LAST_ERROR + return; + } + *res = [NSNumber numberWithDouble:val]; + break; + } + + case napi_boolean: { + bool val = false; + NAPI_GUARD(napi_get_value_bool(env, value, &val)) { + NAPI_THROW_LAST_ERROR + return; + } + *res = [NSNumber numberWithBool:val]; + break; + } + + case napi_bigint: { + int64_t val = 0; + bool lossless = false; + NAPI_GUARD(napi_get_value_bigint_int64(env, value, &val, &lossless)) { + NAPI_THROW_LAST_ERROR + return; + } + *res = [NSNumber numberWithLongLong:val]; + break; + } + + case napi_external: + NAPI_GUARD(napi_get_value_external(env, value, (void**)res)) { + NAPI_THROW_LAST_ERROR + *res = nil; + return; + } + break; + + case napi_object: + case napi_function: { + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto cacheRoundTrip = [&](id nativeObj) { + if (nativeObj == nil || bridgeState == nullptr || + !bridgeState->hasRoundTripCacheFrame()) { + return; + } + + bridgeState->cacheRoundTripObject(env, nativeObj, value); + }; + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + void* pointerData = ptr != nullptr ? ptr->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { + *res = cachedObject; + return; + } + *res = (id)pointerData; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + void* referenceData = ref != nullptr ? ref->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, referenceData); + cachedObject != nil) { + *res = cachedObject; + return; + } + *res = (id)referenceData; + return; + } + + if (bridgeState != nullptr) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && + bridgedType != nil) { + *res = bridgedType; + return; + } + } + + void* wrapped = nullptr; + status = napi_unwrap(env, value, &wrapped); + + if (status != napi_ok) { + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + *res = createNSDataWrapper(env, value, bridgeState); + if (*res != nil) { + cacheRoundTrip(*res); + return; + } + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + if (isTypedArray) { + *res = createNSDataWrapper(env, value, bridgeState); + if (*res != nil) { + cacheRoundTrip(*res); + return; + } + } + + bool isDataView = false; + napi_is_dataview(env, value, &isDataView); + if (isDataView) { + *res = createNSDataWrapper(env, value, bridgeState); + if (*res != nil) { + cacheRoundTrip(*res); + return; + } + } + + ScopedObjectConversion conversion(env, value); + if (!conversion.entered()) { + *res = nil; + if (conversion.status() != napi_ok) { + status = conversion.status(); + NAPI_THROW_LAST_ERROR + } + return; + } + + bool isArray = false; + napi_is_array(env, value, &isArray); + if (isArray) { + uint32_t len = 0; + napi_get_array_length(env, value, &len); + *res = [NSMutableArray arrayWithCapacity:len]; + + for (uint32_t i = 0; i < len; i++) { + napi_value elem; + napi_get_element(env, value, i, &elem); + id obj = nil; + toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + [(*res) addObject:obj != nil ? obj : [NSNull null]]; + } + + cacheRoundTrip(*res); + return; + } else { + napi_value global, jsObject, valueConstructor, DateConstructor, MapConstructor; + napi_value StringConstructor, NumberConstructor, BooleanConstructor; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Object", &jsObject); + napi_get_named_property(env, global, "Date", &DateConstructor); + napi_get_named_property(env, global, "Map", &MapConstructor); + napi_get_named_property(env, global, "String", &StringConstructor); + napi_get_named_property(env, global, "Number", &NumberConstructor); + napi_get_named_property(env, global, "Boolean", &BooleanConstructor); + napi_get_named_property(env, value, "constructor", &valueConstructor); + bool isEqual; + napi_strict_equals(env, jsObject, valueConstructor, &isEqual); + bool isDate; + napi_strict_equals(env, DateConstructor, valueConstructor, &isDate); + bool isMap; + napi_strict_equals(env, MapConstructor, valueConstructor, &isMap); + bool isStringObject = false; + bool isNumberObject = false; + bool isBooleanObject = false; + napi_strict_equals(env, StringConstructor, valueConstructor, &isStringObject); + napi_strict_equals(env, NumberConstructor, valueConstructor, &isNumberObject); + napi_strict_equals(env, BooleanConstructor, valueConstructor, &isBooleanObject); + + if (isStringObject || isNumberObject || isBooleanObject) { + napi_value valueOfMethod; + napi_get_named_property(env, value, "valueOf", &valueOfMethod); + napi_value primitiveValue; + napi_call_function(env, value, valueOfMethod, 0, nullptr, &primitiveValue); + if (hasPendingException(env)) { + *res = nil; + return; + } + toNative(env, primitiveValue, result, shouldFree, shouldFreeAny); + return; + } + + if (isDate) { + // Get the timestamp from the JavaScript Date object + napi_value getTimeMethod; + napi_get_named_property(env, value, "getTime", &getTimeMethod); + napi_value timestamp; + napi_call_function(env, value, getTimeMethod, 0, nullptr, ×tamp); + + double timeInMilliseconds; + napi_get_value_double(env, timestamp, &timeInMilliseconds); + + // Convert milliseconds to seconds for NSDate + NSTimeInterval timeInSeconds = timeInMilliseconds / 1000.0; + *res = [NSDate dateWithTimeIntervalSince1970:timeInSeconds]; + cacheRoundTrip(*res); + return; + } + + if (isMap) { + *res = [NSMutableDictionary dictionary]; + + napi_value entriesMethod; + napi_get_named_property(env, value, "entries", &entriesMethod); + napi_value iterator; + napi_call_function(env, value, entriesMethod, 0, nullptr, &iterator); + + napi_value nextMethod; + napi_get_named_property(env, iterator, "next", &nextMethod); + + while (true) { + napi_value step; + napi_call_function(env, iterator, nextMethod, 0, nullptr, &step); + + napi_value doneValue; + napi_get_named_property(env, step, "done", &doneValue); + bool done = false; + napi_get_value_bool(env, doneValue, &done); + if (done) { + break; + } + + napi_value tuple; + napi_get_named_property(env, step, "value", &tuple); + napi_value keyValue; + napi_value elementValue; + napi_get_element(env, tuple, 0, &keyValue); + napi_get_element(env, tuple, 1, &elementValue); + + id keyObject = nil; + id valueObject = nil; + toNative(env, keyValue, (void*)&keyObject, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + toNative(env, elementValue, (void*)&valueObject, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + + if (keyObject != nil && valueObject != nil) { + [(*res) setObject:valueObject forKey:keyObject]; + } + } + + cacheRoundTrip(*res); + return; + } + + if (!isEqual) { + *res = jsObjectToId(env, value); + return; + } + + bool hasLength = false; + napi_has_named_property(env, value, "length", &hasLength); + if (hasLength) { + napi_value lengthValue; + napi_get_named_property(env, value, "length", &lengthValue); + napi_valuetype lengthType = napi_undefined; + napi_typeof(env, lengthValue, &lengthType); + if (lengthType == napi_number) { + uint32_t len = 0; + napi_get_value_uint32(env, lengthValue, &len); + *res = [NSMutableArray arrayWithCapacity:len]; + for (uint32_t i = 0; i < len; i++) { + bool hasElement = false; + napi_has_element(env, value, i, &hasElement); + if (!hasElement) { + [(*res) addObject:[NSNull null]]; + continue; + } + napi_value elem; + napi_get_element(env, value, i, &elem); + id obj = nil; + toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + [(*res) addObject:obj != nil ? obj : [NSNull null]]; + } + cacheRoundTrip(*res); + return; + } + } + + *res = [NSMutableDictionary dictionary]; + napi_value objectKeysMethod = nullptr; + napi_get_named_property(env, jsObject, "keys", &objectKeysMethod); + napi_value keys = nullptr; + napi_call_function(env, jsObject, objectKeysMethod, 1, &value, &keys); + uint32_t len = 0; + napi_get_array_length(env, keys, &len); + + for (uint32_t i = 0; i < len; i++) { + napi_value key = nullptr; + napi_get_element(env, keys, i, &key); + + if (key == nullptr) { + continue; + } + + napi_value keyString = key; + napi_valuetype keyType = napi_undefined; + if (napi_typeof(env, key, &keyType) != napi_ok) { + continue; + } + + if (keyType == napi_symbol) { + continue; + } + + if (keyType != napi_string) { + if (napi_coerce_to_string(env, key, &keyString) != napi_ok || + keyString == nullptr) { + continue; + } + } + + size_t keyLength = 0; + if (napi_get_value_string_utf8(env, keyString, nullptr, 0, &keyLength) != napi_ok) { + continue; + } + + std::vector keyBuffer(keyLength + 1, '\0'); + if (napi_get_value_string_utf8(env, keyString, keyBuffer.data(), keyBuffer.size(), + &keyLength) != napi_ok) { + continue; + } + + NSString* nsKey = [NSString stringWithUTF8String:keyBuffer.data()]; + if (nsKey == nil) { + continue; + } + + id obj = nil; + napi_value elem = nullptr; + if (napi_get_property(env, value, key, &elem) != napi_ok) { + continue; + } + toNative(env, elem, (void*)&obj, shouldFree, shouldFreeAny); + if (hasPendingException(env)) { + *res = nil; + return; + } + if (obj != nil) { + [(*res) setObject:obj forKey:nsKey]; + } + } + + cacheRoundTrip(*res); + return; + } + } + + if (bridgeState != nullptr && wrapped != nullptr) { + for (const auto& entry : bridgeState->classes) { + auto bridgedClass = entry.second; + if (bridgedClass == wrapped) { + *res = (id)bridgedClass->nativeClass; + return; + } + } + + for (const auto& entry : bridgeState->protocols) { + auto bridgedProtocol = entry.second; + if (bridgedProtocol != wrapped) { + continue; + } + + Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); + if (runtimeProtocol == nil) { + std::string baseName; + if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { + runtimeProtocol = objc_getProtocol(baseName.c_str()); + } + } + + if (runtimeProtocol != nil) { + *res = (id)runtimeProtocol; + return; + } + } + } + + *res = (id)wrapped; + cacheRoundTrip(*res); + return; + + break; + } + + default: + napi_throw_error(env, nullptr, "Invalid object type"); + *res = nil; + break; + } + } + + void free(napi_env env, void* value) override { + id obj = *((id*)value); + auto bridgeState = ObjCBridgeState::InstanceData(env); + bridgeState->unregisterObject(obj); + } + + void encode(std::string* encoding) override { *encoding += "@"; } +}; + +static const std::shared_ptr objcObjectTypeConv = + std::make_shared(); + +class ObjCInstanceObjectTypeConv : public ObjCObjectTypeConv { + public: + ObjCInstanceObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeInstanceObject; + } +}; + +static const auto objcInstanceObjectTypeConv = std::make_shared(); + +class ObjCNSStringObjectTypeConv : public TypeConv { + public: + ObjCNSStringObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeNSStringObject; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + NSString* str = *((NSString**)value); + + if (str == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + NSUInteger length = [str length]; + std::vector chars(length > 0 ? length : 1); + if (length > 0) { + [str getCharacters:(unichar*)chars.data() range:NSMakeRange(0, length)]; + } + napi_value result; + napi_create_string_utf16(env, chars.data(), length, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + ObjCObjectTypeConv typeConv; + typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); + } + + void encode(std::string* encoding) override { *encoding += "@"; } +}; + +static const std::shared_ptr objcNSStringObjectTypeConv = + std::make_shared(); + +class ObjCNSMutableStringObjectTypeConv : public TypeConv { + public: + ObjCNSMutableStringObjectTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeNSMutableStringObject; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + NSMutableString* str = *((NSMutableString**)value); + + if (str == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (napi_value existing = bridgeState->findCachedObjectWrapper(env, str); existing != nullptr) { + return existing; + } + + ObjectOwnership ownership = (flags & kReturnOwned) != 0 ? kOwnedObject : kUnownedObject; + return bridgeState->getObject(env, str, ownership); + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + napi_valuetype type; + napi_typeof(env, value, &type); + if (type == napi_string) { + NSMutableString** res = (NSMutableString**)result; + + size_t len = 0; + NAPI_GUARD(napi_get_value_string_utf16(env, value, nullptr, len, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + std::vector chars(len + 1); + + NAPI_GUARD(napi_get_value_string_utf16(env, value, chars.data(), len + 1, &len)) { + NAPI_THROW_LAST_ERROR + return; + } + + *res = [[NSMutableString alloc] initWithCharacters:(unichar*)chars.data() length:len]; + return; + } + + ObjCObjectTypeConv typeConv; + typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); + } + + void encode(std::string* encoding) override { *encoding += "@"; } +}; + +static const std::shared_ptr objcNSMutableStringObjectTypeConv = + std::make_shared(); + +class ObjCClassTypeConv : public TypeConv { + public: + ObjCClassTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeClass; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + Class cls = *((Class*)value); + + if (cls == nullptr) { + napi_value null; + napi_get_null(env, &null); + return null; + } + + if (napi_value constructor = findRegisteredClassConstructor(env, cls); + constructor != nullptr) { + return constructor; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + return bridgeState != nullptr ? bridgeState->getObject(env, (id)cls, kUnownedObject, 0, nullptr) + : nullptr; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + ObjCObjectTypeConv typeConv; + typeConv.toNative(env, value, result, shouldFree, shouldFreeAny); + } + + void encode(std::string* encoding) override { *encoding += "#"; } +}; + +static const std::shared_ptr objcClassTypeConv = + std::make_shared(); + +char selector_name_buf[256]; + +class SelectorTypeConv : public TypeConv { + public: + SelectorTypeConv() { + type = &ffi_type_pointer; + kind = mdTypeSelector; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + SEL val = *((SEL*)value); + napi_create_string_utf8(env, sel_getName(val), NAPI_AUTO_LENGTH, &result); + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + SEL* res = (SEL*)result; + + napi_valuetype type; + napi_typeof(env, value, &type); + + switch (type) { + case napi_string: + NAPI_GUARD(napi_get_value_string_utf8(env, value, selector_name_buf, 256, NULL)) { + NAPI_THROW_LAST_ERROR + *res = NULL; + return; + } + *res = sel_registerName(selector_name_buf); + break; + + case napi_undefined: + case napi_null: + *res = NULL; + return; + + default: + napi_throw_error(env, nullptr, "Invalid selector type"); + *res = NULL; + return; + } + } + + void encode(std::string* encoding) override { *encoding += ":"; } +}; + +static const std::shared_ptr selectorTypeConv = + std::make_shared(); + +class StructTypeConv : public TypeConv { + public: + MDSectionOffset structOffset; + StructInfo* info = nullptr; + bool structInfoSearched = false; + + StructTypeConv(MDSectionOffset structOffset, ffi_type* type) : structOffset(structOffset) { + this->type = type; + kind = mdTypeStruct; + } + + // ~StructTypeConv() { delete type; } + + inline StructInfo* getInfo(napi_env env) { + if (!structInfoSearched) { + auto bridgeState = ObjCBridgeState::InstanceData(env); + info = bridgeState->getStructInfo(env, structOffset); + structInfoSearched = true; + } + + return info; + } + + inline size_t getStructSize(napi_env env) { + if (this->type != nullptr && this->type->size > 0) { + return this->type->size; + } + + auto info = getInfo(env); + return info != nullptr ? info->size : 0; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + auto info = getInfo(env); + + if (info == nullptr) { + napi_value result; + void* data; + napi_create_arraybuffer(env, type->size, &data, &result); + memcpy(data, value, type->size); + return result; + } else { + return StructObject::fromNative(env, info, value, (flags & kStructZeroCopy) == 0); + } + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + const size_t structSize = getStructSize(env); + if (structSize == 0) { + napi_throw_type_error(env, "TypeError", "Invalid struct size"); + return; + } + + bool isTypedArray = false; + napi_is_typedarray(env, value, &isTypedArray); + + if (isTypedArray) { + void* data; + size_t length = 0; + napi_typedarray_type type; + NAPI_GUARD(napi_get_typedarray_info(env, value, &type, &length, &data, nullptr, nullptr)) { + NAPI_THROW_LAST_ERROR + return; + } + const size_t unitLength = getTypedArrayUnitLength(type); + const size_t byteLength = length * unitLength; + memset(result, 0, structSize); + memcpy(result, data, std::min(byteLength, structSize)); + + return; + } + + napi_valuetype type; + napi_typeof(env, value, &type); + + if (type == napi_null || type == napi_undefined) { + auto info = getInfo(env); + + if (info == nullptr) { + napi_throw_type_error(env, "TypeError", + "Invalid struct type, must be Struct Object, " + "Struct Object Descriptor or TypedArray"); + return; + } + + memset(result, 0, info->size); + return; + } else if (type != napi_object) { + napi_throw_type_error(env, "TypeError", + "Invalid struct type, must be Struct Object, " + "Struct Object Descriptor or TypedArray"); + return; + } + + auto structObject = StructObject::unwrap(env, value); + if (structObject != nullptr) { + const size_t copySize = std::min(static_cast(structObject->info->size), structSize); + memset(result, 0, structSize); + memcpy(result, structObject->data, copySize); + return; + } + + auto info = getInfo(env); + + if (info == nullptr) { + napi_throw_type_error(env, "TypeError", + "Invalid struct type, must be Struct Object or TypedArray"); + return; + } + + if (structSize < info->size) { + std::vector storage(info->size, 0); + StructObject(env, info, value, storage.data()); + memcpy(result, storage.data(), structSize); + return; + } + + // Serialize directly to previously allocated memory. + StructObject(env, info, value, result); + } +}; + +class ArrayTypeConv : public TypeConv { + public: + int arraySize; + std::shared_ptr elementType; + bool decayToPointerForArguments = false; + + ArrayTypeConv(int arraySize, std::shared_ptr elementType) + : arraySize(arraySize), elementType(elementType) { + auto arrayType = new ffi_type(); + arrayType->type = FFI_TYPE_STRUCT; + arrayType->size = 0; + arrayType->alignment = 0; + arrayType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (arraySize + 1)); + for (int i = 0; i < arraySize; i++) { + arrayType->elements[i] = elementType->type; + } + arrayType->elements[arraySize] = nullptr; + type = arrayType; + kind = mdTypeArray; + } + + ffi_type* ffiTypeForArgument() override { + decayToPointerForArguments = true; + return &ffi_type_pointer; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + if (decayToPointerForArguments) { + void* raw = *((void**)value); + if (raw == nullptr) { + napi_value nullValue; + napi_get_null(env, &nullValue); + return nullValue; + } + return Pointer::create(env, raw); + } + + napi_value result; + napi_create_array_with_length(env, arraySize, &result); + + size_t elementSize = getElementSize(); + + auto base = static_cast(value); + for (int i = 0; i < arraySize; i++) { + void* slot = base + (i * elementSize); + napi_value elementValue = elementType->toJS(env, slot, flags); + napi_valuetype elementValueType = napi_undefined; + napi_typeof(env, elementValue, &elementValueType); + if (elementValueType == napi_boolean) { + bool boolValue = false; + napi_get_value_bool(env, elementValue, &boolValue); + napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); + } + napi_set_element(env, result, i, elementValue); + if (StructObject::isInstance(env, elementValue)) { + napi_set_named_property(env, elementValue, "__ns_parent_struct_array", result); + } + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + if (decayToPointerForArguments) { + void** pointerResult = static_cast(result); + *pointerResult = nullptr; + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + return; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + *pointerResult = ptr != nullptr ? ptr->data : nullptr; + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + *pointerResult = ref != nullptr ? ref->data : nullptr; + return; + } + + if (StructObject::isInstance(env, value)) { + StructObject* structObject = StructObject::unwrap(env, value); + *pointerResult = structObject != nullptr ? structObject->data : nullptr; + return; + } + + size_t arrayByteSize = getArrayByteSize(); + if (arrayByteSize == 0) { + return; + } + void* copiedBuffer = malloc(arrayByteSize); + if (copiedBuffer == nullptr) { + napi_throw_error(env, nullptr, "Out of memory while converting C array argument"); + return; + } + + copyToInlineArrayStorage(env, value, copiedBuffer, shouldFree, shouldFreeAny); + + bool hasPendingException = false; + napi_is_exception_pending(env, &hasPendingException); + if (hasPendingException) { + ::free(copiedBuffer); + return; + } + + *pointerResult = copiedBuffer; + if (shouldFree != nullptr) { + *shouldFree = true; + } + if (shouldFreeAny != nullptr) { + *shouldFreeAny = true; + } + return; + } + + copyToInlineArrayStorage(env, value, result, shouldFree, shouldFreeAny); + } + + void free(napi_env env, void* value) override { + if (value != nullptr) { + ::free(value); + } + } + + void encode(std::string* encoding) override { + *encoding += "["; + *encoding += std::to_string(arraySize); + elementType->encode(encoding); + *encoding += "]"; + } + + private: + size_t getElementSize() const { + size_t elementSize = + elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; + if (elementSize == 0 && type != nullptr && arraySize > 0 && type->size >= (size_t)arraySize) { + elementSize = type->size / static_cast(arraySize); + } + if (elementSize == 0) { + elementSize = sizeof(void*); + } + return elementSize; + } + + size_t getArrayByteSize() const { return getElementSize() * static_cast(arraySize); } + + void copyToInlineArrayStorage(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) { + size_t elementSize = getElementSize(); + size_t arrayByteSize = getArrayByteSize(); + memset(result, 0, arrayByteSize); + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + return; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + if (ptr == nullptr || ptr->data == nullptr) { + return; + } + memcpy(result, ptr->data, arrayByteSize); + return; + } + + bool isArray = false; + napi_is_array(env, value, &isArray); + if (isArray) { + for (int i = 0; i < arraySize; i++) { + bool hasElement = false; + napi_has_element(env, value, i, &hasElement); + if (!hasElement) { + continue; + } + + napi_value elementValue; + napi_get_element(env, value, i, &elementValue); + void* slot = static_cast(result) + (i * elementSize); + elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); + } + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + memcpy(result, data, std::min(byteLength, arrayByteSize)); + return; + } + + void* data; + size_t length = 0; + napi_typedarray_type typedArrayType; + napi_status typedArrayStatus = + napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); + if (typedArrayStatus != napi_ok) { + NAPI_THROW_LAST_ERROR + return; + } + + size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); + memcpy(result, data, std::min(copyLength, arrayByteSize)); + } +}; + +class VectorTypeConv : public TypeConv { + public: + uint16_t vectorSize; + std::shared_ptr elementType; + MDTypeKind vectorKind; + + VectorTypeConv(MDTypeKind vectorKind, uint16_t vectorSize, std::shared_ptr elementType) + : vectorSize(vectorSize), elementType(elementType), vectorKind(vectorKind) { + auto vectorType = new ffi_type(); +#if defined(FFI_TYPE_EXT_VECTOR) + vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; +#else + vectorType->type = vectorKind == mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; +#endif + size_t lanes = std::max(vectorSize, 1); + // 3-lane vectors are ABI-lowered to 4-lane storage on Apple platforms. + size_t abiLanes = lanes == 3 ? 4 : lanes; + vectorType->elements = (ffi_type**)malloc(sizeof(ffi_type*) * (abiLanes + 1)); + + ffi_type* elementFfiType = elementType != nullptr && elementType->type != nullptr + ? elementType->type + : &ffi_type_float; + const size_t elementSize = std::max(elementFfiType->size, sizeof(float)); + const size_t elementAlignment = + std::max(elementFfiType->alignment, static_cast(1)); + + for (size_t i = 0; i < abiLanes; i++) { + vectorType->elements[i] = elementFfiType; + } + vectorType->elements[abiLanes] = nullptr; + + size_t vectorAlignment = elementAlignment; + if (vectorKind != mdTypeComplex) { + size_t packedSize = abiLanes * elementSize; + size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; + vectorAlignment = std::max(vectorAlignment, preferredAlignment); + } + vectorAlignment = std::min(vectorAlignment, 16); + vectorType->alignment = static_cast(vectorAlignment); + vectorType->size = alignUp(abiLanes * elementSize, vectorAlignment); + + type = vectorType; + kind = vectorKind; + } + + napi_value toJS(napi_env env, void* value, uint32_t flags) override { + napi_value result; + napi_create_array_with_length(env, vectorSize, &result); + + size_t elementSize = getElementSize(); + auto base = static_cast(value); + for (uint16_t i = 0; i < vectorSize; i++) { + void* slot = base + (static_cast(i) * elementSize); + napi_value elementValue = elementType->toJS(env, slot, flags); + napi_valuetype elementValueType = napi_undefined; + napi_typeof(env, elementValue, &elementValueType); + if (elementValueType == napi_boolean) { + bool boolValue = false; + napi_get_value_bool(env, elementValue, &boolValue); + napi_create_uint32(env, boolValue ? 1 : 0, &elementValue); + } + napi_set_element(env, result, i, elementValue); + } + return result; + } + + void toNative(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) override { + NAPI_PREAMBLE + + memset(result, 0, getVectorByteSize()); + + napi_valuetype valueType = napi_undefined; + napi_typeof(env, value, &valueType); + if (valueType == napi_null || valueType == napi_undefined) { + return; + } + + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + if (ptr != nullptr && ptr->data != nullptr) { + copyFromContiguousBuffer(ptr->data, getVectorByteSize(), result); + } + return; + } + + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + if (ref != nullptr && ref->data != nullptr) { + copyFromContiguousBuffer(ref->data, getVectorByteSize(), result); + } + return; + } + + if (StructObject::isInstance(env, value)) { + StructObject* structObject = StructObject::unwrap(env, value); + if (structObject != nullptr && structObject->data != nullptr) { + copyFromContiguousBuffer(structObject->data, structObject->info->size, result); + } + return; + } + + bool isArray = false; + napi_is_array(env, value, &isArray); + if (isArray) { + writeFromArrayElements(env, value, result, shouldFree, shouldFreeAny); + return; + } + + bool isArrayBuffer = false; + napi_is_arraybuffer(env, value, &isArrayBuffer); + if (isArrayBuffer) { + void* data = nullptr; + size_t byteLength = 0; + napi_get_arraybuffer_info(env, value, &data, &byteLength); + copyFromContiguousBuffer(data, byteLength, result); + return; + } + + void* data = nullptr; + size_t length = 0; + napi_typedarray_type typedArrayType = napi_int8_array; + napi_status typedArrayStatus = + napi_get_typedarray_info(env, value, &typedArrayType, &length, &data, nullptr, nullptr); + if (typedArrayStatus == napi_ok) { + size_t copyLength = length * getTypedArrayUnitLength(typedArrayType); + copyFromContiguousBuffer(data, copyLength, result); + return; + } + + napi_throw_type_error( + env, "TypeError", + "Invalid vector type, expected array, typed array, array buffer, pointer or reference."); + } + + void encode(std::string* encoding) override { + *encoding += "V"; + *encoding += std::to_string(vectorSize); + if (elementType != nullptr) { + elementType->encode(encoding); + } + } + + private: + inline size_t getElementSize() const { + size_t elementSize = + elementType != nullptr && elementType->type != nullptr ? elementType->type->size : 0; + if (elementSize == 0) { + elementSize = sizeof(float); + } + return elementSize; + } + + inline size_t getVectorByteSize() const { + size_t expectedSize = static_cast(vectorSize) * getElementSize(); + if (type != nullptr && type->size > expectedSize) { + expectedSize = type->size; + } + return expectedSize; + } + + inline void copyFromContiguousBuffer(void* source, size_t sourceLength, void* destination) const { + memcpy(destination, source, std::min(sourceLength, getVectorByteSize())); + } + + void writeFromArrayElements(napi_env env, napi_value value, void* result, bool* shouldFree, + bool* shouldFreeAny) { + size_t elementSize = getElementSize(); + auto base = static_cast(result); + + for (uint16_t i = 0; i < vectorSize; i++) { + bool hasElement = false; + napi_has_element(env, value, i, &hasElement); + if (!hasElement) { + continue; + } + + napi_value elementValue; + napi_get_element(env, value, i, &elementValue); + void* slot = base + (static_cast(i) * elementSize); + elementType->toNative(env, elementValue, slot, shouldFree, shouldFreeAny); + } + } +}; + +std::shared_ptr TypeConv::Make(napi_env env, const char** encoding) { + char first = **encoding; + bool readonly = false; + if (first == 'r') { + readonly = true; + first = *(++(*encoding)); + } + + switch (first) { + case 'c': + (*encoding)++; + return scharTypeConv; + case 'i': + (*encoding)++; + return sint32TypeConv; + case 's': + (*encoding)++; + return sint16TypeConv; + case 'l': + case 'q': + (*encoding)++; + return sint64TypeConv; + case 'C': + (*encoding)++; + return uint8TypeConv; + case 'I': + (*encoding)++; + return uint32TypeConv; + case 'S': + (*encoding)++; + return uint16TypeConv; + case 'L': + case 'Q': + (*encoding)++; + return uint64TypeConv; + case 'f': + (*encoding)++; + return float32TypeConv; + case 'd': + (*encoding)++; + return float64TypeConv; + case 'B': + (*encoding)++; + return boolTypeConv; + case 'v': + (*encoding)++; + return voidTypeConv; + case '*': + (*encoding)++; + return stringTypeConv; + case '@': + (*encoding)++; + return objcObjectTypeConv; + case '#': + (*encoding)++; + return objcClassTypeConv; + case ':': + (*encoding)++; + return selectorTypeConv; + case '[': { + char c = **encoding; + std::string num; + while ((c = **encoding) >= '0' && c <= '9') { + num += c; + (*encoding)++; + } + auto arraySize = std::stoi(num); + auto elementType = TypeConv::Make(env, encoding); + while (**encoding != ']') { + (*encoding)++; + } // skip array type + (*encoding)++; // skip ']' + return std::make_shared(ArrayTypeConv(arraySize, elementType)); + } + case '{': { + std::string structname; + const char* c = *encoding + 1; + while (*c != '\0' && *c != '=') { + structname += *c; + c++; + } + if (*c != '=') { + while (**encoding != '\0' && **encoding != '}') { + (*encoding)++; + } + if (**encoding == '}') { + (*encoding)++; + } + return pointerTypeConv; + } + + // Check if we already have a cached StructTypeConv for this encoding-based struct + auto cacheIt = encodingStructCache.find(structname); + if (cacheIt != encodingStructCache.end()) { + return cacheIt->second; + } + + auto bridgeState = ObjCBridgeState::InstanceData(env); + MDSectionOffset structOffset = MD_SECTION_OFFSET_NULL; + if (bridgeState != nullptr) { + auto structOffsetIt = bridgeState->structOffsets.find(structname); + if (structOffsetIt != bridgeState->structOffsets.end()) { + structOffset = structOffsetIt->second; + } + } + auto type = typeFromStruct(env, encoding); + auto structTypeConv = std::make_shared(StructTypeConv(structOffset, type)); + + // Cache the StructTypeConv + encodingStructCache[structname] = structTypeConv; + + return structTypeConv; + } + case 'b': { + (*encoding)++; + char c = **encoding; + while ((c = **encoding) >= '0' && c <= '9') { + (*encoding)++; + } // skip bits + return uint64TypeConv; + } + case '^': + (*encoding)++; + TypeConv::Make(env, encoding); + return pointerTypeConv; + case '?': + // unknown type + return pointerTypeConv; + default: + std::cout << "getTypeInfo unknown encoding: " << *encoding << std::endl; + return pointerTypeConv; + } +} + +std::shared_ptr TypeConv::Make(napi_env env, MDMetadataReader* reader, + MDSectionOffset* offset, uint8_t opaquePointers) { + auto kind = reader->getTypeKind(*offset); + bool next = (MDTypeFlag)kind & mdTypeFlagNext; + kind = (MDTypeKind)((kind & ~mdTypeFlagNext) & ~mdTypeFlagVariadic); + *offset += sizeof(MDTypeKind); + + switch (kind) { + case mdTypeChar: { + return scharTypeConv; + } + + case mdTypeSInt: { + return sint32TypeConv; + } + + case mdTypeSShort: { + return sint16TypeConv; + } + + case mdTypeSLong: + case mdTypeSInt64: { + return sint64TypeConv; + } + + case mdTypeUInt8: { + return uint8TypeConv; + } + + case mdTypeUChar: { + return ucharTypeConv; + } + + case mdTypeUInt: { + return uint32TypeConv; + } + + case mdTypeUShort: { + return uint16TypeConv; + } + + case mdTypeUnichar: { + return unicharTypeConv; + } + + case mdTypeULong: + case mdTypeUInt64: { + return uint64TypeConv; + } + + case mdTypeFloat: { + return float32TypeConv; + } + + case mdTypeF16: { + return float16TypeConv; + } + + case mdTypeDouble: { + return float64TypeConv; + } + + case mdTypeBool: { + return boolTypeConv; + } + + case mdTypeVoid: { + return voidTypeConv; + } + + case mdTypeString: { + return stringTypeConv; + } + + case mdTypeAnyObject: { + return objcObjectTypeConv; + } + + case mdTypeInstanceObject: { + return objcInstanceObjectTypeConv; + } + + case mdTypeClassObject: { + auto classOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool next = (classOffset & mdSectionOffsetNext) != 0; + classOffset &= ~mdSectionOffsetNext; + if (classOffset == MD_SECTION_OFFSET_NULL) { + classOffset = 0; + } else { + classOffset += reader->classesOffset; + } + std::vector protocolOffsets; + while (next) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & mdSectionOffsetNext) != 0; + protocolOffset &= ~mdSectionOffsetNext; + if (protocolOffset == MD_SECTION_OFFSET_NULL) { + protocolOffset = 0; + } else { + protocolOffset += reader->protocolsOffset; + protocolOffsets.push_back(protocolOffset); + } + } + return std::make_shared(classOffset, protocolOffsets); + } + + case mdTypeProtocolObject: { + std::vector protocolOffsets; + bool next = true; + while (next) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & mdSectionOffsetNext) != 0; + protocolOffset &= ~mdSectionOffsetNext; + if (protocolOffset == MD_SECTION_OFFSET_NULL) { + protocolOffset = 0; + } else { + protocolOffset += reader->protocolsOffset; + protocolOffsets.push_back(protocolOffset); + } + } + return std::make_shared(protocolOffsets); + } + + case mdTypeNSStringObject: { + return objcNSStringObjectTypeConv; + } + + case mdTypeNSMutableStringObject: { + return objcNSMutableStringObjectTypeConv; + } + + case mdTypeClass: { + return objcClassTypeConv; + } + + case mdTypeSelector: { + return selectorTypeConv; + } + + case mdTypeArray: { + auto arraySize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset); + return std::make_shared(ArrayTypeConv(arraySize, elementType)); + } + + case mdTypeStruct: { + auto structOffset = reader->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + auto isUnion = (structOffset & mdSectionOffsetNext) != 0; + structOffset &= ~mdSectionOffsetNext; + if (structOffset == MD_SECTION_OFFSET_NULL) { + return pointerTypeConv; + } + structOffset += isUnion ? reader->unionsOffset : reader->structsOffset; + auto structName = reader->getString(structOffset); + + // Check if we already have a cached StructTypeConv for this struct + auto cacheIt = structTypeCache.find(structOffset); + if (cacheIt != structTypeCache.end()) { + return cacheIt->second; + } + + // Check if we're currently processing this struct (recursion detection) + bool isRecursive = processingStructs.find(structOffset) != processingStructs.end(); + + ffi_type* type = nullptr; + if (opaquePointers != 2 && !isRecursive) { + type = typeFromStruct(env, reader, structOffset, isUnion); + } + + auto structTypeConv = std::make_shared(structOffset, type); + + // Cache the StructTypeConv to handle recursion and avoid duplicates + structTypeCache[structOffset] = structTypeConv; + + return structTypeConv; + } + + case mdTypePointer: { + auto pointeeType = TypeConv::Make(env, reader, offset, opaquePointers == 1 ? 2 : 0); + return std::make_shared(pointeeType); + } + + case mdTypeOpaquePointer: { + return pointerTypeConv; + } + + case mdTypeVector: { + auto vectorSize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); + return std::make_shared(kind, vectorSize, elementType); + } + + case mdTypeBlock: { + auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; + *offset += sizeof(MDSectionOffset); + return std::make_shared(blockSignature); + } + + case mdTypeFunctionPointer: { + auto blockSignature = reader->getOffset(*offset) + reader->signaturesOffset; + *offset += sizeof(MDSectionOffset); + return std::make_shared(blockSignature); + } + + case mdTypeUInt128: { + return uint128TypeConv; + } + + case mdTypeExtVector: { + auto vectorSize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); + return std::make_shared(kind, vectorSize, elementType); + } + + case mdTypeComplex: { + auto vectorSize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + auto elementType = TypeConv::Make(env, reader, offset, opaquePointers); + return std::make_shared(kind, vectorSize, elementType); + } + + default: + return pointerTypeConv; + } +} + +namespace { + +bool tryFastConvertStringToNSString(napi_env env, napi_value value, id* out, bool mutableString) { + if (out == nullptr) { + return false; + } + + if (mutableString) { + constexpr size_t kStackUtf16Capacity = 128; + char16_t utf16Stack[kStackUtf16Capacity]; + char16_t* utf16Buffer = utf16Stack; + size_t utf16Capacity = kStackUtf16Capacity; + size_t utf16Length = 0; + if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != + napi_ok) { + return false; + } + + std::vector utf16Heap; + if (utf16Length + 1 >= utf16Capacity) { + if (napi_get_value_string_utf16(env, value, nullptr, 0, &utf16Length) != napi_ok) { + return false; + } + utf16Heap.resize(utf16Length + 1, 0); + utf16Buffer = utf16Heap.data(); + utf16Capacity = utf16Heap.size(); + if (napi_get_value_string_utf16(env, value, utf16Buffer, utf16Capacity, &utf16Length) != + napi_ok) { + return false; + } + } + + *out = [[NSMutableString alloc] initWithCharacters:reinterpret_cast(utf16Buffer) + length:utf16Length]; + return true; + } + + constexpr size_t kStackUtf8Capacity = 256; + char utf8Stack[kStackUtf8Capacity]; + char* utf8Buffer = utf8Stack; + size_t utf8Capacity = kStackUtf8Capacity; + size_t utf8Length = 0; + if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { + return false; + } + + std::vector utf8Heap; + if (utf8Length + 1 >= utf8Capacity) { + if (napi_get_value_string_utf8(env, value, nullptr, 0, &utf8Length) != napi_ok) { + return false; + } + utf8Heap.resize(utf8Length + 1, '\0'); + utf8Buffer = utf8Heap.data(); + utf8Capacity = utf8Heap.size(); + if (napi_get_value_string_utf8(env, value, utf8Buffer, utf8Capacity, &utf8Length) != napi_ok) { + return false; + } + } + + id stringValue = [[[NSString alloc] initWithBytes:utf8Buffer + length:utf8Length + encoding:NSUTF8StringEncoding] autorelease]; + *out = stringValue != nil ? stringValue : [NSString string]; + return true; +} + +bool tryFastConvertObjCObjectValue(napi_env env, napi_value value, napi_valuetype valueType, + MDTypeKind kind, id* out) { + if (out == nullptr) { + return false; + } + + switch (valueType) { + case napi_null: + case napi_undefined: + *out = nil; + return true; + + case napi_external: { + void* external = nullptr; + if (napi_get_value_external(env, value, &external) != napi_ok) { + return false; + } + *out = static_cast(external); + return true; + } + + case napi_number: { + double numericValue = 0; + if (napi_get_value_double(env, value, &numericValue) != napi_ok) { + return false; + } + *out = [NSNumber numberWithDouble:numericValue]; + return true; + } + + case napi_boolean: { + bool boolValue = false; + if (napi_get_value_bool(env, value, &boolValue) != napi_ok) { + return false; + } + *out = [NSNumber numberWithBool:boolValue]; + return true; + } + + case napi_bigint: { + int64_t bigintValue = 0; + bool lossless = false; + if (napi_get_value_bigint_int64(env, value, &bigintValue, &lossless) != napi_ok) { + return false; + } + *out = [NSNumber numberWithLongLong:bigintValue]; + return true; + } + + case napi_string: + return tryFastConvertStringToNSString(env, value, out, kind == mdTypeNSMutableStringObject); + + case napi_object: + case napi_function: { + auto bridgeState = ObjCBridgeState::InstanceData(env); + auto cacheRoundTrip = [&](id nativeObj) { + if (nativeObj == nil || bridgeState == nullptr || !bridgeState->hasRoundTripCacheFrame()) { + return; + } + + bridgeState->cacheRoundTripObject(env, nativeObj, value); + }; + + if (valueType == napi_object) { + if (Pointer::isInstance(env, value)) { + Pointer* ptr = Pointer::unwrap(env, value); + void* pointerData = ptr != nullptr ? ptr->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, pointerData); cachedObject != nil) { + *out = cachedObject; + return true; + } + *out = (id)pointerData; + return true; + } + if (Reference::isInstance(env, value)) { + Reference* ref = Reference::unwrap(env, value); + void* referenceData = ref != nullptr ? ref->data : nullptr; + if (id cachedObject = resolveCachedHandleObject(env, referenceData); + cachedObject != nil) { + *out = cachedObject; + return true; + } + *out = (id)referenceData; + return true; + } + } + + if (bridgeState != nullptr) { + id bridgedType = nil; + if (bridgeState->tryResolveBridgedTypeConstructor(env, value, &bridgedType) && + bridgedType != nil) { + *out = bridgedType; + return true; + } + } + + void* wrapped = nullptr; + if (napi_unwrap(env, value, &wrapped) == napi_ok) { + if (valueType == napi_function || valueType == napi_object) { + auto bridgeState = ObjCBridgeState::InstanceData(env); + if (bridgeState != nullptr && wrapped != nullptr) { + for (const auto& entry : bridgeState->classes) { + auto bridgedClass = entry.second; + if (bridgedClass == wrapped) { + *out = (id)bridgedClass->nativeClass; + return true; + } + } + + for (const auto& entry : bridgeState->protocols) { + auto bridgedProtocol = entry.second; + if (bridgedProtocol == wrapped) { + Protocol* runtimeProtocol = objc_getProtocol(bridgedProtocol->name.c_str()); + if (runtimeProtocol == nil) { + std::string baseName; + if (stripProtocolSuffix(bridgedProtocol->name.c_str(), &baseName)) { + runtimeProtocol = objc_getProtocol(baseName.c_str()); + } + } + if (runtimeProtocol != nil) { + *out = (id)runtimeProtocol; + return true; + } + } + } + } + } + + *out = (id)wrapped; + cacheRoundTrip(*out); + return true; + } + + bool isTypedArray = false; + if (napi_is_typedarray(env, value, &isTypedArray) == napi_ok && isTypedArray) { + *out = createNSDataWrapper(env, value, bridgeState); + if (*out != nil) { + cacheRoundTrip(*out); + return true; + } + return false; + } + + bool isArrayBuffer = false; + if (napi_is_arraybuffer(env, value, &isArrayBuffer) == napi_ok && isArrayBuffer) { + *out = createNSDataWrapper(env, value, bridgeState); + if (*out != nil) { + cacheRoundTrip(*out); + return true; + } + return false; + } + + bool isDataView = false; + if (napi_is_dataview(env, value, &isDataView) == napi_ok && isDataView) { + *out = createNSDataWrapper(env, value, bridgeState); + if (*out != nil) { + cacheRoundTrip(*out); + return true; + } + return false; + } + + return false; + } + + default: + return false; + } +} + +} // namespace + +bool TryFastConvertNapiArgument(napi_env env, MDTypeKind kind, napi_value value, void* result) { + if (result == nullptr || value == nullptr) { + return false; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + + switch (kind) { + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + return tryFastConvertObjCObjectValue(env, value, valueType, static_cast(kind), + reinterpret_cast(result)); + + case mdTypeSelector: { + SEL* selector = reinterpret_cast(result); + switch (valueType) { + case napi_null: + case napi_undefined: + *selector = nullptr; + return true; + + case napi_string: { + constexpr size_t kStackSelectorCapacity = 128; + char selectorStack[kStackSelectorCapacity]; + size_t selectorLength = 0; + if (napi_get_value_string_utf8(env, value, selectorStack, kStackSelectorCapacity, + &selectorLength) != napi_ok) { + return false; + } + const char* selectorName = selectorStack; + std::vector selectorHeap; + if (selectorLength + 1 >= kStackSelectorCapacity) { + if (napi_get_value_string_utf8(env, value, nullptr, 0, &selectorLength) != napi_ok) { + return false; + } + selectorHeap.resize(selectorLength + 1, '\0'); + if (napi_get_value_string_utf8(env, value, selectorHeap.data(), selectorHeap.size(), + &selectorLength) != napi_ok) { + return false; + } + selectorName = selectorHeap.data(); + } + *selector = sel_registerName(selectorName); + return true; + } + + default: + return false; + } + } + + default: + return false; + } +} + +bool TryFastConvertNapiUInt16Argument(napi_env env, napi_value value, uint16_t* result) { + if (result == nullptr || value == nullptr) { + return false; + } + + napi_valuetype valueType = napi_undefined; + if (napi_typeof(env, value, &valueType) != napi_ok) { + return false; + } + + if (valueType == napi_string) { + size_t strLen = 0; + if (napi_get_value_string_utf16(env, value, nullptr, 0, &strLen) != napi_ok) { + return false; + } + if (strLen != 1) { + napi_throw_type_error(env, nullptr, "Expected a single-character string."); + *result = 0; + return false; + } + + char16_t chars[2] = {0, 0}; + if (napi_get_value_string_utf16(env, value, chars, 2, &strLen) != napi_ok) { + return false; + } + + *result = static_cast(chars[0]); + return true; + } + + napi_value coerced = value; + if (napi_coerce_to_number(env, value, &coerced) != napi_ok) { + return false; + } + + uint32_t converted = 0; + if (napi_get_value_uint32(env, coerced, &converted) != napi_ok) { + return false; + } + + *result = static_cast(converted); + return true; +} + +// Cleanup function to clear thread-local caches +void clearStructTypeCaches() { + processingStructs.clear(); + processingEncodingStructs.clear(); + forwardDeclaredStructs.clear(); + forwardDeclaredEncodingStructs.clear(); + structTypeCache.clear(); + encodingStructCache.clear(); +} + +} // namespace nativescript diff --git a/NativeScript/ffi/napi/Util.h b/NativeScript/ffi/objc/napi/Util.h similarity index 100% rename from NativeScript/ffi/napi/Util.h rename to NativeScript/ffi/objc/napi/Util.h diff --git a/NativeScript/ffi/napi/Util.mm b/NativeScript/ffi/objc/napi/Util.mm similarity index 100% rename from NativeScript/ffi/napi/Util.mm rename to NativeScript/ffi/objc/napi/Util.mm diff --git a/NativeScript/ffi/napi/Variable.h b/NativeScript/ffi/objc/napi/Variable.h similarity index 100% rename from NativeScript/ffi/napi/Variable.h rename to NativeScript/ffi/objc/napi/Variable.h diff --git a/NativeScript/ffi/napi/Variable.mm b/NativeScript/ffi/objc/napi/Variable.mm similarity index 100% rename from NativeScript/ffi/napi/Variable.mm rename to NativeScript/ffi/objc/napi/Variable.mm diff --git a/NativeScript/ffi/napi/node_api_util.h b/NativeScript/ffi/objc/napi/node_api_util.h similarity index 100% rename from NativeScript/ffi/napi/node_api_util.h rename to NativeScript/ffi/objc/napi/node_api_util.h diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.h new file mode 100644 index 000000000..c26fe8bfa --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.h @@ -0,0 +1,21 @@ +#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H +#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H + +#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include "quickjs.h" + +namespace nativescript { + +using NativeApiScheduler = NativeApiBackendScheduler; +using NativeApiConfig = NativeApiBackendConfig; + +void InstallNativeApi(JSContext* context, + const NativeApiConfig& config = + NativeApiConfig{}); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSContext* context, + const char* metadataPath); + +#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.mm new file mode 100644 index 000000000..8f234d1bd --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJS.mm @@ -0,0 +1,75 @@ +#include "NativeApiQuickJS.h" + +#ifdef TARGET_ENGINE_QUICKJS + +#include "NativeApiQuickJSRuntime.h" +#include "SignatureDispatch.h" + +namespace nativescript { + +namespace { + +using nativescript::engine::Array; +using nativescript::engine::ArrayBuffer; +using nativescript::engine::BigInt; +using nativescript::engine::Function; +using nativescript::engine::HostObject; +using nativescript::engine::MutableBuffer; +using nativescript::engine::Object; +using nativescript::engine::PropNameID; +using nativescript::engine::Runtime; +using nativescript::engine::String; +using nativescript::engine::StringBuffer; +using nativescript::engine::Value; +using nativescript::engine::JSError; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE 1 +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "quickjs" +#include "../shared/bridge/ObjCBridge.mm" +// clang-format on + +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 +#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 + +#include "NativeApiQuickJSRuntimeSupport.mm" + +// clang-format off +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + +#include "NativeApiQuickJSSelectorGroups.mm" + +} // namespace + +#include "../shared/bridge/Install.mm" + +void InstallNativeApi(JSContext* context, const NativeApiConfig& config) { + if (context == nullptr) { + return; + } + auto state = engine::quickjsengine::stateForContext(context); + nativescript::engine::Runtime runtime(state); + engine::quickjsengine::ensureClasses(runtime); + InstallNativeApi(runtime, config); +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(JSContext* context, const char* metadataPath) { + nativescript::NativeApiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApi(context, config); +} + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSGsd.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSGsd.mm new file mode 100644 index 000000000..998dcfef5 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSGsd.mm @@ -0,0 +1,292 @@ +// --- GSD (Generated Signature Dispatch) for QuickJS --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads JS arguments and writes the JS return value via the QuickJS API. +// Readers require an actual JS number so coercion edge cases defer to the +// fully correct generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + JSContext* context; + JSValueConst* arguments; + const NativeApiType& returnType; + JSValue result = JS_UNDEFINED; + const Value* valueArguments = nullptr; + bool materializeValueResult = false; + Value valueResult = Value::undefined(); + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + bool readNumber(size_t i, double* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isNumber()) return false; + *out = v.getNumber(); + return true; + } + JSValueConst v = arguments[i]; + if (!JS_IsNumber(v)) return false; + return quickJSNumberValue(context, v, out); + } + bool readBool(size_t i, uint8_t* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (!v.isBool()) return false; + *out = v.getBool() ? 1 : 0; + return true; + } + JSValueConst v = arguments[i]; + if (!JS_IsBool(v)) return false; + *out = JS_ToBool(context, v) != 0 ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + template + bool readUnsigned(size_t i, T* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0; + if (!readNumber(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { return readNumber(i, out); } + bool readSelector(size_t i, SEL* out) { + if (valueArguments != nullptr) { + return readFastEngineSelectorArgument(runtime, valueArguments[i], out); + } + return readQuickJSEngineSelectorArgument(runtime, arguments[i], out); + } + bool readClass(size_t i, Class* out) { + if (valueArguments != nullptr) { + Class cls = classFromEngineValue(runtime, valueArguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + if (auto* c = quickJSHostObjectRaw( + runtime, arguments[i])) { + *out = c->nativeClass(); + return true; + } + Class cls = quickJSNativeClassArgument(runtime, arguments[i]); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + if (valueArguments != nullptr) { + const Value& v = valueArguments[i]; + if (v.isNull() || v.isUndefined()) { + *out = nil; + return true; + } + if (!v.isObject()) return false; + Object object = v.asObject(runtime); + if (object.isHostObject(runtime)) { + *out = object.getHostObject(runtime)->object(); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime)->nativeClass()); + return true; + } + Class cls = classFromEngineValue(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (object.isHostObject(runtime)) { + *out = static_cast( + object.getHostObject(runtime) + ->nativeProtocol()); + return true; + } + return false; + } + JSValueConst v = arguments[i]; + if (JS_IsNull(v) || JS_IsUndefined(v)) { + *out = nil; + return true; + } + if (auto* h = quickJSHostObjectRaw(runtime, v)) { + *out = h->object(); + return true; + } + if (auto* c = quickJSHostObjectRaw(runtime, v)) { + *out = static_cast(c->nativeClass()); + return true; + } + if (JS_IsObject(v)) { + Class cls = quickJSNativeClassArgument(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + } + if (auto* p = + quickJSHostObjectRaw(runtime, v)) { + *out = static_cast(p->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() { + if (materializeValueResult) { + valueResult = Value::undefined(); + return; + } + result = JS_UNDEFINED; + } + void setBool(bool v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JS_NewBool(context, v); + } + void setInt32(int32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JS_NewInt32(context, v); + } + void setUInt32(uint32_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JS_NewUint32(context, v); + } + void setUInt16(uint16_t v) { + if (materializeValueResult) { + valueResult = Value(static_cast(v)); + return; + } + result = JS_NewUint32(context, v); + } + void setInt64(int64_t v) { + if (materializeValueResult) { + valueResult = signedInteger64ToEngineValue(runtime, v); + return; + } + result = quickJSInteger64Value(runtime, v); + } + void setUInt64(uint64_t v) { + if (materializeValueResult) { + valueResult = unsignedInteger64ToEngineValue(runtime, v); + return; + } + result = quickJSUnsignedInteger64Value(runtime, v); + } + void setDouble(double v) { + if (materializeValueResult) { + valueResult = Value(v); + return; + } + result = JS_NewFloat64(context, v); + } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + if (materializeValueResult) { + valueResult = name != nullptr ? makeString(runtime, name) : Value::null(); + return; + } + result = name == nullptr ? JS_NULL : JS_NewString(context, name); + } + void setClass(Class v) { + if (materializeValueResult) { + if (v == nil) { + valueResult = Value::null(); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + valueResult = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return; + } + if (v == nil) { + result = JS_NULL; + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value classValue = makeNativeClassValue(runtime, bridge, std::move(symbol)); + result = classValue.local(runtime); + } + void setObject(id obj) { + if (materializeValueResult) { + valueResult = convertNativeReturnValue(runtime, bridge, returnType, &obj); + return; + } + result = setQuickJSEngineObjectReturn(runtime, bridge, returnType, obj); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript; GsdObjCContext/ObjCGsdDispatchEntry stay reachable +// via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("GeneratedGsdSignatureDispatch.inc") +#include "GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSMarshalling.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSMarshalling.mm new file mode 100644 index 000000000..68424417f --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSMarshalling.mm @@ -0,0 +1,478 @@ +// Included by NativeApiQuickJSSelectorGroups.mm inside the NativeScript anonymous namespace. + +std::string quickJSValueToUtf8(JSContext* context, JSValueConst value) { + size_t length = 0; + const char* text = JS_ToCStringLen(context, &length, value); + if (text == nullptr) { + return {}; + } + std::string result(text, length); + JS_FreeCString(context, text); + return result; +} + +bool quickJSNumberValue(JSContext* context, JSValueConst value, + double* result) { + if (result == nullptr) { + return false; + } + double converted = 0; + if (JS_ToFloat64(context, &converted, value) < 0) { + return false; + } + *result = converted; + return true; +} + +template +std::shared_ptr quickJSHostObject(Runtime& runtime, JSValueConst value) { + if (!JS_IsObject(value)) { + return nullptr; + } + engine::quickjsengine::ensureClasses(runtime); + auto* holder = static_cast( + JS_GetOpaque(value, engine::quickjsengine::gHostClassId)); + if (holder == nullptr || + holder->typeToken != engine::quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); +} + +template +T* quickJSHostObjectRaw(Runtime& runtime, JSValueConst value) { + if (!JS_IsObject(value)) { + return nullptr; + } + engine::quickjsengine::ensureClasses(runtime); + auto* holder = static_cast( + JS_GetOpaque(value, engine::quickjsengine::gHostClassId)); + if (holder == nullptr || + holder->typeToken != engine::quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return static_cast(holder->hostObject.get()); +} + +id quickJSNativeObjectArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, JSValueConst value, + NativeApiArgumentFrame& frame) { + JSContext* context = runtime.context(); + if (JS_IsNull(value) || JS_IsUndefined(value)) { + return nil; + } + if (JS_IsString(value)) { + std::string utf8 = quickJSValueToUtf8(context, value); + id string = type.kind == metagen::mdTypeNSMutableStringObject + ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + if (string != nil) { + frame.addObject(string); + } + return string; + } + if (JS_IsBool(value)) { + return [NSNumber numberWithBool:JS_ToBool(context, value) != 0]; + } + if (JS_IsNumber(value) || JS_IsBigInt(value)) { + double converted = 0; + if (quickJSNumberValue(context, value, &converted)) { + return [NSNumber numberWithDouble:converted]; + } + } + if (!JS_IsObject(value)) { + return nil; + } + if (auto objectHost = + quickJSHostObject(runtime, value)) { + return objectHost->object(); + } + if (auto classHost = + quickJSHostObject(runtime, value)) { + return static_cast(classHost->nativeClass()); + } + if (auto protocolHost = + quickJSHostObject(runtime, value)) { + return static_cast(protocolHost->nativeProtocol()); + } + if (auto pointerHost = + quickJSHostObject(runtime, value)) { + return static_cast(pointerHost->pointer()); + } + if (auto referenceHost = + quickJSHostObject(runtime, value)) { + return static_cast(referenceHost->data()); + } + if (auto structHost = + quickJSHostObject(runtime, value)) { + return static_cast(structHost->data()); + } + + JSValue wrappedClassValue = + JS_GetPropertyStr(context, value, "__nativeApiClass"); + if (!JS_IsException(wrappedClassValue)) { + if (auto classHost = quickJSHostObject( + runtime, wrappedClassValue)) { + JS_FreeValue(context, wrappedClassValue); + return static_cast(classHost->nativeClass()); + } + } + JS_FreeValue(context, wrappedClassValue); + + Value wrapped = Value::borrowed(runtime, value); + return objectFromEngineValue(runtime, bridge, wrapped, frame, + type.kind == + metagen::mdTypeNSMutableStringObject); +} + +Class quickJSNativeClassArgument(Runtime& runtime, JSValueConst value) { + if (JS_IsNull(value) || JS_IsUndefined(value)) { + return Nil; + } + if (auto classHost = + quickJSHostObject(runtime, value)) { + return classHost->nativeClass(); + } + if (JS_IsObject(value)) { + JSValue wrappedClassValue = + JS_GetPropertyStr(runtime.context(), value, "__nativeApiClass"); + if (!JS_IsException(wrappedClassValue)) { + if (auto classHost = quickJSHostObject( + runtime, wrappedClassValue)) { + JS_FreeValue(runtime.context(), wrappedClassValue); + return classHost->nativeClass(); + } + } + JS_FreeValue(runtime.context(), wrappedClassValue); + } + Value wrapped = Value::borrowed(runtime, value); + return classFromEngineValue(runtime, wrapped); +} + +bool readQuickJSEngineSelectorArgument(Runtime& runtime, JSValueConst value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (JS_IsNull(value) || JS_IsUndefined(value)) { + *result = nullptr; + return true; + } + if (!JS_IsString(value)) { + return false; + } + std::string selectorName = quickJSValueToUtf8(runtime.context(), value); + *result = sel_registerName(selectorName.c_str()); + return true; +} + +template +bool writeQuickJSNumber(JSContext* context, JSValueConst value, void* target) { + double converted = 0; + if (!quickJSNumberValue(context, value, &converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; +} + +bool prepareQuickJSEngineArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, JSValueConst value, + NativeApiArgumentFrame& frame, size_t index) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + JSContext* context = runtime.context(); + + switch (type.kind) { + case metagen::mdTypeBool: + if (!JS_IsBool(value)) { + return false; + } + *static_cast(target) = JS_ToBool(context, value) != 0 ? 1 : 0; + return true; + case metagen::mdTypeChar: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeSShort: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + if (JS_IsString(value)) { + std::string text = quickJSValueToUtf8(context, value); + if (text.size() != 1) { + return false; + } + *static_cast(target) = + static_cast(static_cast(text[0])); + return true; + } + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeSInt: { + int32_t converted = 0; + if (JS_ToInt32(context, &converted, value) < 0) { + return false; + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeUInt: { + uint32_t converted = 0; + if (JS_ToUint32(context, &converted, value) < 0) { + return false; + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: { + int64_t converted = 0; + if (JS_ToInt64Ext(context, &converted, value) < 0) { + return false; + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: { + uint64_t converted = 0; + if (JS_IsBigInt(value)) { + if (JS_ToBigUint64(context, &converted, value) < 0) { + return false; + } + } else { + int64_t signedValue = 0; + if (JS_ToInt64Ext(context, &signedValue, value) < 0) { + return false; + } + converted = static_cast(signedValue); + } + *static_cast(target) = converted; + return true; + } + case metagen::mdTypeFloat: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeDouble: + return writeQuickJSNumber(context, value, target); + case metagen::mdTypeSelector: + return readQuickJSEngineSelectorArgument(runtime, value, + static_cast(target)); + case metagen::mdTypeClass: { + Class cls = quickJSNativeClassArgument(runtime, value); + if (cls == Nil) { + return false; + } + *static_cast(target) = cls; + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + *static_cast(target) = + quickJSNativeObjectArgument(runtime, bridge, type, value, frame); + return true; + default: + break; + } + + Value wrapped = Value::borrowed(runtime, value); + convertEngineFfiArgument(runtime, bridge, type, wrapped, target, frame); + return true; +} + +JSValue quickJSInteger64Value(Runtime& runtime, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return JS_NewFloat64(runtime.context(), static_cast(value)); + } + return JS_NewBigInt64(runtime.context(), value); +} + +JSValue quickJSUnsignedInteger64Value(Runtime& runtime, uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return JS_NewFloat64(runtime.context(), static_cast(value)); + } + return JS_NewBigUint64(runtime.context(), value); +} + +JSValue setQuickJSEngineObjectReturn( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, id object) { + JSContext* context = runtime.context(); + if (object == nil) { + return JS_NULL; + } + Value roundTrip = + findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + JSValue result = roundTrip.local(runtime); + if (type.returnOwned) { + [object release]; + } + return result; + } + if (nativeObjectReturnMayCoerceToString(type) && + nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + return JS_NewStringLen(context, utf8.data(), utf8.size()); + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + return JS_NULL; + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == + CFBooleanGetTypeID() || + (objCType != nullptr && + std::strcmp(objCType, @encode(BOOL)) == 0); + JSValue result = isBool ? JS_NewBool(context, [number boolValue]) + : JS_NewFloat64(context, [number doubleValue]); + if (type.returnOwned) { + [object release]; + } + return result; + } + + if (const NativeApiSymbol* classSymbol = + bridge->findClassForRuntimePointer((void*)object)) { + Value result = makeNativeClassValue(runtime, bridge, *classSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + Value result = makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + if (type.returnOwned) { + [object release]; + } + return result.local(runtime); + } + Value result = makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + return result.local(runtime); +} + +JSValue setQuickJSEngineReturnValue( + Runtime& runtime, const std::shared_ptr& bridge, + NativeApiType type, void* value, const std::string& selectorName) { + JSContext* context = runtime.context(); + switch (type.kind) { + case metagen::mdTypeVoid: + return JS_UNDEFINED; + case metagen::mdTypeBool: + return JS_NewBool(context, *static_cast(value) != 0); + case metagen::mdTypeChar: + return JS_NewInt32(context, *static_cast(value)); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return JS_NewUint32(context, *static_cast(value)); + case metagen::mdTypeSShort: + return JS_NewInt32(context, *static_cast(value)); + case metagen::mdTypeUShort: + return JS_NewUint32(context, *static_cast(value)); + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + return JS_NewStringLen(context, buffer, length); + } + case metagen::mdTypeSInt: + return JS_NewInt32(context, *static_cast(value)); + case metagen::mdTypeUInt: + return JS_NewUint32(context, *static_cast(value)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return quickJSInteger64Value(runtime, *static_cast(value)); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return quickJSUnsignedInteger64Value(runtime, + *static_cast(value)); + case metagen::mdTypeFloat: + return JS_NewFloat64(context, *static_cast(value)); + case metagen::mdTypeDouble: + return JS_NewFloat64(context, *static_cast(value)); + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + return JS_NULL; + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + return result.local(runtime); + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + if ((selectorName == "valueForKey:" || + selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(type)) { + type.kind = metagen::mdTypeAnyObject; + } + return setQuickJSEngineObjectReturn(runtime, bridge, type, + *static_cast(value)); + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorNameValue = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorNameValue == nullptr) { + return JS_NULL; + } + return JS_NewString(context, selectorNameValue); + } + default: + break; + } + Value result = convertNativeReturnValue(runtime, bridge, type, value); + return result.local(runtime); +} diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h new file mode 100644 index 000000000..302546aef --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h @@ -0,0 +1,40 @@ +#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H +#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H + +// Apple-side entry point for the QuickJS engine layer. +// +// The engine layer itself -- the `nativescript::engine` classes wrapping +// QuickJS -- moved to jsi/quickjs/QuickJSRuntime.h so the Android runtime can +// compile it too. What stays here is exactly what the Apple ObjC bridge needs +// on top of it and Android has no use for: Foundation, the Objective-C +// runtime, the Mach-O metadata section lookup, and the metadata reader. +// +// Apple sources include this path, unchanged, and get both halves. + +#ifdef TARGET_ENGINE_QUICKJS + +#import +#include +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" + +#include "jsi/quickjs/QuickJSRuntime.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +#endif // TARGET_ENGINE_QUICKJS + +#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntimeSupport.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntimeSupport.mm new file mode 100644 index 000000000..91bc9eb12 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntimeSupport.mm @@ -0,0 +1,119 @@ +// Included by NativeApiQuickJS.mm inside the NativeScript anonymous namespace. + +static JSValue NativeApiLazyGlobalGetter(JSContext* context, JSValueConst, int, + JSValueConst*, int, JSValueConst* data) { + JSValue global = JS_GetGlobalObject(context); + JSValue resolver = JS_GetPropertyStr(context, global, "__nativeScriptResolveNativeApiLazyGlobal"); + if (!JS_IsFunction(context, resolver)) { + JS_FreeValue(context, resolver); + JS_FreeValue(context, global); + return JS_UNDEFINED; + } + + JSValueConst args[] = {data[0], data[1]}; + JSValue result = JS_Call(context, resolver, global, 2, args); + JS_FreeValue(context, resolver); + if (JS_IsException(result)) { + JS_FreeValue(context, global); + return result; + } + + JSAtom atom = JS_ValueToAtom(context, data[0]); + if (atom != JS_ATOM_NULL) { + JS_DefinePropertyValue(context, global, atom, JS_DupValue(context, result), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + JS_FreeAtom(context, atom); + } + JS_FreeValue(context, global); + return result; +} + +// Assigning over a lazy global must behave like a plain global assignment +// (@nativescript/core writes shims such as global.System); replace the +// accessor with an ordinary writable property instead of throwing +// "no setter for property". +static JSValue NativeApiLazyGlobalSetter(JSContext* context, JSValueConst, int argc, + JSValueConst* argv, int, JSValueConst* data) { + JSValue global = JS_GetGlobalObject(context); + JSAtom atom = JS_ValueToAtom(context, data[0]); + if (atom != JS_ATOM_NULL) { + JSValue value = argc > 0 ? JS_DupValue(context, argv[0]) : JS_UNDEFINED; + JS_DefinePropertyValue(context, global, atom, value, JS_PROP_C_W_E); + JS_FreeAtom(context, atom); + } + JS_FreeValue(context, global); + return JS_UNDEFINED; +} + +bool InstallNativeApiLazyGlobal(Runtime& runtime, std::shared_ptr, + const std::string& name, const std::string& kind, + bool force) { + if (name.empty() || kind.empty()) { + return false; + } + + JSContext* context = runtime.context(); + JSValue global = JS_GetGlobalObject(context); + JSAtom atom = JS_NewAtomLen(context, name.data(), name.size()); + if (atom == JS_ATOM_NULL) { + JS_FreeValue(context, global); + return false; + } + + int hasProperty = JS_HasProperty(context, global, atom); + if (!force && hasProperty > 0) { + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + if (hasProperty < 0) { + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + + JSValue data[] = { + JS_NewStringLen(context, name.data(), name.size()), + JS_NewStringLen(context, kind.data(), kind.size()), + }; + if (JS_IsException(data[0]) || JS_IsException(data[1])) { + JS_FreeValue(context, data[0]); + JS_FreeValue(context, data[1]); + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + + JSValue getter = JS_NewCFunctionData(context, NativeApiLazyGlobalGetter, 0, 0, 2, data); + JSValue setter = JS_NewCFunctionData(context, NativeApiLazyGlobalSetter, 1, 0, 2, data); + JS_FreeValue(context, data[0]); + JS_FreeValue(context, data[1]); + if (JS_IsException(getter) || JS_IsException(setter)) { + JS_FreeValue(context, getter); + JS_FreeValue(context, setter); + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return false; + } + + int status = JS_DefinePropertyGetSet(context, global, atom, getter, setter, JS_PROP_CONFIGURABLE); + JS_FreeAtom(context, atom); + JS_FreeValue(context, global); + return status >= 0; +} + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + JSValue objectValue = object.local(runtime); + JSValue prototypeValue = prototype.local(runtime); + int status = JS_SetPrototype(runtime.context(), objectValue, prototypeValue); + JS_FreeValue(runtime.context(), prototypeValue); + JS_FreeValue(runtime.context(), objectValue); + if (status < 0) { + throw JSError(runtime, "QuickJS prototype assignment failed."); + } +} + +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::make_shared(runtime.state()); +} diff --git a/NativeScript/ffi/objc/quickjs/NativeApiQuickJSSelectorGroups.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSSelectorGroups.mm new file mode 100644 index 000000000..bb9fe56c2 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSSelectorGroups.mm @@ -0,0 +1,288 @@ +// Included by NativeApiQuickJS.mm inside the NativeScript anonymous namespace. + +#include "../shared/bridge/SelectorGroupData.h" + +#include "NativeApiQuickJSMarshalling.mm" + +#include "NativeApiQuickJSGsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || + !prepared.gsdEngineCallable || dispatchSuperClass != Nil || + count != prepared.gsdEngineArgumentCount) { + return false; + } + + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), nullptr, + prepared.signature.returnType}; + ctx.valueArguments = args; + ctx.materializeValueResult = true; + if (!invoker(ctx)) { + return false; + } + *result = std::move(ctx.valueResult); + return true; +} + +JSValue setQuickJSEnginePreparedObjCResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const std::shared_ptr& receiverHostObject, + const std::optional& initializerClassWrapper, + size_t providedCount, JSValueConst arguments[], + Class dispatchSuperClass) { + const NativeApiSignature& signature = prepared.signature; + if (receiver == nil || signature.variadic || + unsupportedEngineType(signature.returnType)) { + throw JSError(runtime, + "Objective-C selector is not supported by QuickJS engine: " + + prepared.selectorName); + } + + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (providedCount > expected || providedCount + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } else if (providedCount != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + // GSD fast path: the generated invoker reads args directly from the QuickJS + // arguments, calls objc_msgSend with a typed cast, and produces the JS + // return value — bypassing all generic marshalling. + if (prepared.gsdEngineCallable && dispatchSuperClass == Nil && + providedCount == prepared.gsdEngineArgumentCount && + !initializerClassWrapper && !isNSErrorOutMethod) { + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, bridge, receiver, prepared.selector, + runtime.context(), arguments, signature.returnType}; + if (invoker(ctx)) { + return ctx.result; + } + } + + if (dispatchSuperClass == Nil && !initializerClassWrapper && + providedCount <= 2) { + Value fastArgs[2]; + for (size_t i = 0; i < providedCount; i++) { + fastArgs[i] = Value::borrowed(runtime, arguments[i]); + } + Value fastResult; + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, + fastArgs, providedCount, Nil, + &fastResult)) { + return fastResult.local(runtime); + } + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + for (size_t i = 0; i < providedCount; i++) { + if (!prepareQuickJSEngineArgument(runtime, bridge, + signature.argumentTypes[i], + arguments[i], frame, i)) { + throw JSError(runtime, + "Objective-C argument is not supported by QuickJS engine: " + + prepared.selectorName); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && providedCount + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + if (initializerClassWrapper) { + id resultObject = nil; + if (isObjectiveCObjectType(returnType)) { + resultObject = *static_cast(returnStorage.data()); + } + if (receiverHostObject != nullptr && resultObject != receiver) { + receiverHostObject->disownObject(receiver); + } + if (resultObject != nil) { + bridge->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *initializerClassWrapper)); + } + } + return setQuickJSEngineReturnValue(runtime, bridge, returnType, + returnStorage.data(), + prepared.selectorName); +} + +static JSClassID gNativeApiSelectorGroupDataClassId = 0; + +void NativeApiSelectorGroupFinalize(JSRuntime*, JSValue value) { + auto* data = static_cast( + JS_GetOpaque(value, gNativeApiSelectorGroupDataClassId)); + delete data; +} + +void EnsureNativeApiSelectorGroupClass(Runtime& runtime) { + JSRuntime* jsRuntime = JS_GetRuntime(runtime.context()); + if (gNativeApiSelectorGroupDataClassId == 0) { + JS_NewClassID(jsRuntime, &gNativeApiSelectorGroupDataClassId); + } + + auto state = runtime.state(); + if (!state->selectorGroupDataClassRegistered) { + JSClassDef definition = {}; + definition.class_name = "NativeScriptEngineSelectorGroupData"; + definition.finalizer = NativeApiSelectorGroupFinalize; + JS_NewClass(jsRuntime, gNativeApiSelectorGroupDataClassId, + &definition); + state->selectorGroupDataClassRegistered = true; + } +} + +JSValue NativeApiSelectorGroupCall(JSContext* context, JSValue thisValue, + int argc, JSValue* argv, int, + JSValue* dataValues) { + auto* data = static_cast( + JS_GetOpaque(dataValues[0], gNativeApiSelectorGroupDataClassId)); + if (data == nullptr || data->selectors == nullptr || + data->preparedInvocations == nullptr) { + return JS_UNDEFINED; + } + + Runtime& runtime = data->runtime; + try { + NativeApiRoundTripCacheFrameGuard roundTripFrame(data->bridge); + size_t count = argc > 0 ? static_cast(argc) : 0; + auto call = resolveNativeApiSelectorGroupCall( + runtime, *data, count, + [&]() -> id { + auto* host = quickJSHostObjectRaw( + runtime, thisValue); + return host != nullptr ? host->object() : nil; + }, + [&]() { + return data->boundReceiverState == nullptr + ? quickJSHostObject(runtime, + thisValue) + : nullptr; + }, + [](uint64_t dispatchId) { return lookupObjCGsdInvoker(dispatchId); }); + if (call.hasImmediateResult) { + return call.immediateResult.local(runtime); + } + return setQuickJSEnginePreparedObjCResult( + runtime, data->bridge, call.receiver, *call.prepared, + call.receiverHostObject, call.initializerClassWrapper, count, argv, + call.dispatchClass); + } catch (const std::exception& error) { + return engine::quickjsengine::throwError(context, error); + } +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + EnsureNativeApiSelectorGroupClass(runtime); + auto* data = new NativeApiSelectorGroupData( + runtime.state(), std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)); + + JSValue dataObject = + JS_NewObjectClass(runtime.context(), + gNativeApiSelectorGroupDataClassId); + if (JS_IsException(dataObject)) { + delete data; + throw JSError(runtime, "QuickJS selector group allocation failed."); + } + JS_SetOpaque(dataObject, data); + + JSValue function = + JS_NewCFunctionData(runtime.context(), NativeApiSelectorGroupCall, + 0, 0, 1, &dataObject); + JS_FreeValue(runtime.context(), dataObject); + if (JS_IsException(function)) { + throw JSError(runtime, "QuickJS selector group function allocation failed."); + } + + JSValue nameValue = JS_NewStringLen(runtime.context(), "__nativeSelectorGroup", + std::strlen("__nativeSelectorGroup")); + JS_DefinePropertyValueStr(runtime.context(), function, "name", nameValue, + JS_PROP_CONFIGURABLE); + Value functionValue(runtime, function); + Function result = functionValue.asObject(runtime).asFunction(runtime); + JS_FreeValue(runtime.context(), function); + return result; +} diff --git a/NativeScript/ffi/objc/quickjs/SignatureDispatch.h b/NativeScript/ffi/objc/quickjs/SignatureDispatch.h new file mode 100644 index 000000000..2b9c0436b --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/SignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NATIVESCRIPT_FFI_QUICKJS_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_QUICKJS_SIGNATURE_DISPATCH_H + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +#if defined(__has_include) +#if __has_include("GeneratedSignatureDispatch.inc") +#include "GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_QUICKJS_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/objc/shared/EnumMemberAliases.h b/NativeScript/ffi/objc/shared/EnumMemberAliases.h new file mode 100644 index 000000000..4040744db --- /dev/null +++ b/NativeScript/ffi/objc/shared/EnumMemberAliases.h @@ -0,0 +1,85 @@ +#ifndef NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H +#define NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H + +#include +#include +#include + +namespace nativescript { +namespace enum_members { + +struct Alias { + std::string name; + // Only the C name may become a global; the other aliases are enum-object + // conveniences and can shadow unrelated globals (e.g. Center, Zone). + bool isCName = false; +}; + +inline bool startsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && + value.compare(0, prefix.size(), prefix) == 0; +} + +inline bool endsWith(const std::string& value, const std::string& suffix) { + return value.size() >= suffix.size() && + value.compare(value.size() - suffix.size(), suffix.size(), suffix) == + 0; +} + +// The first alias is always the stored member name. cName is empty when the +// metadata does not record C names; then no alias is tagged as the C name. +inline std::vector aliasesFor(const std::string& enumName, + const std::string& strippedEnumName, + const std::string& storedName, + const std::string& cName) { + std::vector names; + names.push_back(storedName); + + if (!strippedEnumName.empty() && startsWith(storedName, strippedEnumName) && + storedName.size() > strippedEnumName.size()) { + names.push_back(storedName.substr(strippedEnumName.size())); + } else if (!strippedEnumName.empty() && + !startsWith(storedName, strippedEnumName)) { + names.push_back(strippedEnumName + storedName); + } + + if (startsWith(enumName, "NS") && !startsWith(storedName, "NS")) { + names.push_back(std::string("NS") + storedName); + } + + if (enumName == "NSStringCompareOptions" && !endsWith(storedName, "Search")) { + names.push_back(storedName + "Search"); + names.push_back(std::string("NS") + storedName + "Search"); + } + + if (!startsWith(storedName, "k")) { + names.push_back(std::string("k") + enumName + storedName); + } + + if (enumName == "NSComparisonResult" && + (storedName == "Ascending" || storedName == "Same" || + storedName == "Descending")) { + names.push_back(std::string("Ordered") + storedName); + names.push_back(std::string("NSOrdered") + storedName); + } + + if (!cName.empty()) { + names.push_back(cName); + } + + std::vector aliases; + aliases.reserve(names.size()); + std::unordered_set seen; + for (auto& name : names) { + if (!name.empty() && seen.insert(name).second) { + bool isCName = name == cName; + aliases.push_back(Alias{std::move(name), isCName}); + } + } + return aliases; +} + +} // namespace enum_members +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_OBJC_SHARED_ENUM_MEMBER_ALIASES_H diff --git a/NativeScript/ffi/shared/direct/EmbeddedMetadata.mm b/NativeScript/ffi/objc/shared/MetadataState.mm similarity index 100% rename from NativeScript/ffi/shared/direct/EmbeddedMetadata.mm rename to NativeScript/ffi/objc/shared/MetadataState.mm diff --git a/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h b/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h new file mode 100644 index 000000000..bc229fd2f --- /dev/null +++ b/NativeScript/ffi/objc/shared/NativeApiBackendConfig.h @@ -0,0 +1,8 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_FORWARD_H +#define NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_FORWARD_H + +// Moved to the shared JSI layer; this forwarding header keeps existing +// Apple include paths working unchanged. +#include "jsi/shared/NativeApiBackendConfig.h" + +#endif // NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_FORWARD_H diff --git a/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h new file mode 100644 index 000000000..4a2577ab7 --- /dev/null +++ b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h @@ -0,0 +1,14 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H + +// Moved to jsi/shared/PreparedSignatureDispatch.h so the Android runtime can +// use it. This forwarding header keeps the Apple include paths unchanged. +// +// SignatureDispatchCore.h is included first because callers of this header +// have always got it transitively, and it is what computes the dispatch ids +// from the Objective-C metadata. + +#include "ffi/objc/shared/SignatureDispatchCore.h" +#include "jsi/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/objc/shared/SignatureDispatchCore.h b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h new file mode 100644 index 000000000..c53fd26dc --- /dev/null +++ b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h @@ -0,0 +1,220 @@ +#ifndef NS_FFI_SHARED_SIGNATURE_DISPATCH_CORE_H +#define NS_FFI_SHARED_SIGNATURE_DISPATCH_CORE_H + +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" + +// The platform-neutral half of GSD (FNV-1a hashing, call kinds, dispatch-entry +// types and table lookup) lives in the shared JSI layer so the Android runtime +// can use the same mechanism. Everything below is Objective-C metadata specific +// and stays here. +#include "jsi/shared/SignatureHashing.h" + +namespace nativescript { + +inline constexpr metagen::MDSectionOffset kNullMetadataSectionOffset = + static_cast(0xFFFFFFFFu >> 1); + +namespace signature_dispatch_detail { + +inline metagen::MDTypeKind canonicalizeSignatureTypeKind( + metagen::MDTypeKind kind) { + switch (kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return metagen::mdTypeAnyObject; + default: + return kind; + } +} + +template +inline void appendIntegralToHash(uint64_t* hash, T value) { + using Unsigned = typename std::make_unsigned::type; + Unsigned unsignedValue = static_cast(value); + for (size_t i = 0; i < sizeof(Unsigned); i++) { + const uint8_t byte = + static_cast((unsignedValue >> (i * 8)) & 0xFF); + *hash = hashBytesFnv1a(&byte, sizeof(byte), *hash); + } +} + +inline metagen::MDTypeKind stripMetadataTypeFlags(metagen::MDTypeKind kind) { + uint8_t raw = static_cast(kind); + raw &= ~(metagen::mdTypeFlagNext | metagen::mdTypeFlagVariadic); + return static_cast(raw); +} + +inline bool appendMetadataSignatureHash( + metagen::MDMetadataReader* reader, metagen::MDSectionOffset signatureOffset, + std::unordered_set* activeSignatures, + uint64_t* hash); + +inline bool appendMetadataTypeHash( + metagen::MDMetadataReader* reader, metagen::MDSectionOffset* offset, + std::unordered_set* activeSignatures, + uint64_t* hash) { + if (reader == nullptr || offset == nullptr || hash == nullptr || + activeSignatures == nullptr) { + return false; + } + + const metagen::MDTypeKind kindWithFlags = reader->getTypeKind(*offset); + *offset += sizeof(metagen::MDTypeKind); + const metagen::MDTypeKind rawKind = stripMetadataTypeFlags(kindWithFlags); + + appendIntegralToHash(hash, 0xB0); + appendIntegralToHash( + hash, static_cast(canonicalizeSignatureTypeKind(rawKind))); + + switch (rawKind) { + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: { + const auto arraySize = reader->getArraySize(*offset); + *offset += sizeof(uint16_t); + appendIntegralToHash(hash, arraySize); + if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { + return false; + } + break; + } + + case metagen::mdTypeStruct: { + const auto structOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + appendIntegralToHash(hash, structOffset); + break; + } + + case metagen::mdTypeClassObject: { + auto classOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + bool hasNext = (classOffset & metagen::mdSectionOffsetNext) != 0; + while (hasNext) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + hasNext = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + + case metagen::mdTypeProtocolObject: { + bool hasNext = true; + while (hasNext) { + auto protocolOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + hasNext = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + + case metagen::mdTypePointer: + if (!appendMetadataTypeHash(reader, offset, activeSignatures, hash)) { + return false; + } + break; + + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: { + const auto nestedSignatureOffset = reader->getOffset(*offset); + *offset += sizeof(metagen::MDSectionOffset); + if (nestedSignatureOffset != kNullMetadataSectionOffset) { + const auto nestedAbsoluteOffset = + reader->signaturesOffset + nestedSignatureOffset; + if (!appendMetadataSignatureHash(reader, nestedAbsoluteOffset, + activeSignatures, hash)) { + return false; + } + } + break; + } + + default: + break; + } + + appendIntegralToHash(hash, 0xBF); + return true; +} + +inline bool appendMetadataSignatureHash( + metagen::MDMetadataReader* reader, metagen::MDSectionOffset signatureOffset, + std::unordered_set* activeSignatures, + uint64_t* hash) { + if (reader == nullptr || hash == nullptr || activeSignatures == nullptr) { + return false; + } + + if (activeSignatures->find(signatureOffset) != activeSignatures->end()) { + appendIntegralToHash(hash, 0xEE); + return true; + } + activeSignatures->insert(signatureOffset); + + metagen::MDSectionOffset offset = signatureOffset; + const metagen::MDTypeKind returnTypeKind = reader->getTypeKind(offset); + bool next = + (static_cast(returnTypeKind) & metagen::mdTypeFlagNext) != 0; + const bool isVariadic = + (static_cast(returnTypeKind) & metagen::mdTypeFlagVariadic) != 0; + + appendIntegralToHash(hash, 0xA0); + appendIntegralToHash(hash, isVariadic ? 1 : 0); + + if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { + activeSignatures->erase(signatureOffset); + return false; + } + + uint32_t argCount = 0; + while (next) { + const metagen::MDTypeKind argTypeKind = reader->getTypeKind(offset); + next = + (static_cast(argTypeKind) & metagen::mdTypeFlagNext) != 0; + if (!appendMetadataTypeHash(reader, &offset, activeSignatures, hash)) { + activeSignatures->erase(signatureOffset); + return false; + } + argCount++; + } + + appendIntegralToHash(hash, argCount); + appendIntegralToHash(hash, 0xAF); + + activeSignatures->erase(signatureOffset); + return true; +} + +} // namespace signature_dispatch_detail + +inline uint64_t metadataSignatureHash( + metagen::MDMetadataReader* reader, + metagen::MDSectionOffset signatureOffset) { + if (reader == nullptr || signatureOffset == kNullMetadataSectionOffset) { + return 0; + } + + uint64_t hash = kSignatureHashOffsetBasis; + std::unordered_set activeSignatures; + if (!signature_dispatch_detail::appendMetadataSignatureHash( + reader, signatureOffset, &activeSignatures, &hash)) { + return 0; + } + return hash; +} + +} // namespace nativescript + +#endif // NS_FFI_SHARED_SIGNATURE_DISPATCH_CORE_H diff --git a/NativeScript/ffi/objc/shared/Tasks.h b/NativeScript/ffi/objc/shared/Tasks.h new file mode 100644 index 000000000..8b9c8a2d3 --- /dev/null +++ b/NativeScript/ffi/objc/shared/Tasks.h @@ -0,0 +1,8 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_TASKS_FORWARD_H +#define NATIVESCRIPT_FFI_SHARED_TASKS_FORWARD_H + +// Moved to the shared JSI layer; this forwarding header keeps existing +// Apple include paths working unchanged. +#include "jsi/shared/Tasks.h" + +#endif // NATIVESCRIPT_FFI_SHARED_TASKS_FORWARD_H diff --git a/NativeScript/ffi/objc/shared/bridge/Callbacks.mm b/NativeScript/ffi/objc/shared/bridge/Callbacks.mm new file mode 100644 index 000000000..0188e6e78 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Callbacks.mm @@ -0,0 +1,2263 @@ +bool isObjectiveCObjectType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +#ifndef NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::shared_ptr(&runtime, [](Runtime*) {}); +} +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE +class NativeApiRuntimeScope final { + public: + explicit NativeApiRuntimeScope(Runtime&) {} +}; +#endif + +struct NativeApiSignature { + ffi_cif cif = {}; + NativeApiType returnType; + std::vector argumentTypes; + std::vector ffiTypes; + std::string selectorName; + uint64_t signatureHash = 0; + uint8_t dispatchFlags = 0; + bool variadic = false; + bool prepared = false; + unsigned int implicitArgumentCount = 0; +}; + +enum class NativeApiCallbackThreadPolicy { + Default, + JS, + Runtime, +}; + +NativeApiCallbackThreadPolicy readEngineCallbackThreadPolicy( + Runtime& runtime, Object& functionObject) { + constexpr const char* propertyName = "__nativeScriptCallbackThread"; + try { + if (!functionObject.hasProperty(runtime, propertyName)) { + return NativeApiCallbackThreadPolicy::Default; + } + Value policyValue = functionObject.getProperty(runtime, propertyName); + if (!policyValue.isString()) { + return NativeApiCallbackThreadPolicy::Default; + } + std::string policy = policyValue.asString(runtime).utf8(runtime); + if (policy == "js") { + return NativeApiCallbackThreadPolicy::JS; + } + if (policy == "runtime" || policy == "worklet") { + return NativeApiCallbackThreadPolicy::Runtime; + } + } catch (const std::exception&) { + } + return NativeApiCallbackThreadPolicy::Default; +} + +bool selectorEndsWithNSErrorParam(const std::string& selectorName) { + constexpr const char* suffix = "error:"; + size_t suffixLength = std::strlen(suffix); + return selectorName.size() >= suffixLength && + selectorName.compare(selectorName.size() - suffixLength, suffixLength, + suffix) == 0; +} + +bool isNSErrorOutEngineMethodSignature(const NativeApiSignature& signature) { + if (signature.argumentTypes.empty() || signature.variadic || + !selectorEndsWithNSErrorParam(signature.selectorName)) { + return false; + } + + return signature.argumentTypes.back().kind == metagen::mdTypePointer; +} + +bool isNSErrorOutEngineMethodCallback(const NativeApiSignature& signature) { + return signature.returnType.kind == metagen::mdTypeBool && + signature.implicitArgumentCount >= 2 && + isNSErrorOutEngineMethodSignature(signature); +} + +class NativeApiArgumentFrame { + public: + explicit NativeApiArgumentFrame(size_t count) : count_(count) { + if (count_ > kInlineArgumentCount) { + heapStorage_.resize(count_); + heapValues_.resize(count_); + } + } + + ~NativeApiArgumentFrame() { + for (char* string : ownedCStrings_) { + free(string); + } + for (void* buffer : ownedBuffers_) { + free(buffer); + } + for (id object : ownedObjects_) { + [object release]; + } + for (const auto& entry : temporaryRoundTripValues_) { + if (entry.bridge != nullptr && entry.runtime != nullptr) { + entry.bridge->forgetRoundTripValue(*entry.runtime, entry.native); + } + } + ownedLifetimes_.clear(); + } + + void* storageAt(size_t index, size_t size) { + if (index >= count_) { + throw std::out_of_range("Native argument index out of range."); + } + + size = std::max(size, sizeof(void*)); + if (count_ <= kInlineArgumentCount && size <= kInlineStorageSize) { + std::memset(inlineStorage_[index], 0, kInlineStorageSize); + inlineValues_[index] = inlineStorage_[index]; + return inlineValues_[index]; + } + + if (count_ <= kInlineArgumentCount) { + overflowStorage_.emplace_back(size, 0); + inlineValues_[index] = overflowStorage_.back().data(); + return inlineValues_[index]; + } + + heapStorage_[index].assign(size, 0); + heapValues_[index] = heapStorage_[index].data(); + return heapValues_[index]; + } + + void addCString(char* value) { ownedCStrings_.push_back(value); } + void* addBuffer(size_t size) { + void* buffer = calloc(1, std::max(size, 1)); + if (buffer == nullptr) { + throw std::bad_alloc(); + } + ownedBuffers_.push_back(buffer); + return buffer; + } + void addObject(id value) { ownedObjects_.push_back(value); } + void retainObject(id value) { + if (value != nil) { + [value retain]; + ownedObjects_.push_back(value); + } + } + void addLifetime(std::shared_ptr value) { + if (value != nullptr) { + ownedLifetimes_.push_back(std::move(value)); + } + } + void rememberRoundTripValue( + const std::shared_ptr& bridge, Runtime& runtime, + const void* native, const Value& value) { + if (bridge == nullptr || native == nullptr) { + return; + } + bridge->rememberRoundTripValue(runtime, native, value); + temporaryRoundTripValues_.push_back({bridge, &runtime, native}); + } + void** values() { + if (count_ == 0) { + return nullptr; + } + return count_ <= kInlineArgumentCount ? inlineValues_ : heapValues_.data(); + } + + private: + static constexpr size_t kInlineArgumentCount = 8; + static constexpr size_t kInlineStorageSize = 32; + + size_t count_ = 0; + alignas(void*) unsigned char + inlineStorage_[kInlineArgumentCount][kInlineStorageSize] = {}; + void* inlineValues_[kInlineArgumentCount] = {}; + std::vector> heapStorage_; + std::vector heapValues_; + std::vector> overflowStorage_; + std::vector ownedCStrings_; + std::vector ownedBuffers_; + std::vector ownedObjects_; + std::vector> ownedLifetimes_; + struct TemporaryRoundTripValue { + std::shared_ptr bridge; + Runtime* runtime = nullptr; + const void* native = nullptr; + }; + std::vector temporaryRoundTripValues_; +}; + +class NativeApiMutableBuffer final : public MutableBuffer { + public: + explicit NativeApiMutableBuffer(size_t size) : data_(size) {} + NativeApiMutableBuffer(const void* data, size_t size) : data_(size) { + if (data != nullptr && size > 0) { + std::memcpy(data_.data(), data, size); + } + } + + size_t size() const override { return data_.size(); } + uint8_t* data() override { return data_.empty() ? nullptr : data_.data(); } + + private: + std::vector data_; +}; + +void convertEngineArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, + const Value& value, void* target, + NativeApiArgumentFrame& frame); + +Value convertNativeReturnValue(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* value); + +Value wrapNativeFunctionPointer(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* pointer, + bool block); + +bool isObjectiveCObjectType(const NativeApiType& type); + +struct NativeApiBlockDescriptor { + unsigned long reserved = 0; + unsigned long size = 0; + void (*copyHelper)(void*, void*) = nullptr; + void (*disposeHelper)(void*) = nullptr; + const char* signature = nullptr; +}; + +struct NativeApiBlockLiteral { + void* isa = nullptr; + int flags = 0; + int reserved = 0; + void* invoke = nullptr; + NativeApiBlockDescriptor* descriptor = nullptr; + void* callback = nullptr; +}; + +constexpr int kNativeApiBlockNeedsFree = (1 << 24); +constexpr int kNativeApiBlockHasCopyDispose = (1 << 25); +constexpr int kNativeApiBlockRefCountOne = (1 << 1); +constexpr int kNativeApiBlockHasSignature = (1 << 30); + +void* nativeApiEngineMallocBlockIsa() { + static void* isa = dlsym(RTLD_DEFAULT, "_NSConcreteMallocBlock"); + return isa; +} + +void nativeApiEngineBlockCopy(void* dst, void* src); +void nativeApiEngineBlockDispose(void* src); + +std::string objcEncodingForEngineType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeVoid: + return "v"; + case metagen::mdTypeBool: + return "B"; + case metagen::mdTypeChar: + return "c"; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return "C"; + case metagen::mdTypeSShort: + return "s"; + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + return "S"; + case metagen::mdTypeSInt: + return "i"; + case metagen::mdTypeUInt: + return "I"; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return "q"; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return "Q"; + case metagen::mdTypeFloat: + return "f"; + case metagen::mdTypeDouble: + return "d"; + case metagen::mdTypeString: + return "*"; + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return "@"; + case metagen::mdTypeClass: + return "#"; + case metagen::mdTypeSelector: + return ":"; + case metagen::mdTypeBlock: + return "@?"; + case metagen::mdTypeFunctionPointer: + return "^?"; + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: + if (type.elementType != nullptr && + type.elementType->kind != metagen::mdTypeVoid) { + return "^" + objcEncodingForEngineType(*type.elementType); + } + return "^v"; + case metagen::mdTypeStruct: + return "{" + + (type.aggregateInfo != nullptr ? type.aggregateInfo->name + : std::string("?")) + + "=}"; + case metagen::mdTypeArray: + return "[" + std::to_string(type.arraySize) + + (type.elementType != nullptr ? objcEncodingForEngineType(*type.elementType) + : std::string("?")) + + "]"; + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + return type.elementType != nullptr ? objcEncodingForEngineType(*type.elementType) + : "?"; + default: + return "?"; + } +} + +std::string objcBlockSignatureForEngineSignature( + const NativeApiSignature& signature) { + std::string encoding = objcEncodingForEngineType(signature.returnType); + encoding += "@?"; + for (const auto& argType : signature.argumentTypes) { + encoding += objcEncodingForEngineType(argType); + } + return encoding; +} + +std::string objcMethodSignatureForEngineSignature( + const NativeApiSignature& signature) { + std::string encoding = objcEncodingForEngineType(signature.returnType); + encoding += "@:"; + for (const auto& argType : signature.argumentTypes) { + encoding += objcEncodingForEngineType(argType); + } + return encoding; +} + +[[noreturn]] void throwNativeApiCallbackException( + const std::string& message) { + NSString* reason = [NSString stringWithUTF8String:message.c_str()]; + @throw [NSException exceptionWithName:@"NativeScriptEngineCallbackException" + reason:reason + userInfo:nil]; +} + +class NativeApiCallback; + +void nativeApiEngineCallbackTrampoline(ffi_cif* cif, void* ret, void* args[], + void* data); + +std::atomic gActiveNativeThreadEngineCallbacks{0}; + +// A callback can outlive the scope in which its function argument was created +// (e.g. a block invoked asynchronously). Round-trip the function through the +// engine value copy constructor so any scope-bound/borrowed handle is promoted +// to a persistent one before it is stored. +Function persistentEngineFunction(Runtime& runtime, const Function& function) { + Value shared(runtime, function); + Value persistent(runtime, shared); + return persistent.asObject(runtime).asFunction(runtime); +} + +class NativeApiCallback final + : public std::enable_shared_from_this { + public: + NativeApiCallback(Runtime& runtime, + std::shared_ptr bridge, + std::shared_ptr signature, + Function function, bool block, + NativeApiCallbackThreadPolicy threadPolicy = + NativeApiCallbackThreadPolicy::Default, + bool bindThis = false, + uintptr_t roundTripValidationKey = 0) + : runtimeOwner_(retainNativeApiRuntime(runtime)), + runtime_(runtimeOwner_.get()), + bridge_(std::move(bridge)), + signature_(std::move(signature)), + function_(std::make_shared( + persistentEngineFunction(runtime, function))), + block_(block), + threadPolicy_(threadPolicy), + bindThis_(bindThis), + roundTripValidationKey_(roundTripValidationKey) { + closure_ = static_cast( + ffi_closure_alloc(sizeof(ffi_closure), &executable_)); + if (closure_ == nullptr || executable_ == nullptr || + signature_ == nullptr || !signature_->prepared) { + throw JSError(runtime, + "Unable to allocate native callback."); + } + + ffi_status status = ffi_prep_closure_loc( + closure_, &signature_->cif, nativeApiEngineCallbackTrampoline, this, + executable_); + if (status != FFI_OK) { + ffi_closure_free(closure_); + closure_ = nullptr; + executable_ = nullptr; + throw JSError(runtime, + "Unable to prepare native callback."); + } + + if (block_) { + blockSignature_ = objcBlockSignatureForEngineSignature(*signature_); + descriptor_ = std::make_unique(); + descriptor_->reserved = 0; + descriptor_->size = sizeof(NativeApiBlockLiteral); + descriptor_->copyHelper = nativeApiEngineBlockCopy; + descriptor_->disposeHelper = nativeApiEngineBlockDispose; + descriptor_->signature = blockSignature_.c_str(); + + blockLiteral_ = static_cast( + calloc(1, sizeof(NativeApiBlockLiteral))); + if (blockLiteral_ == nullptr) { + throw JSError(runtime, "Unable to allocate native block callback."); + } + void* blockIsa = nativeApiEngineMallocBlockIsa(); + if (blockIsa == nullptr) { + free(blockLiteral_); + blockLiteral_ = nullptr; + throw JSError(runtime, + "Objective-C malloc block runtime is unavailable."); + } + blockLiteral_->isa = blockIsa; + blockLiteral_->flags = kNativeApiBlockNeedsFree | + kNativeApiBlockHasCopyDispose | + kNativeApiBlockRefCountOne | + kNativeApiBlockHasSignature; + blockLiteral_->invoke = executable_; + blockLiteral_->descriptor = descriptor_.get(); + blockLiteral_->callback = this; + } + } + + ~NativeApiCallback() { + if (closure_ != nullptr) { + ffi_closure_free(closure_); + closure_ = nullptr; + executable_ = nullptr; + } + } + + void* functionPointer() const { + return block_ && blockLiteral_ != nullptr + ? static_cast(blockLiteral_) + : executable_; + } + + const NativeApiSignature& signature() const { return *signature_; } + + void retainInitialBlockLifetime( + std::shared_ptr lifetime) { + if (block_) { + initialBlockLifetime_ = std::move(lifetime); + } + } + + void retainBlockCopy(const void* blockPointer) { + if (!block_) { + return; + } + auto self = shared_from_this(); + if (bridge_ != nullptr && runtime_ != nullptr && function_ != nullptr && + blockPointer != nullptr) { + bridge_->rememberRoundTripValue(*runtime_, blockPointer, + Value(*runtime_, *function_), false, + roundTripValidationKey_); + } + std::lock_guard lock(retainedBlockCopiesMutex_); + retainedBlockCopies_.push_back({blockPointer, std::move(self)}); + } + + bool releaseBlockCopy(const void* blockPointer) { + if (!block_) { + return false; + } + + bool canRelease = false; + { + std::lock_guard lock(retainedBlockCopiesMutex_); + auto it = retainedBlockCopies_.end(); + if (blockPointer != nullptr) { + it = std::find_if( + retainedBlockCopies_.begin(), retainedBlockCopies_.end(), + [blockPointer](const RetainedBlockCopy& retained) { + return retained.blockPointer == blockPointer; + }); + } + canRelease = + it != retainedBlockCopies_.end() || blockPointer == blockLiteral_; + } + // Forgetting the round-trip value touches the JS engine global/context. + // Block disposal can run during an autorelease-pool drain on an arbitrary + // thread (e.g. an NSOperationQueue worker). Keep the retained block entry + // in place until the JS-thread task runs so the callback and its engine + // function are also destroyed on the JS thread. + if (!canRelease) { + return false; + } + + auto bridge = bridge_; + auto* runtime = runtime_; + auto runtimeOwner = runtimeOwner_; + auto releaseOnJS = [this, bridge, runtime, runtimeOwner, blockPointer]() { + std::shared_ptr keepAlive; + try { + keepAlive = shared_from_this(); + } catch (const std::bad_weak_ptr&) { + return; + } + + const void* pointerToForget = nullptr; + { + std::lock_guard lock(retainedBlockCopiesMutex_); + auto it = retainedBlockCopies_.end(); + if (blockPointer != nullptr) { + it = std::find_if( + retainedBlockCopies_.begin(), retainedBlockCopies_.end(), + [blockPointer](const RetainedBlockCopy& retained) { + return retained.blockPointer == blockPointer; + }); + } + if (it != retainedBlockCopies_.end()) { + pointerToForget = it->blockPointer; + retainedBlockCopies_.erase(it); + } else if (blockPointer == blockLiteral_) { + pointerToForget = blockPointer; + blockLiteral_ = nullptr; + initialBlockLifetime_.reset(); + } + } + + if (bridge != nullptr && runtime != nullptr && + pointerToForget != nullptr) { + NativeApiRuntimeScope runtimeScope(*runtime); + bridge->forgetRoundTripValue(*runtime, pointerToForget); + } + }; + + if (bridge == nullptr) { + releaseOnJS(); + } else if (const auto& asyncInvoker = + bridge->jsThreadAsyncCallbackInvoker()) { + asyncInvoker(std::move(releaseOnJS)); + } else if (auto scheduler = bridge->scheduler()) { + scheduler->invokeOnJS(std::move(releaseOnJS)); + } else if (std::this_thread::get_id() == bridge->jsThreadId()) { + releaseOnJS(); + } else if (const auto& invoker = bridge->jsThreadCallbackInvoker()) { + invoker(std::move(releaseOnJS)); + } else { + releaseOnJS(); + } + return true; + } + + void invoke(void* ret, void* args[]) { + if (runtime_ == nullptr || function_ == nullptr || signature_ == nullptr) { + throwNativeApiCallbackException("Invalid callback."); + } + + std::string error; + auto call = [&]() { invokeOnCurrentThread(ret, args, &error); }; + const auto& nativeCallbackInvoker = bridge_->nativeCallbackInvoker(); + const auto& runtimeCallbackInvoker = bridge_->runtimeCallbackInvoker(); + const auto& jsThreadCallbackInvoker = bridge_->jsThreadCallbackInvoker(); + bool currentThreadIsJs = + std::this_thread::get_id() == bridge_->jsThreadId(); + + auto callOnNativeCallerThread = [&]() { + ScopedNativeCallerThreadEngineCallback callbackScope; + if (nativeCallbackInvoker) { + nativeCallbackInvoker(call); + } else { + call(); + } + }; + auto callOnJSThread = [&]() { + if (currentThreadIsJs) { + call(); + return; + } + if (jsThreadCallbackInvoker) { + jsThreadCallbackInvoker(call); + return; + } + if (auto scheduler = bridge_->scheduler()) { + dispatch_semaphore_t done = dispatch_semaphore_create(0); + scheduler->invokeOnJS([call, done]() mutable { + call(); + dispatch_semaphore_signal(done); + }); + dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); + return; + } + error = "Native callback was invoked off the JS thread without a JS scheduler."; + }; + auto callOnRuntimeThread = [&]() { + if (currentThreadIsJs) { + call(); + return; + } + if (runtimeCallbackInvoker) { + runtimeCallbackInvoker(call); + return; + } + error = "Native callback was invoked off its owning runtime thread without a runtime scheduler."; + }; + + if (threadPolicy_ == NativeApiCallbackThreadPolicy::JS) { + callOnJSThread(); + if (!error.empty()) { + if (!recordNativeCallbackException(error)) { + throwNativeApiCallbackException(error); + } + } + return; + } + if (threadPolicy_ == NativeApiCallbackThreadPolicy::Runtime) { + callOnRuntimeThread(); + if (!error.empty()) { + if (!recordNativeCallbackException(error)) { + throwNativeApiCallbackException(error); + } + } + return; + } + + bool returnsVoid = signature_->returnType.kind == metagen::mdTypeVoid; + bool nativeCallerThreadCallbacks = + bridge_->invokeCallbacksOnNativeCallerThread(); + bool direct = currentThreadIsJs || + gSynchronousNativeInvocationDepth > 0; + bool waitForNativeThreadCallback = + currentThreadIsJs && nativeCallbackInvoker && + gActiveNativeThreadEngineCallbacks.load(std::memory_order_acquire) > 0; + auto dispatchZeroArgVoidBlockAsync = [&]() -> bool { + if (currentThreadIsJs || !returnsVoid || !block_ || + !signature_->argumentTypes.empty()) { + return false; + } + + std::shared_ptr keepAlive; + try { + keepAlive = shared_from_this(); + } catch (const std::bad_weak_ptr&) { + return false; + } + + auto asyncCall = [keepAlive = std::move(keepAlive)]() mutable { + std::string asyncError; + keepAlive->invokeOnCurrentThread(nullptr, nullptr, &asyncError); + if (!asyncError.empty()) { + recordNativeCallbackException(asyncError); + } + }; + + const auto& asyncInvoker = bridge_->jsThreadAsyncCallbackInvoker(); + if (asyncInvoker) { + asyncInvoker(std::move(asyncCall)); + return true; + } + if (auto scheduler = bridge_->scheduler()) { + scheduler->invokeOnJS(std::move(asyncCall)); + return true; + } + return false; + }; + + if (nativeCallerThreadCallbacks && !currentThreadIsJs) { + callOnNativeCallerThread(); + } else if (dispatchZeroArgVoidBlockAsync()) { + return; + } else if (direct && !waitForNativeThreadCallback) { + call(); + } else if (!currentThreadIsJs) { + callOnJSThread(); + } else if (nativeCallbackInvoker) { + bool nativeThreadCallback = !currentThreadIsJs; + if (nativeThreadCallback) { + gActiveNativeThreadEngineCallbacks.fetch_add(1, + std::memory_order_acq_rel); + } + try { + nativeCallbackInvoker(call); + } catch (...) { + if (nativeThreadCallback) { + gActiveNativeThreadEngineCallbacks.fetch_sub( + 1, std::memory_order_acq_rel); + } + throw; + } + if (nativeThreadCallback) { + gActiveNativeThreadEngineCallbacks.fetch_sub(1, + std::memory_order_acq_rel); + } + } else if (auto scheduler = bridge_->scheduler()) { + dispatch_semaphore_t done = dispatch_semaphore_create(0); + scheduler->invokeOnJS([call, done]() mutable { + call(); + dispatch_semaphore_signal(done); + }); + dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); + } else { + error = "Native callback was invoked off the JS thread without a JS scheduler."; + } + + if (!error.empty()) { + if (!recordNativeCallbackException(error)) { + throwNativeApiCallbackException(error); + } + } + } + + private: + void invokeOnCurrentThread(void* ret, void* args[], std::string* error) { + try { + NativeApiRuntimeScope runtimeScope(*runtime_); + size_t nativeArgOffset = signature_->implicitArgumentCount; + std::vector jsArgs; + jsArgs.reserve(signature_->argumentTypes.size()); + for (size_t i = 0; i < signature_->argumentTypes.size(); i++) { + jsArgs.emplace_back(convertNativeReturnValue( + *runtime_, bridge_, signature_->argumentTypes[i], + args[i + nativeArgOffset])); + } + + Value result = Value::undefined(); + if (bindThis_ && nativeArgOffset >= 1) { + id self = *static_cast(args[0]); + Value thisValue = + makeNativeObjectValue(*runtime_, bridge_, self, false); + Object thisObject = thisValue.isObject() + ? thisValue.asObject(*runtime_) + : Object(*runtime_); + result = + jsArgs.empty() + ? function_->callWithThis(*runtime_, thisObject) + : function_->callWithThis( + *runtime_, thisObject, + static_cast(jsArgs.data()), + static_cast(jsArgs.size())); + } else { + result = + jsArgs.empty() + ? function_->call(*runtime_) + : function_->call(*runtime_, + static_cast(jsArgs.data()), + static_cast(jsArgs.size())); + } + storeReturnValue(result, ret); + if (std::this_thread::get_id() == bridge_->jsThreadId() && + gSynchronousNativeInvocationDepth == 0) { + runtime_->drainMicrotasks(); + } + } catch (const std::exception& exception) { + if (isNSErrorOutEngineMethodCallback(*signature_)) { + zeroReturnValue(ret); + populateNSErrorOutArgument(args, exception.what()); + return; + } + if (error != nullptr) { + *error = exception.what(); + } + zeroReturnValue(ret); + } catch (...) { + if (isNSErrorOutEngineMethodCallback(*signature_)) { + zeroReturnValue(ret); + populateNSErrorOutArgument(args, "Unknown exception in native callback."); + return; + } + if (error != nullptr) { + *error = "Unknown exception in native callback."; + } + zeroReturnValue(ret); + } + } + + void populateNSErrorOutArgument(void* args[], const char* message) { + if (args == nullptr || signature_ == nullptr || + signature_->argumentTypes.empty()) { + return; + } + + size_t outArgIndex = signature_->implicitArgumentCount + + signature_->argumentTypes.size() - 1; + void* outArgValue = args[outArgIndex]; + NSError** outError = + outArgValue != nullptr ? *reinterpret_cast(outArgValue) + : nullptr; + if (outError == nullptr) { + return; + } + + NSString* nsMessage = + message != nullptr ? [NSString stringWithUTF8String:message] : nil; + if (nsMessage == nil) { + nsMessage = @"JS error"; + } + NSDictionary* userInfo = @{NSLocalizedDescriptionKey : nsMessage}; + *outError = [NSError errorWithDomain:@"TNSErrorDomain" + code:1 + userInfo:userInfo]; + } + + void zeroReturnValue(void* ret) { + if (ret == nullptr || signature_ == nullptr || + signature_->returnType.kind == metagen::mdTypeVoid) { + return; + } + size_t size = nativeSizeForType(signature_->returnType); + if (size > 0) { + std::memset(ret, 0, size); + } + } + + void storeReturnValue(const Value& result, void* ret) { + if (ret == nullptr || + signature_->returnType.kind == metagen::mdTypeVoid) { + return; + } + + zeroReturnValue(ret); + if (result.isUndefined() || result.isNull()) { + return; + } + const auto& returnType = signature_->returnType; + if (returnType.kind == metagen::mdTypeString && result.isString()) { + std::string utf8 = result.asString(*runtime_).utf8(*runtime_); + *static_cast(ret) = strdup(utf8.c_str()); + return; + } + if ((returnType.kind == metagen::mdTypePointer || + returnType.kind == metagen::mdTypeOpaquePointer) && + result.isString()) { + std::string utf8 = result.asString(*runtime_).utf8(*runtime_); + *static_cast(ret) = strdup(utf8.c_str()); + return; + } + + NativeApiArgumentFrame frame(1); + convertEngineArgument(*runtime_, bridge_, returnType, result, ret, frame); + if (isObjectiveCObjectType(returnType)) { + id object = *static_cast(ret); + if (object != nil) { + [object retain]; + [object autorelease]; + } + } + } + + std::shared_ptr runtimeOwner_; + Runtime* runtime_ = nullptr; + std::shared_ptr bridge_; + std::shared_ptr signature_; + std::shared_ptr function_; + bool block_ = false; + NativeApiCallbackThreadPolicy threadPolicy_ = + NativeApiCallbackThreadPolicy::Default; + bool bindThis_ = false; + uintptr_t roundTripValidationKey_ = 0; + ffi_closure* closure_ = nullptr; + void* executable_ = nullptr; + std::string blockSignature_; + std::unique_ptr descriptor_; + NativeApiBlockLiteral* blockLiteral_ = nullptr; + std::shared_ptr initialBlockLifetime_; + struct RetainedBlockCopy { + const void* blockPointer = nullptr; + std::shared_ptr lifetime; + }; + std::mutex retainedBlockCopiesMutex_; + std::vector retainedBlockCopies_; +}; + +void nativeApiEngineBlockCopy(void* dst, void* src) { + auto* dstBlock = static_cast(dst); + auto* srcBlock = static_cast(src); + if (dstBlock == nullptr || srcBlock == nullptr || + srcBlock->callback == nullptr) { + return; + } + dstBlock->callback = srcBlock->callback; + static_cast(srcBlock->callback) + ->retainBlockCopy(dstBlock); +} + +void nativeApiEngineBlockDispose(void* src) { + auto* block = static_cast(src); + if (block == nullptr || block->callback == nullptr) { + return; + } + bool released = + static_cast(block->callback)->releaseBlockCopy(block); + if (released) { + block->callback = nullptr; + } +} + +void nativeApiEngineCallbackTrampoline(ffi_cif*, void* ret, void* args[], + void* data) { + auto callback = static_cast(data); + if (callback == nullptr) { + return; + } + @try { + callback->invoke(ret, args); + } @catch (NSException* exception) { + const char* description = + exception.description != nil ? exception.description.UTF8String : nullptr; + std::string message = description != nullptr + ? description + : "Objective-C exception in native callback."; + if (!recordNativeCallbackException(message)) { + @throw; + } + } +} + +size_t nativeSizeForType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeStruct: + if (type.aggregateInfo != nullptr) { + return type.aggregateInfo->size; + } + break; + case metagen::mdTypeArray: + if (type.elementType != nullptr) { + return nativeSizeForType(*type.elementType) * + static_cast(type.arraySize); + } + break; + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + if (type.elementType != nullptr) { + size_t lanes = std::max(type.arraySize, 1); + size_t abiLanes = lanes == 3 ? 4 : lanes; + return nativeSizeForType(*type.elementType) * abiLanes; + } + break; + default: + break; + } + + if (type.ffiType != nullptr && type.ffiType->size > 0) { + return type.ffiType->size; + } + if (type.ffiType == &ffi_type_void) { + return 0; + } + return sizeof(void*); +} + +Value signedInteger64ToEngineValue(Runtime& runtime, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return static_cast(value); + } + return BigInt::fromInt64(runtime, value); +} + +Value unsignedInteger64ToEngineValue(Runtime& runtime, uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return static_cast(value); + } + return BigInt::fromUint64(runtime, value); +} + +bool parseIntegerTextToUintptr(const std::string& text, uintptr_t* address) { + if (address == nullptr) { + return false; + } + if (text.empty()) { + return false; + } + + char* end = nullptr; + if (text[0] == '-') { + long long signedValue = std::strtoll(text.c_str(), &end, 10); + if (end == nullptr || *end != '\0') { + return false; + } + *address = static_cast(static_cast(signedValue)); + return true; + } + + int base = 10; + const char* start = text.c_str(); + if (text.size() > 2 && text[0] == '0' && + (text[1] == 'x' || text[1] == 'X')) { + base = 16; + } + unsigned long long unsignedValue = std::strtoull(start, &end, base); + if (end == nullptr || *end != '\0') { + return false; + } + *address = static_cast(unsignedValue); + return true; +} + +bool parseBigIntToUintptr(Runtime& runtime, const BigInt& bigint, + uintptr_t* address) { + return parseIntegerTextToUintptr(bigint.toString(runtime, 10).utf8(runtime), + address); +} + +bool readEngineBuffer(Runtime& runtime, const Object& object, const uint8_t** data, + size_t* byteLength) { + if (data == nullptr || byteLength == nullptr) { + return false; + } + + if (object.isArrayBuffer(runtime)) { + ArrayBuffer buffer = object.getArrayBuffer(runtime); + *data = buffer.data(runtime); + *byteLength = buffer.size(runtime); + return true; + } + + Value bufferValue = object.getProperty(runtime, "buffer"); + if (!bufferValue.isObject()) { + return false; + } + Object bufferObject = bufferValue.asObject(runtime); + if (!bufferObject.isArrayBuffer(runtime)) { + return false; + } + + size_t byteOffset = 0; + size_t viewByteLength = 0; + Value offsetValue = object.getProperty(runtime, "byteOffset"); + if (offsetValue.isNumber()) { + byteOffset = static_cast(std::max(0, offsetValue.getNumber())); + } + Value lengthValue = object.getProperty(runtime, "byteLength"); + if (lengthValue.isNumber()) { + viewByteLength = static_cast(std::max(0, lengthValue.getNumber())); + } + + ArrayBuffer buffer = bufferObject.getArrayBuffer(runtime); + if (byteOffset > buffer.size(runtime)) { + return false; + } + if (viewByteLength == 0 || byteOffset + viewByteLength > buffer.size(runtime)) { + viewByteLength = buffer.size(runtime) - byteOffset; + } + *data = buffer.data(runtime) + byteOffset; + *byteLength = viewByteLength; + return true; +} + +uint32_t rawTypeKind(MDTypeKind kind) { + return static_cast(kind); +} + +MDTypeKind stripTypeFlags(MDTypeKind kind) { + uint32_t raw = rawTypeKind(kind); + raw &= ~static_cast(metagen::mdTypeFlagNext); + raw &= ~static_cast(metagen::mdTypeFlagVariadic); + return static_cast(raw); +} + +size_t alignUp(size_t value, size_t alignment) { + if (alignment == 0) { + return value; + } + return ((value + alignment - 1) / alignment) * alignment; +} + +ffi_type* ffiTypeForEngineKind(MDTypeKind kind) { + switch (kind) { + case metagen::mdTypeChar: + return &ffi_type_sint8; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + case metagen::mdTypeBool: + return &ffi_type_uint8; + case metagen::mdTypeSShort: + return &ffi_type_sint16; + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + return &ffi_type_uint16; + case metagen::mdTypeSInt: + return &ffi_type_sint32; + case metagen::mdTypeUInt: + return &ffi_type_uint32; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return &ffi_type_sint64; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return &ffi_type_uint64; + case metagen::mdTypeFloat: + return &ffi_type_float; + case metagen::mdTypeDouble: + return &ffi_type_double; + case metagen::mdTypeVoid: + return &ffi_type_void; + case metagen::mdTypeString: + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + case metagen::mdTypeClass: + case metagen::mdTypeSelector: + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + return &ffi_type_pointer; + default: + return nullptr; + } +} + +bool isSupportedEngineKind(MDTypeKind kind) { + switch (kind) { + default: + return ffiTypeForEngineKind(kind) != nullptr; + } +} + +void skipMetadataEngineTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, + MDTypeKind kind); + +void skipMetadataEngineType(MDMetadataReader* metadata, MDSectionOffset* offset) { + MDTypeKind kind = stripTypeFlags(metadata->getTypeKind(*offset)); + *offset += sizeof(MDTypeKind); + skipMetadataEngineTypePayload(metadata, offset, kind); +} + +void skipMetadataEngineTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, + MDTypeKind kind) { + switch (kind) { + case metagen::mdTypeClassObject: { + auto classOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + case metagen::mdTypeProtocolObject: { + bool next = true; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + *offset += sizeof(uint16_t); + skipMetadataEngineType(metadata, offset); + break; + case metagen::mdTypeStruct: + *offset += sizeof(MDSectionOffset); + break; + case metagen::mdTypePointer: + skipMetadataEngineType(metadata, offset); + break; + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + *offset += sizeof(MDSectionOffset); + break; + default: + break; + } +} + +NativeApiType parseMetadataEngineType(MDMetadataReader* metadata, + MDSectionOffset* offset, + NativeApiBridge* bridge) { + MDTypeKind rawKind = metadata->getTypeKind(*offset); + MDTypeKind kind = stripTypeFlags(rawKind); + *offset += sizeof(MDTypeKind); + + NativeApiType type; + type.kind = kind; + + switch (kind) { + case metagen::mdTypeArray: { + type.arraySize = metadata->getArraySize(*offset); + *offset += sizeof(uint16_t); + type.elementType = + std::make_shared( + parseMetadataEngineType(metadata, offset, bridge)); + auto ffiOwner = std::make_shared(); + ffiOwner->elements.reserve(static_cast(type.arraySize) + 1); + ffi_type* elementFfiType = type.elementType->ffiType != nullptr + ? type.elementType->ffiType + : &ffi_type_pointer; + for (uint16_t i = 0; i < type.arraySize; i++) { + ffiOwner->elements.push_back(elementFfiType); + } + ffiOwner->finalize(); + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = type.elementType->supported; + return type; + } + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: { + type.arraySize = metadata->getArraySize(*offset); + *offset += sizeof(uint16_t); + type.elementType = + std::make_shared( + parseMetadataEngineType(metadata, offset, bridge)); + auto ffiOwner = std::make_shared(); +#if defined(FFI_TYPE_EXT_VECTOR) + ffiOwner->type.type = + kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; +#else + ffiOwner->type.type = + kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; +#endif + ffi_type* elementFfiType = type.elementType->ffiType != nullptr + ? type.elementType->ffiType + : &ffi_type_float; + size_t lanes = std::max(type.arraySize, 1); + size_t abiLanes = lanes == 3 ? 4 : lanes; + size_t elementSize = std::max(elementFfiType->size, sizeof(float)); + size_t elementAlignment = + std::max(elementFfiType->alignment, static_cast(1)); + ffiOwner->elements.reserve(abiLanes + 1); + for (size_t i = 0; i < abiLanes; i++) { + ffiOwner->elements.push_back(elementFfiType); + } + ffiOwner->finalize(); + size_t vectorAlignment = elementAlignment; + if (kind != metagen::mdTypeComplex) { + size_t packedSize = abiLanes * elementSize; + size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; + vectorAlignment = std::max(vectorAlignment, preferredAlignment); + } + vectorAlignment = std::min(vectorAlignment, 16); + ffiOwner->type.alignment = static_cast(vectorAlignment); + ffiOwner->type.size = alignUp(abiLanes * elementSize, vectorAlignment); + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = type.elementType->supported; + return type; + } + case metagen::mdTypeStruct: { + auto structOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool isUnion = (structOffset & metagen::mdSectionOffsetNext) != 0; + structOffset &= ~metagen::mdSectionOffsetNext; + if (structOffset == MD_SECTION_OFFSET_NULL || bridge == nullptr) { + type.kind = metagen::mdTypePointer; + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; + } + + MDSectionOffset absoluteOffset = + structOffset + (isUnion ? metadata->unionsOffset : metadata->structsOffset); + type.aggregateOffset = absoluteOffset; + type.aggregateIsUnion = isUnion; + type.aggregateInfo = bridge->aggregateInfoFor(absoluteOffset, isUnion); + type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr + ? &type.aggregateInfo->ffi->type + : nullptr; + type.supported = type.ffiType != nullptr; + return type; + } + case metagen::mdTypePointer: + type.elementType = + std::make_shared( + parseMetadataEngineType(metadata, offset, bridge)); + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + type.signatureOffset = metadata->getOffset(*offset) + metadata->signaturesOffset; + *offset += sizeof(MDSectionOffset); + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; + case metagen::mdTypeClassObject: { + auto classOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + case metagen::mdTypeProtocolObject: { + bool next = true; + while (next) { + auto protocolOffset = metadata->getOffset(*offset); + *offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + break; + } + default: + break; + } + + type.ffiType = ffiTypeForEngineKind(kind); + type.supported = type.ffiType != nullptr && isSupportedEngineKind(kind); + return type; +} + +std::shared_ptr NativeApiBridge::aggregateInfoFor( + MDSectionOffset aggregateOffset, bool isUnion) { + if (metadata_ == nullptr || aggregateOffset == MD_SECTION_OFFSET_NULL) { + return nullptr; + } + + auto cached = aggregateInfoByOffset_.find(aggregateOffset); + if (cached != aggregateInfoByOffset_.end()) { + return cached->second; + } + + auto info = std::make_shared(); + info->offset = aggregateOffset; + info->isUnion = isUnion; + aggregateInfoByOffset_[aggregateOffset] = info; + + if (aggregateInfoInProgress_.find(aggregateOffset) != + aggregateInfoInProgress_.end()) { + auto ffiOwner = std::make_shared(); + ffiOwner->elements.push_back(&ffi_type_pointer); + ffiOwner->finalize(); + info->ffi = ffiOwner; + return info; + } + + aggregateInfoInProgress_.insert(aggregateOffset); + + MDSectionOffset offset = aggregateOffset; + const char* name = metadata_->getString(offset); + info->name = name != nullptr ? name : ""; + offset += sizeof(MDSectionOffset); + info->size = metadata_->getArraySize(offset); + offset += sizeof(uint16_t); + + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + + NativeApiAggregateField field; + const char* fieldName = metadata_->resolveString(nameOffset); + field.name = fieldName != nullptr ? fieldName : ""; + if (!isUnion) { + field.offset = metadata_->getArraySize(offset); + offset += sizeof(uint16_t); + } + field.type = parseMetadataEngineType(metadata_.get(), &offset, this); + info->fields.push_back(std::move(field)); + } + + auto ffiOwner = std::make_shared(); + if (isUnion) { + ffi_type* largest = &ffi_type_uint8; + size_t largestSize = 0; + for (const auto& field : info->fields) { + size_t fieldSize = nativeSizeForType(field.type); + if (field.type.ffiType != nullptr && fieldSize >= largestSize) { + largest = field.type.ffiType; + largestSize = fieldSize; + } + } + ffiOwner->elements.push_back(largest); + } else { + for (const auto& field : info->fields) { + ffiOwner->elements.push_back(field.type.ffiType != nullptr + ? field.type.ffiType + : &ffi_type_pointer); + } + if (ffiOwner->elements.empty()) { + ffiOwner->elements.push_back(&ffi_type_uint8); + } + } + ffiOwner->finalize(); + info->ffi = ffiOwner; + aggregateInfoInProgress_.erase(aggregateOffset); + return info; +} + +ffi_type* ffiTypeForEngineArgument(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeArray: + return &ffi_type_pointer; + default: + return type.ffiType != nullptr ? type.ffiType : &ffi_type_pointer; + } +} + +std::optional parseMetadataEngineSignature( + MDMetadataReader* metadata, MDSectionOffset signatureOffset, + unsigned int implicitArgumentCount, NativeApiBridge* bridge, + bool returnOwned = false) { + if (metadata == nullptr || signatureOffset == MD_SECTION_OFFSET_NULL) { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = implicitArgumentCount; + signature.signatureHash = isPreparedGeneratedDispatchRequired() + ? metadataSignatureHash(metadata, signatureOffset) + : 0; + signature.dispatchFlags = returnOwned ? 1 : 0; + + MDSectionOffset offset = signatureOffset; + MDTypeKind returnKind = metadata->getTypeKind(offset); + uint32_t returnKindRaw = rawTypeKind(returnKind); + bool next = + (returnKindRaw & static_cast(metagen::mdTypeFlagNext)) != 0; + signature.variadic = + (returnKindRaw & static_cast(metagen::mdTypeFlagVariadic)) != 0; + signature.returnType = parseMetadataEngineType(metadata, &offset, bridge); + signature.returnType.returnOwned = returnOwned; + + while (next) { + MDTypeKind argKind = metadata->getTypeKind(offset); + next = (rawTypeKind(argKind) & + static_cast(metagen::mdTypeFlagNext)) != 0; + signature.argumentTypes.push_back(parseMetadataEngineType(metadata, &offset, bridge)); + } + + signature.ffiTypes.reserve(signature.argumentTypes.size() + + implicitArgumentCount); + for (unsigned int i = 0; i < implicitArgumentCount; i++) { + signature.ffiTypes.push_back(&ffi_type_pointer); + } + for (const auto& argType : signature.argumentTypes) { + signature.ffiTypes.push_back(ffiTypeForEngineArgument(argType)); + } + + ffi_status status = ffi_prep_cif( + &signature.cif, FFI_DEFAULT_ABI, + static_cast(signature.ffiTypes.size()), + signature.returnType.ffiType != nullptr ? signature.returnType.ffiType + : &ffi_type_void, + signature.ffiTypes.empty() ? nullptr : signature.ffiTypes.data()); + signature.prepared = status == FFI_OK; + return signature; +} + +bool prepareEngineCallbackSignature(NativeApiSignature* signature) { + if (signature == nullptr) { + return false; + } + + signature->ffiTypes.clear(); + signature->ffiTypes.reserve(signature->argumentTypes.size() + + signature->implicitArgumentCount); + for (unsigned int i = 0; i < signature->implicitArgumentCount; i++) { + signature->ffiTypes.push_back(&ffi_type_pointer); + } + for (const auto& argType : signature->argumentTypes) { + signature->ffiTypes.push_back(ffiTypeForEngineArgument(argType)); + } + + ffi_status status = ffi_prep_cif( + &signature->cif, FFI_DEFAULT_ABI, + static_cast(signature->ffiTypes.size()), + signature->returnType.ffiType != nullptr ? signature->returnType.ffiType + : &ffi_type_void, + signature->ffiTypes.empty() ? nullptr : signature->ffiTypes.data()); + signature->prepared = status == FFI_OK; + return signature->prepared; +} + +const char* skipObjCTypeQualifiers(const char* encoding) { + while (encoding != nullptr && *encoding != '\0' && + std::strchr("rnNoORV", *encoding) != nullptr) { + encoding++; + } + return encoding; +} + +const char* skipObjCTypeFrameOffset(const char* encoding) { + while (encoding != nullptr && *encoding >= '0' && *encoding <= '9') { + encoding++; + } + return encoding; +} + +const char* skipObjCTypeFieldName(const char* encoding, std::string* name) { + if (encoding == nullptr || *encoding != '"') { + return encoding; + } + + encoding++; + const char* start = encoding; + while (*encoding != '\0' && *encoding != '"') { + encoding++; + } + if (name != nullptr) { + *name = std::string(start, static_cast(encoding - start)); + } + return *encoding == '"' ? encoding + 1 : encoding; +} + +std::string normalizedObjCAggregateName(std::string name) { + if (!name.empty() && name.front() == '_') { + name.erase(name.begin()); + } + return name; +} + +std::vector knownObjCAggregateFieldNames( + const std::string& aggregateName, size_t fieldCount) { + std::string name = normalizedObjCAggregateName(aggregateName); + std::vector fields; + if (name == "CGPoint" || name == "NSPoint") { + fields = {"x", "y"}; + } else if (name == "CGSize" || name == "NSSize") { + fields = {"width", "height"}; + } else if (name == "CGRect" || name == "NSRect") { + fields = {"origin", "size"}; + } else if (name == "CGVector") { + fields = {"dx", "dy"}; + } else if (name == "UIEdgeInsets" || name == "NSEdgeInsets") { + fields = {"top", "left", "bottom", "right"}; + } else if (name == "NSDirectionalEdgeInsets") { + fields = {"top", "leading", "bottom", "trailing"}; + } else if (name == "NSRange" || name == "CFRange") { + fields = {"location", "length"}; + } else if (name == "CGAffineTransform") { + fields = {"a", "b", "c", "d", "tx", "ty"}; + } else if (name == "CATransform3D") { + fields = {"m11", "m12", "m13", "m14", "m21", "m22", "m23", "m24", + "m31", "m32", "m33", "m34", "m41", "m42", "m43", "m44"}; + } + + if (fields.size() != fieldCount) { + fields.clear(); + } + return fields; +} + +const NativeApiSymbol* findObjCAggregateSymbol( + NativeApiBridge* bridge, const std::string& name, bool isUnion) { + if (bridge == nullptr || name.empty()) { + return nullptr; + } + + std::vector candidates; + candidates.push_back(name); + std::string normalized = normalizedObjCAggregateName(name); + if (normalized != name) { + candidates.push_back(normalized); + } else { + candidates.push_back("_" + name); + } + constexpr const char* suffix = "Struct"; + if (normalized.size() > std::strlen(suffix) && + normalized.compare(normalized.size() - std::strlen(suffix), + std::strlen(suffix), suffix) == 0) { + candidates.push_back( + normalized.substr(0, normalized.size() - std::strlen(suffix))); + } else { + candidates.push_back(normalized + suffix); + } + + for (const auto& candidate : candidates) { + const NativeApiSymbol* symbol = + isUnion ? bridge->findUnion(candidate) : bridge->findStruct(candidate); + if (symbol == nullptr) { + symbol = bridge->findAggregate(candidate); + } + if (symbol != nullptr) { + return symbol; + } + } + + return nullptr; +} + +void applyObjCEncodingSizeAndAlignment(const char* encoding, + NativeApiFfiType* ffiType, + uint16_t* sizeOut = nullptr) { + if (encoding == nullptr || ffiType == nullptr) { + return; + } + + NSUInteger size = 0; + NSUInteger alignment = 0; + NSGetSizeAndAlignment(encoding, &size, &alignment); + if (size > 0) { + ffiType->type.size = static_cast(size); + if (sizeOut != nullptr) { + *sizeOut = static_cast(std::min( + size, static_cast(std::numeric_limits::max()))); + } + } + if (alignment > 0) { + ffiType->type.alignment = static_cast(alignment); + } +} + +NativeApiType parseObjCEncodedEngineType( + const char* encoding, NativeApiBridge* bridge = nullptr, + const char** endEncoding = nullptr); + +bool unsupportedEngineType(const NativeApiType& type); + +NativeApiType parseObjCEncodedAggregateEngineType( + const char* encoding, NativeApiBridge* bridge, const char** endEncoding) { + NativeApiType type; + type.kind = metagen::mdTypeStruct; + + const bool isUnion = *encoding == '('; + const char close = isUnion ? ')' : '}'; + const char* cursor = encoding + 1; + const char* nameStart = cursor; + while (*cursor != '\0' && *cursor != '=' && *cursor != close) { + cursor++; + } + std::string aggregateName(nameStart, static_cast(cursor - nameStart)); + + if (const NativeApiSymbol* symbol = + findObjCAggregateSymbol(bridge, aggregateName, isUnion)) { + type.aggregateOffset = symbol->offset; + type.aggregateIsUnion = symbol->kind == NativeApiSymbolKind::Union; + type.aggregateInfo = bridge->aggregateInfoFor(*symbol); + type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr + ? &type.aggregateInfo->ffi->type + : nullptr; + type.supported = type.ffiType != nullptr; + + int depth = 0; + const char* end = encoding; + do { + if (*end == *encoding) { + depth++; + } else if (*end == close) { + depth--; + } + end++; + } while (*end != '\0' && depth > 0); + if (endEncoding != nullptr) { + *endEncoding = end; + } + return type; + } + + auto info = std::make_shared(); + info->name = aggregateName; + info->isUnion = isUnion; + info->offset = MD_SECTION_OFFSET_NULL; + + if (*cursor == '=') { + cursor++; + } + + size_t computedOffset = 0; + size_t maxFieldSize = 0; + size_t fieldIndex = 0; + while (*cursor != '\0' && *cursor != close) { + NativeApiAggregateField field; + std::string encodedFieldName; + cursor = skipObjCTypeFieldName(cursor, &encodedFieldName); + const char* fieldStart = cursor; + const char* fieldEnd = cursor; + field.type = parseObjCEncodedEngineType(cursor, bridge, &fieldEnd); + if (fieldEnd == fieldStart || unsupportedEngineType(field.type)) { + type.supported = false; + type.ffiType = nullptr; + if (endEncoding != nullptr) { + *endEncoding = fieldEnd; + } + return type; + } + + NSUInteger fieldSize = 0; + NSUInteger fieldAlignment = 0; + NSGetSizeAndAlignment(fieldStart, &fieldSize, &fieldAlignment); + size_t nativeFieldSize = + fieldSize > 0 ? static_cast(fieldSize) + : nativeSizeForType(field.type); + size_t nativeFieldAlignment = + fieldAlignment > 0 ? static_cast(fieldAlignment) + : std::max(1, field.type.ffiType != nullptr + ? field.type.ffiType->alignment + : 1); + if (isUnion) { + field.offset = 0; + maxFieldSize = std::max(maxFieldSize, nativeFieldSize); + } else { + computedOffset = alignUp(computedOffset, nativeFieldAlignment); + field.offset = static_cast(std::min( + computedOffset, std::numeric_limits::max())); + computedOffset += nativeFieldSize; + } + field.name = !encodedFieldName.empty() + ? encodedFieldName + : "field" + std::to_string(fieldIndex); + info->fields.push_back(std::move(field)); + fieldIndex++; + cursor = fieldEnd; + } + + if (*cursor == close) { + cursor++; + } + if (endEncoding != nullptr) { + *endEncoding = cursor; + } + + auto knownNames = knownObjCAggregateFieldNames(aggregateName, info->fields.size()); + for (size_t i = 0; i < knownNames.size(); i++) { + info->fields[i].name = knownNames[i]; + } + + auto ffiOwner = std::make_shared(); + if (isUnion) { + ffi_type* largest = &ffi_type_uint8; + size_t largestSize = 0; + for (const auto& field : info->fields) { + size_t fieldSize = nativeSizeForType(field.type); + if (field.type.ffiType != nullptr && fieldSize >= largestSize) { + largest = field.type.ffiType; + largestSize = fieldSize; + } + } + ffiOwner->elements.push_back(largest); + } else { + for (const auto& field : info->fields) { + ffiOwner->elements.push_back(field.type.ffiType != nullptr + ? field.type.ffiType + : &ffi_type_pointer); + } + } + if (ffiOwner->elements.empty()) { + ffiOwner->elements.push_back(&ffi_type_uint8); + } + ffiOwner->finalize(); + applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get(), &info->size); + if (info->size == 0) { + info->size = static_cast(std::min( + isUnion ? maxFieldSize : computedOffset, + std::numeric_limits::max())); + } + + info->ffi = ffiOwner; + type.aggregateInfo = info; + type.aggregateOffset = MD_SECTION_OFFSET_NULL; + type.aggregateIsUnion = isUnion; + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = true; + return type; +} + +NativeApiType parseObjCEncodedArrayEngineType( + const char* encoding, NativeApiBridge* bridge, const char** endEncoding) { + NativeApiType type; + type.kind = metagen::mdTypeArray; + + const char* cursor = encoding + 1; + uint16_t count = 0; + while (*cursor >= '0' && *cursor <= '9') { + count = static_cast( + std::min(std::numeric_limits::max(), + (count * 10) + (*cursor - '0'))); + cursor++; + } + type.arraySize = count; + + const char* elementEnd = cursor; + type.elementType = std::make_shared( + parseObjCEncodedEngineType(cursor, bridge, &elementEnd)); + cursor = elementEnd; + if (*cursor == ']') { + cursor++; + } + if (endEncoding != nullptr) { + *endEncoding = cursor; + } + + auto ffiOwner = std::make_shared(); + ffi_type* elementFfiType = + type.elementType != nullptr && type.elementType->ffiType != nullptr + ? type.elementType->ffiType + : &ffi_type_pointer; + for (uint16_t i = 0; i < count; i++) { + ffiOwner->elements.push_back(elementFfiType); + } + if (ffiOwner->elements.empty()) { + ffiOwner->elements.push_back(&ffi_type_uint8); + } + ffiOwner->finalize(); + applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get()); + + type.ownedFfiType = ffiOwner; + type.ffiType = &ffiOwner->type; + type.supported = type.elementType != nullptr && type.elementType->supported; + return type; +} + +NativeApiType parseObjCEncodedEngineType( + const char* encoding, NativeApiBridge* bridge, const char** endEncoding) { + encoding = skipObjCTypeQualifiers(encoding); + NativeApiType type; + + if (encoding == nullptr || *encoding == '\0') { + type.kind = metagen::mdTypePointer; + type.ffiType = &ffi_type_pointer; + if (endEncoding != nullptr) { + *endEncoding = encoding; + } + return type; + } + + auto finishPrimitive = [&](const char* end) { + type.ffiType = ffiTypeForEngineKind(type.kind); + type.supported = type.ffiType != nullptr; + if (endEncoding != nullptr) { + *endEncoding = end; + } + return type; + }; + + switch (*encoding) { + case 'c': + type.kind = metagen::mdTypeChar; + break; + case 'i': + type.kind = metagen::mdTypeSInt; + break; + case 's': + type.kind = metagen::mdTypeSShort; + break; + case 'l': + case 'q': + type.kind = metagen::mdTypeSInt64; + break; + case 'C': + type.kind = metagen::mdTypeUInt8; + break; + case 'I': + type.kind = metagen::mdTypeUInt; + break; + case 'S': + type.kind = metagen::mdTypeUShort; + break; + case 'L': + case 'Q': + type.kind = metagen::mdTypeUInt64; + break; + case 'f': + type.kind = metagen::mdTypeFloat; + break; + case 'd': + type.kind = metagen::mdTypeDouble; + break; + case 'B': + type.kind = metagen::mdTypeBool; + break; + case 'v': + type.kind = metagen::mdTypeVoid; + break; + case '*': + type.kind = metagen::mdTypeString; + break; + case '@': + if (encoding[1] == '?') { + type.kind = metagen::mdTypeBlock; + return finishPrimitive(encoding + 2); + } + { + const char* objectEnd = encoding + 1; + if (*objectEnd == '"') { + objectEnd++; + while (*objectEnd != '\0' && *objectEnd != '"') { + objectEnd++; + } + if (*objectEnd == '"') { + objectEnd++; + } + } + if (std::strncmp(encoding, "@\"NSString\"", 11) == 0) { + type.kind = metagen::mdTypeNSStringObject; + } else if (std::strncmp(encoding, "@\"NSMutableString\"", 18) == 0) { + type.kind = metagen::mdTypeNSMutableStringObject; + } else { + type.kind = metagen::mdTypeAnyObject; + } + return finishPrimitive(objectEnd); + } + case '#': + type.kind = metagen::mdTypeClass; + break; + case ':': + type.kind = metagen::mdTypeSelector; + break; + case '^': + type.kind = metagen::mdTypePointer; + { + const char* elementEnd = encoding + 1; + type.elementType = std::make_shared( + parseObjCEncodedEngineType(encoding + 1, bridge, &elementEnd)); + type.ffiType = &ffi_type_pointer; + type.supported = true; + if (elementEnd == encoding + 1 && encoding[1] != '\0') { + elementEnd = encoding + 2; + } + if (endEncoding != nullptr) { + *endEncoding = elementEnd; + } + } + return type; + case '{': + case '(': + return parseObjCEncodedAggregateEngineType(encoding, bridge, endEncoding); + case '[': + return parseObjCEncodedArrayEngineType(encoding, bridge, endEncoding); + case 'b': { + type.kind = metagen::mdTypeUInt; + const char* cursor = encoding + 1; + while (*cursor >= '0' && *cursor <= '9') { + cursor++; + } + return finishPrimitive(cursor); + } + case '?': + type.kind = metagen::mdTypeOpaquePointer; + break; + default: + type.kind = metagen::mdTypePointer; + break; + } + + return finishPrimitive(encoding + 1); +} + +std::optional parseObjCCallbackEngineSignature( + const std::string& encodingString, bool block, NativeApiBridge* bridge) { + const char* cursor = skipObjCTypeQualifiers(encodingString.c_str()); + if (cursor == nullptr || *cursor == '\0') { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = block ? 1 : 0; + + const char* returnEnd = cursor; + signature.returnType = parseObjCEncodedEngineType(cursor, bridge, &returnEnd); + if (returnEnd == cursor) { + return std::nullopt; + } + cursor = skipObjCTypeFrameOffset(returnEnd); + + if (block) { + const char* blockSelf = skipObjCTypeQualifiers(cursor); + if (blockSelf != nullptr && blockSelf[0] == '@' && blockSelf[1] == '?') { + cursor = skipObjCTypeFrameOffset(blockSelf + 2); + } + } + + while (cursor != nullptr && *cursor != '\0') { + const char* argStart = skipObjCTypeQualifiers(cursor); + if (argStart == nullptr || *argStart == '\0') { + break; + } + const char* argEnd = argStart; + NativeApiType argType = parseObjCEncodedEngineType(argStart, bridge, &argEnd); + if (argEnd == argStart) { + return std::nullopt; + } + signature.argumentTypes.push_back(std::move(argType)); + cursor = skipObjCTypeFrameOffset(argEnd); + } + + prepareEngineCallbackSignature(&signature); + return signature; +} + +std::optional parseObjCMethodEngineSignature( + Method method, NativeApiBridge* bridge = nullptr) { + if (method == nullptr) { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = 2; + + char* returnEncoding = method_copyReturnType(method); + signature.returnType = parseObjCEncodedEngineType(returnEncoding, bridge); + if (returnEncoding != nullptr) { + free(returnEncoding); + } + + unsigned int totalArgc = method_getNumberOfArguments(method); + for (unsigned int i = 2; i < totalArgc; i++) { + char* argEncoding = method_copyArgumentType(method, i); + signature.argumentTypes.push_back(parseObjCEncodedEngineType(argEncoding, bridge)); + if (argEncoding != nullptr) { + free(argEncoding); + } + } + + signature.ffiTypes.reserve(totalArgc); + signature.ffiTypes.push_back(&ffi_type_pointer); + signature.ffiTypes.push_back(&ffi_type_pointer); + for (const auto& argType : signature.argumentTypes) { + signature.ffiTypes.push_back(ffiTypeForEngineArgument(argType)); + } + + ffi_status status = ffi_prep_cif( + &signature.cif, FFI_DEFAULT_ABI, + static_cast(signature.ffiTypes.size()), + signature.returnType.ffiType != nullptr ? signature.returnType.ffiType + : &ffi_type_void, + signature.ffiTypes.data()); + signature.prepared = status == FFI_OK; + return signature; +} + +bool prepareEngineMethodSignature(NativeApiSignature* signature) { + if (signature == nullptr) { + return false; + } + signature->implicitArgumentCount = 2; + signature->ffiTypes.clear(); + signature->ffiTypes.reserve(signature->argumentTypes.size() + 2); + signature->ffiTypes.push_back(&ffi_type_pointer); + signature->ffiTypes.push_back(&ffi_type_pointer); + for (const auto& argType : signature->argumentTypes) { + ffi_type* ffiType = ffiTypeForEngineArgument(argType); + if (ffiType == nullptr) { + signature->prepared = false; + return false; + } + signature->ffiTypes.push_back(ffiType); + } + ffi_type* returnFfiType = + signature->returnType.ffiType != nullptr ? signature->returnType.ffiType + : &ffi_type_void; + signature->prepared = + ffi_prep_cif(&signature->cif, FFI_DEFAULT_ABI, + static_cast(signature->ffiTypes.size()), + returnFfiType, signature->ffiTypes.data()) == FFI_OK; + return signature->prepared; +} + +bool reconcileObjCMethodRuntimeType(NativeApiType* metadataType, + const NativeApiType& runtimeType, + bool* abiChanged) { + if (metadataType == nullptr || unsupportedEngineType(runtimeType)) { + return false; + } + + if (runtimeType.kind == metagen::mdTypeBlock && + metadataType->kind == metagen::mdTypeFunctionPointer) { + metadataType->kind = metagen::mdTypeBlock; + metadataType->ffiType = runtimeType.ffiType; + metadataType->supported = runtimeType.supported; + return true; + } + + // Do not overwrite aggregate (struct/union) metadata types with the + // anonymous ObjC runtime encoding: the metadata type carries the real + // field names and layout that the runtime encoding (e.g. "{?=qqq}") lacks. + (void)abiChanged; + return false; +} + +bool reconcileObjCMethodRuntimeSignature(NativeApiSignature* signature, + const NativeApiSignature& runtime) { + if (signature == nullptr || + signature->argumentTypes.size() != runtime.argumentTypes.size()) { + return false; + } + + bool changed = false; + bool abiChanged = false; + changed |= reconcileObjCMethodRuntimeType(&signature->returnType, + runtime.returnType, &abiChanged); + for (size_t i = 0; i < signature->argumentTypes.size(); i++) { + changed |= reconcileObjCMethodRuntimeType(&signature->argumentTypes[i], + runtime.argumentTypes[i], + &abiChanged); + } + + if (abiChanged) { + signature->signatureHash = 0; + } + return !changed || prepareEngineMethodSignature(signature); +} + +bool unsupportedEngineType(const NativeApiType& type) { + if (type.kind == metagen::mdTypeStruct && type.aggregateInfo != nullptr && + type.aggregateInfo->ffi != nullptr) { + return false; + } + return !type.supported || type.ffiType == nullptr; +} + +bool signatureSupportedForEngineCallback(const NativeApiSignature& signature) { + if (!signature.prepared || signature.variadic || + unsupportedEngineType(signature.returnType)) { + return false; + } + for (const auto& argType : signature.argumentTypes) { + if (unsupportedEngineType(argType)) { + return false; + } + } + return true; +} + +std::shared_ptr createEngineCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, Function function, bool block, + NativeApiCallbackThreadPolicy threadPolicy = + NativeApiCallbackThreadPolicy::Default) { + if (bridge == nullptr || bridge->metadata() == nullptr || + type.signatureOffset == MD_SECTION_OFFSET_NULL) { + throw JSError( + runtime, "Native callback metadata is unavailable."); + } + + auto parsed = parseMetadataEngineSignature( + bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); + if (!parsed || !signatureSupportedForEngineCallback(*parsed)) { + throw JSError( + runtime, "Native callback signature is not supported by backend."); + } + + auto signature = + std::make_shared(std::move(*parsed)); + uintptr_t roundTripValidationKey = + NativeApiBridge::callbackRoundTripValidationKey(type); + auto callback = std::make_shared( + runtime, bridge, std::move(signature), std::move(function), block, + threadPolicy, false, roundTripValidationKey); + if (block) { + callback->retainInitialBlockLifetime(callback); + } else { + bridge->retainEngineLifetime(callback); + } + return callback; +} + +std::shared_ptr createEngineCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& objcSignatureEncoding, Function function, bool block, + NativeApiCallbackThreadPolicy threadPolicy = + NativeApiCallbackThreadPolicy::Default, + uintptr_t roundTripValidationKey = 0) { + if (bridge == nullptr || objcSignatureEncoding.empty()) { + throw JSError(runtime, "Native callback encoding is unavailable."); + } + + auto parsed = parseObjCCallbackEngineSignature( + objcSignatureEncoding, block, bridge.get()); + if (!parsed || !signatureSupportedForEngineCallback(*parsed)) { + throw JSError( + runtime, "Native callback signature is not supported by backend."); + } + + auto signature = + std::make_shared(std::move(*parsed)); + auto callback = std::make_shared( + runtime, bridge, std::move(signature), std::move(function), block, + threadPolicy, false, roundTripValidationKey); + if (block) { + callback->retainInitialBlockLifetime(callback); + } else { + bridge->retainEngineLifetime(callback); + } + return callback; +} + +std::shared_ptr createEngineMethodCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& selectorName, MDSectionOffset signatureOffset, + Function function, bool returnOwned) { + if (bridge == nullptr || bridge->metadata() == nullptr || + signatureOffset == MD_SECTION_OFFSET_NULL) { + throw JSError( + runtime, "Native method callback metadata is unavailable."); + } + + auto parsed = parseMetadataEngineSignature( + bridge->metadata(), signatureOffset, 2, bridge.get(), returnOwned); + if (!parsed || !signatureSupportedForEngineCallback(*parsed)) { + throw JSError( + runtime, "Native method callback signature is not supported by backend."); + } + parsed->selectorName = selectorName; + + auto signature = + std::make_shared(std::move(*parsed)); + auto threadPolicy = readEngineCallbackThreadPolicy(runtime, function); + auto callback = std::make_shared( + runtime, bridge, std::move(signature), std::move(function), false, + threadPolicy, true); + bridge->retainEngineLifetime(callback); + return callback; +} + +std::shared_ptr createEngineMethodCallback( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& selectorName, NativeApiSignature signature, + Function function) { + signature.selectorName = selectorName; + prepareEngineMethodSignature(&signature); + if (!signatureSupportedForEngineCallback(signature)) { + throw JSError( + runtime, "Native method callback signature is not supported by backend."); + } + + auto sharedSignature = + std::make_shared(std::move(signature)); + auto threadPolicy = readEngineCallbackThreadPolicy(runtime, function); + auto callback = std::make_shared( + runtime, bridge, std::move(sharedSignature), std::move(function), false, + threadPolicy, true); + bridge->retainEngineLifetime(callback); + return callback; +} diff --git a/NativeScript/ffi/objc/shared/bridge/ClassBuilder.mm b/NativeScript/ffi/objc/shared/bridge/ClassBuilder.mm new file mode 100644 index 000000000..761fe1680 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/ClassBuilder.mm @@ -0,0 +1,797 @@ +std::string readOptionalStringProperty(Runtime& runtime, const Object& object, + const char* name) { + if (name == nullptr || !object.hasProperty(runtime, name)) { + return ""; + } + Value value = object.getProperty(runtime, name); + return value.isString() ? value.asString(runtime).utf8(runtime) : ""; +} + +struct NativeApiClassBuilderRegistration { + std::shared_ptr runtimeOwner; + Runtime* runtime = nullptr; + std::shared_ptr bridge; +}; + +std::mutex gNativeApiClassBuilderMutex; +std::unordered_map + gNativeApiClassBuilders; +struct NativeApiKnownExposedMethod { + std::string selectorName; + NativeApiSignature signature; +}; +std::mutex gNativeApiKnownExposedMethodsMutex; +std::unordered_map + gNativeApiKnownExposedMethods; + +void rememberNativeApiClassBuilder( + Runtime& runtime, const std::shared_ptr& bridge, + Class cls) { + if (cls == Nil) { + return; + } + std::lock_guard lock(gNativeApiClassBuilderMutex); + auto runtimeOwner = retainNativeApiRuntime(runtime); + gNativeApiClassBuilders[cls] = NativeApiClassBuilderRegistration{ + .runtimeOwner = runtimeOwner, + .runtime = runtimeOwner.get(), + .bridge = bridge, + }; +} + +void rememberNativeApiKnownExposedMethod( + const std::string& selectorName, const NativeApiSignature& signature) { + if (selectorName.empty()) { + return; + } + NativeApiKnownExposedMethod method{ + .selectorName = selectorName, + .signature = signature, + }; + std::lock_guard lock(gNativeApiKnownExposedMethodsMutex); + gNativeApiKnownExposedMethods[selectorName] = method; + gNativeApiKnownExposedMethods[jsifySelector(selectorName.c_str())] = + std::move(method); +} + +std::optional knownNativeApiExposedMethod( + const std::string& name) { + std::lock_guard lock(gNativeApiKnownExposedMethodsMutex); + auto it = gNativeApiKnownExposedMethods.find(name); + if (it == gNativeApiKnownExposedMethods.end()) { + return std::nullopt; + } + NativeApiKnownExposedMethod method = it->second; + prepareEngineMethodSignature(&method.signature); + return method; +} + +std::optional +findNativeApiClassBuilder(id object) { + Class cls = object != nil ? object_getClass(object) : Nil; + std::lock_guard lock(gNativeApiClassBuilderMutex); + while (cls != Nil) { + auto it = gNativeApiClassBuilders.find(cls); + if (it != gNativeApiClassBuilders.end()) { + return it->second; + } + cls = class_getSuperclass(cls); + } + return std::nullopt; +} + +const char* nativeApiEngineFastEnumerationEncoding() { + static const char* encoding = nullptr; + if (encoding == nullptr) { + struct objc_method_description desc = protocol_getMethodDescription( + @protocol(NSFastEnumeration), + @selector(countByEnumeratingWithState:objects:count:), YES, YES); + encoding = desc.types; + } + return encoding; +} + +NSUInteger nativeApiEngineSymbolIteratorCountByEnumerating( + id self, SEL, NSFastEnumerationState* state, + id __unsafe_unretained stackbuf[], NSUInteger len) { + if (len == 0 || state == nullptr || stackbuf == nullptr) { + return 0; + } + + auto registration = findNativeApiClassBuilder(self); + if (!registration || registration->runtime == nullptr || + registration->bridge == nullptr) { + return 0; + } + + Runtime& runtime = *registration->runtime; + NativeApiRuntimeScope runtimeScope(runtime); + auto bridge = registration->bridge; + try { + Value receiver = makeNativeObjectValue(runtime, bridge, self, false); + if (!receiver.isObject()) { + return 0; + } + + Value iteratorFactoryValue = + runtime.global().getProperty(runtime, + "__nativeScriptCreateNativeApiIterator"); + if (!iteratorFactoryValue.isObject() || + !iteratorFactoryValue.asObject(runtime).isFunction(runtime)) { + return 0; + } + + Function iteratorFactory = + iteratorFactoryValue.asObject(runtime).asFunction(runtime); + Value prototype = + bridge->findClassPrototype(runtime, object_getClass(self)); + Value iteratorValue = + prototype.isObject() + ? iteratorFactory.call(runtime, Value(runtime, receiver), + Value(runtime, prototype)) + : iteratorFactory.call(runtime, Value(runtime, receiver)); + if (!iteratorValue.isObject()) { + return 0; + } + Object iterator = iteratorValue.asObject(runtime); + Value nextValue = iterator.getProperty(runtime, "next"); + if (!nextValue.isObject() || + !nextValue.asObject(runtime).isFunction(runtime)) { + return 0; + } + Function next = nextValue.asObject(runtime).asFunction(runtime); + + auto callNext = [&]() -> Value { + return next.callWithThis(runtime, iterator); + }; + + for (unsigned long skipped = 0; skipped < state->state; skipped++) { + Value skippedResult = callNext(); + if (!skippedResult.isObject()) { + return 0; + } + Value doneValue = + skippedResult.asObject(runtime).getProperty(runtime, "done"); + if (doneValue.isBool() && doneValue.getBool()) { + return 0; + } + } + + NSUInteger count = 0; + while (count < len) { + Value nextResult = callNext(); + if (!nextResult.isObject()) { + break; + } + Object nextObject = nextResult.asObject(runtime); + Value doneValue = nextObject.getProperty(runtime, "done"); + if (doneValue.isBool() && doneValue.getBool()) { + break; + } + + Value value = nextObject.getProperty(runtime, "value"); + NativeApiArgumentFrame frame(1); + id nativeValue = objectFromEngineValue(runtime, bridge, value, frame, false); + if (nativeValue != nil) { + [nativeValue retain]; + [nativeValue autorelease]; + } + stackbuf[count++] = nativeValue; + } + + state->itemsPtr = stackbuf; + state->mutationsPtr = &state->extra[0]; + state->extra[0] = 0; + state->state += count; + return count; + } catch (const std::exception&) { + return 0; + } +} + +NativeApiSymbol runtimeSymbolForClass( + const std::shared_ptr& bridge, Class cls) { + if (bridge != nullptr) { + if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { + return *symbol; + } + } + + const char* name = cls != Nil ? class_getName(cls) : ""; + return NativeApiSymbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; +} + +std::string nextAvailableEngineClassName(const std::string& requestedName) { + if (requestedName.empty()) { + return ""; + } + if (objc_lookUpClass(requestedName.c_str()) == Nil) { + return requestedName; + } + + size_t suffix = 1; + std::string candidate; + do { + candidate = requestedName + std::to_string(suffix++); + } while (objc_lookUpClass(candidate.c_str()) != Nil); + return candidate; +} + +std::vector methodOverridesForName( + const std::vector& members, const std::string& name) { + std::vector result; + std::unordered_set selectors; + for (const auto& member : members) { + if (member.property || member.name != name || + (member.flags & metagen::mdMemberStatic) != 0 || + member.selectorName.empty()) { + continue; + } + if (selectors.insert(member.selectorName).second) { + result.push_back(member); + } + } + return result; +} + +const NativeApiMember* propertyOverrideForName( + const std::vector& members, const std::string& name) { + const NativeApiMember* propertyMember = nullptr; + for (const auto& member : members) { + if (member.property && member.name == name && + (member.flags & metagen::mdMemberStatic) == 0) { + if (propertyMember == nullptr) { + propertyMember = &member; + } + if (!member.readonly && !member.setterSelectorName.empty()) { + return &member; + } + } + } + return propertyMember; +} + +void addEngineOverrideMethod(Runtime& runtime, + const std::shared_ptr& bridge, + Class nativeClass, Class baseClass, + const std::string& selectorName, + MDSectionOffset signatureOffset, + bool returnOwned, Function function) { + if (selectorName.empty() || signatureOffset == MD_SECTION_OFFSET_NULL) { + return; + } + + auto callback = createEngineMethodCallback(runtime, bridge, selectorName, + signatureOffset, std::move(function), + returnOwned); + SEL selector = sel_registerName(selectorName.c_str()); + std::string metadataEncoding = + objcMethodSignatureForEngineSignature(callback->signature()); + class_replaceMethod(nativeClass, selector, + reinterpret_cast(callback->functionPointer()), + metadataEncoding.c_str()); +} + +Value getObjectPropertyOrUndefined(Runtime& runtime, const Object& object, + const std::string& name) { + return object.hasProperty(runtime, name.c_str()) + ? object.getProperty(runtime, name.c_str()) + : Value::undefined(); +} + +Class dispatchSuperclassForEngineDerivedReceiver(id receiver, + Class defaultSuperclass) { + if (receiver == nil) { + return Nil; + } + + Class receiverClass = object_getClass(receiver); + if (receiverClass == Nil || + !class_conformsToProtocol(receiverClass, + @protocol(NativeApiClassBuilderProtocol))) { + return Nil; + } + + Class superclass = class_getSuperclass(receiverClass); + return superclass != Nil ? superclass : defaultSuperclass; +} + +std::optional functionForSelector(Runtime& runtime, + const Object& methods, + const std::string& selectorName) { + Value value = getObjectPropertyOrUndefined(runtime, methods, selectorName); + if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { + std::string jsName = jsifySelector(selectorName.c_str()); + if (jsName != selectorName) { + value = getObjectPropertyOrUndefined(runtime, methods, jsName); + } + } + if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { + return std::nullopt; + } + return value.asObject(runtime).asFunction(runtime); +} + +std::optional readExposedType( + Runtime& runtime, const std::shared_ptr& bridge, + const Object& descriptor, const char* propertyName) { + if (!descriptor.hasProperty(runtime, propertyName)) { + return std::nullopt; + } + return interopTypeFromValue(runtime, bridge, + descriptor.getProperty(runtime, propertyName)); +} + +std::optional exposedMethodSignature( + Runtime& runtime, const std::shared_ptr& bridge, + const std::string& selectorName, const Object& descriptor) { + NativeApiSignature signature; + if (auto returnType = readExposedType(runtime, bridge, descriptor, "returns")) { + signature.returnType = *returnType; + } else { + signature.returnType = primitiveInteropType(metagen::mdTypeVoid); + } + + Value paramsValue = getObjectPropertyOrUndefined(runtime, descriptor, "params"); + if (!paramsValue.isUndefined() && !paramsValue.isNull()) { + if (!paramsValue.isObject() || !paramsValue.asObject(runtime).isArray(runtime)) { + throw JSError( + runtime, "exposedMethods params must be an array."); + } + Array params = paramsValue.asObject(runtime).getArray(runtime); + for (size_t i = 0; i < params.size(runtime); i++) { + Value typeValue = params.getValueAtIndex(runtime, i); + auto type = interopTypeFromValue(runtime, bridge, typeValue); + if (!type) { + throw JSError( + runtime, "exposedMethods contains an unsupported parameter type."); + } + signature.argumentTypes.push_back(*type); + } + } + + // A colon-less selector may still declare params (@nativescript/core + // exposes onReceive with one NSNotification param); the Objective-C runtime + // accepts such methods and callers like NSNotificationCenter invoke them + // with the argument. Only reject a mismatch when the selector explicitly + // declares argument slots. + size_t selectorArguments = selectorArgumentCount(selectorName); + if (selectorArguments != signature.argumentTypes.size() && selectorArguments != 0) { + throw JSError( + runtime, "exposedMethods selector argument count does not match params."); + } + + prepareEngineMethodSignature(&signature); + return signature; +} + +std::optional runtimeProtocolMethodSignature( + const char* types) { + if (types == nullptr) { + return std::nullopt; + } + + NSMethodSignature* methodSignature = + [NSMethodSignature signatureWithObjCTypes:types]; + if (methodSignature == nil || methodSignature.numberOfArguments < 2) { + return std::nullopt; + } + + NativeApiSignature signature; + signature.implicitArgumentCount = 2; + signature.returnType = + parseObjCEncodedEngineType(methodSignature.methodReturnType); + for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { + signature.argumentTypes.push_back( + parseObjCEncodedEngineType([methodSignature getArgumentTypeAtIndex:i])); + } + if (unsupportedEngineType(signature.returnType)) { + return std::nullopt; + } + for (const auto& argumentType : signature.argumentTypes) { + if (unsupportedEngineType(argumentType)) { + return std::nullopt; + } + } + return signature; +} + +std::optional protocolSymbolFromEngineValue( + Runtime& runtime, const std::shared_ptr& bridge, + const Value& value) { + if (value.isString()) { + std::string name = value.asString(runtime).utf8(runtime); + if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { + return *symbol; + } + return std::nullopt; + } + if (!value.isObject()) { + return std::nullopt; + } + + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->symbol(); + } + + if (stringPropertyOrEmpty(runtime, object, "kind") != "protocol") { + return std::nullopt; + } + + std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); + if (!runtimeName.empty()) { + if (const NativeApiSymbol* symbol = bridge->findProtocol(runtimeName)) { + return *symbol; + } + } + + std::string name = stringPropertyOrEmpty(runtime, object, "name"); + if (!name.empty()) { + if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { + return *symbol; + } + } + + return std::nullopt; +} + +void addEngineExposedMethod(Runtime& runtime, + const std::shared_ptr& bridge, + Class nativeClass, const std::string& selectorName, + NativeApiSignature signature, Function function) { + if (selectorName.empty()) { + return; + } + auto callback = createEngineMethodCallback(runtime, bridge, selectorName, + std::move(signature), std::move(function)); + std::string encoding = objcMethodSignatureForEngineSignature(callback->signature()); + class_replaceMethod(nativeClass, sel_registerName(selectorName.c_str()), + reinterpret_cast(callback->functionPointer()), + encoding.c_str()); +} + +bool addRuntimeProtocolOverrideForName( + Runtime& runtime, const std::shared_ptr& bridge, + Class nativeClass, const std::vector& protocols, + const std::string& propertyName, Function function) { + std::unordered_set visited; + std::function visit = [&](Protocol* protocol) -> bool { + if (protocol == nullptr || !visited.insert(protocol).second) { + return false; + } + + Protocol** inherited = protocol_copyProtocolList(protocol, nullptr); + if (inherited != nullptr) { + unsigned int inheritedCount = 0; + free(inherited); + inherited = protocol_copyProtocolList(protocol, &inheritedCount); + for (unsigned int i = 0; i < inheritedCount; i++) { + if (visit(inherited[i])) { + free(inherited); + return true; + } + } + free(inherited); + } + + for (BOOL required : {YES, NO}) { + unsigned int count = 0; + objc_method_description* descriptions = + protocol_copyMethodDescriptionList(protocol, required, YES, &count); + for (unsigned int i = 0; i < count; i++) { + SEL selector = descriptions[i].name; + const char* selectorName = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorName == nullptr || + jsifySelector(selectorName) != propertyName) { + continue; + } + auto signature = runtimeProtocolMethodSignature(descriptions[i].types); + if (signature) { + addEngineExposedMethod(runtime, bridge, nativeClass, selectorName, + std::move(*signature), std::move(function)); + free(descriptions); + return true; + } + } + free(descriptions); + } + return false; + }; + + for (Protocol* protocol : protocols) { + if (visit(protocol)) { + return true; + } + } + return false; +} + +Object getOwnPropertyDescriptor(Runtime& runtime, const Object& object, + const std::string& name) { + Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectCtor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); + Value args[] = {Value(runtime, object), makeString(runtime, name)}; + Value descriptorValue = + getOwnPropertyDescriptor.call(runtime, static_cast(args), + static_cast(2)); + return descriptorValue.isObject() ? descriptorValue.asObject(runtime) + : Object(runtime); +} + +Value extendNativeApiClass( + Runtime& runtime, const std::shared_ptr& bridge, + const Value* args, size_t count) { + if (count < 2 || !args[0].isObject() || !args[1].isObject()) { + throw JSError( + runtime, "extendClass expects a native class and method object."); + } + + Class baseClass = classFromEngineValue(runtime, args[0]); + if (baseClass == Nil) { + throw JSError( + runtime, "extendClass can only extend native class constructors."); + } + if (class_conformsToProtocol(baseClass, + @protocol(NativeApiClassBuilderProtocol))) { + throw JSError(runtime, + "Cannot extend an already extended class."); + } + + Object methods = args[1].asObject(runtime); + Object options = count >= 3 && args[2].isObject() + ? args[2].asObject(runtime) + : Object(runtime); + std::string requestedName = readOptionalStringProperty(runtime, options, "name"); + if (requestedName.empty()) { + const char* baseName = class_getName(baseClass); + requestedName = std::string(baseName != nullptr ? baseName : "NSObject") + + "_Extended_" + std::to_string(rand()); + } + + std::string className = nextAvailableEngineClassName(requestedName); + Class nativeClass = objc_allocateClassPair(baseClass, className.c_str(), 0); + if (nativeClass == Nil) { + throw JSError(runtime, "Failed to allocate Objective-C class."); + } + + class_addProtocol(nativeClass, @protocol(NativeApiClassBuilderProtocol)); + rememberNativeApiClassBuilder(runtime, bridge, nativeClass); + + NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); + std::vector extensionMembers = + bridge->membersForClass(baseSymbol); + std::vector optionProtocols; + Value protocolsValue = getObjectPropertyOrUndefined(runtime, options, "protocols"); + if (protocolsValue.isObject() && + protocolsValue.asObject(runtime).isArray(runtime)) { + Array protocols = protocolsValue.asObject(runtime).getArray(runtime); + for (size_t i = 0; i < protocols.size(runtime); i++) { + Value protocolValue = protocols.getValueAtIndex(runtime, i); + Protocol* protocol = protocolFromEngineValue(runtime, protocolValue); + std::optional protocolSymbol = + protocolSymbolFromEngineValue(runtime, bridge, protocolValue); + if (protocol != nullptr) { + optionProtocols.push_back(protocol); + class_addProtocol(nativeClass, protocol); + if (!protocolSymbol) { + if (const NativeApiSymbol* runtimeSymbol = + bridge->findProtocolForRuntimePointer(protocol)) { + protocolSymbol = *runtimeSymbol; + } + } + } + if (protocolSymbol) { + const auto& protocolMembers = bridge->membersForProtocol(*protocolSymbol); + extensionMembers.insert(extensionMembers.begin(), + protocolMembers.begin(), + protocolMembers.end()); + } + } + } + const auto& members = extensionMembers; + Array propertyNames = methods.getPropertyNames(runtime); + for (size_t i = 0; i < propertyNames.size(runtime); i++) { + Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); + if (!propertyNameValue.isString()) { + continue; + } + + std::string propertyName = propertyNameValue.asString(runtime).utf8(runtime); + Object descriptor = getOwnPropertyDescriptor(runtime, methods, propertyName); + + Value value = descriptor.getProperty(runtime, "value"); + if (value.isObject() && value.asObject(runtime).isFunction(runtime)) { + auto overrides = methodOverridesForName(members, propertyName); + bool addedOverride = false; + for (const auto& member : overrides) { + if (member.selectorName.empty() || + member.signatureOffset == MD_SECTION_OFFSET_NULL || + member.signatureOffset == 0) { + continue; + } + addEngineOverrideMethod( + runtime, bridge, nativeClass, baseClass, member.selectorName, + member.signatureOffset, + (member.flags & metagen::mdMemberReturnOwned) != 0, + value.asObject(runtime).asFunction(runtime)); + addedOverride = true; + } + if (!addedOverride) { + bool addedRuntimeProtocolOverride = addRuntimeProtocolOverrideForName( + runtime, bridge, nativeClass, optionProtocols, propertyName, + value.asObject(runtime).asFunction(runtime)); + if (!addedRuntimeProtocolOverride) { + if (auto known = knownNativeApiExposedMethod(propertyName)) { + addEngineExposedMethod(runtime, bridge, nativeClass, + known->selectorName, + std::move(known->signature), + value.asObject(runtime).asFunction(runtime)); + } + } + } + } + + const NativeApiMember* propertyMember = + propertyOverrideForName(members, propertyName); + + Value getter = descriptor.getProperty(runtime, "get"); + if (propertyMember != nullptr && getter.isObject() && + getter.asObject(runtime).isFunction(runtime)) { + addEngineOverrideMethod( + runtime, bridge, nativeClass, baseClass, + propertyMember->selectorName, propertyMember->signatureOffset, + (propertyMember->flags & metagen::mdMemberReturnOwned) != 0, + getter.asObject(runtime).asFunction(runtime)); + } else if (propertyMember == nullptr && getter.isObject() && + getter.asObject(runtime).isFunction(runtime)) { + auto overrides = methodOverridesForName(members, propertyName); + for (const auto& member : overrides) { + if (selectorArgumentCount(member.selectorName) != 0) { + continue; + } + addEngineOverrideMethod( + runtime, bridge, nativeClass, baseClass, member.selectorName, + member.signatureOffset, + (member.flags & metagen::mdMemberReturnOwned) != 0, + getter.asObject(runtime).asFunction(runtime)); + } + } + + Value setter = descriptor.getProperty(runtime, "set"); + if (propertyMember != nullptr && + setter.isObject() && setter.asObject(runtime).isFunction(runtime) && + !propertyMember->setterSelectorName.empty()) { + addEngineOverrideMethod(runtime, bridge, nativeClass, baseClass, + propertyMember->setterSelectorName, + propertyMember->setterSignatureOffset, false, + setter.asObject(runtime).asFunction(runtime)); + } + } + + Value exposedMethodsValue = + getObjectPropertyOrUndefined(runtime, options, "exposedMethods"); + if (!exposedMethodsValue.isObject()) { + exposedMethodsValue = + getObjectPropertyOrUndefined(runtime, methods, "ObjCExposedMethods"); + } + if (exposedMethodsValue.isObject()) { + Object exposedMethods = exposedMethodsValue.asObject(runtime); + Array exposedNames = exposedMethods.getPropertyNames(runtime); + for (size_t i = 0; i < exposedNames.size(runtime); i++) { + Value selectorValue = exposedNames.getValueAtIndex(runtime, i); + if (!selectorValue.isString()) { + continue; + } + std::string selectorName = selectorValue.asString(runtime).utf8(runtime); + Value descriptorValue = + getObjectPropertyOrUndefined(runtime, exposedMethods, selectorName); + if (!descriptorValue.isObject()) { + continue; + } + auto function = functionForSelector(runtime, methods, selectorName); + if (!function) { + continue; + } + auto signature = exposedMethodSignature( + runtime, bridge, selectorName, descriptorValue.asObject(runtime)); + if (signature) { + rememberNativeApiKnownExposedMethod(selectorName, *signature); + addEngineExposedMethod(runtime, bridge, nativeClass, selectorName, + std::move(*signature), std::move(*function)); + } + } + } + + Value hasIteratorValue = + getObjectPropertyOrUndefined(runtime, options, "__hasIterator"); + if (hasIteratorValue.isBool() && hasIteratorValue.getBool()) { + class_addProtocol(nativeClass, @protocol(NSFastEnumeration)); + if (const char* encoding = nativeApiEngineFastEnumerationEncoding()) { + class_replaceMethod( + nativeClass, + @selector(countByEnumeratingWithState:objects:count:), + reinterpret_cast(nativeApiEngineSymbolIteratorCountByEnumerating), + encoding); + } + } + + objc_registerClassPair(nativeClass); + + NativeApiSymbol newSymbol = baseSymbol; + newSymbol.name = className; + newSymbol.runtimeName = className; + newSymbol.superclassOffset = baseSymbol.offset; + return makeNativeClassValue(runtime, bridge, std::move(newSymbol)); + } + +Value invokeNativeApiBaseMethod( + Runtime& runtime, const std::shared_ptr& bridge, + const Value* args, size_t count) { + if (count < 3 || !args[0].isObject() || !args[1].isObject() || + !args[2].isString()) { + throw JSError( + runtime, "__invokeBase expects base class, receiver, and member name."); + } + + Class baseClass = classFromEngineValue(runtime, args[0]); + if (baseClass == Nil) { + throw JSError(runtime, "__invokeBase base class is invalid."); + } + + Object receiverObject = args[1].asObject(runtime); + if (!receiverObject.isHostObject(runtime)) { + throw JSError(runtime, "__invokeBase receiver is not native."); + } + + auto receiverHostObject = + receiverObject.getHostObject(runtime); + id receiver = receiverHostObject->object(); + std::string memberName = args[2].asString(runtime).utf8(runtime); + size_t actualArgc = count - 3; + + NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); + const auto& members = bridge->membersForClass(baseSymbol); + const NativeApiMember* member = + selectMethodMember(members, memberName, false, actualArgc); + if (member == nullptr) { + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, memberName, false)) { + if (actualArgc == 0) { + Class dispatchClass = + dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass); + return receiverHostObject->callObjectSelector( + runtime, propertyMember->selectorName, propertyMember, nullptr, 0, + dispatchClass); + } + if (actualArgc == 1 && !propertyMember->setterSelectorName.empty() && + !propertyMember->readonly) { + Class dispatchClass = + dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass); + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + return receiverHostObject->callObjectSelector( + runtime, setterMember.selectorName, &setterMember, args + 3, + actualArgc, dispatchClass); + } + } + } + if (member == nullptr) { + throw JSError( + runtime, "Objective-C base selector is not available: " + memberName); + } + + Class dispatchClass = + dispatchSuperclassForEngineDerivedReceiver(receiver, baseClass); + return receiverHostObject->callObjectSelector(runtime, member->selectorName, + member, args + 3, actualArgc, + dispatchClass); +} diff --git a/NativeScript/ffi/objc/shared/bridge/HostObject.mm b/NativeScript/ffi/objc/shared/bridge/HostObject.mm new file mode 100644 index 000000000..5150f0aed --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/HostObject.mm @@ -0,0 +1,631 @@ +#ifndef NATIVESCRIPT_NATIVE_API_BACKEND_NAME +#error Engine backends must define NATIVESCRIPT_NATIVE_API_BACKEND_NAME. +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_RUNTIME_NAME +#define NATIVESCRIPT_NATIVE_API_RUNTIME_NAME NATIVESCRIPT_NATIVE_API_BACKEND_NAME +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS +inline bool InstallNativeApiLazyGlobal( + Runtime&, std::shared_ptr, const std::string&, + const std::string&, bool) { + return false; +} +#endif + +#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION +#error Engine backends must provide an engine selector group function. +#endif + +class NativeApiHostObject final : public HostObject { + public: + explicit NativeApiHostObject(std::shared_ptr bridge) + : bridge_(std::move(bridge)) {} + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "runtime") { + return makeString(runtime, NATIVESCRIPT_NATIVE_API_RUNTIME_NAME); + } + if (property == "backend") { + return makeString(runtime, NATIVESCRIPT_NATIVE_API_BACKEND_NAME); + } + if (property == "metadata") { + return metadataObject(runtime); + } + if (property == "hasScheduler") { + return bridge_->scheduler() != nullptr; + } + if (property == "interop") { + return createInteropObject(runtime, bridge_); + } +#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS + if (property == "__defineLazyGlobal") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__defineLazyGlobal"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string name = readStringArg(runtime, args, count, 0, "name"); + std::string kind = readStringArg(runtime, args, count, 1, "kind"); + bool force = count > 2 && args[2].isBool() && args[2].getBool(); + return InstallNativeApiLazyGlobal(runtime, bridge, name, kind, + force); + }); + } +#endif + if (property == "__fastEnumeration") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__fastEnumeration"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + throw JSError( + runtime, "Fast enumeration expects a native object."); + } + id object = NativeApiObjectHostObject::nativeObjectFromValue(runtime, args[0]); + if (object == nil) { + throw JSError( + runtime, "Fast enumeration expects a native object."); + } + if (![object conformsToProtocol:@protocol(NSFastEnumeration)]) { + throw JSError( + runtime, "Object does not conform to NSFastEnumeration."); + } + return Object::createFromHostObject( + runtime, + std::make_shared( + bridge, static_cast>(object))); + }); + } + if (property == "import") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "import"), 1, + [](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string path = readStringArg(runtime, args, count, 0, "path"); + std::string frameworkPath = path; + if (!frameworkPath.empty() && frameworkPath[0] != '/') { + frameworkPath = "/System/Library/Frameworks/" + frameworkPath + + ".framework"; + } + + NSBundle* bundle = [NSBundle + bundleWithPath:[NSString stringWithUTF8String:frameworkPath.c_str()]]; + if (bundle == nil || ![bundle load]) { + throw JSError( + runtime, "Could not load bundle: " + frameworkPath); + } + return true; + }); + } + if (property == "lookup") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "lookup"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string symbolName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->find(symbolName); + if (symbol == nullptr) { + return Value::null(); + } + return symbolToObject(runtime, *symbol); + }); + } + if (property == "getClass") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getClass"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string className = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findClass(className); + if (symbol == nullptr) { + Class cls = objc_lookUpClass(className.c_str()); + if (cls == nil) { + return Value::null(); + } + NativeApiSymbol runtimeSymbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = className, + .runtimeName = className, + }; + return makeNativeClassValue(runtime, bridge, + std::move(runtimeSymbol)); + } + + return makeNativeClassValue(runtime, bridge, *symbol); + }); + } + if (property == "__extendClass") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__extendClass"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + return extendNativeApiClass(runtime, bridge, args, count); + }); + } + if (property == "__invokeBase") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__invokeBase"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + return invokeNativeApiBaseMethod(runtime, bridge, args, count); + }); + } + if (property == "__makeSelectorGroupFunction") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__makeSelectorGroupFunction"), + 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 3 || !args[1].isBool() || !args[2].isObject() || + !args[2].asObject(runtime).isArray(runtime)) { + throw JSError( + runtime, + "__makeSelectorGroupFunction expects class, receiver kind, " + "and selector table."); + } + + Class lookupClass = classFromEngineValue(runtime, args[0]); + if (lookupClass == Nil) { + throw JSError(runtime, + "__makeSelectorGroupFunction class is invalid."); + } + + bool receiverIsClass = args[1].getBool(); + Array selectorTable = args[2].asObject(runtime).getArray(runtime); + size_t selectorCount = selectorTable.size(runtime); + auto selectors = + std::make_shared< + std::vector>( + selectorCount); + for (size_t i = 0; i < selectorCount; i++) { + Value selectorValue = selectorTable.getValueAtIndex(runtime, i); + if (selectorValue.isString()) { + (*selectors)[i].selectorName = + selectorValue.asString(runtime).utf8(runtime); + } else if (selectorValue.isObject()) { + Object descriptor = selectorValue.asObject(runtime); + Value selectorNameValue = + descriptor.getProperty(runtime, "selectorName"); + if (!selectorNameValue.isString()) { + continue; + } + NativeApiMember member; + member.selectorName = + selectorNameValue.asString(runtime).utf8(runtime); + Value nameValue = descriptor.getProperty(runtime, "name"); + if (nameValue.isString()) { + member.name = nameValue.asString(runtime).utf8(runtime); + } + Value setterSelectorNameValue = + descriptor.getProperty(runtime, "setterSelectorName"); + if (setterSelectorNameValue.isString()) { + member.setterSelectorName = + setterSelectorNameValue.asString(runtime).utf8(runtime); + } + Value signatureOffsetValue = + descriptor.getProperty(runtime, "signatureOffset"); + if (signatureOffsetValue.isNumber()) { + member.signatureOffset = static_cast( + signatureOffsetValue.getNumber()); + } + Value setterSignatureOffsetValue = + descriptor.getProperty(runtime, "setterSignatureOffset"); + if (setterSignatureOffsetValue.isNumber()) { + member.setterSignatureOffset = static_cast( + setterSignatureOffsetValue.getNumber()); + } + Value flagsValue = descriptor.getProperty(runtime, "flags"); + if (flagsValue.isNumber()) { + member.flags = static_cast( + static_cast(flagsValue.getNumber())); + } + Value propertyValue = descriptor.getProperty(runtime, "property"); + if (propertyValue.isBool()) { + member.property = propertyValue.getBool(); + } + Value readonlyValue = descriptor.getProperty(runtime, "readonly"); + if (readonlyValue.isBool()) { + member.readonly = readonlyValue.getBool(); + } + (*selectors)[i].selectorName = member.selectorName; + (*selectors)[i].member = std::move(member); + (*selectors)[i].hasMember = true; + } + } + + auto preparedInvocations = std::make_shared>>( + selectors->size()); + + return CreateNativeApiSelectorGroupFunction( + runtime, bridge, lookupClass, receiverIsClass, selectors, + preparedInvocations); + }); + } + if (property == "__rememberClassWrapper") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__rememberClassWrapper"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 2) { + return Value::undefined(); + } + Class cls = classFromEngineValue(runtime, args[0]); + if (cls == Nil) { + return Value::undefined(); + } + bridge->rememberClassValue(runtime, cls, args[1]); + if (count >= 3 && args[2].isObject()) { + bridge->rememberClassPrototype(runtime, cls, args[2]); + } + return Value::undefined(); + }); + } + if (property == "__rememberObjectClassWrapper") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "__rememberObjectClassWrapper"), + 2, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 2) { + return Value::undefined(); + } + id object = NativeApiObjectHostObject::nativeObjectFromValue( + runtime, args[0]); + if (object == nil) { + return Value::undefined(); + } + // A factory/class method may return an instance of a different + // class (e.g. +[TNSSwiftLikeFactory create] returns a TNSSwiftLike). + // Only label the object with this wrapper when it actually is an + // instance of the wrapper's class, so `constructor` resolves to the + // object's real class instead of the calling class. + if (args[1].isObject()) { + Class wrapperClass = classFromEngineValue(runtime, args[1]); + if (wrapperClass != Nil && ![object isKindOfClass:wrapperClass]) { + return Value::undefined(); + } + } + bridge->setObjectExpando(runtime, object, + "__nativeApiClassWrapper", args[1]); + if (args[1].isObject()) { + Object classWrapper = args[1].asObject(runtime); + Value prototypeValue = + classWrapper.getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + Object instanceObject = args[0].asObject(runtime); + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, instanceObject, + prototype); + } + } + return Value::undefined(); + }); + } + if (property == "CC_SHA256") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "CC_SHA256"), 3, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 3 || !args[1].isNumber()) { + throw JSError( + runtime, "CC_SHA256 expects data, length, and output."); + } + void* commonCrypto = + dlopen("/usr/lib/system/libcommonCrypto.dylib", + RTLD_NOW | RTLD_LOCAL); + void* symbol = commonCrypto != nullptr + ? dlsym(commonCrypto, "CC_SHA256") + : nullptr; + if (symbol == nullptr && commonCrypto != nullptr) { + symbol = dlsym(commonCrypto, "_CC_SHA256"); + } + if (symbol == nullptr) { + throw JSError(runtime, + "CC_SHA256 is not available."); + } + NativeApiArgumentFrame frame(3); + void* data = pointerFromEngineValue(runtime, bridge, args[0], frame); + void* output = + pointerFromEngineValue(runtime, bridge, args[2], frame); + using CC_SHA256_Fn = unsigned char* (*)(const void*, unsigned long, + unsigned char*); + auto fn = reinterpret_cast(symbol); + unsigned char* result = + fn(data, static_cast(args[1].getNumber()), + static_cast(output)); + return createPointer(runtime, bridge, result); + }); + } + if (property == "getFunction") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getFunction"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string functionName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findFunction(functionName); + if (symbol == nullptr) { + return Value::null(); + } + auto prepared = + std::make_shared(); + prepared->symbol = *symbol; + auto function = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, symbol->name), 0, + [bridge, prepared](Runtime& runtime, const Value&, + const Value* args, + size_t count) -> Value { + return callCFunction(runtime, bridge, prepared, args, count); + }); + function.setProperty(runtime, "kind", makeString(runtime, "function")); + function.setProperty(runtime, "nativeName", + makeString(runtime, symbol->name)); + function.setProperty(runtime, "metadataOffset", + static_cast(symbol->offset)); + function.setProperty(runtime, "sizeof", + static_cast(sizeof(void*))); + return function; + }); + } + if (property == "getConstant") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getConstant"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string constantName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findConstant(constantName); + if (symbol == nullptr) { + return Value::undefined(); + } + return constantToValue(runtime, bridge, *symbol); + }); + } + if (property == "getEnum") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getEnum"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string enumName = readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findEnum(enumName); + if (symbol == nullptr) { + return Value::undefined(); + } + return enumToObject(runtime, bridge->metadata(), *symbol); + }); + } + if (property == "getEnumGlobalNames") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getEnumGlobalNames"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string enumName = readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findEnum(enumName); + if (symbol == nullptr) { + return namesToArray(runtime, {}); + } + return namesToArray(runtime, + enumGlobalNames(bridge->metadata(), *symbol)); + }); + } + if (property == "getProtocol") { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "getProtocol"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string protocolName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = bridge->findProtocol(protocolName); + if (symbol == nullptr) { + Protocol* protocol = lookupProtocolByNativeName(protocolName); + if (protocol == nullptr) { + return Value::null(); + } + const char* runtimeName = protocol_getName(protocol); + NativeApiSymbol runtimeSymbol{ + .kind = NativeApiSymbolKind::Protocol, + .offset = MD_SECTION_OFFSET_NULL, + .name = protocolName, + .runtimeName = runtimeName != nullptr ? runtimeName : protocolName, + }; + return makeNativeProtocolValue(runtime, bridge, + std::move(runtimeSymbol)); + } + return makeNativeProtocolValue(runtime, bridge, *symbol); + }); + } + if (property == "getStruct" || property == "getUnion") { + auto bridge = bridge_; + bool isUnion = property == "getUnion"; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, isUnion](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string aggregateName = + readStringArg(runtime, args, count, 0, "name"); + const NativeApiSymbol* symbol = + isUnion ? bridge->findUnion(aggregateName) + : bridge->findStruct(aggregateName); + if (symbol == nullptr) { + return Value::undefined(); + } + return makeAggregateConstructor(runtime, bridge, *symbol); + }); + } + + if (const NativeApiSymbol* classSymbol = bridge_->findClass(property)) { + return makeNativeClassValue(runtime, bridge_, *classSymbol); + } + + if (const NativeApiSymbol* functionSymbol = bridge_->findFunction(property)) { + auto prepared = + std::make_shared(); + prepared->symbol = *functionSymbol; + auto bridge = bridge_; + Function function = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [bridge, prepared](Runtime& runtime, const Value&, + const Value* args, + size_t count) -> Value { + return callCFunction(runtime, bridge, prepared, args, count); + }); + function.setProperty(runtime, "kind", makeString(runtime, "function")); + function.setProperty(runtime, "nativeName", + makeString(runtime, functionSymbol->name)); + function.setProperty(runtime, "metadataOffset", + static_cast(functionSymbol->offset)); + function.setProperty(runtime, "sizeof", + static_cast(sizeof(void*))); + return function; + } + + if (const NativeApiSymbol* constantSymbol = bridge_->findConstant(property)) { + return constantToValue(runtime, bridge_, *constantSymbol); + } + + if (const NativeApiSymbol* enumSymbol = bridge_->findEnum(property)) { + return enumToObject(runtime, bridge_->metadata(), *enumSymbol); + } + + if (const NativeApiSymbol* protocolSymbol = + bridge_->findProtocol(property)) { + return makeNativeProtocolValue(runtime, bridge_, *protocolSymbol); + } + + if (const NativeApiSymbol* aggregateSymbol = + bridge_->findAggregate(property)) { + return makeAggregateConstructor(runtime, bridge_, *aggregateSymbol); + } + + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(11); + addPropertyName(runtime, names, "runtime"); + addPropertyName(runtime, names, "backend"); + addPropertyName(runtime, names, "metadata"); + addPropertyName(runtime, names, "hasScheduler"); + addPropertyName(runtime, names, "interop"); +#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS + addPropertyName(runtime, names, "__defineLazyGlobal"); +#endif + addPropertyName(runtime, names, "import"); + addPropertyName(runtime, names, "lookup"); + addPropertyName(runtime, names, "getClass"); + addPropertyName(runtime, names, "__extendClass"); + addPropertyName(runtime, names, "__invokeBase"); + addPropertyName(runtime, names, "__makeSelectorGroupFunction"); + addPropertyName(runtime, names, "__rememberClassWrapper"); + addPropertyName(runtime, names, "__rememberObjectClassWrapper"); + addPropertyName(runtime, names, "getFunction"); + addPropertyName(runtime, names, "getConstant"); + addPropertyName(runtime, names, "getEnum"); + addPropertyName(runtime, names, "getEnumGlobalNames"); + addPropertyName(runtime, names, "getProtocol"); + addPropertyName(runtime, names, "getStruct"); + addPropertyName(runtime, names, "getUnion"); + return names; + } + + private: + Object metadataObject(Runtime& runtime) const { + Object metadata(runtime); + metadata.setProperty(runtime, "classes", + static_cast(bridge_->classCount())); + metadata.setProperty(runtime, "functions", + static_cast(bridge_->functionCount())); + metadata.setProperty(runtime, "constants", + static_cast(bridge_->constantCount())); + metadata.setProperty(runtime, "protocols", + static_cast(bridge_->protocolCount())); + metadata.setProperty(runtime, "enums", + static_cast(bridge_->enumCount())); + metadata.setProperty(runtime, "structs", + static_cast(bridge_->structCount())); + metadata.setProperty(runtime, "unions", + static_cast(bridge_->unionCount())); + + metadata.setProperty( + runtime, "classNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "classNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->classNames()); + })); + metadata.setProperty( + runtime, "functionNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "functionNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->functionNames()); + })); + metadata.setProperty( + runtime, "constantNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "constantNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->constantNames()); + })); + metadata.setProperty( + runtime, "protocolNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "protocolNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->protocolNames()); + })); + metadata.setProperty( + runtime, "enumNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "enumNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->enumNames()); + })); + metadata.setProperty( + runtime, "structNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "structNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->structNames()); + })); + metadata.setProperty( + runtime, "unionNames", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "unionNames"), 0, + [bridge = bridge_](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + return namesToArray(runtime, bridge->unionNames()); + })); + return metadata; + } + + std::shared_ptr bridge_; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/HostObjects.mm b/NativeScript/ffi/objc/shared/bridge/HostObjects.mm new file mode 100644 index 000000000..7a88d07e3 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/HostObjects.mm @@ -0,0 +1,54 @@ +// HostObject::set returns bool on engines whose interceptors can defer an +// unhandled set to the JS prototype chain. JSI's HostObject::set is void, so +// the Hermes backend defines NATIVESCRIPT_NATIVE_API_HOST_SET_VOID and the +// set overrides below collapse their return type/values accordingly. +#ifdef NATIVESCRIPT_NATIVE_API_HOST_SET_VOID +using NativeApiHostSetResult = void; +#define NATIVE_API_SET_RETURN(handled) return +#else +using NativeApiHostSetResult = bool; +#define NATIVE_API_SET_RETURN(handled) return (handled) +#endif + +// Engine-neutral factory for native object instance wrappers. V8 uses its +// kNonMasking native instance template (fast prototype-based property access); +// every other engine uses its standard host-object creation. Selected at +// compile time so the shared bridge code stays engine-agnostic. +template +Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { +#if defined(TARGET_ENGINE_V8) || defined(TARGET_ENGINE_JSC) + return Object::createNativeInstanceHostObject(runtime, std::move(host)); +#else + return Object::createFromHostObject(runtime, std::move(host)); +#endif +} + +class NativeApiObjectLifetimeState final { + public: + explicit NativeApiObjectLifetimeState(id object) + : object_(reinterpret_cast(object)) {} + + id object() const { + return reinterpret_cast(object_.load(std::memory_order_relaxed)); + } + + void setObject(id object) { + object_.store(reinterpret_cast(object), std::memory_order_relaxed); + } + + void clear() { object_.store(nullptr, std::memory_order_relaxed); } + + private: + std::atomic object_{nullptr}; +}; + + +#include "host_objects/Interop.mm" + +#include "host_objects/Struct.mm" + +#include "host_objects/Object.mm" + +#include "host_objects/Class.mm" + +#include "host_objects/Protocol.mm" diff --git a/NativeScript/ffi/objc/shared/bridge/Install.mm b/NativeScript/ffi/objc/shared/bridge/Install.mm new file mode 100644 index 000000000..4c219a0c0 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Install.mm @@ -0,0 +1,1855 @@ +Object CreateNativeApi(Runtime& runtime, const NativeApiConfig& config) { + auto bridge = std::make_shared(config); + return Object::createFromHostObject(runtime, + std::make_shared(std::move(bridge))); +} + +void NativeApiWriteSmokeStage(const char* stage) { + const char* enabled = getenv("NATIVESCRIPT_RN_TURBO_SMOKE_MARKER"); + if (enabled == nullptr || enabled[0] == '\0') { + return; + } + + NSString* path = + [NSTemporaryDirectory() stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; + NSString* content = [NSString stringWithFormat:@"stage=%s\n", stage != nullptr ? stage : ""]; + [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; +} + +void InstallAggregateGlobals(Runtime& runtime, Object& api, const char* namesFunction) { + Value metadataValue = api.getProperty(runtime, "metadata"); + if (!metadataValue.isObject()) { + return; + } + Object metadata = metadataValue.asObject(runtime); + Value namesValue = metadata.getProperty(runtime, namesFunction); + if (!namesValue.isObject()) { + return; + } + Object namesObject = namesValue.asObject(runtime); + if (!namesObject.isFunction(runtime)) { + return; + } + Value namesResult = namesObject.asFunction(runtime).call(runtime); + if (!namesResult.isObject() || !namesResult.asObject(runtime).isArray(runtime)) { + return; + } + Array names = namesResult.asObject(runtime).getArray(runtime); + Object global = runtime.global(); + for (size_t i = 0; i < names.size(runtime); i++) { + Value nameValue = names.getValueAtIndex(runtime, i); + if (!nameValue.isString()) { + continue; + } + std::string name = nameValue.asString(runtime).utf8(runtime); + if (name.empty() || global.hasProperty(runtime, name.c_str())) { + continue; + } + try { + Value aggregate = api.getProperty(runtime, name.c_str()); + if (!aggregate.isUndefined()) { + global.setProperty(runtime, name.c_str(), aggregate); + } + } catch (const std::exception&) { + // Some React Native globals are read-only even when hasProperty misses + // them. Keep NativeScript initialization resilient and skip collisions. + } + } +} + +std::string jsStringLiteral(const char* value) { + std::string result = "'"; + if (value != nullptr) { + for (const char* current = value; *current != '\0'; current++) { + switch (*current) { + case '\\': + result += "\\\\"; + break; + case '\'': + result += "\\'"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += *current; + break; + } + } + } + result += "'"; + return result; +} + +void InstallNativeApiGlobalSymbols(Runtime& runtime, const char* globalName) { + NativeApiWriteSmokeStage("engine:globals:before-eval"); + static const char* GlobalInstaller = R"Engine_GLOBALS( +(function(nativeApiGlobalName) { + 'use strict'; + var api = globalThis[nativeApiGlobalName]; + var installedFlagName = '__nativeScriptNativeApiGlobalsInstalled'; + if (!api || globalThis[installedFlagName]) { + return; + } + + var cacheName = '__nativeScriptNativeApiGlobalCache'; + var typeCodeKey = '__nativeApiTypeCode'; + var classWrappers = typeof WeakMap === 'function' ? new WeakMap() : null; + var classWrappersByName = Object.create(null); + var resolvingGlobal = Object.create(null); + + function globalCache() { + var existing = globalThis[cacheName]; + if (existing && typeof existing === 'object') { + return existing; + } + var cache = Object.create(null); + Object.defineProperty(globalThis, cacheName, { + configurable: false, + enumerable: false, + writable: false, + value: cache + }); + return cache; + } + + function cacheGlobal(name, value) { + if (name && value !== undefined) { + globalCache()[name] = value; + } + } + + function resolveCachedGlobal(name, expectedKind) { + if (!name) { + return undefined; + } + var cached = globalCache()[name]; + if (cached && (typeof cached === 'object' || typeof cached === 'function') && cached.kind === expectedKind) { + return cached; + } + if (resolvingGlobal[name] || !Object.prototype.hasOwnProperty.call(globalThis, name)) { + return undefined; + } + resolvingGlobal[name] = true; + try { + var value = globalThis[name]; + if (value && (typeof value === 'object' || typeof value === 'function') && value.kind === expectedKind) { + cacheGlobal(name, value); + return value; + } + } finally { + delete resolvingGlobal[name]; + } + return undefined; + } + + function defineLazyGlobal(name, resolve, force, nativeKind) { + if (!name) { + return; + } + if (!force && Object.prototype.hasOwnProperty.call(globalThis, name)) { + try { + var existingDescriptor = Object.getOwnPropertyDescriptor(globalThis, name); + if (existingDescriptor && Object.prototype.hasOwnProperty.call(existingDescriptor, 'value')) { + cacheGlobal(name, existingDescriptor.value); + } + } catch (_) { + } + return; + } + var nativeDefineLazyGlobal = api.__defineLazyGlobal; + if (nativeKind && typeof nativeDefineLazyGlobal === 'function' && + typeof globalThis.__nativeScriptResolveNativeApiLazyGlobal === 'function') { + try { + if (nativeDefineLazyGlobal(name, nativeKind, !!force)) { + return; + } + } catch (_) { + } + } + try { + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + get: function() { + var value = resolve(name); + cacheGlobal(name, value); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: true, + value: value + }); + return value; + }, + set: function(value) { + // Assignment over a lazy global must behave like a plain global + // assignment (@nativescript/core writes shims such as + // global.System), not throw "no setter for property". + cacheGlobal(name, value); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + } + }); + } catch (_) { + var value = resolve(name); + if (value !== undefined) { + cacheGlobal(name, value); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: true, + value: value + }); + } + } + } + + Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiGlobal', { + configurable: false, + enumerable: false, + writable: false, + value: resolveCachedGlobal + }); + + Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiClassWrapper', { + configurable: false, + enumerable: false, + writable: false, + value: function(name) { + return name ? classWrappersByName[name] : undefined; + } + }); + + function findPrototypeDescriptor(className, property) { + var prototype; + if (className && (typeof className === 'object' || typeof className === 'function')) { + prototype = className; + } else { + var wrapper = className ? classWrappersByName[className] : undefined; + prototype = wrapper && wrapper.prototype; + } + while (prototype != null) { + var descriptor = Object.getOwnPropertyDescriptor(prototype, property); + if (descriptor) { + return descriptor; + } + prototype = Object.getPrototypeOf(prototype); + } + return undefined; + } + + Object.defineProperty(globalThis, '__nativeScriptCreateNativeApiIterator', { + configurable: false, + enumerable: false, + writable: false, + value: function(receiver, prototype) { + if (!receiver || typeof Symbol !== 'function') { + return undefined; + } + var descriptor = findPrototypeDescriptor(prototype || receiver.className, Symbol.iterator); + if (descriptor && typeof descriptor.value === 'function') { + return descriptor.value.call(receiver); + } + if (descriptor && typeof descriptor.get === 'function') { + var getterValue = descriptor.get.call(receiver); + if (typeof getterValue === 'function') { + return getterValue.call(receiver); + } + } + var iteratorMethod = receiver[Symbol.iterator]; + return typeof iteratorMethod === 'function' + ? iteratorMethod.call(receiver) + : undefined; + } + }); + + function wrapAggregateConstructor(nativeConstructor) { + if (typeof nativeConstructor !== 'function') { + return nativeConstructor; + } + var aggregate = function NativeScriptAggregate(initialValue) { + return nativeConstructor(initialValue); + }; + try { + Object.defineProperty(aggregate, Symbol.hasInstance, { + configurable: true, + enumerable: false, + value: function(value) { + return !!value && + typeof value === 'object' && + value.kind === nativeConstructor.kind && + value.name === nativeConstructor.runtimeName; + } + }); + } catch (_) { + } + ['kind', 'runtimeName', 'metadataOffset', 'sizeof', 'fields', 'equals'].forEach(function(key) { + try { + Object.defineProperty(aggregate, key, { + configurable: true, + enumerable: false, + writable: false, + value: nativeConstructor[key] + }); + } catch (_) { + } + }); + return aggregate; + } + + function setDescriptorValue(target, property, receiver, value) { + for (var current = target; current; current = Object.getPrototypeOf(current)) { + var descriptor = Object.getOwnPropertyDescriptor(current, property); + if (!descriptor) { + continue; + } + if (typeof descriptor.set === 'function') { + descriptor.set.call(receiver, value); + return true; + } + if (descriptor.writable) { + if (receiver && receiver !== current) { + Object.defineProperty(receiver, property, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + } else { + current[property] = value; + } + return true; + } + return false; + } + return false; + } + + function setInheritedNativeClassValue(target, property, value) { + for (var current = Object.getPrototypeOf(target); + current && current !== Function.prototype; + current = Object.getPrototypeOf(current)) { + var nativeClassValue; + try { + nativeClassValue = current.__nativeApiClass; + } catch (_) { + nativeClassValue = null; + } + if (!nativeClassValue) { + continue; + } + try { + nativeClassValue[property] = value; + return true; + } catch (_) { + } + } + return false; + } + + function isConstructorOptions(value) { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return false; + } + if (value.kind || value.nativeAddress || value instanceof Date) { + return false; + } + return Object.getPrototypeOf(value) === Object.prototype || + Object.getPrototypeOf(value) === null; + } + + function capitalizeToken(value) { + value = String(value || ''); + return value ? value.charAt(0).toUpperCase() + value.slice(1) : value; + } + + function selectorCandidatesFromOptions(options) { + var keys = Object.keys(options || {}); + if (!keys.length) { + return []; + } + var first = capitalizeToken(keys[0]); + var tail = ''; + for (var i = 1; i < keys.length; i++) { + tail += keys[i] + ':'; + } + return [ + 'initWith' + first + ':' + tail, + 'init' + first + ':' + tail + ]; + } + + function valuesFromOptions(options) { + return Object.keys(options || {}).map(function(key) { + return options[key]; + }); + } + + function selectorScoreForArguments(selectorName, args) { + if (!selectorName || selectorName.indexOf('init') !== 0) { + return -1; + } + if (selectorName === 'init') { + return args.length === 0 ? 100 : -1; + } + if (args.length === 0) { + return -1; + } + var lower = selectorName.toLowerCase(); + var first = args[0]; + var score = 1; + if (Array.isArray(first)) { + if (lower.indexOf('array') !== -1) { + score += 40; + } + } else if (typeof first === 'string') { + if (lower.indexOf('string') !== -1) { + score += 40; + } + if (lower.indexOf('url') !== -1) { + score += 10; + } + } else if (typeof first === 'number') { + if (lower.indexOf('primitive') !== -1) { + score += 50; + } + if (lower.indexOf('int') !== -1 || + lower.indexOf('integer') !== -1 || + lower.indexOf('number') !== -1 || + lower.indexOf('float') !== -1 || + lower.indexOf('double') !== -1 || + lower.indexOf('long') !== -1 || + lower.indexOf('short') !== -1) { + score += 30; + } + } else if (isConstructorOptions(first)) { + if (lower.indexOf('struct') !== -1 || + lower.indexOf('structure') !== -1) { + score += 40; + } + if (lower.indexOf('dictionary') !== -1) { + score += 20; + } + } else if (first === null || typeof first === 'undefined') { + score += 5; + } else if (lower.indexOf('object') !== -1 || + lower.indexOf('url') !== -1 || + lower.indexOf('data') !== -1) { + score += 20; + } + + var allStrings = args.length > 1 && args.every(function(value) { + return typeof value === 'string'; + }); + var allNumbers = args.length > 1 && args.every(function(value) { + return typeof value === 'number'; + }); + if (allStrings && lower.indexOf('string') !== -1) { + score += 25; + } + if (allNumbers && + (lower.indexOf('int') !== -1 || lower.indexOf('number') !== -1)) { + score += 25; + } + return score; + } + + function initializerMembers(nativeClass, argumentCount) { + var metadataMembers = nativeClass.__instanceMembers || []; + var runtimeMembers = nativeClass.__runtimeInstanceMembers || []; + var members = metadataMembers.concat(runtimeMembers); + var result = []; + for (var i = 0; i < members.length; i++) { + var member = members[i]; + if (!member || member.property || !member.selectorName) { + continue; + } + if (member.selectorName.indexOf('init') !== 0) { + continue; + } + if (typeof argumentCount === 'number' && + member.argumentCount !== argumentCount) { + continue; + } + result.push(member); + } + return result; + } + + function chooseInitializer(nativeClass, args, optionSelectors) { + var members = initializerMembers(nativeClass, args.length); + if (!members.length) { + return null; + } + if (optionSelectors && optionSelectors.length) { + for (var i = 0; i < optionSelectors.length; i++) { + for (var j = 0; j < members.length; j++) { + if (members[j].selectorName === optionSelectors[i]) { + return members[j]; + } + } + } + } + + var best = null; + var bestScore = -1; + for (var k = 0; k < members.length; k++) { + var score = selectorScoreForArguments(members[k].selectorName, args); + if (score > bestScore) { + bestScore = score; + best = members[k]; + } + } + return bestScore >= 0 ? best : null; + } + + function chooseInitializerBySelectors(nativeClass, args, selectors) { + if (!selectors || !selectors.length) { + return null; + } + var members = initializerMembers(nativeClass, args.length); + for (var i = 0; i < selectors.length; i++) { + for (var j = 0; j < members.length; j++) { + if (members[j].selectorName === selectors[i]) { + return members[j]; + } + } + } + return null; + } + + function unavailableInitializerError(error) { + return error && + /Objective-C selector is not available/.test(String(error.message || error)); + } + + function constructNativeInstance(nativeClass, args, rememberInstance) { + if (args.length === 1 && + args[0] && + typeof args[0] === 'object' && + (args[0].kind === 'pointer' || args[0].kind === 'reference') && + typeof nativeClass.construct === 'function') { + return nativeClass.construct(args[0]); + } + + var actualArgs = args; + var initializer = null; + if (args.length === 1 && isConstructorOptions(args[0])) { + var optionSelectors = selectorCandidatesFromOptions(args[0]); + if (!optionSelectors.length) { + throw new Error('No initializer found that matches constructor invocation.'); + } + var optionArgs = valuesFromOptions(args[0]); + initializer = chooseInitializerBySelectors( + nativeClass, + optionArgs, + optionSelectors + ); + if (initializer) { + actualArgs = optionArgs; + } + } + if (!initializer) { + initializer = chooseInitializer(nativeClass, actualArgs, null); + } + if (!initializer) { + throw new Error('No initializer found that matches constructor invocation.'); + } + if (typeof nativeClass.alloc !== 'function') { + throw new Error('Native class cannot be allocated'); + } + var instance = nativeClass.alloc(); + if (typeof rememberInstance === 'function') { + instance = rememberInstance(instance); + } + if (initializer.selectorName === 'init') { + if (typeof instance.init !== 'function') { + throw new Error('No initializer found that matches constructor invocation.'); + } + return instance.init(); + } + try { + if (initializer.name && typeof instance[initializer.name] === 'function') { + return instance[initializer.name](...actualArgs); + } + var invokeArgs = [initializer.selectorName]; + for (var invokeArgIndex = 0; invokeArgIndex < actualArgs.length; invokeArgIndex++) { + invokeArgs.push(actualArgs[invokeArgIndex]); + } + return instance.invoke(...invokeArgs); + } catch (error) { + if (unavailableInitializerError(error)) { + throw new Error('No initializer found that matches constructor invocation.'); + } + throw error; + } + } + + function wrapNativeClass(nativeClass) { + if (!nativeClass || (typeof nativeClass !== 'object' && typeof nativeClass !== 'function')) { + return nativeClass; + } + var nativeClassName = nativeClass.runtimeName || nativeClass.name || ''; + if (nativeClassName && classWrappersByName[nativeClassName]) { + if (classWrappers) { + try { + classWrappers.set(nativeClass, classWrappersByName[nativeClassName]); + } catch (_) { + } + } + return classWrappersByName[nativeClassName]; + } + if (classWrappers) { + var cached = classWrappers.get(nativeClass); + if (cached) { + return cached; + } + } + var constructable = function NativeScriptNativeClass() { + var args = Array.prototype.slice.call(arguments); + var redirectConstructor = this && this.constructor; + if (redirectConstructor && + redirectConstructor !== constructable && + redirectConstructor !== wrapper && + typeof redirectConstructor.__nativeApiEnsureClass === 'function') { + var redirectedWrapper = redirectConstructor.__nativeApiEnsureClass(); + if (redirectedWrapper && + redirectedWrapper !== constructable && + redirectedWrapper !== wrapper && + typeof redirectedWrapper === 'function') { + return rememberClassOnInstance( + redirectedWrapper.call(this, ...args), + redirectConstructor + ); + } + } + if (args.length > 0) { + return rememberInstanceClass(constructNativeInstance(nativeClass, args, rememberInstanceClass)); + } + if (typeof nativeClass.new !== 'function') { + throw new Error('Native class cannot be initialized'); + } + return rememberInstanceClass(nativeClass.new()); + }; + function rememberInstanceClass(instance) { + return rememberClassOnInstance(instance, wrapper || constructable); + } + // Static allocators may be invoked with a TypeScript-derived class as + // the receiver (core does `_super.new.call(this)`); those must + // materialize and allocate the derived Objective-C class, not the base. + function derivedClassWrapper(target) { + if (target && target !== constructable && target !== wrapper && + typeof target.__nativeApiEnsureClass === 'function') { + var derived = target.__nativeApiEnsureClass(); + if (derived && derived !== constructable && derived !== wrapper) { + return derived; + } + } + return undefined; + } + try { + Object.defineProperty(constructable, 'name', { + configurable: true, + enumerable: false, + value: nativeClassName || nativeClass.name || 'NativeScriptNativeClass' + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'extend', { + configurable: true, + enumerable: false, + writable: false, + value: function(methods, options) { + if (methods == null || typeof methods !== 'object') { + throw new Error('extend() first parameter must be an object'); + } + var extendOptions = options || {}; + if (typeof Symbol === 'function' && + Object.prototype.hasOwnProperty.call(methods, Symbol.iterator)) { + try { + extendOptions = Object.assign({}, extendOptions, { + __hasIterator: true + }); + } catch (_) { + extendOptions.__hasIterator = true; + } + } + var extendedNativeClass = api.__extendClass(nativeClass, methods, extendOptions); + var extended = wrapNativeClass(extendedNativeClass); + try { + Object.setPrototypeOf(extended, wrapper || constructable); + } catch (_) { + } + var extendedPrototype = Object.create(constructable.prototype || null); + try { + Object.defineProperties(extendedPrototype, Object.getOwnPropertyDescriptors(methods)); + } catch (_) { + Object.keys(methods).forEach(function(key) { + extendedPrototype[key] = methods[key]; + }); + } + try { + Object.defineProperty(extendedPrototype, 'constructor', { + configurable: true, + enumerable: false, + writable: true, + value: extended + }); + } catch (_) { + } + extended.prototype = extendedPrototype; + try { + api.__rememberClassWrapper(extendedNativeClass, extended, extendedPrototype); + } catch (_) { + } + return extended; + } + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'alloc', { + configurable: true, + enumerable: false, + writable: true, + value: function() { + if (arguments.length !== 0) { + throw new Error('alloc does not take arguments; use invoke for an explicit Objective-C selector.'); + } + var derived = derivedClassWrapper(this); + if (derived && typeof derived.alloc === 'function') { + return rememberClassOnInstance(derived.alloc.apply(derived, arguments), this); + } + return rememberInstanceClass(nativeClass.alloc.apply(nativeClass, arguments)); + } + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'new', { + configurable: true, + enumerable: false, + writable: false, + value: function() { + if (arguments.length !== 0) { + throw new Error('new does not take arguments; use invoke for an explicit Objective-C selector.'); + } + var derived = derivedClassWrapper(this); + if (derived && typeof derived.new === 'function') { + return rememberClassOnInstance(derived.new(), this); + } + if (typeof nativeClass.new !== 'function') { + throw new Error('Native class cannot be initialized'); + } + return rememberInstanceClass(nativeClass.new()); + } + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'caller', { + configurable: true, + enumerable: false, + writable: false, + value: null + }); + } catch (_) { + } + try { + Object.defineProperty(constructable, 'arguments', { + configurable: true, + enumerable: false, + writable: false, + value: null + }); + } catch (_) { + } + var basePrototypeTarget = {}; + var classMembersInstalled = false; + function selectorArgumentCount(selectorName) { + var count = 0; + if (typeof selectorName !== 'string') { + return count; + } + for (var i = 0; i < selectorName.length; i++) { + if (selectorName.charCodeAt(i) === 58) { + count++; + } + } + return count; + } + function selectorDescriptor(member, selectorName, signatureOffset, argumentCount, runtimeOnly) { + return { + name: member.name || '', + selectorName: selectorName || '', + setterSelectorName: member.setterSelectorName || '', + signatureOffset: typeof signatureOffset === 'number' ? signatureOffset : 0, + setterSignatureOffset: typeof member.setterSignatureOffset === 'number' + ? member.setterSignatureOffset + : 0, + flags: typeof member.flags === 'number' ? member.flags : 0, + property: !!member.property, + readonly: !!member.readonly, + argumentCount: typeof argumentCount === 'number' + ? argumentCount + : selectorArgumentCount(selectorName), + runtimeOnly: !!runtimeOnly + }; + } + function addSelectorGroups(groups, members, runtimeOnly) { + if (!groups || !members || typeof members.length !== 'number') { + return; + } + for (var i = 0; i < members.length; i++) { + var member = members[i]; + if (!member || member.property || !member.name || !member.selectorName) { + continue; + } + // Skip methods that need special interceptor handling with kNonMasking. + if (member.name === 'superclass' || member.name === 'class' || + member.name === 'constructor' || member.name === 'className') { + continue; + } + var argumentCount = typeof member.argumentCount === 'number' + ? member.argumentCount + : 0; + var group = groups[member.name]; + if (!group) { + group = []; + groups[member.name] = group; + } + if (group[argumentCount] === undefined) { + group[argumentCount] = selectorDescriptor( + member, + member.selectorName, + member.signatureOffset, + argumentCount, + runtimeOnly + ); + } + // Methods with a trailing NSError** out-parameter (selector ending in + // "error:") may be called with the error argument omitted, so register + // the error-omitted arity too. + if (argumentCount > 0 && + /error:$/.test(member.selectorName) && + group[argumentCount - 1] === undefined) { + group[argumentCount - 1] = selectorDescriptor( + member, + member.selectorName, + member.signatureOffset, + argumentCount - 1, + runtimeOnly + ); + } + } + } + function installSelectorGroups(target, groups, receiverIsClass) { + if (!target || !groups) { + return; + } + for (var name in groups) { + if (!Object.prototype.hasOwnProperty.call(groups, name) || + Object.prototype.hasOwnProperty.call(target, name)) { + continue; + } + var selectors = groups[name]; + if (!selectors || !selectors.length) { + continue; + } + var hasMetadataSelector = false; + for (var selectorIndex = 0; selectorIndex < selectors.length; selectorIndex++) { + if (selectors[selectorIndex] && !selectors[selectorIndex].runtimeOnly) { + hasMetadataSelector = true; + break; + } + } + if (!hasMetadataSelector && receiverIsClass && name in target) { + continue; + } + var selectorFunction = + api.__makeSelectorGroupFunction(nativeClass, !!receiverIsClass, selectors); + Object.defineProperty(target, name, { + configurable: true, + enumerable: false, + writable: true, + value: receiverIsClass + ? (function(fn, memberName) { + return function() { + if (this && typeof this === 'object' && this.kind === 'object') { + var baseArgs = [nativeClass, this, memberName]; + for (var baseArgIndex = 0; baseArgIndex < arguments.length; baseArgIndex++) { + baseArgs.push(arguments[baseArgIndex]); + } + return api.__invokeBase(...baseArgs); + } + var args = []; + for (var argIndex = 0; argIndex < arguments.length; argIndex++) { + args.push(arguments[argIndex]); + } + return rememberInstanceClass(fn(...args)); + }; + })(selectorFunction, name) + : selectorFunction + }); + } + } + function installClassMembers(target, members, receiverIsClass, runtimeMembers) { + var hasMetadataMembers = members && typeof members.length === 'number'; + var hasRuntimeMembers = runtimeMembers && typeof runtimeMembers.length === 'number'; + if (!target || (!hasMetadataMembers && !hasRuntimeMembers)) { + return; + } + var selectorGroups = Object.create(null); + addSelectorGroups(selectorGroups, members, false); + for (var i = 0; hasMetadataMembers && i < members.length; i++) { + var member = members[i]; + if (!member || !member.name) { + continue; + } + if (member.property) { + // Skip properties that need special interceptor handling (they + // return wrapped class constructors, not raw native values). + if (member.name === 'superclass' || member.name === 'class' || + member.name === 'constructor' || member.name === 'debugDescription' || + member.name === 'className') { + continue; + } + var existingDescriptor = Object.getOwnPropertyDescriptor(target, member.name); + if (existingDescriptor && + (typeof existingDescriptor.get === 'function' || + typeof existingDescriptor.set === 'function')) { + continue; + } + var getterFunction = member.selectorName + ? api.__makeSelectorGroupFunction( + nativeClass, + !!receiverIsClass, + [selectorDescriptor(member, member.selectorName, member.signatureOffset, 0)] + ) + : undefined; + var setterFunction = !member.readonly && member.setterSelectorName + ? api.__makeSelectorGroupFunction( + nativeClass, + !!receiverIsClass, + [ + null, + selectorDescriptor( + member, + member.setterSelectorName, + member.setterSignatureOffset, + 1 + ) + ] + ) + : undefined; + var descriptor = { + configurable: true, + enumerable: false, + get: receiverIsClass + ? (function(name, selectorName) { + return function() { + return selectorName + ? nativeClass.invoke(selectorName) + : nativeClass[name]; + }; + })(member.name, member.selectorName) + : (getterFunction || (function(name) { + return function() { + return api.__invokeBase(nativeClass, this, name); + }; + })(member.name)) + }; + if (!member.readonly) { + descriptor.set = receiverIsClass + ? (function(name, setterSelectorName) { + return function(value) { + if (setterSelectorName) { + return nativeClass.invoke(setterSelectorName, value); + } + nativeClass[name] = value; + }; + })(member.name, member.setterSelectorName) + : (setterFunction || (function(name) { + return function(value) { + return api.__invokeBase(nativeClass, this, name, value); + }; + })(member.name)); + } + Object.defineProperty(target, member.name, descriptor); + } else { + continue; + } + } + installSelectorGroups(target, selectorGroups, receiverIsClass); + } + function installNativeClassMembersIfNeeded() { + if (classMembersInstalled) { + return; + } + classMembersInstalled = true; + installClassMembers( + constructable, + nativeClass.__staticMembers, + true, + nativeClass.__runtimeStaticMembers + ); + installClassMembers( + basePrototypeTarget, + nativeClass.__instanceMembers, + false, + nativeClass.__runtimeInstanceMembers + ); + try { + delete constructable.__nativeApiInstallMembers; + } catch (_) { + } + } + try { + Object.defineProperty(constructable, '__nativeApiInstallMembers', { + configurable: true, + enumerable: false, + writable: false, + value: installNativeClassMembersIfNeeded + }); + } catch (_) { + } + try { + Object.defineProperty(basePrototypeTarget, 'constructor', { + configurable: true, + enumerable: false, + writable: true, + value: constructable + }); + } catch (_) { + } + try { + Object.defineProperty(basePrototypeTarget, 'toString', { + configurable: true, + enumerable: false, + writable: true, + value: function() { + return '[object NativeScriptObject]'; + } + }); + } catch (_) { + } + try { + if (typeof Symbol === 'function' && Symbol.iterator && + typeof api.__fastEnumeration === 'function') { + Object.defineProperty(basePrototypeTarget, Symbol.iterator, { + configurable: true, + enumerable: false, + writable: true, + value: function() { + return api.__fastEnumeration(this); + } + }); + } + } catch (_) { + } + constructable.prototype = basePrototypeTarget; + try { + Object.defineProperty(constructable, Symbol.hasInstance, { + configurable: true, + enumerable: false, + value: function(value) { + if (!value || typeof value !== 'object') { + return false; + } + // `this` is the constructor instanceof was invoked on. A + // TypeScript-derived native class inherits this method through the + // wrapper prototype chain until it materializes, so membership must + // be answered for the DERIVED Objective-C class — and before + // materialization no instance of it can exist. + try { + if (this && this !== constructable && this !== wrapper && + this.__nativeApiTypeScriptState) { + var derivedWrapper = this.__nativeApiTypeScriptState.wrapper; + if (!derivedWrapper) { + return false; + } + if (derivedWrapper !== constructable && derivedWrapper !== wrapper) { + return derivedWrapper[Symbol.hasInstance](value); + } + } + } catch (_) { + } + var expectedName = nativeClass.runtimeName || nativeClass.name; + try { + // Pass the proxied wrapper: the raw constructable carries no + // __nativeApiClass, so it does not marshal to the Objective-C + // Class and isKindOfClass() misreports. + if (typeof value.isKindOfClass === 'function' && + value.isKindOfClass(wrapper || constructable) === true) { + return true; + } + } catch (_) { + } + try { + var current = typeof value.class === 'function' ? value.class() : null; + while (current) { + if (current === wrapper || current === constructable) { + return true; + } + var currentName = current.runtimeName || current.name; + if (typeof expectedName === 'string' && currentName === expectedName) { + return true; + } + var next = current.superclass || null; + if (typeof next === 'function' && next.kind !== 'class') { + next = next.call(current); + } + current = next || null; + } + } catch (_) { + } + return typeof expectedName === 'string' && value.className === expectedName; + } + }); + } catch (_) { + } + var cachedNativeFunctions = typeof Map === 'function' ? new Map() : null; + var wrapper = typeof Proxy === 'function' + ? new Proxy(constructable, { + get: function(target, property, receiver) { + if (property === '__nativeApiClass') { + return nativeClass; + } + if (property === 'toString') { + return function() { + return String(nativeClass); + }; + } + if (property === 'prototype') { + installNativeClassMembersIfNeeded(); + return Reflect.get(target, property, receiver); + } + if (property === 'hasOwnProperty') { + return function(key) { + installNativeClassMembersIfNeeded(); + return Object.prototype.hasOwnProperty.call(target, key); + }; + } + if (Object.prototype.hasOwnProperty.call(target, property) || + property === 'prototype' || + property === 'length' || + property === 'name') { + return Reflect.get(target, property, receiver); + } + installNativeClassMembersIfNeeded(); + if (Object.prototype.hasOwnProperty.call(target, property)) { + return Reflect.get(target, property, receiver); + } + if (cachedNativeFunctions && cachedNativeFunctions.has(property)) { + return cachedNativeFunctions.get(property); + } + var nativeValue = nativeClass[property]; + if (nativeValue !== undefined) { + if (typeof nativeValue === 'function') { + if (cachedNativeFunctions) { + cachedNativeFunctions.set(property, nativeValue); + } + try { + Object.defineProperty(target, property, { + configurable: true, + enumerable: false, + writable: false, + value: nativeValue + }); + } catch (_) { + } + } + return nativeValue; + } + var reflected = Reflect.get(target, property, receiver); + if (reflected !== undefined || property in target) { + return reflected; + } + installNativeClassMembersIfNeeded(); + reflected = Reflect.get(target, property, receiver); + if (reflected !== undefined || property in target) { + return reflected; + } + return reflected; + }, + set: function(target, property, value, receiver) { + if (property === 'prototype') { + target[property] = value; + return true; + } + installNativeClassMembersIfNeeded(); + if (setDescriptorValue(target, property, receiver, value)) { + return true; + } + if (setInheritedNativeClassValue(target, property, value)) { + return true; + } + try { + nativeClass[property] = value; + return true; + } catch (_) { + } + if (receiver && receiver !== target) { + Object.defineProperty(receiver, property, { + configurable: true, + enumerable: true, + writable: true, + value: value + }); + return true; + } + return Reflect.set(target, property, value, receiver); + }, + has: function(target, property) { + installNativeClassMembersIfNeeded(); + return property in target || property in nativeClass; + }, + ownKeys: function(target) { + installNativeClassMembersIfNeeded(); + return Reflect.ownKeys(target).filter(function(key) { + return key !== 'new' && + key !== 'hasOwnProperty' && + key !== '__nativeApiInstallMembers'; + }); + }, + getOwnPropertyDescriptor: function(target, property) { + installNativeClassMembersIfNeeded(); + return Reflect.getOwnPropertyDescriptor(target, property); + } + }) + : constructable; + if (classWrappers) { + classWrappers.set(nativeClass, wrapper); + } + try { + var nativeSuperclass = nativeClass.__superclass; + if (nativeSuperclass && nativeSuperclass !== nativeClass) { + var superclassWrapper = wrapNativeClass(nativeSuperclass); + if (superclassWrapper && superclassWrapper !== wrapper && + typeof Object.setPrototypeOf === 'function') { + Object.setPrototypeOf(wrapper, superclassWrapper); + if (superclassWrapper.prototype) { + Object.setPrototypeOf(constructable.prototype, superclassWrapper.prototype); + } + } + } + } catch (_) { + } + try { + api.__rememberClassWrapper(nativeClass, wrapper, constructable.prototype); + } catch (_) { + } + if (nativeClassName) { + classWrappersByName[nativeClassName] = wrapper; + cacheGlobal(nativeClassName, wrapper); + if (!Object.prototype.hasOwnProperty.call(globalThis, nativeClassName)) { + try { + Object.defineProperty(globalThis, nativeClassName, { + configurable: true, + enumerable: false, + writable: false, + value: wrapper + }); + } catch (_) { + } + } + } + if (nativeClass.name && nativeClass.name !== nativeClassName) { + classWrappersByName[nativeClass.name] = wrapper; + cacheGlobal(nativeClass.name, wrapper); + } + return wrapper; + } + + function rememberClassOnInstance(instance, classWrapper) { + if (instance && typeof instance === 'object' && classWrapper) { + try { + if (typeof classWrapper.__nativeApiInstallMembers === 'function') { + classWrapper.__nativeApiInstallMembers(); + } + if (typeof api.__rememberObjectClassWrapper === 'function') { + api.__rememberObjectClassWrapper(instance, classWrapper); + } else { + instance.__nativeApiClassWrapper = classWrapper; + } + } catch (_) { + } + } + return instance; + } + + function isNativeClassLike(value) { + if (!value || (typeof value !== 'object' && typeof value !== 'function')) { + return false; + } + if (value.kind === 'class') { + return true; + } + try { + return !!value.__nativeApiClass; + } catch (_) { + return false; + } + } + + function nativeClassLikeHandle(value) { + if (!value || (typeof value !== 'object' && typeof value !== 'function')) { + return value; + } + try { + if (typeof value.__nativeApiEnsureClass === 'function') { + value = value.__nativeApiEnsureClass(); + } + } catch (_) { + } + try { + return value.__nativeApiClass || value; + } catch (_) { + return value; + } + } + + function materializeTypeScriptNativeClass(constructor) { + if (!constructor || typeof constructor !== 'function') { + return undefined; + } + var state = constructor.__nativeApiTypeScriptState; + if (!state) { + return undefined; + } + if (state.wrapper) { + return state.wrapper; + } + if (state.materializing) { + return state.base; + } + + state.materializing = true; + try { + var baseWrapper = state.base; + if (baseWrapper && typeof baseWrapper.__nativeApiEnsureClass === 'function') { + baseWrapper = baseWrapper.__nativeApiEnsureClass(); + } + + var options = {}; + var className = constructor.ObjCClassName || constructor.name; + if (className) { + options.name = className; + } + if (constructor.ObjCProtocols) { + options.protocols = constructor.ObjCProtocols; + } + if (constructor.ObjCExposedMethods) { + options.exposedMethods = constructor.ObjCExposedMethods; + } + + var nativeBase = nativeClassLikeHandle(baseWrapper); + var nativeClass = api.__extendClass(nativeBase, constructor.prototype || {}, options); + var wrapper = wrapNativeClass(nativeClass); + state.wrapper = wrapper; + + try { + Object.setPrototypeOf(constructor, wrapper); + } catch (_) { + } + try { + api.__rememberClassWrapper(nativeClass, constructor, constructor.prototype || {}); + } catch (_) { + } + return wrapper; + } finally { + state.materializing = false; + } + } + + function defineTypeScriptStaticForwarder(constructor, name, isProperty, readonly) { + if (!name || name === 'length' || name === 'name' || name === 'prototype' || + Object.prototype.hasOwnProperty.call(constructor, name)) { + return; + } + + var descriptor = { + configurable: true, + enumerable: false + }; + + if (isProperty) { + descriptor.get = function() { + var wrapper = materializeTypeScriptNativeClass(constructor); + return wrapper ? wrapper[name] : undefined; + }; + if (!readonly) { + descriptor.set = function(value) { + var wrapper = materializeTypeScriptNativeClass(constructor); + if (wrapper) { + wrapper[name] = value; + } + }; + } + } else { + descriptor.writable = true; + descriptor.value = function() { + if (name === 'class') { + materializeTypeScriptNativeClass(constructor); + return constructor; + } + if (name === 'superclass') { + var state = constructor.__nativeApiTypeScriptState; + return state && state.base; + } + var wrapper = materializeTypeScriptNativeClass(constructor); + var member = wrapper && wrapper[name]; + if (typeof member !== 'function') { + throw new TypeError(String(name) + ' is not a function'); + } + var memberArgs = []; + for (var memberArgIndex = 0; memberArgIndex < arguments.length; memberArgIndex++) { + memberArgs.push(arguments[memberArgIndex]); + } + var result = wrapper[name](...memberArgs); + if (name === 'alloc' || name === 'new' || name === 'construct') { + return rememberClassOnInstance(result, constructor); + } + return result; + }; + } + + try { + Object.defineProperty(constructor, name, descriptor); + } catch (_) { + } + } + + function installTypeScriptNativeClassSupport(constructor, base) { + if (!constructor || typeof constructor !== 'function' || !isNativeClassLike(base)) { + return false; + } + if (constructor.__nativeApiTypeScriptState) { + return true; + } + + try { + Object.defineProperty(constructor, '__nativeApiTypeScriptState', { + configurable: false, + enumerable: false, + writable: false, + value: { + base: base, + wrapper: null, + materializing: false + } + }); + } catch (_) { + constructor.__nativeApiTypeScriptState = { + base: base, + wrapper: null, + materializing: false + }; + } + + try { + Object.defineProperty(constructor, '__nativeApiEnsureClass', { + configurable: false, + enumerable: false, + writable: false, + value: function() { + return materializeTypeScriptNativeClass(constructor); + } + }); + } catch (_) { + } + + try { + Object.defineProperty(constructor, '__nativeApiClass', { + configurable: true, + enumerable: false, + get: function() { + var wrapper = materializeTypeScriptNativeClass(constructor); + return wrapper && wrapper.__nativeApiClass; + } + }); + } catch (_) { + } + + ['alloc', 'new', 'class', 'superclass', 'extend'].forEach(function(name) { + defineTypeScriptStaticForwarder(constructor, name, false, false); + }); + + try { + var members = base.__staticMembers || []; + for (var i = 0; i < members.length; i++) { + var member = members[i]; + if (member && member.name) { + defineTypeScriptStaticForwarder( + constructor, + member.name, + !!member.property, + !!member.readonly + ); + } + } + } catch (_) { + } + + return true; + } + + function installTypeScriptNativeHelpers() { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function(d, b) { d.__proto__ = b; }) || + function(d, b) { + for (var p in b) { + if (Object.prototype.hasOwnProperty.call(b, p)) { + d[p] = b[p]; + } + } + }; + + globalThis.__extends = function(d, b) { + if (typeof b !== 'function' && b !== null) { + throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); + } + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + if (b !== null) { + installTypeScriptNativeClassSupport(d, b); + } + }; + + globalThis.NativeClass = function NativeClass(constructor) { + if (constructor && typeof constructor.__nativeApiEnsureClass === 'function') { + constructor.__nativeApiEnsureClass(); + } + return constructor; + }; + + globalThis.ObjCClass = function ObjCClass() { + var protocols = Array.prototype.slice.call(arguments); + return function(constructor) { + if (constructor.ObjCProtocols) { + for (var protocolIndex = 0; protocolIndex < protocols.length; protocolIndex++) { + constructor.ObjCProtocols.push(protocols[protocolIndex]); + } + } else { + constructor.ObjCProtocols = protocols; + } + if (typeof constructor.__nativeApiEnsureClass === 'function') { + constructor.__nativeApiEnsureClass(); + } + return constructor; + }; + }; + } + + function wrapInteropFactory(nativeFactory, properties) { + if (typeof nativeFactory !== 'function' || nativeFactory.__nativeScriptConstructable) { + return nativeFactory; + } + var constructable = function NativeScriptInteropValue() { + var factoryArgs = []; + for (var factoryArgIndex = 0; factoryArgIndex < arguments.length; factoryArgIndex++) { + factoryArgs.push(arguments[factoryArgIndex]); + } + return nativeFactory(...factoryArgs); + }; + try { + if (nativeFactory.prototype) { + constructable.prototype = nativeFactory.prototype; + } + } catch (_) { + } + try { + Object.defineProperty(constructable, Symbol.hasInstance, { + configurable: true, + enumerable: false, + value: function(value) { + return !!value && typeof value === 'object' && value.kind === properties.kind; + } + }); + } catch (_) { + } + Object.keys(properties).forEach(function(key) { + try { + Object.defineProperty(constructable, key, { + configurable: true, + enumerable: false, + writable: false, + value: properties[key] + }); + } catch (_) { + } + }); + Object.defineProperty(constructable, '__nativeScriptConstructable', { + configurable: false, + enumerable: false, + writable: false, + value: true + }); + return constructable; + } + + function installInteropConstructors() { + var interop = globalThis.interop; + if (!interop || typeof interop !== 'object') { + return; + } + var pointerSize; + try { + if (typeof interop.sizeof === 'function' && interop.types && interop.types.pointer !== undefined) { + pointerSize = interop.sizeof(interop.types.pointer); + } + } catch (_) { + pointerSize = undefined; + } + interop.Pointer = wrapInteropFactory(interop.Pointer, { kind: 'pointer', sizeof: pointerSize }); + interop.Reference = wrapInteropFactory(interop.Reference, { kind: 'reference', sizeof: pointerSize }); + interop.Block = wrapInteropFactory(interop.Block, { kind: 'block', sizeof: pointerSize }); + interop.FunctionReference = wrapInteropFactory( + interop.FunctionReference, + { kind: 'functionReference', sizeof: pointerSize } + ); + if (interop.types && typeof interop.types === 'object') { + Object.keys(interop.types).forEach(function(name) { + var value = interop.types[name]; + if (typeof value !== 'number') { + return; + } + var boxed = { + valueOf: function() { return value; }, + toString: function() { return String(value); } + }; + Object.defineProperty(boxed, typeCodeKey, { + configurable: false, + enumerable: false, + writable: false, + value: value + }); + interop.types[name] = boxed; + }); + } + } + + function defineInlineFunction(name, value) { + if (Object.prototype.hasOwnProperty.call(globalThis, name)) { + return; + } + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: true, + value: value + }); + } + + function installInlineFunctions() { + var makePoint = function(x, y) { return { x: x, y: y }; }; + var makeSize = function(width, height) { return { width: width, height: height }; }; + var makeRect = function(x, y, width, height) { + return { origin: { x: x, y: y }, size: { width: width, height: height } }; + }; + defineInlineFunction('CGPointMake', makePoint); + defineInlineFunction('NSMakePoint', makePoint); + defineInlineFunction('CGSizeMake', makeSize); + defineInlineFunction('NSMakeSize', makeSize); + defineInlineFunction('CGRectMake', makeRect); + defineInlineFunction('NSMakeRect', makeRect); + defineInlineFunction('NSMakeRange', function(location, length) { + return { location: location, length: length }; + }); + defineInlineFunction('UIEdgeInsetsMake', function(top, left, bottom, right) { + return { top: top, left: left, bottom: bottom, right: right }; + }); + } + + function names(kind) { + var metadata = api.metadata; + var fn = metadata && metadata[kind]; + return typeof fn === 'function' ? fn() : []; + } + + function nameSet(values) { + var result = Object.create(null); + (values || []).forEach(function(value) { + result[value] = true; + }); + return result; + } + + var classNameList = names('classNames'); + var functionNameList = names('functionNames'); + var constantNameList = names('constantNames'); + var protocolNameList = names('protocolNames'); + var enumNameList = names('enumNames'); + var functionNameSet = nameSet(functionNameList); + var constantNameSet = nameSet(constantNameList); + var classNameSet = nameSet(classNameList); + var protocolNameSet = nameSet(protocolNameList); + var enumNameSet = nameSet(enumNameList); + + function resolveNativeApiEnum(enumName) { + return (api.getEnum && api.getEnum(enumName)) || api[enumName]; + } + + Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiLazyGlobal', { + configurable: false, + enumerable: false, + writable: false, + value: function(name, kind) { + var value; + if (kind === 'class') { + value = wrapNativeClass(api[name]); + } else if (kind === 'function' || kind === 'constant') { + value = api[name]; + } else if (kind === 'protocol') { + value = (api.getProtocol && api.getProtocol(name)) || api[name]; + } else if (kind === 'enum') { + value = resolveNativeApiEnum(name); + } else if (kind === 'struct') { + value = wrapAggregateConstructor((api.getStruct && api.getStruct(name)) || api[name]); + } else if (kind === 'union') { + value = wrapAggregateConstructor((api.getUnion && api.getUnion(name)) || api[name]); + } else if (kind && kind.indexOf('enumMember:') === 0) { + var enumValue = resolveNativeApiEnum(kind.slice('enumMember:'.length)); + value = enumValue && enumValue[name]; + } else { + value = api[name]; + } + cacheGlobal(name, value); + return value; + } + }); + + classNameList.forEach(function(name) { + defineLazyGlobal(name, function(className) { + return wrapNativeClass(api[className]); + }, false, 'class'); + }); + functionNameList.forEach(function(name) { + defineLazyGlobal(name, function(functionName) { + return api[functionName]; + }, false, 'function'); + }); + constantNameList.forEach(function(name) { + defineLazyGlobal(name, function(constantName) { + return api[constantName]; + }, false, 'constant'); + }); + protocolNameList.forEach(function(name) { + defineLazyGlobal(name, function(protocolName) { + return (api.getProtocol && api.getProtocol(protocolName)) || api[protocolName]; + }, false, 'protocol'); + }); + enumNameList.forEach(function(name) { + defineLazyGlobal(name, resolveNativeApiEnum, false, 'enum'); + api.getEnumGlobalNames(name).forEach(function(memberName) { + defineLazyGlobal(memberName, function() { + var enumValue = resolveNativeApiEnum(name); + return enumValue && enumValue[memberName]; + }, false, 'enumMember:' + name); + }); + }); + names('structNames').forEach(function(name) { + var conflictsWithValue = + !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || + !!protocolNameSet[name] || !!enumNameSet[name]; + defineLazyGlobal(name, function(structName) { + return wrapAggregateConstructor((api.getStruct && api.getStruct(structName)) || api[structName]); + }, !conflictsWithValue, 'struct'); + }); + names('unionNames').forEach(function(name) { + var conflictsWithValue = + !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || + !!protocolNameSet[name] || !!enumNameSet[name]; + defineLazyGlobal(name, function(unionName) { + return wrapAggregateConstructor((api.getUnion && api.getUnion(unionName)) || api[unionName]); + }, !conflictsWithValue, 'union'); + }); + + if (typeof globalThis.UIColor === 'undefined' && + typeof globalThis.NSColor !== 'undefined') { + globalThis.UIColor = globalThis.NSColor; + cacheGlobal('UIColor', globalThis.UIColor); + } + var colorCtor = globalThis.UIColor || globalThis.NSColor; + if (colorCtor && colorCtor.prototype && + typeof colorCtor.prototype.initWithRedGreenBlueAlpha !== 'function') { + colorCtor.prototype.initWithRedGreenBlueAlpha = function(red, green, blue, alpha) { + if (typeof this.initWithSRGBRedGreenBlueAlpha === 'function') { + return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof this.initWithCalibratedRedGreenBlueAlpha === 'function') { + return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === 'function') { + return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); + } + if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === 'function') { + return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); + } + return this; + }; + } + defineLazyGlobal('CC_SHA256', function() { return api.CC_SHA256; }); + + installInteropConstructors(); + installTypeScriptNativeHelpers(); + installInlineFunctions(); + + try { + Object.defineProperty(globalThis, installedFlagName, { + configurable: false, + enumerable: false, + writable: false, + value: true + }); + } catch (_) { + } +}) +)Engine_GLOBALS"; + + std::string script(GlobalInstaller); + script += "("; + script += jsStringLiteral(globalName); + script += ");"; + runtime.evaluateJavaScript(std::make_shared(std::move(script)), + "NativeApiGlobals.js"); + NativeApiWriteSmokeStage("engine:globals:after-eval"); +} + +void InstallNativeApi(Runtime& runtime, const NativeApiConfig& config) { + const char* globalName = config.globalName != nullptr && config.globalName[0] != '\0' + ? config.globalName + : "__nativeScriptNativeApi"; + NativeApiWriteSmokeStage("engine:create-api"); + Object api = CreateNativeApi(runtime, config); + Object global = runtime.global(); + NativeApiWriteSmokeStage("engine:set-global"); + global.setProperty(runtime, globalName, api); + + NativeApiWriteSmokeStage("engine:set-interop"); + Value existingInterop = global.getProperty(runtime, "interop"); + if (existingInterop.isUndefined() || existingInterop.isNull()) { + global.setProperty(runtime, "interop", api.getProperty(runtime, "interop")); + } + if (config.installGlobalSymbols) { + NativeApiWriteSmokeStage("engine:install-globals"); + InstallNativeApiGlobalSymbols(runtime, globalName); + } else { + NativeApiWriteSmokeStage("engine:install-aggregate-globals"); + InstallAggregateGlobals(runtime, api, "protocolNames"); + } + NativeApiWriteSmokeStage("engine:installed"); +} diff --git a/NativeScript/ffi/objc/shared/bridge/Invocation.mm b/NativeScript/ffi/objc/shared/bridge/Invocation.mm new file mode 100644 index 000000000..0086c26a7 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Invocation.mm @@ -0,0 +1,1729 @@ +bool isValidMetadataStringOffset(MDMetadataReader* metadata, + MDSectionOffset offset) { + if (metadata == nullptr || metadata->constantsOffset < metadata->stringsOffset) { + return false; + } + return offset < metadata->constantsOffset - metadata->stringsOffset; +} + +bool startsWith(const std::string& value, const std::string& prefix) { + return value.size() >= prefix.size() && + value.compare(0, prefix.size(), prefix) == 0; +} + +bool endsWith(const std::string& value, const std::string& suffix) { + return value.size() >= suffix.size() && + value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +std::string stripEnumSuffix(const std::string& enumName) { + static const std::vector suffixes = { + "Options", "Option", "Enums", "Enum", "Result", "Enumeration", + "Orientation", "Style", "Mask", "Type", "Status", "Modes", "Mode", "s"}; + + for (const auto& suffix : suffixes) { + if (enumName.size() > suffix.size() && endsWith(enumName, suffix)) { + return enumName.substr(0, enumName.size() - suffix.size()); + } + } + + return enumName; +} + +class NativeApiReturnStorage { + public: + explicit NativeApiReturnStorage(size_t size) + : size_(std::max(size, sizeof(void*))) { + if (size_ > kInlineSize) { + heap_.assign(size_, 0); + } else { + std::memset(inline_, 0, kInlineSize); + } + } + + void* data() { return heap_.empty() ? inline_ : heap_.data(); } + unsigned char* bytes() { return static_cast(data()); } + + private: + static constexpr size_t kInlineSize = 64; + + size_t size_ = 0; + alignas(std::max_align_t) unsigned char inline_[kInlineSize] = {}; + std::vector heap_; +}; + +class NativeApiPointerFrame { + public: + explicit NativeApiPointerFrame(size_t count) : count_(count) { + if (count_ > kInlineCount) { + heap_.resize(count_); + } + } + + void set(size_t index, void* value) { + if (index >= count_) { + throw std::out_of_range("Native invocation argument index out of range."); + } + if (count_ <= kInlineCount) { + inline_[index] = value; + } else { + heap_[index] = value; + } + } + + void** data() { + if (count_ == 0) { + return nullptr; + } + return count_ <= kInlineCount ? inline_ : heap_.data(); + } + + private: + static constexpr size_t kInlineCount = 10; + + size_t count_ = 0; + void* inline_[kInlineCount] = {}; + std::vector heap_; +}; + +Value enumToObject(Runtime& runtime, MDMetadataReader* metadata, + const NativeApiSymbol& symbol) { + Object result(runtime); + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return result; + } + + const std::string& enumName = symbol.name; + std::string strippedPrefix = stripEnumSuffix(enumName); + metadata->forEachEnumMember( + symbol.offset, [&](const char* memberName, const std::string& cName, + int64_t value) { + std::string storedName = memberName != nullptr ? memberName : ""; + auto aliases = enum_members::aliasesFor(enumName, strippedPrefix, + storedName, cName); + for (const auto& alias : aliases) { + result.setProperty(runtime, alias.name.c_str(), + static_cast(value)); + } + + char valueKey[32] = {}; + snprintf(valueKey, sizeof(valueKey), "%lld", + static_cast(value)); + if (!result.hasProperty(runtime, valueKey)) { + const std::string& reverseName = + aliases.size() > 1 ? aliases[1].name : storedName; + result.setProperty(runtime, valueKey, + makeString(runtime, reverseName)); + } + }); + return result; +} + +std::vector enumGlobalNames(MDMetadataReader* metadata, + const NativeApiSymbol& symbol) { + std::vector names; + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return names; + } + metadata->forEachEnumMember( + symbol.offset, [&](const char*, const std::string& cName, int64_t) { + if (!cName.empty()) { + names.push_back(cName); + } + }); + return names; +} + +Value constantToValue(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSymbol& symbol) { + MDMetadataReader* metadata = bridge->metadata(); + if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { + return Value::undefined(); + } + + MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); + auto evalKind = metadata->getVariableEvalKind(offset); + offset += sizeof(metagen::MDVariableEvalKind); + + switch (evalKind) { + case metagen::mdEvalInt64: + return static_cast(metadata->getInt64(offset)); + case metagen::mdEvalDouble: + return metadata->getDouble(offset); + case metagen::mdEvalString: { + if (isValidMetadataStringOffset(metadata, offset)) { + auto stringOffset = metadata->getOffset(offset); + return makeString(runtime, metadata->resolveString(stringOffset)); + } + + void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); + if (symbolPtr == nullptr) { + return Value::undefined(); + } + + NativeApiType stringObjectType; + stringObjectType.kind = metagen::mdTypeNSStringObject; + stringObjectType.ffiType = &ffi_type_pointer; + stringObjectType.supported = true; + return convertNativeReturnValue(runtime, bridge, stringObjectType, + symbolPtr); + } + case metagen::mdEvalNone: + break; + } + + MDSectionOffset typeOffset = offset; + NativeApiType type = parseMetadataEngineType(metadata, &typeOffset, bridge.get()); + if (unsupportedEngineType(type)) { + throw JSError( + runtime, "Native constant type is not supported by backend: " + + symbol.name); + } + + void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); + if (symbolPtr == nullptr) { + return Value::undefined(); + } + return convertNativeReturnValue(runtime, bridge, type, symbolPtr); +} + +void prepareEngineArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, const Value& arg, + size_t index, NativeApiArgumentFrame& frame) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + convertEngineFfiArgument(runtime, bridge, type, arg, target, frame); +} + +void prepareEngineArguments(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSignature& signature, + const Value* args, size_t count, + NativeApiArgumentFrame& frame) { + if (count != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(count) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + prepareEngineArgument(runtime, bridge, signature.argumentTypes[i], args[i], i, + frame); + } +} + +inline uint64_t dispatchIdForEngineSignature( + const NativeApiSignature& signature, SignatureCallKind kind) { + if (signature.signatureHash == 0) { + return 0; + } + return composeSignatureDispatchId(signature.signatureHash, kind, + signature.dispatchFlags); +} + +struct NativeApiPreparedCFunctionInvocation { + NativeApiSymbol symbol; + bool initialized = false; + void* function = nullptr; + NativeApiSignature signature; + CFunctionPreparedInvoker preparedInvoker = nullptr; +}; + +bool tryCallFastEngineCFunction( + Runtime& runtime, const std::shared_ptr& bridge, + void* function, const NativeApiSignature& signature, const Value* args, + size_t count, Value* result); + +Value callNativeFunctionPointer( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, void* pointer, bool block, const Value* args, + size_t count) { + if (pointer == nullptr) { + throw JSError(runtime, "Native function pointer is null."); + } + if (bridge == nullptr || bridge->metadata() == nullptr || + type.signatureOffset == MD_SECTION_OFFSET_NULL) { + throw JSError( + runtime, "Native function pointer metadata is unavailable."); + } + + auto signature = parseMetadataEngineSignature( + bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); + if (!signature || !signature->prepared || signature->variadic || + unsupportedEngineType(signature->returnType)) { + throw JSError( + runtime, + "Native function pointer signature is not supported by backend."); + } + + NativeApiArgumentFrame frame(signature->argumentTypes.size()); + prepareEngineArguments(runtime, bridge, *signature, args, count, frame); + + NativeApiPointerFrame values(signature->argumentTypes.size() + 1); + if (block) { + values.set(0, &pointer); + for (size_t i = 0; i < signature->argumentTypes.size(); i++) { + values.set(i + 1, frame.values()[i]); + } + } + + void* callable = pointer; + if (block) { + auto literal = static_cast(pointer); + if (literal == nullptr || literal->invoke == nullptr) { + throw JSError(runtime, "Native block invoke pointer is null."); + } + callable = literal->invoke; + } + + if (!block) { + Value fastResult; + if (tryCallFastEngineCFunction(runtime, bridge, callable, *signature, args, + count, &fastResult)) { + return fastResult; + } + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature->returnType)); + BlockPreparedInvoker blockPreparedInvoker = nullptr; + CFunctionPreparedInvoker functionPreparedInvoker = nullptr; + if (block) { + blockPreparedInvoker = lookupBlockPreparedInvoker(dispatchIdForEngineSignature( + *signature, SignatureCallKind::BlockInvoke)); + } else { + functionPreparedInvoker = lookupCFunctionPreparedInvoker( + dispatchIdForEngineSignature(*signature, SignatureCallKind::CFunction)); + } + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (block) { + if (blockPreparedInvoker != nullptr) { + blockPreparedInvoker(callable, values.data(), returnStorage.data()); + return; + } + } else { + if (functionPreparedInvoker != nullptr) { + functionPreparedInvoker(callable, frame.values(), returnStorage.data()); + return; + } + } + ffi_call(&signature->cif, FFI_FN(callable), returnStorage.data(), + block ? values.data() : frame.values()); + }); + + return convertNativeReturnValue(runtime, bridge, signature->returnType, + returnStorage.data()); +} + +Value wrapNativeFunctionPointer(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* pointer, + bool block) { + const char* functionName = block ? "NativeApiBlock" : "NativeApiFunctionPointer"; + auto function = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, functionName), 0, + [bridge, type, pointer, block](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + return callNativeFunctionPointer(runtime, bridge, type, pointer, block, + args, count); + }); + function.setProperty(runtime, "kind", + makeString(runtime, block ? "block" : "functionPointer")); + function.setProperty( + runtime, "__nativeApiPointerObject", + createPointer(runtime, bridge, pointer)); + function.setProperty( + runtime, "__nativeApiPointer", + static_cast(reinterpret_cast(pointer))); + function.setProperty( + runtime, "nativeAddress", + static_cast(reinterpret_cast(pointer))); + function.setProperty(runtime, "sizeof", + static_cast(sizeof(void*))); + function.setProperty( + runtime, "toString", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [pointer, block](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", pointer); + return makeString(runtime, + std::string("[NativeApi ") + + (block ? "Block " : "FunctionPointer ") + + address + "]"); + })); + return function; +} + +Value callCFunction(Runtime& runtime, + const std::shared_ptr& bridge, + const std::shared_ptr& prepared, + const Value* args, + size_t count) { + if (prepared == nullptr) { + throw JSError(runtime, "Native function state is unavailable."); + } + NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge); + + MDMetadataReader* metadata = bridge->metadata(); + if (metadata == nullptr) { + throw JSError(runtime, "Native metadata is not loaded."); + } + + if (!prepared->initialized) { + void* fnptr = dlsym(bridge->selfDl(), prepared->symbol.name.c_str()); + if (fnptr == nullptr) { + throw JSError(runtime, + "Native function is not available: " + + prepared->symbol.name); + } + + MDSectionOffset signatureOffset = + metadata->signaturesOffset + + metadata->getOffset(prepared->symbol.offset + sizeof(MDSectionOffset)); + auto signature = parseMetadataEngineSignature( + metadata, signatureOffset, 0, bridge.get(), + (metadata->getFunctionFlag( + prepared->symbol.offset + sizeof(MDSectionOffset) * 2) & + metagen::mdFunctionReturnOwned) != 0); + if (!signature || !signature->prepared || signature->variadic || + unsupportedEngineType(signature->returnType)) { + throw JSError( + runtime, "Native function signature is not supported by backend: " + + prepared->symbol.name); + } + + prepared->function = fnptr; + prepared->signature = std::move(*signature); + prepared->preparedInvoker = lookupCFunctionPreparedInvoker( + dispatchIdForEngineSignature(prepared->signature, + SignatureCallKind::CFunction)); + prepared->initialized = true; + } + + NativeApiSignature& signature = prepared->signature; + Value fastResult; + if (tryCallFastEngineCFunction(runtime, bridge, prepared->function, signature, + args, count, &fastResult)) { + return fastResult; + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + prepareEngineArguments(runtime, bridge, signature, args, count, frame); + + if (prepared->symbol.name == "NSApplicationMain" || + prepared->symbol.name == "UIApplicationMain") { + runtime.drainMicrotasks(); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared->preparedInvoker != nullptr) { + prepared->preparedInvoker(prepared->function, frame.values(), + returnStorage.data()); + } else { + ffi_call(&signature.cif, FFI_FN(prepared->function), returnStorage.data(), + frame.values()); + } + }); + + NativeApiType returnType = signature.returnType; + if (prepared->symbol.name == "CFBagContainsValue" && + (returnType.kind == metagen::mdTypeChar || + returnType.kind == metagen::mdTypeUChar || + returnType.kind == metagen::mdTypeUInt8)) { + return *returnStorage.bytes() != 0; + } + return convertNativeReturnValue(runtime, bridge, returnType, + returnStorage.data()); +} + +Value callCFunction(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSymbol& symbol, const Value* args, + size_t count) { + auto prepared = std::make_shared(); + prepared->symbol = symbol; + return callCFunction(runtime, bridge, prepared, args, count); +} + +bool signatureSupportedForEngineInvocation( + const std::optional& signature) { + if (!signature || !signature->prepared || signature->variadic || + unsupportedEngineType(signature->returnType)) { + return false; + } + for (const auto& argType : signature->argumentTypes) { + if (unsupportedEngineType(argType)) { + return false; + } + } + return true; +} + +bool signatureSupportedForEngineInvocation( + const NativeApiSignature& signature) { + if (!signature.prepared || signature.variadic || + unsupportedEngineType(signature.returnType)) { + return false; + } + for (const auto& argType : signature.argumentTypes) { + if (unsupportedEngineType(argType)) { + return false; + } + } + return true; +} + +struct NativeApiPreparedObjCInvocation { + SEL selector = nullptr; + Class receiverClass = Nil; + std::string selectorName; + NativeApiSignature signature; + ObjCPreparedInvoker preparedInvoker = nullptr; + void* engineInvoker = nullptr; // Engine-neutral GSD invoker (ObjCGsdInvoker) + bool isNSErrorOutMethod = false; // Cached: avoids per-call selector scan. + bool isInitMethod = false; // Cached: avoids per-call "init" rfind. + bool gsdEngineCallable = false; + uint8_t gsdEngineArgumentCount = 0; + bool fastEngineCallable = false; + uint8_t fastEngineArgumentCount = 0; + uint8_t fastEngineFirstArgKind = 0; + uint8_t fastEngineSecondArgKind = 0; +}; + +bool preparedObjCInvocationIsInit( + const NativeApiPreparedObjCInvocation& prepared) { + return prepared.isInitMethod; +} + +bool isFastEngineObjectType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +bool isFastEngineSignedIntegerType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeChar: + case metagen::mdTypeSShort: + case metagen::mdTypeSInt: + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return true; + default: + return false; + } +} + +bool isFastEngineUnsignedIntegerType(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + case metagen::mdTypeUInt: + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return true; + default: + return false; + } +} + +enum class NativeApiFastEngineArgKind : uint8_t { + Bool, + SignedInteger, + UnsignedInteger, + Float, + Double, + Object, + Class, + Selector, +}; + +std::optional fastEngineArgKind( + const NativeApiType& type) { + if (isFastEngineObjectType(type)) { + return NativeApiFastEngineArgKind::Object; + } + if (isFastEngineSignedIntegerType(type)) { + return NativeApiFastEngineArgKind::SignedInteger; + } + if (isFastEngineUnsignedIntegerType(type)) { + return NativeApiFastEngineArgKind::UnsignedInteger; + } + switch (type.kind) { + case metagen::mdTypeBool: + return NativeApiFastEngineArgKind::Bool; + case metagen::mdTypeFloat: + return NativeApiFastEngineArgKind::Float; + case metagen::mdTypeDouble: + return NativeApiFastEngineArgKind::Double; + case metagen::mdTypeClass: + return NativeApiFastEngineArgKind::Class; + case metagen::mdTypeSelector: + return NativeApiFastEngineArgKind::Selector; + default: + return std::nullopt; + } +} + +bool readFastEngineBoolArgument(Runtime& runtime, const Value& value, + BOOL* result) { + if (result == nullptr || !value.isBool()) { + return false; + } + *result = value.getBool() ? YES : NO; + return true; +} + +bool readFastEngineSignedIntegerArgument(Runtime& runtime, const Value& value, + NSInteger* result) { + if (result == nullptr) { + return false; + } + if (value.isNumber()) { + *result = static_cast(value.getNumber()); + return true; + } + return false; +} + +bool readFastEngineUnsignedIntegerArgument(Runtime& runtime, const Value& value, + NSUInteger* result) { + if (result == nullptr) { + return false; + } + if (value.isNumber()) { + *result = static_cast(value.getNumber()); + return true; + } + return false; +} + +bool readFastEngineFloatArgument(Runtime&, const Value& value, float* result) { + if (result == nullptr || !value.isNumber()) { + return false; + } + *result = static_cast(value.getNumber()); + return true; +} + +bool readFastEngineDoubleArgument(Runtime&, const Value& value, double* result) { + if (result == nullptr || !value.isNumber()) { + return false; + } + *result = value.getNumber(); + return true; +} + +bool readFastEngineObjectArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, + NativeApiArgumentFrame& frame, id* result) { + if (result == nullptr) { + return false; + } + *result = objectFromEngineValue( + runtime, bridge, value, frame, + type.kind == metagen::mdTypeNSMutableStringObject); + if (valueIsNativeObjectHostObject(runtime, value)) { + frame.retainObject(*result); + } + return true; +} + +class NativeApiScopedObjCObjectRetain { + public: + explicit NativeApiScopedObjCObjectRetain(id object) : object_(object) { + if (object_ != nil) { + [object_ retain]; + } + } + + ~NativeApiScopedObjCObjectRetain() { + if (object_ != nil) { + [object_ release]; + } + } + + private: + id object_ = nil; +}; + +bool readFastEngineClassArgument(Runtime& runtime, const Value& value, + Class* result) { + if (result == nullptr) { + return false; + } + *result = classFromEngineValue(runtime, value); + return *result != Nil; +} + +bool readFastEngineSelectorArgument(Runtime& runtime, const Value& value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (value.isNull() || value.isUndefined()) { + *result = nullptr; + return true; + } + if (!value.isString()) { + return false; + } + std::string selectorName = value.asString(runtime).utf8(runtime); + *result = sel_registerName(selectorName.c_str()); + return true; +} + +template +Value callFastEngineCFunctionWithReturn( + Runtime& runtime, const std::shared_ptr& bridge, + void* function, NativeApiType returnType, Args... nativeArgs) { + auto finalizeObjectReturn = [&](id object) -> Value { + return convertNativeReturnValue(runtime, bridge, returnType, &object); + }; + + switch (returnType.kind) { + case metagen::mdTypeVoid: { + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = void (*)(Args...); + reinterpret_cast(function)(nativeArgs...); + }); + return Value::undefined(); + } + case metagen::mdTypeBool: { + BOOL nativeResult = NO; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = BOOL (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + uint8_t storage = nativeResult ? 1 : 0; + return convertNativeReturnValue(runtime, bridge, returnType, &storage); + } + case metagen::mdTypeFloat: { + float nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = float (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + case metagen::mdTypeDouble: { + double nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = double (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + default: + break; + } + + if (isFastEngineObjectType(returnType) || + returnType.kind == metagen::mdTypeClass) { + id nativeResult = nil; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = id (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return finalizeObjectReturn(nativeResult); + } + + if (returnType.kind == metagen::mdTypeSelector) { + SEL nativeResult = nullptr; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = SEL (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + if (isFastEngineSignedIntegerType(returnType)) { + int64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = int64_t (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + if (isFastEngineUnsignedIntegerType(returnType)) { + uint64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = uint64_t (*)(Args...); + nativeResult = reinterpret_cast(function)(nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + throw JSError(runtime, "C function return type is not engine fast-callable."); +} + +bool isFastEngineCallableReturnType(const NativeApiType& returnType) { + return isFastEngineObjectType(returnType) || + returnType.kind == metagen::mdTypeVoid || + returnType.kind == metagen::mdTypeBool || + returnType.kind == metagen::mdTypeFloat || + returnType.kind == metagen::mdTypeDouble || + returnType.kind == metagen::mdTypeClass || + returnType.kind == metagen::mdTypeSelector || + isFastEngineSignedIntegerType(returnType) || + isFastEngineUnsignedIntegerType(returnType); +} + +bool tryCallFastEngineCFunction( + Runtime& runtime, const std::shared_ptr& bridge, + void* function, const NativeApiSignature& signature, const Value* args, + size_t count, Value* result) { + if (result == nullptr || function == nullptr || signature.variadic || + count != signature.argumentTypes.size() || count > 2 || + unsupportedEngineType(signature.returnType) || + !isFastEngineCallableReturnType(signature.returnType)) { + return false; + } + + std::optional firstArgKind; + std::optional secondArgKind; + if (count > 0) { + firstArgKind = fastEngineArgKind(signature.argumentTypes[0]); + if (!firstArgKind) { + return false; + } + } + if (count > 1) { + secondArgKind = fastEngineArgKind(signature.argumentTypes[1]); + if (!secondArgKind) { + return false; + } + } + + if (count == 0) { + *result = callFastEngineCFunctionWithReturn( + runtime, bridge, function, signature.returnType); + return true; + } + + NativeApiArgumentFrame frame(count); + auto callOne = [&](auto nativeArg0) -> Value { + return callFastEngineCFunctionWithReturn(runtime, bridge, function, + signature.returnType, nativeArg0); + }; + auto callTwo = [&](auto nativeArg0, auto nativeArg1) -> Value { + return callFastEngineCFunctionWithReturn(runtime, bridge, function, + signature.returnType, nativeArg0, + nativeArg1); + }; + + auto callWithSecondArg = [&](auto nativeArg0) -> bool { + switch (*secondArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg1 = NO; + if (!readFastEngineBoolArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg1 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg1 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Float: { + float arg1 = 0; + if (!readFastEngineFloatArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Double: { + double arg1 = 0; + if (!readFastEngineDoubleArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Object: { + id arg1 = nil; + if (!readFastEngineObjectArgument( + runtime, bridge, signature.argumentTypes[1], args[1], frame, + &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Class: { + Class arg1 = Nil; + if (!readFastEngineClassArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg1 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + } + return false; + }; + + switch (*firstArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg0 = NO; + if (!readFastEngineBoolArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg0 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg0 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Float: { + float arg0 = 0; + if (!readFastEngineFloatArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Double: { + double arg0 = 0; + if (!readFastEngineDoubleArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Object: { + id arg0 = nil; + if (!readFastEngineObjectArgument(runtime, bridge, + signature.argumentTypes[0], args[0], + frame, &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Class: { + Class arg0 = Nil; + if (!readFastEngineClassArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg0 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + } + + return false; +} + +template +Value callFastEngineObjCWithReturn( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, SEL selector, NativeApiType returnType, + const std::string& selectorName, Args... nativeArgs) { + auto finalizeObjectReturn = [&](id object) -> Value { + NativeApiType effectiveReturnType = returnType; + if ((selectorName == "valueForKey:" || selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(effectiveReturnType)) { + effectiveReturnType.kind = metagen::mdTypeAnyObject; + } + if (startsWith(selectorName, "init") && + isObjectiveCObjectType(effectiveReturnType)) { + effectiveReturnType.kind = metagen::mdTypeInstanceObject; + } + return convertNativeReturnValue(runtime, bridge, effectiveReturnType, + &object); + }; + + switch (returnType.kind) { + case metagen::mdTypeVoid: { + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = void (*)(id, SEL, Args...); + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return Value::undefined(); + } + case metagen::mdTypeBool: { + BOOL nativeResult = NO; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = BOOL (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + uint8_t storage = nativeResult ? 1 : 0; + return convertNativeReturnValue(runtime, bridge, returnType, &storage); + } + case metagen::mdTypeFloat: { + float nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = float (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + case metagen::mdTypeDouble: { + double nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = double (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + default: + break; + } + + if (isFastEngineObjectType(returnType) || returnType.kind == metagen::mdTypeClass) { + id nativeResult = nil; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = id (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return finalizeObjectReturn(nativeResult); + } + + if (isFastEngineSignedIntegerType(returnType)) { + int64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = int64_t (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + if (isFastEngineUnsignedIntegerType(returnType)) { + uint64_t nativeResult = 0; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + using Fn = uint64_t (*)(id, SEL, Args...); + nativeResult = + reinterpret_cast(objc_msgSend)(receiver, selector, nativeArgs...); + }); + return convertNativeReturnValue(runtime, bridge, returnType, + &nativeResult); + } + + throw JSError(runtime, "Objective-C return type is not engine fast-callable."); +} + +template +Value callFastEngineObjC1( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, SEL selector, const NativeApiType& returnType, + const std::string& selectorName, A0 arg0) { + return callFastEngineObjCWithReturn(runtime, bridge, receiver, selector, + returnType, selectorName, arg0); +} + +template +Value callFastEngineObjC2( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, SEL selector, const NativeApiType& returnType, + const std::string& selectorName, A0 arg0, A1 arg1) { + return callFastEngineObjCWithReturn(runtime, bridge, receiver, selector, + returnType, selectorName, arg0, arg1); +} + +bool tryCallFastEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result) { + if (result == nullptr || receiver == nil || dispatchSuperClass != Nil) { + return false; + } + + const NativeApiSignature& signature = prepared.signature; + if (!prepared.fastEngineCallable || + count != prepared.fastEngineArgumentCount) { + return false; + } + + NativeApiFastEngineArgKind firstArgKind = + static_cast(prepared.fastEngineFirstArgKind); + NativeApiFastEngineArgKind secondArgKind = + static_cast(prepared.fastEngineSecondArgKind); + + SEL selector = prepared.selector; + if (count == 0) { + *result = callFastEngineObjCWithReturn( + runtime, bridge, receiver, selector, signature.returnType, + prepared.selectorName); + return true; + } + + NativeApiArgumentFrame frame(count); + auto callOne = [&](auto nativeArg0) -> Value { + return callFastEngineObjC1(runtime, bridge, receiver, selector, + signature.returnType, prepared.selectorName, + nativeArg0); + }; + auto callTwo = [&](auto nativeArg0, auto nativeArg1) -> Value { + return callFastEngineObjC2(runtime, bridge, receiver, selector, + signature.returnType, prepared.selectorName, + nativeArg0, nativeArg1); + }; + auto callWithSecondArg = [&](auto nativeArg0) -> bool { + switch (secondArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg1 = NO; + if (!readFastEngineBoolArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg1 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg1 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Float: { + float arg1 = 0; + if (!readFastEngineFloatArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Double: { + double arg1 = 0; + if (!readFastEngineDoubleArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Object: { + id arg1 = nil; + if (!readFastEngineObjectArgument( + runtime, bridge, signature.argumentTypes[1], args[1], frame, + &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Class: { + Class arg1 = Nil; + if (!readFastEngineClassArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg1 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[1], &arg1)) { + return false; + } + *result = callTwo(nativeArg0, arg1); + return true; + } + } + return false; + }; + + switch (firstArgKind) { + case NativeApiFastEngineArgKind::Bool: { + BOOL arg0 = NO; + if (!readFastEngineBoolArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::SignedInteger: { + NSInteger arg0 = 0; + if (!readFastEngineSignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::UnsignedInteger: { + NSUInteger arg0 = 0; + if (!readFastEngineUnsignedIntegerArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Float: { + float arg0 = 0; + if (!readFastEngineFloatArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Double: { + double arg0 = 0; + if (!readFastEngineDoubleArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Object: { + id arg0 = nil; + if (!readFastEngineObjectArgument(runtime, bridge, signature.argumentTypes[0], + args[0], frame, &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Class: { + Class arg0 = Nil; + if (!readFastEngineClassArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + case NativeApiFastEngineArgKind::Selector: { + SEL arg0 = nullptr; + if (!readFastEngineSelectorArgument(runtime, args[0], &arg0)) { + return false; + } + if (count == 1) { + *result = callOne(arg0); + return true; + } + return callWithSecondArg(arg0); + } + } + + return false; +} + +bool isFastEngineObjCReturnType(const NativeApiType& returnType) { + return !unsupportedEngineType(returnType) && + (isFastEngineObjectType(returnType) || + returnType.kind == metagen::mdTypeVoid || + returnType.kind == metagen::mdTypeBool || + returnType.kind == metagen::mdTypeFloat || + returnType.kind == metagen::mdTypeDouble || + returnType.kind == metagen::mdTypeClass || + isFastEngineSignedIntegerType(returnType) || + isFastEngineUnsignedIntegerType(returnType)); +} + +void configureFastEngineObjCInvocation( + NativeApiPreparedObjCInvocation& prepared) { + prepared.fastEngineCallable = false; + prepared.fastEngineArgumentCount = 0; + prepared.fastEngineFirstArgKind = 0; + prepared.fastEngineSecondArgKind = 0; + + const NativeApiSignature& signature = prepared.signature; + if (signature.variadic || prepared.isNSErrorOutMethod || + signature.argumentTypes.size() > 2 || + !isFastEngineObjCReturnType(signature.returnType)) { + return; + } + + if (!signature.argumentTypes.empty()) { + std::optional firstArgKind = + fastEngineArgKind(signature.argumentTypes[0]); + if (!firstArgKind) { + return; + } + prepared.fastEngineFirstArgKind = static_cast(*firstArgKind); + } + if (signature.argumentTypes.size() > 1) { + std::optional secondArgKind = + fastEngineArgKind(signature.argumentTypes[1]); + if (!secondArgKind) { + return; + } + prepared.fastEngineSecondArgKind = static_cast(*secondArgKind); + } + + prepared.fastEngineArgumentCount = + static_cast(signature.argumentTypes.size()); + prepared.fastEngineCallable = true; +} + +void configureGeneratedEngineObjCInvocation( + NativeApiPreparedObjCInvocation& prepared) { + prepared.gsdEngineCallable = false; + prepared.gsdEngineArgumentCount = 0; + + const NativeApiSignature& signature = prepared.signature; + if (prepared.engineInvoker == nullptr || signature.variadic || + prepared.isNSErrorOutMethod || signature.argumentTypes.size() > 255) { + return; + } + + prepared.gsdEngineArgumentCount = + static_cast(signature.argumentTypes.size()); + prepared.gsdEngineCallable = true; +} + +std::shared_ptr +prepareNativeApiObjCInvocation( + Runtime& runtime, const std::shared_ptr& bridge, + Class lookupClass, bool receiverIsClass, const std::string& selectorName, + const NativeApiMember* member) { + if (lookupClass == Nil) { + throw JSError(runtime, + "Objective-C class is not available for selector: " + + selectorName); + } + + SEL selector = sel_registerName(selectorName.c_str()); + Method method = receiverIsClass ? class_getClassMethod(lookupClass, selector) + : class_getInstanceMethod(lookupClass, selector); + if (method == nullptr) { + throw JSError(runtime, + "Objective-C selector is not available: " + selectorName); + } + + std::optional signature; + std::optional runtimeSignature; + if (member != nullptr && + member->signatureOffset != MD_SECTION_OFFSET_NULL && + member->signatureOffset != 0) { + signature = parseMetadataEngineSignature( + bridge->metadata(), member->signatureOffset, 2, bridge.get(), + (member->flags & metagen::mdMemberReturnOwned) != 0); + } + if (method != nullptr) { + runtimeSignature = parseObjCMethodEngineSignature(method, bridge.get()); + } + if (signatureSupportedForEngineInvocation(signature) && + signatureSupportedForEngineInvocation(runtimeSignature)) { + reconcileObjCMethodRuntimeSignature(&*signature, *runtimeSignature); + } + if (!signatureSupportedForEngineInvocation(signature) && runtimeSignature) { + signature = std::move(runtimeSignature); + } + + if (!signatureSupportedForEngineInvocation(signature)) { + throw JSError( + runtime, "Objective-C signature is not supported by backend: " + + selectorName); + } + signature->selectorName = selectorName; + + auto prepared = std::make_shared(); + prepared->selector = selector; + prepared->receiverClass = receiverIsClass ? lookupClass : Nil; + prepared->selectorName = selectorName; + prepared->signature = std::move(*signature); + prepared->preparedInvoker = lookupObjCPreparedInvoker( + dispatchIdForEngineSignature(prepared->signature, + SignatureCallKind::ObjCMethod)); + prepared->engineInvoker = lookupGeneratedEngineObjCGsdInvoker( + dispatchIdForEngineSignature(prepared->signature, + SignatureCallKind::ObjCMethod)); + prepared->isNSErrorOutMethod = + isNSErrorOutEngineMethodSignature(prepared->signature); + prepared->isInitMethod = prepared->selectorName.rfind("init", 0) == 0; + configureGeneratedEngineObjCInvocation(*prepared); + configureFastEngineObjCInvocation(*prepared); + return prepared; +} + +Value callPreparedObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const NativeApiPreparedObjCInvocation& prepared, const Value* args, + size_t count, Class dispatchSuperClass) { + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge); + + const NativeApiSignature& signature = prepared.signature; + Value fastResult; + if (tryCallGeneratedEngineObjCSelector(runtime, bridge, receiver, prepared, + args, count, dispatchSuperClass, + &fastResult)) { + return fastResult; + } + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, args, + count, dispatchSuperClass, &fastResult)) { + return fastResult; + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + frame.retainObject(receiver); + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (count > expected || count + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(count) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && count + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + for (size_t i = 0; i < count; i++) { + prepareEngineArgument(runtime, bridge, signature.argumentTypes[i], args[i], + i, frame); + } + + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } else { + prepareEngineArguments(runtime, bridge, signature, args, count, frame); + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if ((prepared.selectorName == "valueForKey:" || + prepared.selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeAnyObject; + } + if (prepared.isInitMethod && + isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeInstanceObject; + } + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + return convertNativeReturnValue(runtime, bridge, returnType, + returnStorage.data()); +} + +Value callObjCSelector(Runtime& runtime, + const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const std::string& selectorName, + const NativeApiMember* member, + const Value* args, size_t count, + Class dispatchSuperClass) { + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + NativeApiRoundTripCacheFrameGuard roundTripFrame(bridge); + + SEL selector = sel_registerName(selectorName.c_str()); + Class receiverClass = + receiverIsClass ? static_cast(receiver) : object_getClass(receiver); + Class lookupClass = dispatchSuperClass != Nil ? dispatchSuperClass : receiverClass; + Method method = receiverIsClass ? class_getClassMethod(lookupClass, selector) + : class_getInstanceMethod(lookupClass, selector); + if (method == nullptr && + (dispatchSuperClass != Nil || ![receiver respondsToSelector:selector])) { + throw JSError(runtime, + "Objective-C selector is not available: " + + selectorName); + } + + std::optional signature; + std::optional runtimeSignature; + if (member != nullptr && + member->signatureOffset != MD_SECTION_OFFSET_NULL && + member->signatureOffset != 0) { + signature = parseMetadataEngineSignature( + bridge->metadata(), member->signatureOffset, 2, bridge.get(), + (member->flags & metagen::mdMemberReturnOwned) != 0); + } + if (method != nullptr) { + runtimeSignature = parseObjCMethodEngineSignature(method, bridge.get()); + } + if (signatureSupportedForEngineInvocation(signature) && + signatureSupportedForEngineInvocation(runtimeSignature)) { + reconcileObjCMethodRuntimeSignature(&*signature, *runtimeSignature); + } + if (!signatureSupportedForEngineInvocation(signature) && runtimeSignature) { + signature = std::move(runtimeSignature); + } + + if (!signatureSupportedForEngineInvocation(signature)) { + throw JSError( + runtime, "Objective-C signature is not supported by backend: " + + selectorName); + } + signature->selectorName = selectorName; + + NativeApiPreparedObjCInvocation engineInvocation; + engineInvocation.selector = selector; + engineInvocation.selectorName = selectorName; + engineInvocation.signature = *signature; + engineInvocation.engineInvoker = lookupGeneratedEngineObjCGsdInvoker( + dispatchIdForEngineSignature(*signature, SignatureCallKind::ObjCMethod)); + engineInvocation.isNSErrorOutMethod = + isNSErrorOutEngineMethodSignature(*signature); + engineInvocation.isInitMethod = selectorName.rfind("init", 0) == 0; + configureGeneratedEngineObjCInvocation(engineInvocation); + configureFastEngineObjCInvocation(engineInvocation); + Value fastResult; + if (tryCallGeneratedEngineObjCSelector(runtime, bridge, receiver, + engineInvocation, args, count, + dispatchSuperClass, &fastResult)) { + return fastResult; + } + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, + engineInvocation, args, count, + dispatchSuperClass, &fastResult)) { + return fastResult; + } + + NativeApiArgumentFrame frame(signature->argumentTypes.size()); + const bool isNSErrorOutMethod = engineInvocation.isNSErrorOutMethod; + if (isNSErrorOutMethod) { + size_t expected = signature->argumentTypes.size(); + if (count > expected || count + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(count) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && count + 1 == signature->argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + for (size_t i = 0; i < count; i++) { + prepareEngineArgument(runtime, bridge, signature->argumentTypes[i], args[i], i, + frame); + } + + size_t outArgIndex = signature->argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } else { + prepareEngineArguments(runtime, bridge, *signature, args, count, frame); + } + + NativeApiPointerFrame values(signature->argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, &selector); + for (size_t i = 0; i < signature->argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature->returnType)); + auto preparedInvoker = + dispatchSuperClass == Nil + ? lookupObjCPreparedInvoker(dispatchIdForEngineSignature( + *signature, SignatureCallKind::ObjCMethod)) + : nullptr; + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (preparedInvoker != nullptr) { + preparedInvoker(reinterpret_cast(objc_msgSend), values.data(), + returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature->returnType.ffiType->size > 16 && + signature->returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(&signature->cif, target, returnStorage.data(), values.data()); +#else + ffi_call(&signature->cif, + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature->returnType; + if ((selectorName == "valueForKey:" || selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeAnyObject; + } + if (engineInvocation.isInitMethod && isObjectiveCObjectType(returnType)) { + returnType.kind = metagen::mdTypeInstanceObject; + } + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + return convertNativeReturnValue(runtime, bridge, returnType, + returnStorage.data()); +} diff --git a/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm new file mode 100644 index 000000000..4dddd0017 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm @@ -0,0 +1,2424 @@ +thread_local int gSynchronousNativeInvocationDepth = 0; +thread_local int gNativeCallerThreadEngineCallbackDepth = 0; +thread_local std::vector gNativeCallbackExceptionCaptureStack; +std::atomic gActiveSynchronousNativeInvocationDepth{0}; + +class ScopedNativeApiSynchronousInvocation final { + public: + ScopedNativeApiSynchronousInvocation() { + gSynchronousNativeInvocationDepth += 1; + gActiveSynchronousNativeInvocationDepth.fetch_add(1, + std::memory_order_acq_rel); + } + + ~ScopedNativeApiSynchronousInvocation() { + gSynchronousNativeInvocationDepth -= 1; + gActiveSynchronousNativeInvocationDepth.fetch_sub(1, + std::memory_order_acq_rel); + } +}; + +class ScopedNativeCallerThreadEngineCallback final { + public: + ScopedNativeCallerThreadEngineCallback() { + gNativeCallerThreadEngineCallbackDepth += 1; + } + + ~ScopedNativeCallerThreadEngineCallback() { + gNativeCallerThreadEngineCallbackDepth -= 1; + } + + ScopedNativeCallerThreadEngineCallback( + const ScopedNativeCallerThreadEngineCallback&) = delete; + ScopedNativeCallerThreadEngineCallback& operator=( + const ScopedNativeCallerThreadEngineCallback&) = delete; +}; + +class ScopedNativeCallbackExceptionCapture final { + public: + explicit ScopedNativeCallbackExceptionCapture(std::string* message) + : message_(message) { + gNativeCallbackExceptionCaptureStack.push_back(message_); + } + + ~ScopedNativeCallbackExceptionCapture() { + if (!gNativeCallbackExceptionCaptureStack.empty() && + gNativeCallbackExceptionCaptureStack.back() == message_) { + gNativeCallbackExceptionCaptureStack.pop_back(); + } + } + + ScopedNativeCallbackExceptionCapture( + const ScopedNativeCallbackExceptionCapture&) = delete; + ScopedNativeCallbackExceptionCapture& operator=( + const ScopedNativeCallbackExceptionCapture&) = delete; + + private: + std::string* message_ = nullptr; +}; + +bool recordNativeCallbackException(const std::string& message) { + if (gNativeCallbackExceptionCaptureStack.empty()) { + return false; + } + + std::string* captured = gNativeCallbackExceptionCaptureStack.back(); + if (captured == nullptr) { + return false; + } + + if (captured->empty()) { + *captured = message; + } + return true; +} + +template +void performNativeInvocation(Runtime& runtime, + const std::function)>& + invoker, + Invocation&& invocation) { + NSString* exceptionDescription = nil; + std::string callbackException; + auto run = [&]() { + ScopedNativeApiSynchronousInvocation synchronousInvocation; + ScopedNativeCallbackExceptionCapture callbackExceptionCapture( + &callbackException); + @try { + invocation(); + } @catch (NSException* exception) { + exceptionDescription = [exception.description copy]; + } + }; + + bool skipInvoker = gNativeCallerThreadEngineCallbackDepth > 0; + if (invoker && !skipInvoker) { + invoker(run); + } else { + run(); + } + + if (exceptionDescription != nil) { + std::string message = exceptionDescription.UTF8String ?: ""; + [exceptionDescription release]; + throw JSError(runtime, message); + } + if (!callbackException.empty()) { + throw JSError(runtime, callbackException); + } +} + +template +void performDirectObjCInvocation(Runtime& runtime, Invocation&& invocation) { + NSString* exceptionDescription = nil; + auto run = [&]() { + @try { + invocation(); + } @catch (NSException* exception) { + exceptionDescription = [exception.description copy]; + } + }; + + run(); + + if (exceptionDescription != nil) { + std::string message = exceptionDescription.UTF8String ?: ""; + [exceptionDescription release]; + throw JSError(runtime, message); + } +} + +enum class NativeApiSymbolKind { + Class, + Function, + Constant, + Protocol, + Enum, + Struct, + Union, +}; + +struct NativeApiSymbol { + NativeApiSymbolKind kind; + MDSectionOffset offset = 0; + MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL; + std::string name; + std::string runtimeName; +}; + +struct NativeApiMember { + std::string name; + std::string selectorName; + std::string setterSelectorName; + MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; + MDSectionOffset setterSignatureOffset = MD_SECTION_OFFSET_NULL; + MDMemberFlag flags = metagen::mdMemberFlagNull; + bool property = false; + bool readonly = false; +}; + +struct NativeApiSelectorGroupEntry { + std::string selectorName; + NativeApiMember member; + bool hasMember = false; + bool propertyGetterResolved = false; + bool propertyGetterCanPrepare = true; + bool propertyGetterHasAdjustedMember = false; + std::string propertyGetterSelectorName; + NativeApiMember propertyGetterMember; +}; + +struct NativeApiSelectorGroupCallTarget { + const std::string* selectorName = nullptr; + const NativeApiMember* member = nullptr; + bool canPrepare = true; +}; + +struct NativeApiAggregateInfo; + +struct NativeApiFfiType { + ffi_type type = {}; + std::vector elements; + + NativeApiFfiType() { + type.type = FFI_TYPE_STRUCT; + type.size = 0; + type.alignment = 0; + type.elements = nullptr; + } + + void finalize() { + elements.push_back(nullptr); + type.elements = elements.data(); + } +}; + +struct NativeApiType { + MDTypeKind kind = metagen::mdTypeVoid; + ffi_type* ffiType = &ffi_type_void; + bool supported = true; + bool returnOwned = false; + MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; + MDSectionOffset aggregateOffset = MD_SECTION_OFFSET_NULL; + bool aggregateIsUnion = false; + uint16_t arraySize = 0; + std::shared_ptr elementType; + std::shared_ptr aggregateInfo; + std::shared_ptr ownedFfiType; +}; + +struct NativeApiAggregateField { + std::string name; + uint16_t offset = 0; + NativeApiType type; +}; + +struct NativeApiAggregateInfo { + std::string name; + uint16_t size = 0; + bool isUnion = false; + MDSectionOffset offset = MD_SECTION_OFFSET_NULL; + std::vector fields; + std::shared_ptr ffi; +}; + +std::string jsifySelector(const char* selector) { + std::string jsifiedSelector; + bool nextUpper = false; + for (const char* c = selector; c != nullptr && *c != '\0'; c++) { + if (*c == ':') { + nextUpper = true; + } else if (nextUpper) { + jsifiedSelector += static_cast(toupper(*c)); + nextUpper = false; + } else { + jsifiedSelector += *c; + } + } + return jsifiedSelector; +} + +std::string booleanGetterSelectorForProperty(const std::string& property) { + if (property.empty()) { + return property; + } + + std::string selector = "is"; + selector += static_cast(toupper(property[0])); + selector += property.substr(1); + return selector; +} + +std::optional respondingPropertyGetterSelector( + id receiver, const std::string& property, + const std::string& preferredSelector) { + if (receiver == nil) { + return std::nullopt; + } + + auto respondsToSelectorName = [receiver](const std::string& selectorName) { + return !selectorName.empty() && + [receiver respondsToSelector:sel_getUid(selectorName.c_str())]; + }; + + if (respondsToSelectorName(preferredSelector)) { + return preferredSelector; + } + if (preferredSelector != property && respondsToSelectorName(property)) { + return property; + } + + std::string booleanSelector = booleanGetterSelectorForProperty(property); + if (booleanSelector != preferredSelector && booleanSelector != property && + respondsToSelectorName(booleanSelector)) { + return booleanSelector; + } + + return std::nullopt; +} + +std::string setterSelectorForProperty(const std::string& property) { + if (property.empty()) { + return property; + } + + std::string selector = "set"; + selector += static_cast(toupper(property[0])); + selector += property.substr(1); + selector += ":"; + return selector; +} + +size_t selectorArgumentCount(const std::string& selector) { + return static_cast( + std::count(selector.begin(), selector.end(), ':')); +} + +const NativeApiMember* selectMethodMember( + const std::vector& members, const std::string& property, + bool staticMethod, size_t argumentCount) { + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMethod) { + continue; + } + + if (selectorArgumentCount(member.selectorName) == argumentCount) { + return &member; + } + } + return nullptr; +} + +bool hasMethodMember(const std::vector& members, + const std::string& property, bool staticMethod) { + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic == staticMethod) { + return true; + } + } + return false; +} + +std::shared_ptr> +selectorGroupEntriesForMethod(const std::vector& members, + const std::string& property, bool staticMethod) { + auto selectors = std::make_shared>(); + for (const auto& member : members) { + if (member.property || member.name != property || member.selectorName.empty()) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMethod) { + continue; + } + + size_t argumentCount = selectorArgumentCount(member.selectorName); + if (selectors->size() <= argumentCount) { + selectors->resize(argumentCount + 1); + } + if ((*selectors)[argumentCount].selectorName.empty()) { + (*selectors)[argumentCount].selectorName = member.selectorName; + (*selectors)[argumentCount].member = member; + (*selectors)[argumentCount].hasMember = true; + } + + if (argumentCount > 0 && member.selectorName.size() >= 6 && + member.selectorName.compare(member.selectorName.size() - 6, 6, + "error:") == 0) { + size_t omittedErrorCount = argumentCount - 1; + if (selectors->size() <= omittedErrorCount) { + selectors->resize(omittedErrorCount + 1); + } + if ((*selectors)[omittedErrorCount].selectorName.empty()) { + (*selectors)[omittedErrorCount].selectorName = member.selectorName; + (*selectors)[omittedErrorCount].member = member; + (*selectors)[omittedErrorCount].hasMember = true; + } + } + } + return selectors->empty() ? nullptr : selectors; +} + +bool selectorGroupCanPrepareSelector(id receiver, Class lookupClass, + bool receiverIsClass, + const std::string& selectorName) { + if (selectorName.empty()) { + return false; + } + SEL selector = sel_registerName(selectorName.c_str()); + if (receiverIsClass) { + return lookupClass != Nil && + class_getClassMethod(lookupClass, selector) != nullptr; + } + if (lookupClass != Nil && + class_getInstanceMethod(lookupClass, selector) != nullptr) { + return true; + } + return receiver != nil && + class_getInstanceMethod(object_getClass(receiver), selector) != nullptr; +} + +std::string selectorGroupPropertyGetterSelector( + id receiver, Class lookupClass, bool receiverIsClass, + const NativeApiMember& member) { + if (selectorGroupCanPrepareSelector(receiver, lookupClass, receiverIsClass, + member.selectorName)) { + return member.selectorName; + } + if (member.selectorName != member.name && + selectorGroupCanPrepareSelector(receiver, lookupClass, receiverIsClass, + member.name)) { + return member.name; + } + + std::string booleanSelector = booleanGetterSelectorForProperty(member.name); + if (booleanSelector != member.selectorName && booleanSelector != member.name && + selectorGroupCanPrepareSelector(receiver, lookupClass, receiverIsClass, + booleanSelector)) { + return booleanSelector; + } + + if (auto responding = respondingPropertyGetterSelector( + receiver, member.name, member.selectorName)) { + return *responding; + } + + return member.selectorName != member.name ? member.name : member.selectorName; +} + +NativeApiSelectorGroupCallTarget selectorGroupMemberForCall( + id receiver, Class lookupClass, bool receiverIsClass, + NativeApiSelectorGroupEntry& entry, size_t count) { + if (!entry.hasMember) { + return {&entry.selectorName, nullptr, true}; + } + if (count == 0 && entry.member.property) { + if (!entry.propertyGetterResolved) { + entry.propertyGetterSelectorName = selectorGroupPropertyGetterSelector( + receiver, lookupClass, receiverIsClass, entry.member); + entry.propertyGetterCanPrepare = selectorGroupCanPrepareSelector( + receiver, lookupClass, receiverIsClass, + entry.propertyGetterSelectorName); + if (entry.propertyGetterSelectorName != entry.member.selectorName) { + entry.propertyGetterMember = entry.member; + entry.propertyGetterMember.selectorName = + entry.propertyGetterSelectorName; + entry.propertyGetterHasAdjustedMember = true; + } + entry.propertyGetterResolved = true; + } + return {&entry.propertyGetterSelectorName, + entry.propertyGetterHasAdjustedMember ? &entry.propertyGetterMember + : &entry.member, + entry.propertyGetterCanPrepare}; + } + return {&entry.selectorName, &entry.member, true}; +} + +inline NativeApiSelectorGroupCallTarget selectorGroupCallTargetForEntry( + id receiver, Class lookupClass, bool receiverIsClass, + NativeApiSelectorGroupEntry& entry, size_t count) { + if (entry.hasMember && (!entry.member.property || count != 0)) { + return {&entry.selectorName, &entry.member, true}; + } + return selectorGroupMemberForCall(receiver, lookupClass, receiverIsClass, + entry, count); +} + +const NativeApiMember* selectPropertyMember( + const std::vector& members, const std::string& property, + bool staticMethod) { + for (const auto& member : members) { + if (!member.property || member.name != property) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic == staticMethod) { + return &member; + } + } + return nullptr; +} + +const NativeApiMember* selectWritablePropertyMember( + const std::vector& members, const std::string& property, + bool staticMethod) { + const NativeApiMember* propertyMember = nullptr; + for (const auto& member : members) { + if (!member.property || member.name != property) { + continue; + } + + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMethod) { + continue; + } + + if (propertyMember == nullptr) { + propertyMember = &member; + } + if (!member.readonly && !member.setterSelectorName.empty()) { + return &member; + } + } + return propertyMember; +} + +void skipMetadataEngineType(MDMetadataReader* metadata, MDSectionOffset* offset); +Protocol* lookupProtocolByNativeName(const std::string& name); +struct NativeApiPreparedObjCInvocation; +bool preparedObjCInvocationIsInit( + const NativeApiPreparedObjCInvocation& prepared); + +inline uintptr_t normalizeRuntimePointer(uintptr_t pointer) { +#if INTPTR_MAX == INT64_MAX + return pointer & 0x0000FFFFFFFFFFFFULL; +#else + return pointer; +#endif +} + +class NativeApiBridge { + struct NativeApiRoundTripValue { + std::shared_ptr value; + bool stringLikeNative = false; + bool persistBeyondFrame = true; + uintptr_t validationKey = 0; + }; + using NativeApiRoundTripReleaseList = + std::vector; + using NativeApiRoundTripFrame = + std::unordered_map; + using NativeApiRoundTripFrameStack = std::vector; + + static constexpr size_t kRecentRoundTripValueLimit = 2; + + public: + explicit NativeApiBridge(const NativeApiConfig& config) + : metadata_(loadMetadata(config)), + scheduler_(config.scheduler), + nativeInvocationInvoker_(config.nativeInvocationInvoker), + nativeCallbackInvoker_(config.nativeCallbackInvoker), + runtimeCallbackInvoker_(config.runtimeCallbackInvoker), + jsThreadCallbackInvoker_(config.jsThreadCallbackInvoker), + jsThreadAsyncCallbackInvoker_(config.jsThreadAsyncCallbackInvoker), + invokeCallbacksOnNativeCallerThread_( + config.invokeCallbacksOnNativeCallerThread) { + selfDl_ = dlopen(nullptr, RTLD_NOW); + buildSymbolIndexes(); + } + + ~NativeApiBridge() { + if (selfDl_ != nullptr) { + dlclose(selfDl_); + } + } + + MDMetadataReader* metadata() const { return metadata_.get(); } + + void* selfDl() const { return selfDl_; } + + const NativeApiSymbol* find(const std::string& name) const { + auto it = symbolsByName_.find(name); + return it != symbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findClass(const std::string& name) const { + const NativeApiSymbol* symbol = find(name); + if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Class) { + return symbol; + } + auto it = classSymbolsByRuntimeName_.find(name); + return it != classSymbolsByRuntimeName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findClassByOffset(MDSectionOffset offset) const { + auto it = classSymbolsByOffset_.find(offset); + return it != classSymbolsByOffset_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findClassForRuntimeClass(Class cls) const { + Class current = cls; + while (current != Nil) { + const char* name = class_getName(current); + if (name != nullptr) { + if (const NativeApiSymbol* symbol = findClass(name)) { + return symbol; + } + } + current = class_getSuperclass(current); + } + return nullptr; + } + + const NativeApiSymbol* findClassForRuntimePointer(void* pointer) const { + if (pointer == nullptr) { + return nullptr; + } + + auto it = classSymbolsByRuntimePointer_.find( + normalizeRuntimePointer(reinterpret_cast(pointer))); + return it != classSymbolsByRuntimePointer_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findProtocolForRuntimePointer(void* pointer) const { + if (pointer == nullptr) { + return nullptr; + } + + auto it = protocolSymbolsByRuntimePointer_.find( + normalizeRuntimePointer(reinterpret_cast(pointer))); + return it != protocolSymbolsByRuntimePointer_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findFunction(const std::string& name) const { + auto it = functionSymbolsByName_.find(name); + return it != functionSymbolsByName_.end() ? &it->second : nullptr; + } + + static uintptr_t callbackRoundTripValidationKey( + const NativeApiType& type) { + if (type.signatureOffset == 0 || + type.signatureOffset == MD_SECTION_OFFSET_NULL) { + return 0; + } + return (static_cast(type.signatureOffset) << 8) | + (static_cast(type.kind) & 0xff); + } + + void rememberRoundTripValue(Runtime& runtime, const void* native, + const Value& value, + bool stringLikeNative = false, + uintptr_t validationKey = 0) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + storeRoundTripEntry( + roundTripValues_, key, + NativeApiRoundTripValue{ + std::make_shared(runtime, value), stringLikeNative, true, + validationKey}, + releaseAfterUnlock); + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } +#ifdef TARGET_ENGINE_HERMES + rootRoundTripValue(runtime, key, value); +#endif + } + + void rememberScopedRoundTripValue(Runtime& runtime, const void* native, + const Value& value, + bool stringLikeNative = false, + bool persistBeyondFrame = true) { + rememberScopedRoundTripValueWithValidationKey( + runtime, native, value, stringLikeNative, persistBeyondFrame, + nativeObjectClassKey(native)); + } + + void rememberScopedRawRoundTripValue(Runtime& runtime, const void* native, + const Value& value, + bool stringLikeNative = false, + bool persistBeyondFrame = true) { + rememberScopedRoundTripValueWithValidationKey(runtime, native, value, + stringLikeNative, + persistBeyondFrame, 0); + } + + void rememberScopedRoundTripValueWithValidationKey(Runtime& runtime, + const void* native, + const Value& value, + bool stringLikeNative, + bool persistBeyondFrame, + uintptr_t validationKey) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + NativeApiRoundTripValue entry{ + std::make_shared(runtime, value), stringLikeNative, + persistBeyondFrame, validationKey}; + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + auto framesIt = + roundTripCacheFramesByThread_.find(std::this_thread::get_id()); + if (framesIt != roundTripCacheFramesByThread_.end() && + !framesIt->second.empty()) { + storeRoundTripEntry(framesIt->second.back(), key, std::move(entry), + releaseAfterUnlock); + } else if (persistBeyondFrame) { + rememberRecentRoundTripValue(key, std::move(entry), + releaseAfterUnlock); + } + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + Value findRoundTripValue(Runtime& runtime, const void* native, + bool* stringLikeNative = nullptr, + bool nativeIsObject = false, + uintptr_t validationKey = 0) { + if (stringLikeNative != nullptr) { + *stringLikeNative = false; + } + if (native == nullptr) { + return Value::undefined(); + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + const uintptr_t expectedValidationKey = + validationKey != 0 + ? validationKey + : (nativeIsObject ? nativeObjectClassKey(native) : 0); + struct RoundTripCacheEntry { + uint64_t bridgeId = 0; + uintptr_t key = 0; + uint64_t generation = 0; + std::weak_ptr value; + bool miss = false; + bool stringLikeNative = false; + uintptr_t validationKey = 0; + }; + static thread_local RoundTripCacheEntry cache[4]; + const uint64_t generation = + roundTripValuesGeneration_.load(std::memory_order_acquire); + const size_t firstSlot = (key >> 4) & 3; + for (size_t i = 0; i < 4; i++) { + RoundTripCacheEntry& entry = cache[(firstSlot + i) & 3]; + if (entry.bridgeId == instanceId_ && entry.key == key && + entry.generation == generation) { + if (entry.validationKey != expectedValidationKey) { + break; + } + if (entry.miss) { + return Value::undefined(); + } + if (auto cached = entry.value.lock()) { + if (roundTripValuesGeneration_.load(std::memory_order_acquire) == + generation) { + if (stringLikeNative != nullptr) { + *stringLikeNative = entry.stringLikeNative; + } + return Value(runtime, *cached); + } + } + break; + } + } + + std::shared_ptr storedValue; + bool cachedStringLike = false; + { + std::lock_guard lock(roundTripValuesMutex_); + auto findEntry = [&](const auto& map) -> const NativeApiRoundTripValue* { + auto it = map.find(key); + if (it == map.end() || it->second.value == nullptr) { + return nullptr; + } + if (it->second.validationKey != expectedValidationKey) { + return nullptr; + } + return &it->second; + }; + + const NativeApiRoundTripValue* entry = findEntry(roundTripValues_); + if (entry == nullptr) { + auto framesIt = + roundTripCacheFramesByThread_.find(std::this_thread::get_id()); + if (framesIt != roundTripCacheFramesByThread_.end()) { + for (auto frame = framesIt->second.rbegin(); + frame != framesIt->second.rend(); ++frame) { + entry = findEntry(*frame); + if (entry != nullptr) { + break; + } + } + } + } + if (entry == nullptr) { + entry = findEntry(recentRoundTripValues_); + } + if (entry == nullptr) { + cache[firstSlot] = RoundTripCacheEntry{ + instanceId_, key, generation, {}, true, false, expectedValidationKey}; + return Value::undefined(); + } + storedValue = entry->value; + cachedStringLike = entry->stringLikeNative; + cache[firstSlot] = RoundTripCacheEntry{ + instanceId_, key, generation, storedValue, false, cachedStringLike, + entry->validationKey}; + } + if (stringLikeNative != nullptr) { + *stringLikeNative = cachedStringLike; + } + return Value(runtime, *storedValue); + } + + void forgetRoundTripValue(Runtime& runtime, const void* native) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); +#ifdef TARGET_ENGINE_HERMES + bool rooted = false; + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + eraseRoundTripMapKey(roundTripValues_, key, releaseAfterUnlock); + eraseRoundTripKeyFromScopedCaches(key, releaseAfterUnlock); + rooted = rootedRoundTripValues_.erase(key) > 0; + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + if (rooted) { + unrootRoundTripValue(runtime, key); + } +#else + forgetRoundTripKey(key); +#endif + } + + void forgetRoundTripKey(uintptr_t key) { + if (key == 0) { + return; + } + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + eraseRoundTripMapKey(roundTripValues_, key, releaseAfterUnlock); + eraseRoundTripKeyFromScopedCaches(key, releaseAfterUnlock); + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + void forgetRoundTripValue(const void* native) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + NativeApiRoundTripReleaseList releaseAfterUnlock; + { + std::lock_guard lock(roundTripValuesMutex_); + eraseRoundTripMapKey(roundTripValues_, key, releaseAfterUnlock); + eraseRoundTripKeyFromScopedCaches(key, releaseAfterUnlock); + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + uint64_t roundTripValuesGeneration() const { + return roundTripValuesGeneration_.load(std::memory_order_acquire); + } + + void beginRoundTripCacheFrame() { + std::lock_guard lock(roundTripValuesMutex_); + roundTripCacheFramesByThread_[std::this_thread::get_id()].emplace_back(); + } + + void endRoundTripCacheFrame() { + NativeApiRoundTripReleaseList releaseAfterUnlock; + NativeApiRoundTripFrame frame; + { + std::lock_guard lock(roundTripValuesMutex_); + auto framesIt = + roundTripCacheFramesByThread_.find(std::this_thread::get_id()); + if (framesIt == roundTripCacheFramesByThread_.end() || + framesIt->second.empty()) { + return; + } + + auto& frames = framesIt->second; + frame = std::move(frames.back()); + frames.pop_back(); + if (!frames.empty()) { + auto& parent = frames.back(); + for (auto& entry : frame) { + storeRoundTripEntry(parent, entry.first, std::move(entry.second), + releaseAfterUnlock); + } + } else { + roundTripCacheFramesByThread_.erase(framesIt); + for (auto& entry : frame) { + if (entry.second.persistBeyondFrame) { + rememberRecentRoundTripValue(entry.first, std::move(entry.second), + releaseAfterUnlock); + } else { + releaseAfterUnlock.push_back(std::move(entry.second)); + } + } + } + roundTripValuesGeneration_.fetch_add(1, std::memory_order_release); + } + } + + void rememberClassValue(Runtime& runtime, Class cls, const Value& value) { + if (cls == Nil) { + return; + } + classValues_[normalizeRuntimePointer(reinterpret_cast(cls))] = + std::make_shared(runtime, value); + } + + Value findClassValue(Runtime& runtime, Class cls) const { + if (cls == Nil) { + return Value::undefined(); + } + auto it = classValues_.find( + normalizeRuntimePointer(reinterpret_cast(cls))); + if (it == classValues_.end() || it->second == nullptr) { + return Value::undefined(); + } + return Value(runtime, *it->second); + } + + void rememberClassPrototype(Runtime& runtime, Class cls, const Value& value) { + if (cls == Nil) { + return; + } + classPrototypes_[normalizeRuntimePointer(reinterpret_cast(cls))] = + std::make_shared(runtime, value); + } + + Value findClassPrototype(Runtime& runtime, Class cls) const { + if (cls == Nil) { + return Value::undefined(); + } + auto it = classPrototypes_.find( + normalizeRuntimePointer(reinterpret_cast(cls))); + if (it == classPrototypes_.end() || it->second == nullptr) { + return Value::undefined(); + } + return Value(runtime, *it->second); + } + + void setObjectExpando(Runtime& runtime, const void* native, + const std::string& property, const Value& value) { + if (native == nullptr || property.empty()) { + return; + } + objectExpandos_[normalizeRuntimePointer(reinterpret_cast(native))] + [property] = std::make_shared(runtime, value); + objectExpandosGeneration_.fetch_add(1, std::memory_order_release); + } + + void retainObjectExpandoOwner(const void* native) { + if (native == nullptr) { + return; + } + objectExpandoOwnerCounts_[ + normalizeRuntimePointer(reinterpret_cast(native))] += 1; + } + + void releaseObjectExpandoOwner(const void* native, + bool preserveExpandos = false) { + if (native == nullptr) { + return; + } + uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + auto ownerIt = objectExpandoOwnerCounts_.find(key); + if (ownerIt != objectExpandoOwnerCounts_.end()) { + if (ownerIt->second > 1) { + ownerIt->second -= 1; + return; + } + objectExpandoOwnerCounts_.erase(ownerIt); + } + if (!preserveExpandos) { + forgetObjectExpandos(native); + } + } + + Value findObjectExpando(Runtime& runtime, const void* native, + const std::string& property) const { + if (native == nullptr || property.empty()) { + return Value::undefined(); + } + struct ObjectExpandoCacheEntry { + uint64_t bridgeId = 0; + uintptr_t key = 0; + uint64_t generation = 0; + std::string property; + std::weak_ptr value; + bool miss = false; + }; + static thread_local ObjectExpandoCacheEntry cache[8]; + static thread_local size_t nextSlot = 0; + + const uintptr_t key = + normalizeRuntimePointer(reinterpret_cast(native)); + const uint64_t generation = + objectExpandosGeneration_.load(std::memory_order_acquire); + for (auto& entry : cache) { + if (entry.bridgeId == instanceId_ && entry.key == key && + entry.generation == generation && entry.property == property) { + if (entry.miss) { + return Value::undefined(); + } + if (auto cached = entry.value.lock()) { + return Value(runtime, *cached); + } + break; + } + } + + auto objectIt = objectExpandos_.find(key); + const size_t slot = nextSlot++ & 7; + if (objectIt == objectExpandos_.end()) { + cache[slot] = + ObjectExpandoCacheEntry{instanceId_, key, generation, property, {}, true}; + return Value::undefined(); + } + auto propertyIt = objectIt->second.find(property); + if (propertyIt == objectIt->second.end() || propertyIt->second == nullptr) { + cache[slot] = + ObjectExpandoCacheEntry{instanceId_, key, generation, property, {}, true}; + return Value::undefined(); + } + cache[slot] = ObjectExpandoCacheEntry{ + instanceId_, key, generation, property, propertyIt->second, false}; + return Value(runtime, *propertyIt->second); + } + + void forgetObjectExpandos(const void* native) { + if (native == nullptr) { + return; + } + auto key = normalizeRuntimePointer(reinterpret_cast(native)); + objectExpandos_.erase( + key); + objectExpandosGeneration_.fetch_add(1, std::memory_order_release); + } + + // Per-class cache of resolved metadata property-getter members. Lets the + // instance property interceptor skip the special-name chain + metadata + // discovery on every `object.prop` access (the engines without V8's + // kNonMasking prototype fast path otherwise re-resolve on each access). + // membersByClassOffset_ vectors are permanent, so the member pointer is + // stable for the bridge's lifetime. + struct CachedPropertyGetter { + const NativeApiMember* member; + std::string selectorName; + std::shared_ptr preparedInvocation; + }; + const CachedPropertyGetter* findCachedPropertyGetter( + Class cls, const std::string& property) const { + if (cls == Nil || property.empty()) { + return nullptr; + } + struct PropertyGetterCacheEntry { + uint64_t bridgeId = 0; + Class cls = Nil; + uint64_t generation = 0; + std::string property; + const CachedPropertyGetter* getter = nullptr; + bool miss = false; + }; + static thread_local PropertyGetterCacheEntry cache[8]; + static thread_local size_t nextSlot = 0; + + const uint64_t generation = + propertyGetterCacheGeneration_.load(std::memory_order_acquire); + for (auto& entry : cache) { + if (entry.bridgeId == instanceId_ && entry.cls == cls && + entry.generation == generation && entry.property == property) { + return entry.miss ? nullptr : entry.getter; + } + } + + auto classIt = propertyGetterCache_.find(cls); + const size_t slot = nextSlot++ & 7; + if (classIt == propertyGetterCache_.end()) { + cache[slot] = PropertyGetterCacheEntry{ + instanceId_, cls, generation, property, nullptr, true}; + return nullptr; + } + auto propIt = classIt->second.find(property); + if (propIt == classIt->second.end()) { + cache[slot] = PropertyGetterCacheEntry{ + instanceId_, cls, generation, property, nullptr, true}; + return nullptr; + } + cache[slot] = + PropertyGetterCacheEntry{instanceId_, cls, generation, property, + &propIt->second, false}; + return &propIt->second; + } + void cachePropertyGetter(Class cls, const std::string& property, + const NativeApiMember* member, + const std::string& selectorName, + std::shared_ptr + preparedInvocation = nullptr) { + propertyGetterCache_[cls][property] = + CachedPropertyGetter{member, selectorName, + std::move(preparedInvocation)}; + propertyGetterCacheGeneration_.fetch_add(1, std::memory_order_release); + } + + void rememberPointerValue(Runtime& runtime, const void* native, + const Value& value) { + pointerValues_[reinterpret_cast(native)] = + std::make_shared(runtime, value); + } + + Value findPointerValue(Runtime& runtime, const void* native) const { + auto it = pointerValues_.find(reinterpret_cast(native)); + if (it == pointerValues_.end() || it->second == nullptr) { + return Value::undefined(); + } + return Value(runtime, *it->second); + } + + void forgetPointerValue(const void* native) { + if (native == nullptr) { + return; + } + pointerValues_.erase(reinterpret_cast(native)); + } + + const NativeApiSymbol* findConstant(const std::string& name) const { + auto it = constantSymbolsByName_.find(name); + return it != constantSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findProtocol(const std::string& name) const { + const NativeApiSymbol* symbol = find(name); + if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Protocol) { + return symbol; + } + auto it = protocolSymbolsByRuntimeName_.find(name); + return it != protocolSymbolsByRuntimeName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findEnum(const std::string& name) const { + auto it = enumSymbolsByName_.find(name); + return it != enumSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findStruct(const std::string& name) const { + auto it = structSymbolsByName_.find(name); + return it != structSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findUnion(const std::string& name) const { + auto it = unionSymbolsByName_.find(name); + return it != unionSymbolsByName_.end() ? &it->second : nullptr; + } + + const NativeApiSymbol* findAggregate(const std::string& name) const { + const NativeApiSymbol* symbol = findStruct(name); + if (symbol != nullptr) { + return symbol; + } + return findUnion(name); + } + + size_t classCount() const { return classNames_.size(); } + size_t functionCount() const { return functionNames_.size(); } + size_t constantCount() const { return constantNames_.size(); } + size_t protocolCount() const { return protocolNames_.size(); } + size_t enumCount() const { return enumNames_.size(); } + size_t structCount() const { return structNames_.size(); } + size_t unionCount() const { return unionNames_.size(); } + + const std::vector& classNames() const { return classNames_; } + const std::vector& functionNames() const { return functionNames_; } + const std::vector& constantNames() const { return constantNames_; } + const std::vector& protocolNames() const { return protocolNames_; } + const std::vector& enumNames() const { return enumNames_; } + const std::vector& structNames() const { return structNames_; } + const std::vector& unionNames() const { return unionNames_; } + std::shared_ptr scheduler() const { return scheduler_; } + const std::function)>& nativeInvocationInvoker() + const { + return nativeInvocationInvoker_; + } + const std::function)>& nativeCallbackInvoker() + const { + return nativeCallbackInvoker_; + } + const std::function)>& runtimeCallbackInvoker() + const { + return runtimeCallbackInvoker_; + } + const std::function)>& jsThreadCallbackInvoker() + const { + return jsThreadCallbackInvoker_; + } + const std::function)>& + jsThreadAsyncCallbackInvoker() const { + return jsThreadAsyncCallbackInvoker_; + } + bool invokeCallbacksOnNativeCallerThread() const { + return invokeCallbacksOnNativeCallerThread_; + } + + std::thread::id jsThreadId() const { return jsThreadId_; } + + void retainEngineLifetime(std::shared_ptr lifetime) { + if (lifetime == nullptr) { + return; + } + std::lock_guard lock(retainedLifetimesMutex_); + retainedLifetimes_.push_back(std::move(lifetime)); + } + +#ifdef TARGET_ENGINE_HERMES + std::string roundTripRootKey(uintptr_t key) const { + char buffer[32] = {}; + snprintf(buffer, sizeof(buffer), "p%llx", + static_cast(key)); + return buffer; + } + + Object roundTripRootObject(Runtime& runtime) { + if (roundTripRootCache_) { + return roundTripRootCache_->asObject(runtime); + } + static constexpr const char* kRootName = + "__nativeScriptNativeApiRoundTripValues"; + Object global = runtime.global(); + if (global.hasProperty(runtime, kRootName)) { + Value existing = global.getProperty(runtime, kRootName); + if (existing.isObject()) { + Object root = existing.asObject(runtime); + roundTripRootCache_ = std::make_shared(runtime, root); + return root; + } + } + + Object root(runtime); + global.setProperty(runtime, kRootName, root); + roundTripRootCache_ = std::make_shared(runtime, root); + return root; + } + + void rootRoundTripValue(Runtime& runtime, uintptr_t key, + const Value& value) { + roundTripRootObject(runtime) + .setProperty(runtime, roundTripRootKey(key).c_str(), value); + std::lock_guard lock(roundTripValuesMutex_); + rootedRoundTripValues_.insert(key); + } + + void unrootRoundTripValue(Runtime& runtime, uintptr_t key) { + roundTripRootObject(runtime) + .setProperty(runtime, roundTripRootKey(key).c_str(), + Value::undefined()); + } +#endif + + uintptr_t nativeObjectClassKey(const void* native) const { + if (native == nullptr) { + return 0; + } + return normalizeRuntimePointer( + reinterpret_cast(object_getClass(static_cast(native)))); + } + + void storeRoundTripEntry( + std::unordered_map& map, + uintptr_t key, NativeApiRoundTripValue&& entry, + NativeApiRoundTripReleaseList& releaseAfterUnlock) { + auto it = map.find(key); + if (it == map.end()) { + map.emplace(key, std::move(entry)); + return; + } + + releaseAfterUnlock.push_back(std::move(it->second)); + it->second = std::move(entry); + } + + void eraseRoundTripMapKey( + std::unordered_map& map, + uintptr_t key, NativeApiRoundTripReleaseList& releaseAfterUnlock) { + auto it = map.find(key); + if (it == map.end()) { + return; + } + + releaseAfterUnlock.push_back(std::move(it->second)); + map.erase(it); + } + + void eraseRoundTripKeyFromScopedCaches( + uintptr_t key, NativeApiRoundTripReleaseList& releaseAfterUnlock) { + eraseRoundTripMapKey(recentRoundTripValues_, key, releaseAfterUnlock); + recentRoundTripValueOrder_.erase( + std::remove(recentRoundTripValueOrder_.begin(), + recentRoundTripValueOrder_.end(), key), + recentRoundTripValueOrder_.end()); + for (auto& stackEntry : roundTripCacheFramesByThread_) { + for (auto& frame : stackEntry.second) { + eraseRoundTripMapKey(frame, key, releaseAfterUnlock); + } + } + } + + void rememberRecentRoundTripValue(uintptr_t key, + NativeApiRoundTripValue&& entry, + NativeApiRoundTripReleaseList& releaseAfterUnlock) { + if (recentRoundTripValues_.find(key) == recentRoundTripValues_.end()) { + recentRoundTripValueOrder_.push_back(key); + } + storeRoundTripEntry(recentRoundTripValues_, key, std::move(entry), + releaseAfterUnlock); + while (recentRoundTripValueOrder_.size() > kRecentRoundTripValueLimit) { + uintptr_t evicted = recentRoundTripValueOrder_.front(); + recentRoundTripValueOrder_.erase(recentRoundTripValueOrder_.begin()); + eraseRoundTripMapKey(recentRoundTripValues_, evicted, + releaseAfterUnlock); + } + } + + const std::vector& membersForClass( + const NativeApiSymbol& symbol) const { + auto cached = membersByClassOffset_.find(symbol.offset); + if (cached != membersByClassOffset_.end()) { + return cached->second; + } + + auto inserted = membersByClassOffset_.emplace( + symbol.offset, readMembersForClassHierarchy(symbol)); + return inserted.first->second; + } + + const std::vector& surfaceMembersForClass( + const NativeApiSymbol& symbol) const { + auto cached = surfaceMembersByClassOffset_.find(symbol.offset); + if (cached != surfaceMembersByClassOffset_.end()) { + return cached->second; + } + + auto inserted = surfaceMembersByClassOffset_.emplace( + symbol.offset, readSurfaceMembersForClass(symbol)); + return inserted.first->second; + } + + const std::vector& membersForProtocol( + const NativeApiSymbol& symbol) const { + auto cached = membersByProtocolOffset_.find(symbol.offset); + if (cached != membersByProtocolOffset_.end()) { + return cached->second; + } + + auto inserted = membersByProtocolOffset_.emplace( + symbol.offset, readMembersForProtocolHierarchy(symbol.offset)); + return inserted.first->second; + } + + std::shared_ptr aggregateInfoFor( + MDSectionOffset aggregateOffset, bool isUnion); + + std::shared_ptr aggregateInfoFor( + const NativeApiSymbol& symbol) { + return aggregateInfoFor(symbol.offset, + symbol.kind == NativeApiSymbolKind::Union); + } + + private: + static std::unique_ptr loadMetadataFromFile( + const char* metadataPath) { + const char* path = metadataPath != nullptr ? metadataPath : "metadata.nsmd"; + FILE* file = fopen(path, "rb"); + if (file == nullptr) { + throw std::runtime_error(std::string("metadata.nsmd not found: ") + path); + } + + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + if (size <= 0) { + fclose(file); + throw std::runtime_error(std::string("metadata.nsmd is empty: ") + path); + } + + void* buffer = malloc(static_cast(size)); + if (buffer == nullptr) { + fclose(file); + throw std::bad_alloc(); + } + + size_t read = fread(buffer, 1, static_cast(size), file); + fclose(file); + if (read != static_cast(size)) { + free(buffer); + throw std::runtime_error(std::string("failed to read metadata: ") + path); + } + + return std::make_unique(buffer, true); + } + + static std::unique_ptr loadMetadata( + const NativeApiConfig& config) { + if (config.metadataPtr != nullptr && + *static_cast(config.metadataPtr) != '\0') { +#ifdef EMBED_METADATA_SIZE + return std::make_unique((void*)embedded_metadata); +#else + return std::make_unique( + const_cast(config.metadataPtr)); +#endif + } + +#ifdef EMBED_METADATA_SIZE + if (config.metadataPath == nullptr) { + return std::make_unique((void*)embedded_metadata); + } +#endif + + unsigned long segmentSize = 0; + auto segmentData = getsegmentdata( + reinterpret_cast(_dyld_get_image_header(0)), + "__objc_metadata", &segmentSize); + if (segmentData != nullptr && segmentSize > 0) { + return std::make_unique(segmentData); + } + + return loadMetadataFromFile(config.metadataPath); + } + + void addSymbol(NativeApiSymbolKind kind, MDSectionOffset offset, + const char* name, const char* runtimeName = nullptr, + MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL) { + if (name == nullptr || name[0] == '\0') { + return; + } + + NativeApiSymbol symbol{ + .kind = kind, + .offset = offset, + .superclassOffset = superclassOffset, + .name = name, + .runtimeName = runtimeName != nullptr ? runtimeName : name, + }; + + switch (kind) { + case NativeApiSymbolKind::Class: + classNames_.push_back(symbol.name); + break; + case NativeApiSymbolKind::Function: + functionNames_.push_back(symbol.name); + functionSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Constant: + constantNames_.push_back(symbol.name); + constantSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Protocol: + protocolNames_.push_back(symbol.name); + break; + case NativeApiSymbolKind::Enum: + enumNames_.push_back(symbol.name); + enumSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Struct: + structNames_.push_back(symbol.name); + structSymbolsByName_[symbol.name] = symbol; + break; + case NativeApiSymbolKind::Union: + unionNames_.push_back(symbol.name); + unionSymbolsByName_[symbol.name] = symbol; + break; + } + + symbolsByName_[symbol.name] = symbol; + if (kind == NativeApiSymbolKind::Class) { + classSymbolsByOffset_[symbol.offset] = symbol; + classSymbolsByRuntimeName_[symbol.runtimeName] = symbol; + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls != Nil) { + classSymbolsByRuntimePointer_[normalizeRuntimePointer( + reinterpret_cast(cls))] = symbol; + } + } else if (kind == NativeApiSymbolKind::Protocol) { + protocolSymbolsByOffset_[symbol.offset] = symbol; + protocolSymbolsByRuntimeName_[symbol.runtimeName] = symbol; + auto rememberProtocolRuntimeName = [&](const std::string& runtimeName) { + if (runtimeName.empty()) { + return; + } + protocolSymbolsByRuntimeName_[runtimeName] = symbol; + Protocol* runtimeProtocol = lookupProtocolByNativeName(runtimeName); + if (runtimeProtocol != nullptr) { + protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( + reinterpret_cast(runtimeProtocol))] = symbol; + } + }; + if (symbol.name.size() > 9 && + std::isdigit(static_cast(symbol.name.back()))) { + size_t digitsStart = symbol.name.size(); + while (digitsStart > 0 && + std::isdigit(static_cast(symbol.name[digitsStart - 1]))) { + digitsStart--; + } + constexpr const char* protocolSuffix = "Protocol"; + size_t protocolSuffixLength = std::strlen(protocolSuffix); + if (digitsStart > protocolSuffixLength && + symbol.name.compare(digitsStart - protocolSuffixLength, + protocolSuffixLength, protocolSuffix) == 0) { + rememberProtocolRuntimeName( + symbol.name.substr(0, digitsStart - protocolSuffixLength)); + } + } + Protocol* protocol = lookupProtocolByNativeName(symbol.runtimeName); + if (protocol == nullptr && symbol.runtimeName != symbol.name) { + protocol = lookupProtocolByNativeName(symbol.name); + } + if (protocol != nullptr) { + protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( + reinterpret_cast(protocol))] = symbol; + } + } else if (kind == NativeApiSymbolKind::Struct) { + structSymbolsByOffset_[symbol.offset] = symbol; + } else if (kind == NativeApiSymbolKind::Union) { + unionSymbolsByOffset_[symbol.offset] = symbol; + } + } + + void addAggregateAliases(NativeApiSymbolKind kind, MDSectionOffset offset, + const std::string& name) { + if (name.empty()) { + return; + } + + if (!name.empty() && name[0] == '_') { + std::string alias = name.substr(1); + if (!alias.empty() && symbolsByName_.find(alias) == symbolsByName_.end()) { + addSymbol(kind, offset, alias.c_str(), name.c_str()); + } + } + + constexpr const char* suffix = "Struct"; + if (name.size() < std::strlen(suffix) || + name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), + suffix) != 0) { + std::string alias = name + suffix; + if (symbolsByName_.find(alias) == symbolsByName_.end()) { + addSymbol(kind, offset, alias.c_str(), name.c_str()); + } + } + } + + void buildSymbolIndexes() { + if (metadata_ == nullptr) { + return; + } + + indexConstants(); + indexEnums(); + indexFunctions(); + indexProtocols(); + indexClasses(); + indexStructs(); + indexUnions(); + } + + static void skipConstantValue(MDMetadataReader* metadata, + MDSectionOffset& offset, + metagen::MDVariableEvalKind evalKind) { + switch (evalKind) { + case metagen::mdEvalNone: + skipMetadataEngineType(metadata, &offset); + break; + case metagen::mdEvalInt64: + offset += sizeof(int64_t); + break; + case metagen::mdEvalDouble: + offset += sizeof(double); + break; + case metagen::mdEvalString: + offset += sizeof(MDSectionOffset); + break; + } + } + + void indexConstants() { + MDSectionOffset offset = metadata_->constantsOffset; + while (offset < metadata_->enumsOffset) { + MDSectionOffset originalOffset = offset; + addSymbol(NativeApiSymbolKind::Constant, originalOffset, + metadata_->getString(offset)); + offset += sizeof(MDSectionOffset); + auto evalKind = metadata_->getVariableEvalKind(offset); + offset += sizeof(metagen::MDVariableEvalKind); + skipConstantValue(metadata_.get(), offset, evalKind); + } + } + + void indexEnums() { + MDSectionOffset offset = metadata_->enumsOffset; + while (offset < metadata_->signaturesOffset) { + MDSectionOffset originalOffset = offset; + addSymbol(NativeApiSymbolKind::Enum, originalOffset, + metadata_->getString(offset)); + offset = metadata_->skipEnum(originalOffset); + } + } + + void indexFunctions() { + MDSectionOffset offset = metadata_->functionsOffset; + while (offset < metadata_->protocolsOffset) { + MDSectionOffset originalOffset = offset; + addSymbol(NativeApiSymbolKind::Function, originalOffset, + metadata_->getString(offset)); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + offset += sizeof(metagen::MDFunctionFlag); + } + } + + void indexProtocols() { + MDSectionOffset offset = metadata_->protocolsOffset; + while (offset < metadata_->classesOffset) { + MDSectionOffset originalOffset = offset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + addSymbol(NativeApiSymbolKind::Protocol, originalOffset, + metadata_->resolveString(nameOffset)); + + while (next) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + + next = true; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + if (flags == metagen::mdMemberFlagNull) { + break; + } + + skipMember(flags, offset); + } + } + } + + void indexClasses() { + MDSectionOffset offset = metadata_->classesOffset; + while (offset < metadata_->structsOffset) { + MDSectionOffset originalOffset = offset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + auto runtimeNameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + + auto name = metadata_->resolveString(nameOffset); + const char* runtimeName = name; + if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { + runtimeName = metadata_->resolveString(runtimeNameOffset); + } + + while (hasProtocols) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + + auto superclass = metadata_->getOffset(offset); + offset += sizeof(superclass); + MDSectionOffset superclassOffset = + superclass & ~metagen::mdSectionOffsetNext; + if (superclassOffset != MD_SECTION_OFFSET_NULL) { + superclassOffset += metadata_->classesOffset; + } + + addSymbol(NativeApiSymbolKind::Class, originalOffset, name, runtimeName, + superclassOffset); + + bool next = (superclass & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + skipMember(flags, offset); + } + } + } + + void skipAggregateFields(MDSectionOffset& offset, bool isUnion) const { + bool next = true; + while (next) { + MDSectionOffset nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + next = (nameOffset & metagen::mdSectionOffsetNext) != 0; + nameOffset &= ~metagen::mdSectionOffsetNext; + if (nameOffset == MD_SECTION_OFFSET_NULL) { + break; + } + if (!isUnion) { + offset += sizeof(uint16_t); + } + skipMetadataEngineType(metadata_.get(), &offset); + } + } + + void indexStructs() { + MDSectionOffset offset = metadata_->structsOffset; + while (offset < metadata_->unionsOffset) { + if (metadata_->getOffset(offset) == 0) { + break; + } + MDSectionOffset originalOffset = offset; + const char* name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(uint16_t); + addSymbol(NativeApiSymbolKind::Struct, originalOffset, name); + addAggregateAliases(NativeApiSymbolKind::Struct, originalOffset, + name != nullptr ? name : ""); + skipAggregateFields(offset, false); + } + } + + void indexUnions() { + MDSectionOffset offset = metadata_->unionsOffset; + while (metadata_->getOffset(offset) != 0) { + MDSectionOffset originalOffset = offset; + const char* name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(uint16_t); + addSymbol(NativeApiSymbolKind::Union, originalOffset, name); + addAggregateAliases(NativeApiSymbolKind::Union, originalOffset, + name != nullptr ? name : ""); + skipAggregateFields(offset, true); + } + } + + void skipMember(MDMemberFlag flags, MDSectionOffset& offset) const { + if ((flags & metagen::mdMemberProperty) != 0) { + bool readonly = (flags & metagen::mdMemberReadonly) != 0; + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + if (!readonly) { + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + } + return; + } + + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + } + + std::vector readProtocolOffsetsForClass( + MDSectionOffset classOffset, MDSectionOffset* memberOffset = nullptr, + MDSectionOffset* superclassOffsetOut = nullptr) const { + std::vector protocols; + if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { + return protocols; + } + + MDSectionOffset offset = classOffset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + + while (hasProtocols) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + protocolOffset &= ~metagen::mdSectionOffsetNext; + if (protocolOffset != MD_SECTION_OFFSET_NULL) { + protocols.push_back(protocolOffset + metadata_->protocolsOffset); + } + } + + auto superclass = metadata_->getOffset(offset); + offset += sizeof(superclass); + const bool hasMembers = (superclass & metagen::mdSectionOffsetNext) != 0; + if (superclassOffsetOut != nullptr) { + MDSectionOffset superclassOffset = + superclass & ~metagen::mdSectionOffsetNext; + *superclassOffsetOut = + superclassOffset != MD_SECTION_OFFSET_NULL + ? superclassOffset + metadata_->classesOffset + : MD_SECTION_OFFSET_NULL; + } + if (memberOffset != nullptr) { + *memberOffset = hasMembers ? offset : MD_SECTION_OFFSET_NULL; + } + return protocols; + } + + std::vector readOwnMembersForClass( + MDSectionOffset classOffset) const { + std::vector members; + if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + MDSectionOffset memberOffset = MD_SECTION_OFFSET_NULL; + for (MDSectionOffset protocolOffset : + readProtocolOffsetsForClass(classOffset, &memberOffset)) { + auto protocol = protocolSymbolsByOffset_.find(protocolOffset); + if (protocol == protocolSymbolsByOffset_.end()) { + continue; + } + const auto& protocolMembers = membersForProtocol(protocol->second); + members.insert(members.end(), protocolMembers.begin(), + protocolMembers.end()); + } + + if (memberOffset != MD_SECTION_OFFSET_NULL) { + std::vector ownMembers = + readMembersAtOffset(memberOffset); + members.insert(members.end(), ownMembers.begin(), ownMembers.end()); + } + return members; + } + + std::vector readMembersForClass( + MDSectionOffset classOffset) const { + std::vector members; + if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + MDSectionOffset offset = classOffset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + + while (hasProtocols) { + auto protocolOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; + } + + auto superclass = metadata_->getOffset(offset); + offset += sizeof(superclass); + + bool next = (superclass & metagen::mdSectionOffsetNext) != 0; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + if (flags == metagen::mdMemberFlagNull) { + break; + } + + NativeApiMember member; + member.flags = flags; + if ((flags & metagen::mdMemberProperty) != 0) { + member.property = true; + member.readonly = (flags & metagen::mdMemberReadonly) != 0; + member.name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + + if (!member.readonly) { + member.setterSelectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.setterSignatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + } + } else { + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + member.name = jsifySelector(member.selectorName.c_str()); + } + members.push_back(std::move(member)); + } + + return members; + } + + static bool memberIsStatic(const NativeApiMember& member) { + return (member.flags & metagen::mdMemberStatic) != 0; + } + + static bool sameMemberSlot(const NativeApiMember& lhs, + const NativeApiMember& rhs) { + return lhs.name == rhs.name && lhs.property == rhs.property && + memberIsStatic(lhs) == memberIsStatic(rhs); + } + + static bool sameMethodSelector(const NativeApiMember& lhs, + const NativeApiMember& rhs) { + return !lhs.property && !rhs.property && sameMemberSlot(lhs, rhs) && + lhs.selectorName == rhs.selectorName; + } + + static const NativeApiMember* findPropertyMember( + const std::vector& members, + const NativeApiMember& candidate) { + for (const auto& member : members) { + if (member.property && sameMemberSlot(member, candidate)) { + return &member; + } + } + return nullptr; + } + + static bool selectorExistsInMembers( + const std::vector& members, + const NativeApiMember& candidate) { + for (const auto& member : members) { + if (sameMethodSelector(member, candidate)) { + return true; + } + } + return false; + } + + static bool shouldSkipPropertyOverride( + const NativeApiMember* inherited, const NativeApiMember& member) { + if (inherited == nullptr || !inherited->property) { + return false; + } + + bool sameGetter = inherited->selectorName == member.selectorName; + bool sameSetter = + inherited->setterSelectorName == member.setterSelectorName; + if ((!inherited->readonly && member.readonly) || + (inherited->readonly == member.readonly && sameGetter && + (member.readonly || sameSetter))) { + return true; + } + return false; + } + + static void appendSurfaceMember( + std::vector& surface, + const std::vector& inheritedMembers, + const NativeApiMember& member) { + if (member.name.empty()) { + return; + } + + if (member.property) { + const NativeApiMember* inherited = + findPropertyMember(inheritedMembers, member); + if (shouldSkipPropertyOverride(inherited, member)) { + return; + } + + for (auto& existing : surface) { + if (!existing.property || !sameMemberSlot(existing, member)) { + continue; + } + if (existing.readonly && !member.readonly) { + existing = member; + } + return; + } + surface.push_back(member); + return; + } + + const bool keepInheritedMethod = + member.name == "alloc" || member.name == "toString" || + member.name == "superclass"; + if (!keepInheritedMethod && + selectorExistsInMembers(inheritedMembers, member)) { + return; + } + if (selectorExistsInMembers(surface, member)) { + return; + } + surface.push_back(member); + } + + std::vector readSurfaceMembersForClass( + const NativeApiSymbol& symbol) const { + std::vector inheritedMembers; + if (symbol.superclassOffset != MD_SECTION_OFFSET_NULL) { + auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); + if (superclass != classSymbolsByOffset_.end()) { + const auto& inherited = surfaceMembersForClass(superclass->second); + inheritedMembers.insert(inheritedMembers.end(), inherited.begin(), + inherited.end()); + } + } + + std::vector surface; + for (const auto& member : readOwnMembersForClass(symbol.offset)) { + appendSurfaceMember(surface, inheritedMembers, member); + } + return surface; + } + + std::vector readMembersAtOffset( + MDSectionOffset& offset) const { + std::vector members; + bool next = true; + while (next) { + auto flags = metadata_->getMemberFlag(offset); + next = (flags & metagen::mdMemberNext) != 0; + offset += sizeof(flags); + if (flags == metagen::mdMemberFlagNull) { + break; + } + + NativeApiMember member; + member.flags = flags; + if ((flags & metagen::mdMemberProperty) != 0) { + member.property = true; + member.readonly = (flags & metagen::mdMemberReadonly) != 0; + member.name = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + + if (!member.readonly) { + member.setterSelectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.setterSignatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + } + } else { + member.selectorName = metadata_->getString(offset); + offset += sizeof(MDSectionOffset); + member.signatureOffset = + metadata_->signaturesOffset + metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + member.name = jsifySelector(member.selectorName.c_str()); + } + members.push_back(std::move(member)); + } + return members; + } + + std::vector readMembersForProtocolHierarchy( + MDSectionOffset protocolOffset) const { + std::vector members; + if (metadata_ == nullptr || protocolOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + MDSectionOffset offset = protocolOffset; + auto nameOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; + + while (hasProtocols) { + auto inheritedOffset = metadata_->getOffset(offset); + offset += sizeof(MDSectionOffset); + hasProtocols = (inheritedOffset & metagen::mdSectionOffsetNext) != 0; + inheritedOffset &= ~metagen::mdSectionOffsetNext; + if (inheritedOffset == MD_SECTION_OFFSET_NULL) { + continue; + } + + MDSectionOffset absoluteOffset = + inheritedOffset + metadata_->protocolsOffset; + auto inheritedSymbol = protocolSymbolsByOffset_.find(absoluteOffset); + if (inheritedSymbol != protocolSymbolsByOffset_.end()) { + const auto& inheritedMembers = + membersForProtocol(inheritedSymbol->second); + members.insert(members.end(), inheritedMembers.begin(), + inheritedMembers.end()); + } + } + + std::vector ownMembers = readMembersAtOffset(offset); + members.insert(members.end(), ownMembers.begin(), ownMembers.end()); + return members; + } + + std::vector readMembersForClassHierarchy( + const NativeApiSymbol& symbol) const { + std::vector members = readOwnMembersForClass(symbol.offset); + if (symbol.superclassOffset == MD_SECTION_OFFSET_NULL) { + return members; + } + + auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); + if (superclass != classSymbolsByOffset_.end()) { + const auto& inheritedMembers = membersForClass(superclass->second); + members.insert(members.end(), inheritedMembers.begin(), + inheritedMembers.end()); + } + return members; + } + + std::unique_ptr metadata_; + void* selfDl_ = nullptr; + std::unordered_map symbolsByName_; + std::unordered_map functionSymbolsByName_; + std::unordered_map constantSymbolsByName_; + std::unordered_map enumSymbolsByName_; + std::unordered_map structSymbolsByName_; + std::unordered_map unionSymbolsByName_; + std::unordered_map classSymbolsByRuntimeName_; + std::unordered_map protocolSymbolsByRuntimeName_; + std::unordered_map classSymbolsByRuntimePointer_; + std::unordered_map protocolSymbolsByRuntimePointer_; + mutable std::mutex roundTripValuesMutex_; + std::unordered_map roundTripValues_; + std::unordered_map + roundTripCacheFramesByThread_; + std::unordered_map + recentRoundTripValues_; + std::vector recentRoundTripValueOrder_; +#ifdef TARGET_ENGINE_HERMES + std::unordered_set rootedRoundTripValues_; + std::shared_ptr roundTripRootCache_; +#endif + std::atomic roundTripValuesGeneration_{1}; + std::unordered_map> classValues_; + std::unordered_map> classPrototypes_; + std::unordered_map> pointerValues_; + std::unordered_map>> + objectExpandos_; + std::unordered_map objectExpandoOwnerCounts_; + std::atomic objectExpandosGeneration_{1}; + // Identifies this bridge in the thread_local lookup caches. Its address + // cannot: a bridge allocated where a destroyed one lived would match that + // one's entries, and the property-getter cache holds raw pointers into it. + const uint64_t instanceId_ = [] { + static std::atomic next{1}; + return next.fetch_add(1, std::memory_order_relaxed); + }(); + std::unordered_map> + propertyGetterCache_; + std::atomic propertyGetterCacheGeneration_{1}; + std::unordered_map classSymbolsByOffset_; + std::unordered_map protocolSymbolsByOffset_; + std::vector classNames_; + std::vector functionNames_; + std::vector constantNames_; + std::vector protocolNames_; + std::vector enumNames_; + std::vector structNames_; + std::vector unionNames_; + std::shared_ptr scheduler_; + std::function)> nativeInvocationInvoker_; + std::function)> nativeCallbackInvoker_; + std::function)> runtimeCallbackInvoker_; + std::function)> jsThreadCallbackInvoker_; + std::function)> jsThreadAsyncCallbackInvoker_; + bool invokeCallbacksOnNativeCallerThread_ = false; + mutable std::unordered_map> + membersByClassOffset_; + mutable std::unordered_map> + surfaceMembersByClassOffset_; + mutable std::unordered_map> + membersByProtocolOffset_; + std::unordered_map structSymbolsByOffset_; + std::unordered_map unionSymbolsByOffset_; + std::unordered_map> + aggregateInfoByOffset_; + std::unordered_set aggregateInfoInProgress_; + std::thread::id jsThreadId_ = std::this_thread::get_id(); + std::mutex retainedLifetimesMutex_; + std::vector> retainedLifetimes_; +}; + +class NativeApiRoundTripCacheFrameGuard final { + public: + explicit NativeApiRoundTripCacheFrameGuard( + const std::shared_ptr& bridge) + : bridge_(bridge) { + if (bridge_ != nullptr) { + bridge_->beginRoundTripCacheFrame(); + } + } + + ~NativeApiRoundTripCacheFrameGuard() { + if (bridge_ != nullptr) { + bridge_->endRoundTripCacheFrame(); + } + } + + NativeApiRoundTripCacheFrameGuard( + const NativeApiRoundTripCacheFrameGuard&) = delete; + NativeApiRoundTripCacheFrameGuard& operator=( + const NativeApiRoundTripCacheFrameGuard&) = delete; + + private: + std::shared_ptr bridge_; +}; + +template +void performGeneratedObjCInvocation( + Runtime& runtime, const std::shared_ptr& bridge, + Invocation&& invocation) { + const auto& invoker = bridge->nativeInvocationInvoker(); + if (invoker || bridge->invokeCallbacksOnNativeCallerThread()) { + performNativeInvocation(runtime, invoker, [&]() { invocation(); }); + } else { + invocation(); + } +} + +bool nativeObjectReturnMayCoerceToString(const NativeApiType& type) { + return type.kind == metagen::mdTypeAnyObject || + type.kind == metagen::mdTypeNSStringObject; +} + +bool nativeObjectIsStringLike(id object) { + if (object == nil) { + return false; + } + Class cls = object_getClass(object); + struct StringLikeClassCacheEntry { + Class cls = Nil; + bool stringLike = false; + }; + static thread_local StringLikeClassCacheEntry cache[4]; + const size_t firstSlot = (reinterpret_cast(cls) >> 4) & 3; + for (size_t i = 0; i < 4; i++) { + const auto& entry = cache[(firstSlot + i) & 3]; + if (entry.cls == cls) { + return entry.stringLike; + } + } + bool stringLike = [object isKindOfClass:[NSString class]]; + cache[firstSlot] = StringLikeClassCacheEntry{cls, stringLike}; + return stringLike; +} + +Value findCachedNativeObjectReturn(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, id object) { + bool roundTripStringLike = false; + const bool stringReturnCandidate = nativeObjectReturnMayCoerceToString(type); + // AnyObject/NSString returns intentionally coerce string-like native objects + // to JS strings, so cached identity is only valid for non-string wrappers. + if (stringReturnCandidate && nativeObjectIsStringLike(object)) { + return Value::undefined(); + } + Value roundTrip = bridge->findRoundTripValue( + runtime, object, stringReturnCandidate ? &roundTripStringLike : nullptr, + true); + if (!roundTrip.isUndefined() && !roundTripStringLike) { + return roundTrip; + } + return Value::undefined(); +} + +Value makeString(Runtime& runtime, const std::string& value) { + return String::createFromUtf8(runtime, value); +} + +std::string readStringArg(Runtime& runtime, const Value* args, size_t count, + size_t index, const char* argumentName) { + if (index >= count || !args[index].isString()) { + throw JSError( + runtime, std::string(argumentName) + " must be a string."); + } + return args[index].asString(runtime).utf8(runtime); +} + +const char* kindName(NativeApiSymbolKind kind) { + switch (kind) { + case NativeApiSymbolKind::Class: + return "class"; + case NativeApiSymbolKind::Function: + return "function"; + case NativeApiSymbolKind::Constant: + return "constant"; + case NativeApiSymbolKind::Protocol: + return "protocol"; + case NativeApiSymbolKind::Enum: + return "enum"; + case NativeApiSymbolKind::Struct: + return "struct"; + case NativeApiSymbolKind::Union: + return "union"; + } + return "unknown"; +} + +Array namesToArray(Runtime& runtime, const std::vector& names) { + Array result(runtime, names.size()); + for (size_t i = 0; i < names.size(); i++) { + result.setValueAtIndex(runtime, i, makeString(runtime, names[i])); + } + return result; +} + +void addPropertyName(Runtime& runtime, std::vector& names, + const char* name) { + names.push_back(PropNameID::forAscii(runtime, name)); +} + +class NativeApiPointerHostObject; +class NativeApiObjectHostObject; +class NativeApiClassHostObject; +class NativeApiProtocolHostObject; +class NativeApiArgumentFrame; +struct NativeApiPreparedCFunctionInvocation; +struct NativeApiPreparedObjCInvocation; + +Value callCFunction(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiSymbol& symbol, const Value* args, + size_t count); +Value callCFunction( + Runtime& runtime, const std::shared_ptr& bridge, + const std::shared_ptr& prepared, + const Value* args, size_t count); + +Value callObjCSelector(Runtime& runtime, + const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const std::string& selectorName, + const NativeApiMember* member, + const Value* args, size_t count, + Class dispatchSuperClass = Nil); + +std::shared_ptr +prepareNativeApiObjCInvocation( + Runtime& runtime, const std::shared_ptr& bridge, + Class lookupClass, bool receiverIsClass, const std::string& selectorName, + const NativeApiMember* member); + +Value callPreparedObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, bool receiverIsClass, + const NativeApiPreparedObjCInvocation& prepared, const Value* args, + size_t count, Class dispatchSuperClass = Nil); + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId); +bool tryCallGeneratedEngineObjCSelector( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass, Value* result); + +Function CreateNativeApiSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations); + +Function CreateNativeApiBoundSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, Class lookupClass, + std::shared_ptr receiverHostObject, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations); + +Value makeNativeObjectValue(Runtime& runtime, + const std::shared_ptr& bridge, + id object, bool ownsObject); + +Value makeNativeClassValue(Runtime& runtime, + const std::shared_ptr& bridge, + NativeApiSymbol symbol); + +Object symbolToObject(Runtime& runtime, const NativeApiSymbol& symbol) { + Object result(runtime); + result.setProperty(runtime, "kind", makeString(runtime, kindName(symbol.kind))); + result.setProperty(runtime, "name", makeString(runtime, symbol.name)); + result.setProperty(runtime, "runtimeName", + makeString(runtime, symbol.runtimeName)); + result.setProperty(runtime, "metadataOffset", + static_cast(symbol.offset)); + + if (symbol.kind == NativeApiSymbolKind::Class) { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + result.setProperty(runtime, "available", cls != nil); + if (cls != nil) { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", cls); + result.setProperty(runtime, "nativeAddress", makeString(runtime, address)); + } + } else if (symbol.kind == NativeApiSymbolKind::Struct || + symbol.kind == NativeApiSymbolKind::Union) { + result.setProperty(runtime, "available", true); + } + + return result; +} + +size_t nativeSizeForType(const NativeApiType& type); +std::optional parseArrayIndexProperty(const std::string& property); + +NativeApiType nativeObjectReturnType( + MDTypeKind kind = metagen::mdTypeAnyObject) { + NativeApiType type; + type.kind = kind; + type.ffiType = &ffi_type_pointer; + type.supported = true; + return type; +} + +NativeApiType nativeObjectReturnTypeForClass(Class cls) { + if (cls != Nil) { + const char* name = class_getName(cls); + if (name != nullptr && std::strcmp(name, "NSString") == 0) { + return nativeObjectReturnType(metagen::mdTypeNSStringObject); + } + if (name != nullptr && std::strcmp(name, "NSMutableString") == 0) { + return nativeObjectReturnType(metagen::mdTypeNSMutableStringObject); + } + } + return nativeObjectReturnType(metagen::mdTypeInstanceObject); +} + +Value convertNativeReturnValue(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, void* value); +Object createPointer(Runtime& runtime, + const std::shared_ptr& bridge, + void* pointer, bool adopted = false, + std::shared_ptr backingValue = nullptr); + +NativeApiType primitiveInteropType(MDTypeKind kind); diff --git a/NativeScript/ffi/objc/shared/bridge/SelectorGroupCall.h b/NativeScript/ffi/objc/shared/bridge/SelectorGroupCall.h new file mode 100644 index 000000000..47c52b0f7 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/SelectorGroupCall.h @@ -0,0 +1,192 @@ +#pragma once + +#include "SelectorGroupState.h" + +struct NativeApiResolvedSelectorGroupCall { + id receiver = nil; + NativeApiPreparedObjCInvocation* prepared = nullptr; + std::shared_ptr receiverHostObject; + std::optional initializerClassWrapper; + Class dispatchClass = Nil; + bool hasImmediateResult = false; + Value immediateResult; +}; + +template +inline NativeApiResolvedSelectorGroupCall resolveNativeApiSelectorGroupCall( + Runtime& runtime, NativeApiSelectorGroupState& data, size_t argumentCount, + ResolveReceiver&& resolveReceiver, + ResolveReceiverHost&& resolveReceiverHost, + LookupGsdInvoker&& lookupGsdInvoker) { + if (argumentCount >= data.selectors->size() || + (*data.selectors)[argumentCount].selectorName.empty()) { + throw JSError( + runtime, + "Objective-C selector is not available for the provided arguments count."); + } + + NativeApiResolvedSelectorGroupCall result; + NativeApiSelectorGroupEntry& entry = (*data.selectors)[argumentCount]; + auto& prepared = (*data.preparedInvocations)[argumentCount]; + Class selectorLookupClass = data.lookupClass; + result.receiver = + data.receiverIsClass ? static_cast(data.lookupClass) : nil; + if (!data.receiverIsClass) { + // The bound receiver's lifetime state is cleared when its ObjC object goes + // away, so "bound" does not mean "still has an object". Falling back to the + // call's own receiver keeps a live call working; the ternary turned it into + // "requires a native receiver". This mirrors how the init path below + // resolves the receiver host. + if (data.boundReceiverState != nullptr) { + result.receiver = data.boundReceiverState->object(); + } + if (result.receiver == nil) { + result.receiver = resolveReceiver(); + } + } + if (result.receiver == nil) { + throw JSError(runtime, + "Objective-C selector requires a native receiver."); + } + + const bool propertyGetterCall = + entry.hasMember && entry.member.property && argumentCount == 0; + const std::string* selectorNamePtr = &entry.selectorName; + const NativeApiMember* selectedMember = + entry.hasMember ? &entry.member : nullptr; + bool callTargetCanPrepare = true; + if (prepared == nullptr || propertyGetterCall) { + NativeApiSelectorGroupCallTarget callTarget = + selectorGroupCallTargetForEntry( + result.receiver, selectorLookupClass, data.receiverIsClass, entry, + argumentCount); + selectorNamePtr = callTarget.selectorName; + selectedMember = callTarget.member; + callTargetCanPrepare = callTarget.canPrepare; + if (prepared != nullptr && prepared->selectorName != *selectorNamePtr) { + prepared = nullptr; + } + } + const std::string& selectorName = + prepared != nullptr && !propertyGetterCall ? prepared->selectorName + : *selectorNamePtr; + + if (data.receiverIsClass) { + Class methodClass = prepared != nullptr ? prepared->receiverClass : Nil; + if (methodClass == Nil) { + SEL selector = sel_registerName(selectorName.c_str()); + methodClass = NativeApiClassHostObject::classRespondingToClassSelector( + data.lookupClass, selector); + } + if (methodClass == Nil) { + throw JSError(runtime, + "Objective-C selector is not available: " + + entry.selectorName); + } + selectorLookupClass = methodClass; + result.receiver = static_cast(methodClass); + } + if (propertyGetterCall && !callTargetCanPrepare) { + result.immediateResult = + callObjCSelector(runtime, data.bridge, result.receiver, + data.receiverIsClass, selectorName, selectedMember, + nullptr, 0); + result.hasImmediateResult = true; + return result; + } + + if (prepared == nullptr) { + if (!data.receiverIsClass) { + SEL selector = sel_registerName(selectorName.c_str()); + if (class_getInstanceMethod(selectorLookupClass, selector) == nullptr) { + Class receiverClass = object_getClass(result.receiver); + if (class_getInstanceMethod(receiverClass, selector) != nullptr) { + selectorLookupClass = receiverClass; + } + } + } + prepared = prepareNativeApiObjCInvocation( + runtime, data.bridge, selectorLookupClass, data.receiverIsClass, + selectorName, selectedMember); + if (prepared->engineInvoker == nullptr) { + uint64_t dispatchId = dispatchIdForEngineSignature( + prepared->signature, SignatureCallKind::ObjCMethod); + if (auto gsdInvoker = lookupGsdInvoker(dispatchId)) { + prepared->engineInvoker = reinterpret_cast(gsdInvoker); + configureGeneratedEngineObjCInvocation(*prepared); + } + } + } + result.prepared = prepared.get(); + + if constexpr (PrepareInitializer) { + if (!data.receiverIsClass && prepared->isInitMethod) { + if (data.boundReceiverState != nullptr) { + result.receiverHostObject = data.boundReceiver.lock(); + } + if (!result.receiverHostObject) { + result.receiverHostObject = resolveReceiverHost(); + } + Value classWrapperValue = data.bridge->findObjectExpando( + runtime, result.receiver, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + result.initializerClassWrapper.emplace( + classWrapperValue.asObject(runtime)); + } + data.bridge->forgetRoundTripValue(result.receiver); + data.bridge->forgetObjectExpandos(result.receiver); + } + } + + if (!data.receiverIsClass) { + Class receiverClass = object_getClass(result.receiver); + if (receiverClass == data.cachedReceiverClass) { + result.dispatchClass = data.cachedDispatchClass; + } else { + result.dispatchClass = dispatchSuperclassForEngineDerivedReceiver( + result.receiver, data.lookupClass); + data.cachedReceiverClass = receiverClass; + data.cachedDispatchClass = result.dispatchClass; + } + } + return result; +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState); + +inline Function CreateNativeApiSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations) { + return CreateNativeApiSelectorGroupFunctionImpl( + runtime, std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), {}, nullptr); +} + +inline Function CreateNativeApiBoundSelectorGroupFunction( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, + std::shared_ptr receiverHostObject, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations) { + return CreateNativeApiSelectorGroupFunctionImpl( + runtime, std::move(bridge), lookupClass, false, std::move(selectors), + std::move(preparedInvocations), receiverHostObject, + receiverHostObject != nullptr ? receiverHostObject->lifetimeState() + : nullptr); +} diff --git a/NativeScript/ffi/objc/shared/bridge/SelectorGroupData.h b/NativeScript/ffi/objc/shared/bridge/SelectorGroupData.h new file mode 100644 index 000000000..d82878aa8 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/SelectorGroupData.h @@ -0,0 +1,24 @@ +#pragma once + +#include "SelectorGroupState.h" + +struct NativeApiSelectorGroupData : NativeApiSelectorGroupState { + template + NativeApiSelectorGroupData( + std::shared_ptr state, + std::shared_ptr bridge, Class lookupClass, + bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver = {}, + std::shared_ptr boundReceiverState = nullptr) + : NativeApiSelectorGroupState( + std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)), + runtime(std::move(state)) {} + + Runtime runtime; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/SelectorGroupState.h b/NativeScript/ffi/objc/shared/bridge/SelectorGroupState.h new file mode 100644 index 000000000..175988210 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/SelectorGroupState.h @@ -0,0 +1,32 @@ +#pragma once + +struct NativeApiSelectorGroupState { + NativeApiSelectorGroupState( + std::shared_ptr bridge, Class lookupClass, + bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver = {}, + std::shared_ptr boundReceiverState = nullptr) + : bridge(std::move(bridge)), + lookupClass(lookupClass), + receiverIsClass(receiverIsClass), + selectors(std::move(selectors)), + preparedInvocations(std::move(preparedInvocations)), + boundReceiver(std::move(boundReceiver)), + boundReceiverState(std::move(boundReceiverState)) {} + + std::shared_ptr bridge; + Class lookupClass = Nil; + bool receiverIsClass = false; + std::shared_ptr> selectors; + std::shared_ptr< + std::vector>> + preparedInvocations; + std::weak_ptr boundReceiver; + std::shared_ptr boundReceiverState; + Class cachedReceiverClass = Nil; + Class cachedDispatchClass = Nil; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/TypeConv.mm b/NativeScript/ffi/objc/shared/bridge/TypeConv.mm new file mode 100644 index 000000000..908c144c6 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/TypeConv.mm @@ -0,0 +1,2239 @@ +std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, const char* name); +void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object); + +class NativeApiObjectConversionStack final { + public: + bool contains(Runtime& runtime, const Value& value) const { + for (const auto& active : activeValues_) { + if (Value::strictEquals(runtime, active, value)) { + return true; + } + } + return false; + } + + void push(Runtime& runtime, const Value& value) { + activeValues_.emplace_back(runtime, value); + } + + void pop() { activeValues_.pop_back(); } + + private: + std::vector activeValues_; +}; + +class NativeApiObjectConversionGuard final { + public: + NativeApiObjectConversionGuard(Runtime& runtime, const Value& value, + NativeApiObjectConversionStack& stack) + : stack_(stack) { + if (stack_.contains(runtime, value)) { + throw JSError( + runtime, + "Circular JavaScript object graphs cannot be converted to Objective-C collections."); + } + stack_.push(runtime, value); + } + + ~NativeApiObjectConversionGuard() { stack_.pop(); } + + private: + NativeApiObjectConversionStack& stack_; +}; + +id objectFromEngineValueImpl( + Runtime& runtime, const std::shared_ptr& bridge, + const Value& value, NativeApiArgumentFrame& frame, bool mutableString, + NativeApiObjectConversionStack& conversionStack) { + if (value.isNull() || value.isUndefined()) { + return nil; + } + if (value.isString()) { + std::string utf8 = value.asString(runtime).utf8(runtime); + id string = mutableString ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + frame.addObject(string); + return string; + } + if (value.isBool()) { + return [NSNumber numberWithBool:value.getBool()]; + } + if (value.isNumber()) { + return [NSNumber numberWithDouble:value.getNumber()]; + } + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->object(); + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return static_cast(cls); + } + if (object.isHostObject(runtime)) { + return static_cast( + object.getHostObject(runtime)->nativeProtocol()); + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + return static_cast(symbolPointer); + } + if (object.isHostObject(runtime)) { + return static_cast(object.getHostObject(runtime)->pointer()); + } + if (object.isHostObject(runtime)) { + return static_cast(object.getHostObject(runtime)->data()); + } + if (object.isHostObject(runtime)) { + return static_cast( + object.getHostObject(runtime)->data()); + } + + Value getTimeValue = object.getProperty(runtime, "getTime"); + Value toISOStringValue = object.getProperty(runtime, "toISOString"); + if (getTimeValue.isObject() && getTimeValue.asObject(runtime).isFunction(runtime) && + toISOStringValue.isObject() && toISOStringValue.asObject(runtime).isFunction(runtime)) { + Value millisValue = getTimeValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (millisValue.isNumber()) { + NSDate* date = [NSDate dateWithTimeIntervalSince1970:millisValue.getNumber() / 1000.0]; + bridge->rememberScopedRoundTripValue(runtime, date, value, false, true); + return date; + } + } + + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitiveValue = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitiveValue.isString() || primitiveValue.isBool() || primitiveValue.isNumber()) { + return objectFromEngineValueImpl(runtime, bridge, primitiveValue, frame, + mutableString, conversionStack); + } + } + + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + NSData* data = [NSData dataWithBytes:bytes length:byteLength]; + bridge->rememberScopedRoundTripValue(runtime, data, value, false, false); + return data; + } + + NativeApiObjectConversionGuard conversionGuard(runtime, value, + conversionStack); + + if (object.isArray(runtime)) { + Array array = object.getArray(runtime); + NSMutableArray* nativeArray = [NSMutableArray arrayWithCapacity:array.size(runtime)]; + for (size_t i = 0; i < array.size(runtime); i++) { + id element = + objectFromEngineValueImpl(runtime, bridge, + array.getValueAtIndex(runtime, i), frame, + false, conversionStack); + [nativeArray addObject:element != nil ? element : [NSNull null]]; + } + bridge->rememberScopedRoundTripValue(runtime, nativeArray, value, false, false); + return nativeArray; + } + + Value lengthValue = object.getProperty(runtime, "length"); + if (lengthValue.isNumber() && std::isfinite(lengthValue.getNumber()) && + lengthValue.getNumber() >= 0) { + size_t length = static_cast(std::floor(lengthValue.getNumber())); + NSMutableArray* nativeArray = [NSMutableArray arrayWithCapacity:length]; + for (size_t i = 0; i < length; i++) { + std::string key = std::to_string(i); + id element = objectFromEngineValueImpl( + runtime, bridge, object.getProperty(runtime, key.c_str()), frame, + false, conversionStack); + [nativeArray addObject:element != nil ? element : [NSNull null]]; + } + bridge->rememberScopedRoundTripValue(runtime, nativeArray, value, false, false); + return nativeArray; + } + + Value entriesValue = object.getProperty(runtime, "entries"); + Value sizeValue = object.getProperty(runtime, "size"); + Value getValue = object.getProperty(runtime, "get"); + if (entriesValue.isObject() && entriesValue.asObject(runtime).isFunction(runtime) && + sizeValue.isNumber() && getValue.isObject() && + getValue.asObject(runtime).isFunction(runtime)) { + Object arrayCtor = runtime.global().getPropertyAsObject(runtime, "Array"); + Function arrayFrom = arrayCtor.getPropertyAsFunction(runtime, "from"); + Value iterator = entriesValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + Value pairsValue = arrayFrom.call(runtime, iterator); + if (pairsValue.isObject() && pairsValue.asObject(runtime).isArray(runtime)) { + Array pairs = pairsValue.asObject(runtime).getArray(runtime); + NSMutableDictionary* nativeMap = + [NSMutableDictionary dictionaryWithCapacity:pairs.size(runtime)]; + for (size_t i = 0; i < pairs.size(runtime); i++) { + Value pairValue = pairs.getValueAtIndex(runtime, i); + if (!pairValue.isObject() || !pairValue.asObject(runtime).isArray(runtime)) { + continue; + } + Array pair = pairValue.asObject(runtime).getArray(runtime); + if (pair.size(runtime) < 2) { + continue; + } + id key = objectFromEngineValueImpl( + runtime, bridge, pair.getValueAtIndex(runtime, 0), frame, false, + conversionStack); + id nativeValue = objectFromEngineValueImpl( + runtime, bridge, pair.getValueAtIndex(runtime, 1), frame, false, + conversionStack); + if (key != nil) { + [nativeMap setObject:nativeValue != nil ? nativeValue : [NSNull null] forKey:key]; + } + } + bridge->rememberScopedRoundTripValue(runtime, nativeMap, value, false, false); + return nativeMap; + } + } + + NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; + Array propertyNames = object.getPropertyNames(runtime); + for (size_t i = 0; i < propertyNames.size(runtime); i++) { + Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); + if (!propertyNameValue.isString()) { + continue; + } + std::string key = propertyNameValue.asString(runtime).utf8(runtime); + Value propertyValue = object.getProperty(runtime, key.c_str()); + if (propertyValue.isUndefined()) { + continue; + } + id nativeValue = objectFromEngineValueImpl( + runtime, bridge, propertyValue, frame, false, conversionStack); + NSString* nativeKey = [NSString stringWithUTF8String:key.c_str()]; + if (nativeKey != nil) { + [dictionary setObject:nativeValue != nil ? nativeValue : [NSNull null] forKey:nativeKey]; + } + } + bridge->rememberScopedRoundTripValue(runtime, dictionary, value, false, false); + return dictionary; + } + throw JSError(runtime, "Value cannot be converted to Objective-C object."); +} + +id objectFromEngineValue(Runtime& runtime, + const std::shared_ptr& bridge, + const Value& value, NativeApiArgumentFrame& frame, + bool mutableString) { + NativeApiObjectConversionStack conversionStack; + return objectFromEngineValueImpl(runtime, bridge, value, frame, + mutableString, conversionStack); +} + +std::string utf8StringFromNSString(NSString* string) { + if (string == nil) { + return ""; + } + NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + std::string result(length, '\0'); + NSUInteger usedLength = 0; + NSRange remainingRange = NSMakeRange(0, 0); + BOOL ok = [string getBytes:result.data() + maxLength:length + usedLength:&usedLength + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(0, string.length) + remainingRange:&remainingRange]; + if (!ok) { + return string.UTF8String ?: ""; + } + result.resize(usedLength); + return result; +} + +char* copyCStringForReference(const char* string, size_t* byteLength = nullptr) { + size_t length = string != nullptr ? std::strlen(string) + 1 : 1; + char* copy = static_cast(malloc(length)); + if (copy == nullptr) { + throw std::bad_alloc(); + } + if (string != nullptr) { + std::memcpy(copy, string, length); + } else { + copy[0] = '\0'; + } + if (byteLength != nullptr) { + *byteLength = length; + } + return copy; +} + +bool readNativePointerProperty(Runtime& runtime, const Object& object, void** pointer) { + if (pointer == nullptr) { + return false; + } + + Value nativePointerObjectValue = object.getProperty(runtime, "__nativeApiPointerObject"); + if (nativePointerObjectValue.isObject()) { + Object nativePointerObject = nativePointerObjectValue.asObject(runtime); + if (nativePointerObject.isHostObject(runtime)) { + *pointer = nativePointerObject.getHostObject(runtime)->pointer(); + return true; + } + } + + Value nativePointerValue = object.getProperty(runtime, "__nativeApiPointer"); + if (nativePointerValue.isNumber()) { + *pointer = reinterpret_cast(static_cast(nativePointerValue.getNumber())); + return true; + } + + Value nativeAddressValue = object.getProperty(runtime, "nativeAddress"); + if (nativeAddressValue.isNumber()) { + *pointer = reinterpret_cast(static_cast(nativeAddressValue.getNumber())); + return true; + } + + return false; +} + +std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, const char* name) { + if (name == nullptr || !object.hasProperty(runtime, name)) { + return ""; + } + Value value = object.getProperty(runtime, name); + return value.isString() ? value.asString(runtime).utf8(runtime) : ""; +} + +constexpr const char* kNativeApiCallbackEncodingProperty = "__nativeApiCallbackEncoding"; + +Function interopCallbackFromArguments(Runtime& runtime, const char* constructorName, + const char* kind, const Value* args, size_t count) { + if (count < 1) { + throw JSError(runtime, std::string(constructorName) + " expects a function."); + } + + std::optional callbackObject; + bool hasCallback = false; + std::string encoding; + + for (size_t i = 0; i < count; i++) { + const Value& arg = args[i]; + if (arg.isUndefined() || arg.isNull()) { + continue; + } + if (arg.isString()) { + if (!encoding.empty()) { + throw JSError(runtime, std::string(constructorName) + + " expects only one Objective-C encoding string."); + } + encoding = arg.asString(runtime).utf8(runtime); + continue; + } + if (arg.isObject()) { + Object object = arg.asObject(runtime); + if (object.isFunction(runtime)) { + if (hasCallback) { + throw JSError(runtime, std::string(constructorName) + " expects only one function."); + } + callbackObject.emplace(std::move(object)); + hasCallback = true; + continue; + } + } + + throw JSError(runtime, std::string(constructorName) + + " expects a function and an optional Objective-C encoding string."); + } + + if (!hasCallback) { + throw JSError(runtime, std::string(constructorName) + " expects a function."); + } + + Function function = callbackObject->asFunction(runtime); + function.setProperty(runtime, "kind", makeString(runtime, kind)); + function.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + if (!encoding.empty()) { + function.setProperty(runtime, kNativeApiCallbackEncodingProperty, + makeString(runtime, encoding)); + } + + return function; +} + +void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object) { + std::string kind = stringPropertyOrEmpty(runtime, object, "kind"); + if (kind != "class" && kind != "protocol") { + return nullptr; + } + + std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); + if (runtimeName.empty()) { + runtimeName = stringPropertyOrEmpty(runtime, object, "name"); + } + if (runtimeName.empty()) { + return nullptr; + } + + if (kind == "class") { + return objc_lookUpClass(runtimeName.c_str()); + } + return lookupProtocolByNativeName(runtimeName); +} + +void* pointerFromEngineValue(Runtime& runtime, const std::shared_ptr& bridge, + const Value& value, NativeApiArgumentFrame& frame) { + if (value.isNull() || value.isUndefined()) { + return nullptr; + } + if (value.isNumber()) { + return reinterpret_cast(static_cast(value.getNumber())); + } + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->pointer(); + } + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->object(); + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return cls; + } + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeProtocol(); + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + return symbolPointer; + } + if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + if (reference->data() == nullptr) { + reference->ensureStorage(runtime, reference->type(), frame); + } + return reference->data(); + } + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->data(); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return nativePointer; + } + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + return const_cast(bytes); + } + } + if (value.isString()) { + std::string utf8 = value.asString(runtime).utf8(runtime); + char* string = strdup(utf8.c_str()); + if (string == nullptr) { + throw std::bad_alloc(); + } + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, string, value, true, false); + } + frame.addCString(string); + return string; + } + throw JSError(runtime, "Value cannot be converted to pointer."); +} + +bool readPointerLikeValue(Runtime& runtime, const Value& value, void** pointer) { + if (pointer == nullptr || !value.isObject()) { + return false; + } + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->pointer(); + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->data(); + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->data(); + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->object(); + return true; + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + *pointer = cls; + return true; + } + if (object.isHostObject(runtime)) { + *pointer = object.getHostObject(runtime)->nativeProtocol(); + return true; + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + *pointer = symbolPointer; + return true; + } + return readNativePointerProperty(runtime, object, pointer); +} + +template +void writeNumericArgument(Runtime& runtime, const Value& value, void* target, + const char* typeName) { + const Value* numericValue = &value; + Value primitiveValue = Value::undefined(); + if (value.isObject()) { + Object object = value.asObject(runtime); + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + primitiveValue = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + numericValue = &primitiveValue; + } + } + + if (!numericValue->isNumber() && !numericValue->isBool()) { + throw JSError(runtime, std::string("Expected numeric ") + typeName + " argument."); + } + double number = + numericValue->isBool() ? (numericValue->getBool() ? 1.0 : 0.0) : numericValue->getNumber(); + *static_cast(target) = static_cast(number); +} + +void convertEngineArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame); + +Value convertNativeReturnValue(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, void* value); + +Class classFromEngineValue(Runtime& runtime, const Value& value); +Protocol* protocolFromEngineValue(Runtime& runtime, const Value& value); + +bool valueIsNativeObjectHostObject(Runtime& runtime, const Value& value) { + if (!value.isObject()) { + return false; + } + return value.asObject(runtime).isHostObject(runtime); +} + +bool nativeTypeStoresObjectiveCObject(const NativeApiType& type) { + switch (type.kind) { + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +void NativeApiReferenceHostObject::retainObjectSlot(size_t index, id object) { + if (retainedObjects_.size() <= index) { + retainedObjects_.resize(index + 1, nil); + } + id previous = retainedObjects_[index]; + if (previous == object) { + return; + } + [object retain]; + retainedObjects_[index] = object; + [previous release]; +} + +std::optional parseArrayIndexProperty(const std::string& property) { + if (property.empty()) { + return std::nullopt; + } + size_t index = 0; + for (char c : property) { + if (!std::isdigit(static_cast(c))) { + return std::nullopt; + } + size_t digit = static_cast(c - '0'); + if (index > (std::numeric_limits::max() - digit) / 10) { + return std::nullopt; + } + index = (index * 10) + digit; + } + return index; +} + +size_t referenceElementStride(const NativeApiType& type) { + return std::max(nativeSizeForType(type), 1); +} + +void convertAggregateArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + size_t size = nativeSizeForType(type); + if (size == 0) { + return; + } + + std::memset(target, 0, size); + if (value.isNull() || value.isUndefined()) { + return; + } + + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + auto structObject = object.getHostObject(runtime); + if (structObject->data() != nullptr) { + std::memcpy(target, structObject->data(), + std::min(size, static_cast(structObject->info()->size))); + } + return; + } + if (object.isHostObject(runtime)) { + void* data = object.getHostObject(runtime)->data(); + if (data != nullptr) { + std::memcpy(target, data, size); + } + return; + } + if (object.isHostObject(runtime)) { + void* data = object.getHostObject(runtime)->pointer(); + if (data != nullptr) { + std::memcpy(target, data, size); + } + return; + } + + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + if (bytes != nullptr) { + std::memcpy(target, bytes, std::min(byteLength, size)); + } + return; + } + } + + if (type.aggregateInfo == nullptr) { + throw JSError(runtime, "Missing native struct metadata."); + } + if (!value.isObject()) { + throw JSError(runtime, "Expected struct descriptor object."); + } + + Object object = value.asObject(runtime); + for (const auto& field : type.aggregateInfo->fields) { + bool hasField = object.hasProperty(runtime, field.name.c_str()); + if (!hasField) { + continue; + } + Value fieldValue = object.getProperty(runtime, field.name.c_str()); + void* fieldTarget = static_cast(target) + field.offset; + convertEngineArgument(runtime, bridge, field.type, fieldValue, fieldTarget, frame); + } +} + +void convertIndexedAggregateArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + size_t size = nativeSizeForType(type); + std::memset(target, 0, size); + if (value.isNull() || value.isUndefined()) { + return; + } + if (value.isObject()) { + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, value.asObject(runtime), &bytes, &byteLength)) { + if (bytes != nullptr) { + std::memcpy(target, bytes, std::min(byteLength, size)); + } + return; + } + } + if (!value.isObject() || !value.asObject(runtime).isArray(runtime)) { + throw JSError(runtime, "Expected array, ArrayBuffer, or typed array."); + } + + Array array = value.asObject(runtime).getArray(runtime); + size_t elementSize = type.elementType != nullptr ? nativeSizeForType(*type.elementType) : 0; + if (elementSize == 0 || type.elementType == nullptr) { + throw JSError(runtime, "Invalid native array element type."); + } + size_t count = std::min(type.arraySize, array.size(runtime)); + for (size_t i = 0; i < count; i++) { + void* slot = static_cast(target) + (i * elementSize); + convertEngineArgument(runtime, bridge, *type.elementType, array.getValueAtIndex(runtime, i), + slot, frame); + } +} + +void convertEngineFfiArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + if (type.kind != metagen::mdTypeArray) { + convertEngineArgument(runtime, bridge, type, value, target, frame); + return; + } + + void* pointer = nullptr; + if (!value.isNull() && !value.isUndefined()) { + if (value.isObject()) { + Object object = value.asObject(runtime); + if (!readPointerLikeValue(runtime, value, &pointer)) { + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + pointer = const_cast(bytes); + } + } + } + + if (pointer == nullptr) { + size_t byteLength = nativeSizeForType(type); + void* buffer = frame.addBuffer(byteLength); + convertIndexedAggregateArgument(runtime, bridge, type, value, buffer, frame); + pointer = buffer; + } + } + + *static_cast(target) = pointer; +} + +void convertEngineArgument(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, const Value& value, void* target, + NativeApiArgumentFrame& frame) { + if (unsupportedEngineType(type)) { + throw JSError(runtime, "This native signature is not supported by " + "the engine bridge yet."); + } + + switch (type.kind) { + case metagen::mdTypeBool: + if (!value.isNumber() && !value.isBool()) { + throw JSError(runtime, "Expected boolean or numeric argument."); + } + *static_cast(target) = value.isBool() + ? static_cast(value.getBool()) + : static_cast(value.getNumber() != 0); + break; + case metagen::mdTypeChar: + writeNumericArgument(runtime, value, target, "int8"); + break; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + writeNumericArgument(runtime, value, target, "uint8"); + break; + case metagen::mdTypeSShort: + writeNumericArgument(runtime, value, target, "int16"); + break; + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + if (value.isString()) { + std::string text = value.asString(runtime).utf8(runtime); + if (text.size() != 1) { + throw JSError(runtime, "Expected a single-character string."); + } + *static_cast(target) = + static_cast(static_cast(text[0])); + } else { + writeNumericArgument(runtime, value, target, "uint16"); + } + break; + case metagen::mdTypeSInt: + writeNumericArgument(runtime, value, target, "int32"); + break; + case metagen::mdTypeUInt: + writeNumericArgument(runtime, value, target, "uint32"); + break; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + writeNumericArgument(runtime, value, target, "int64"); + break; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + writeNumericArgument(runtime, value, target, "uint64"); + break; + case metagen::mdTypeFloat: + writeNumericArgument(runtime, value, target, "float"); + break; + case metagen::mdTypeDouble: + writeNumericArgument(runtime, value, target, "double"); + break; + case metagen::mdTypeString: { + if (value.isNull() || value.isUndefined()) { + *static_cast(target) = nullptr; + break; + } + if (value.isObject()) { + Object object = value.asObject(runtime); + void* pointer = nullptr; + if (readPointerLikeValue(runtime, value, &pointer)) { + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, pointer, value, false, true); + } + *static_cast(target) = static_cast(pointer); + break; + } + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, bytes, value, false, true); + } + *static_cast(target) = reinterpret_cast(const_cast(bytes)); + break; + } + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitive = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitive.isString()) { + std::string utf8 = primitive.asString(runtime).utf8(runtime); + char* string = strdup(utf8.c_str()); + if (string == nullptr) { + throw std::bad_alloc(); + } + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, string, value, true, false); + } + frame.addCString(string); + *static_cast(target) = string; + break; + } + } + } + if (!value.isString()) { + throw JSError(runtime, "Expected string argument."); + } + std::string utf8 = value.asString(runtime).utf8(runtime); + char* string = strdup(utf8.c_str()); + if (string == nullptr) { + throw std::bad_alloc(); + } + if (bridge != nullptr) { + bridge->rememberScopedRawRoundTripValue(runtime, string, value, true, false); + } + frame.addCString(string); + *static_cast(target) = string; + break; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: { + id object = objectFromEngineValue(runtime, bridge, value, frame, + type.kind == metagen::mdTypeNSMutableStringObject); + if (valueIsNativeObjectHostObject(runtime, value)) { + frame.retainObject(object); + } + *static_cast(target) = object; + break; + } + case metagen::mdTypeClass: { + *static_cast(target) = classFromEngineValue(runtime, value); + break; + } + case metagen::mdTypeSelector: { + if (value.isNull() || value.isUndefined()) { + *static_cast(target) = nullptr; + break; + } + if (!value.isString()) { + throw JSError(runtime, "Expected selector string."); + } + std::string selectorName = value.asString(runtime).utf8(runtime); + *static_cast(target) = sel_registerName(selectorName.c_str()); + break; + } + case metagen::mdTypePointer: + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + if (reference->data() == nullptr && type.elementType != nullptr) { + reference->ensureStorage(runtime, *type.elementType, frame); + } else if (reference->data() == nullptr) { + reference->ensureStorage(runtime, reference->type(), frame); + } + void* pointer = reference->data(); + frame.rememberRoundTripValue(bridge, runtime, pointer, value); + *static_cast(target) = pointer; + break; + } + if (object.isHostObject(runtime)) { + void* pointer = object.getHostObject(runtime)->data(); + frame.rememberRoundTripValue(bridge, runtime, pointer, value); + *static_cast(target) = pointer; + break; + } + const uint8_t* bytes = nullptr; + size_t byteLength = 0; + if (readEngineBuffer(runtime, object, &bytes, &byteLength)) { + void* pointer = const_cast(bytes); + frame.rememberRoundTripValue(bridge, runtime, pointer, value); + *static_cast(target) = pointer; + break; + } + } + *static_cast(target) = pointerFromEngineValue(runtime, bridge, value, frame); + break; + case metagen::mdTypeOpaquePointer: + *static_cast(target) = pointerFromEngineValue(runtime, bridge, value, frame); + break; + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: { + if (value.isNull() || value.isUndefined()) { + *static_cast(target) = nullptr; + break; + } + if (value.isObject()) { + Object object = value.asObject(runtime); + void* nativePointer = nullptr; + if (object.isFunction(runtime)) { + std::string functionKind = stringPropertyOrEmpty(runtime, object, "kind"); + if (functionKind == "block" || functionKind == "functionPointer" || + functionKind == "functionReference") { + if (readNativePointerProperty(runtime, object, &nativePointer)) { + *static_cast(target) = nativePointer; + break; + } + } + + uintptr_t roundTripValidationKey = NativeApiBridge::callbackRoundTripValidationKey(type); + auto threadPolicy = readEngineCallbackThreadPolicy(runtime, object); + std::string callbackEncoding = + stringPropertyOrEmpty(runtime, object, kNativeApiCallbackEncodingProperty); + auto callback = + callbackEncoding.empty() + ? createEngineCallback(runtime, bridge, type, object.asFunction(runtime), + type.kind == metagen::mdTypeBlock, threadPolicy) + : createEngineCallback( + runtime, bridge, callbackEncoding, object.asFunction(runtime), + type.kind == metagen::mdTypeBlock, threadPolicy, roundTripValidationKey); + void* pointer = callback->functionPointer(); + if (type.kind == metagen::mdTypeBlock) { + frame.addObject(static_cast(pointer)); + frame.addLifetime(callback); + bridge->rememberRoundTripValue(runtime, pointer, value, false, roundTripValidationKey); + } else { + bridge->rememberRoundTripValue(runtime, pointer, value, false, roundTripValidationKey); + } + try { + object.setProperty(runtime, "__nativeApiPointerObject", + createPointer(runtime, bridge, pointer)); + object.setProperty(runtime, "__nativeApiPointer", + static_cast(reinterpret_cast(pointer))); + } catch (const std::exception&) { + } + *static_cast(target) = pointer; + break; + } + } + *static_cast(target) = pointerFromEngineValue(runtime, bridge, value, frame); + break; + } + case metagen::mdTypeStruct: + convertAggregateArgument(runtime, bridge, type, value, target, frame); + break; + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: + convertIndexedAggregateArgument(runtime, bridge, type, value, target, frame); + break; + default: + throw JSError(runtime, "Unsupported Engine argument type."); + } +} + +Value convertNativeReturnValue(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, void* value) { + if (unsupportedEngineType(type)) { + throw JSError(runtime, "This native return type is not supported by " + "the engine bridge yet."); + } + + switch (type.kind) { + case metagen::mdTypeVoid: + return Value::undefined(); + case metagen::mdTypeBool: + return *static_cast(value) != 0; + case metagen::mdTypeChar: + return static_cast(*static_cast(value)); + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + return static_cast(*static_cast(value)); + case metagen::mdTypeSShort: + return static_cast(*static_cast(value)); + case metagen::mdTypeUShort: + return static_cast(*static_cast(value)); + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + return String::createFromUtf8( + runtime, reinterpret_cast(buffer), length); + } + case metagen::mdTypeSInt: + return static_cast(*static_cast(value)); + case metagen::mdTypeUInt: + return static_cast(*static_cast(value)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + return signedInteger64ToEngineValue(runtime, *static_cast(value)); + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + return unsignedInteger64ToEngineValue(runtime, *static_cast(value)); + case metagen::mdTypeFloat: + return static_cast(*static_cast(value)); + case metagen::mdTypeDouble: + return *static_cast(value); + case metagen::mdTypeString: { + const char* string = *static_cast(value); + if (string == nullptr) { + return Value::null(); + } + NativeApiType cStringType = primitiveInteropType(metagen::mdTypeChar); + std::shared_ptr backingValue; + bool stringLikeNative = false; + if (bridge != nullptr) { + Value roundTrip = bridge->findRoundTripValue(runtime, string, &stringLikeNative); + if (!roundTrip.isUndefined()) { + backingValue = std::make_shared(runtime, roundTrip); + } + } + if (stringLikeNative) { + size_t byteLength = 0; + char* copy = copyCStringForReference(string, &byteLength); + return Object::createFromHostObject( + runtime, std::make_shared(bridge, cStringType, copy, true, + byteLength)); + } + return Object::createFromHostObject( + runtime, std::make_shared( + bridge, cStringType, const_cast(string), false, 0, nullptr, + std::move(backingValue))); + } + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + return Value::null(); + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + return makeNativeClassValue(runtime, bridge, std::move(symbol)); + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: { + id object = *static_cast(value); + if (object == nil) { + return Value::null(); + } + Value roundTrip = findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + if (type.returnOwned) { + [object release]; + } + return roundTrip; + } + if (nativeObjectReturnMayCoerceToString(type) && nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + return makeString(runtime, utf8); + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + return Value::null(); + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == CFBooleanGetTypeID() || + (objCType != nullptr && std::strcmp(objCType, @encode(BOOL)) == 0); + Value result = + isBool ? Value(static_cast([number boolValue])) : Value([number doubleValue]); + if (type.returnOwned) { + [object release]; + } + return result; + } + if (const NativeApiSymbol* classSymbol = bridge->findClassForRuntimePointer((void*)object)) { + return makeNativeClassValue(runtime, bridge, *classSymbol); + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + } + return makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + } + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; + return selectorName != nullptr ? makeString(runtime, selectorName) : Value::null(); + } + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: { + void* pointer = *static_cast(value); + if (pointer == nullptr) { + return Value::null(); + } + if (const NativeApiSymbol* classSymbol = bridge->findClassForRuntimePointer(pointer)) { + return makeNativeClassValue(runtime, bridge, *classSymbol); + } + if (const NativeApiSymbol* protocolSymbol = bridge->findProtocolForRuntimePointer(pointer)) { + return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + } + if (type.kind == metagen::mdTypePointer && type.elementType != nullptr) { + std::shared_ptr backingValue; + bool stringLikeNative = false; + Value roundTrip = bridge->findRoundTripValue(runtime, pointer, &stringLikeNative); + if (stringLikeNative) { + size_t byteLength = 0; + char* copy = copyCStringForReference(static_cast(pointer), &byteLength); + return Object::createFromHostObject( + runtime, std::make_shared(bridge, *type.elementType, + copy, true, byteLength)); + } + if (!roundTrip.isUndefined()) { + backingValue = std::make_shared(runtime, roundTrip); + } + return Object::createFromHostObject(runtime, std::make_shared( + bridge, *type.elementType, pointer, false, + 0, nullptr, std::move(backingValue))); + } + return createPointer(runtime, bridge, pointer); + } + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: { + void* pointer = *static_cast(value); + if (pointer == nullptr) { + return Value::null(); + } + Value roundTrip = bridge->findRoundTripValue( + runtime, pointer, nullptr, false, NativeApiBridge::callbackRoundTripValidationKey(type)); + if (!roundTrip.isUndefined()) { + return roundTrip; + } + return wrapNativeFunctionPointer(runtime, bridge, type, pointer, + type.kind == metagen::mdTypeBlock); + } + case metagen::mdTypeStruct: + if (type.aggregateInfo == nullptr) { + return ArrayBuffer( + runtime, std::make_shared(value, nativeSizeForType(type))); + } + return Object::createFromHostObject( + runtime, std::make_shared(bridge, type.aggregateInfo, + value, true)); + case metagen::mdTypeArray: + case metagen::mdTypeVector: + case metagen::mdTypeExtVector: + case metagen::mdTypeComplex: { + Array result(runtime, type.arraySize); + if (type.elementType == nullptr) { + return result; + } + size_t elementSize = nativeSizeForType(*type.elementType); + auto base = static_cast(value); + for (uint16_t i = 0; i < type.arraySize; i++) { + result.setValueAtIndex( + runtime, i, + convertNativeReturnValue(runtime, bridge, *type.elementType, + base + (static_cast(i) * elementSize))); + } + return result; + } + default: + throw JSError(runtime, "Unsupported Engine return type."); + } +} + +void NativeApiReferenceHostObject::ensureStorage(Runtime& runtime, NativeApiType type, + NativeApiArgumentFrame& frame, size_t elements) { + size_t elementCount = std::max(elements, 1); + NativeApiType storageType = std::move(type); + size_t stride = std::max(nativeSizeForType(storageType), 1); + size_t required = std::max(stride * elementCount, sizeof(void*)); + type_ = std::move(storageType); + + if (data_ == nullptr) { + data_ = calloc(1, required); + ownsData_ = true; + byteLength_ = required; + } else if (ownsData_ && byteLength_ < required) { + void* expanded = realloc(data_, required); + if (expanded == nullptr) { + throw std::bad_alloc(); + } + std::memset(static_cast(expanded) + byteLength_, 0, required - byteLength_); + data_ = expanded; + byteLength_ = required; + } + + if (data_ != nullptr && pendingValue_ != nullptr) { + Value pending(runtime, *pendingValue_); + convertEngineArgument(runtime, bridge_, type_, pending, data_, frame); + if (nativeTypeStoresObjectiveCObject(type_)) { + retainObjectSlot(0, *static_cast(data_)); + } + pendingValue_.reset(); + } +} + +Value NativeApiReferenceHostObject::get(Runtime& runtime, const PropNameID& name) { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "reference"); + } + if (property == "address") { + return static_cast(reinterpret_cast(data_)); + } + if (property == "value") { + if (data_ == nullptr) { + if (pendingValue_ != nullptr) { + return Value(runtime, *pendingValue_); + } + return Value::undefined(); + } + if (backingValue_ != nullptr && nativeTypeStoresObjectiveCObject(type_)) { + return Value(runtime, *backingValue_); + } + return convertNativeReturnValue(runtime, bridge_, type_, data_); + } + if (auto index = parseArrayIndexProperty(property)) { + if (data_ == nullptr) { + return Value::undefined(); + } + void* slot = static_cast(data_) + (*index * referenceElementStride(type_)); + return convertNativeReturnValue(runtime, bridge_, type_, slot); + } + if (property == "toString") { + void* data = data_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [data](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", data); + return makeString(runtime, ""); + }); + } + return Value::undefined(); +} + +NativeApiHostSetResult NativeApiReferenceHostObject::set(Runtime& runtime, const PropNameID& name, + const Value& value) { + std::string property = name.utf8(runtime); + auto index = parseArrayIndexProperty(property); + if (property != "value" && !index) { + NATIVE_API_SET_RETURN(true); + } + size_t slotIndex = index.value_or(0); + NativeApiArgumentFrame frame(1); + if (data_ == nullptr) { + if (slotIndex == 0) { + pendingValue_ = std::make_shared(runtime, value); + NATIVE_API_SET_RETURN(true); + } + ensureStorage(runtime, type_, frame, slotIndex + 1); + } + pendingValue_.reset(); + backingValue_.reset(); + void* slot = static_cast(data_) + (slotIndex * referenceElementStride(type_)); + convertEngineArgument(runtime, bridge_, type_, value, slot, frame); + if (nativeTypeStoresObjectiveCObject(type_)) { + retainObjectSlot(slotIndex, *static_cast(slot)); + } + NATIVE_API_SET_RETURN(true); +} + +Value NativeApiStructObjectHostObject::get(Runtime& runtime, const PropNameID& name) { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, info_ != nullptr && info_->isUnion ? "union" : "struct"); + } + if (property == "name") { + return makeString(runtime, info_ != nullptr ? info_->name : ""); + } + if (property == "sizeof") { + return static_cast(info_ != nullptr ? info_->size : 0); + } + if (property == "address") { + return static_cast(reinterpret_cast(data_)); + } + if (property == "toString") { + auto info = info_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [info](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return makeString(runtime, std::string("[NativeApi ") + + (info != nullptr && info->isUnion ? "Union " : "Struct ") + + (info != nullptr ? info->name : "") + "]"); + }); + } + + if (info_ != nullptr && data_ != nullptr) { + for (const auto& field : info_->fields) { + if (field.name != property) { + continue; + } + void* fieldData = static_cast(data_) + field.offset; + if (field.type.kind == metagen::mdTypeStruct && field.type.aggregateInfo != nullptr) { + return Object::createFromHostObject( + runtime, + std::make_shared( + bridge_, field.type.aggregateInfo, fieldData, false, ownedData_, backingValue_)); + } + return convertNativeReturnValue(runtime, bridge_, field.type, fieldData); + } + } + return Value::undefined(); +} + +NativeApiHostSetResult NativeApiStructObjectHostObject::set(Runtime& runtime, + const PropNameID& name, + const Value& value) { + std::string property = name.utf8(runtime); + if (info_ == nullptr || data_ == nullptr) { + throw JSError(runtime, "Struct is not initialized."); + } + for (const auto& field : info_->fields) { + if (field.name != property) { + continue; + } + NativeApiArgumentFrame frame(1); + convertEngineArgument(runtime, bridge_, field.type, value, + static_cast(data_) + field.offset, frame); + NATIVE_API_SET_RETURN(true); + } + throw JSError(runtime, "No native struct field: " + property); +} + +std::vector NativeApiStructObjectHostObject::getPropertyNames(Runtime& runtime) { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "name"); + addPropertyName(runtime, names, "sizeof"); + addPropertyName(runtime, names, "address"); + addPropertyName(runtime, names, "toString"); + if (info_ != nullptr) { + for (const auto& field : info_->fields) { + addPropertyName(runtime, names, field.name.c_str()); + } + } + return names; +} + +NativeApiType primitiveInteropType(MDTypeKind kind) { + NativeApiType type; + type.kind = kind; + type.ffiType = ffiTypeForEngineKind(kind); + type.supported = type.ffiType != nullptr; + return type; +} + +std::optional primitiveInteropTypeFromCode(int32_t code) { + MDTypeKind kind = static_cast(code); + switch (kind) { + case metagen::mdTypeVoid: + case metagen::mdTypeBool: + case metagen::mdTypeChar: + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + case metagen::mdTypeSShort: + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: + case metagen::mdTypeSInt: + case metagen::mdTypeUInt: + case metagen::mdTypeSLong: + case metagen::mdTypeULong: + case metagen::mdTypeSInt64: + case metagen::mdTypeUInt64: + case metagen::mdTypeFloat: + case metagen::mdTypeDouble: + case metagen::mdTypeString: + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClass: + case metagen::mdTypeSelector: + case metagen::mdTypePointer: + case metagen::mdTypeOpaquePointer: + case metagen::mdTypeBlock: + case metagen::mdTypeFunctionPointer: + return primitiveInteropType(kind); + default: + return std::nullopt; + } +} + +std::optional interopTypeFromValue(Runtime& runtime, + const std::shared_ptr& bridge, + const Value& value) { + if (value.isNumber()) { + return primitiveInteropTypeFromCode(static_cast(value.getNumber())); + } + + if (!value.isObject()) { + return std::nullopt; + } + + Object object = value.asObject(runtime); + Value typeCodeValue = object.getProperty(runtime, "__nativeApiTypeCode"); + if (typeCodeValue.isNumber()) { + return primitiveInteropTypeFromCode(static_cast(typeCodeValue.getNumber())); + } + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitive = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitive.isNumber()) { + return primitiveInteropTypeFromCode(static_cast(primitive.getNumber())); + } + } + + Class descriptorClass = nativeClassFromEngineObject(runtime, object); + if (descriptorClass == Nil && stringPropertyOrEmpty(runtime, object, "kind") == "class") { + descriptorClass = static_cast(pointerFromSymbolLikeObject(runtime, object)); + } + if (descriptorClass != Nil) { + return nativeObjectReturnTypeForClass(descriptorClass); + } + + if (object.isHostObject(runtime)) { + auto structObject = object.getHostObject(runtime); + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = structObject->info(); + type.aggregateOffset = + type.aggregateInfo != nullptr ? type.aggregateInfo->offset : MD_SECTION_OFFSET_NULL; + type.aggregateIsUnion = type.aggregateInfo != nullptr && type.aggregateInfo->isUnion; + type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr + ? &type.aggregateInfo->ffi->type + : nullptr; + type.supported = type.ffiType != nullptr; + return type; + } + + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString()) { + std::string kindName = kindValue.asString(runtime).utf8(runtime); + if (kindName == "pointer") { + return primitiveInteropType(metagen::mdTypePointer); + } + if (kindName == "reference") { + return primitiveInteropType(metagen::mdTypePointer); + } + if (kindName == "class") { + return nativeObjectReturnType(metagen::mdTypeInstanceObject); + } + if (kindName == "selector") { + return primitiveInteropType(metagen::mdTypeSelector); + } + if (kindName == "protocol") { + return primitiveInteropType(metagen::mdTypeProtocolObject); + } + if (kindName == "block") { + return primitiveInteropType(metagen::mdTypeBlock); + } + if (kindName == "functionPointer") { + return primitiveInteropType(metagen::mdTypeFunctionPointer); + } + if (kindName == "functionReference") { + return primitiveInteropType(metagen::mdTypeFunctionPointer); + } + } + Value offsetValue = object.getProperty(runtime, "metadataOffset"); + if (kindValue.isString() && offsetValue.isNumber()) { + std::string kindName = kindValue.asString(runtime).utf8(runtime); + if (kindName == "struct" || kindName == "union") { + bool isUnion = kindName == "union"; + auto info = + bridge->aggregateInfoFor(static_cast(offsetValue.getNumber()), isUnion); + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = info; + type.aggregateOffset = info != nullptr ? info->offset : MD_SECTION_OFFSET_NULL; + type.aggregateIsUnion = isUnion; + type.ffiType = info != nullptr && info->ffi != nullptr ? &info->ffi->type : nullptr; + type.supported = type.ffiType != nullptr; + return type; + } + } + + return std::nullopt; +} + +Value makeAggregateConstructor(Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiSymbol& symbol) { + auto info = bridge->aggregateInfoFor(symbol); + auto constructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, symbol.name.c_str()), 1, + [bridge, symbol, info](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (info == nullptr) { + throw JSError(runtime, "Native aggregate metadata is unavailable: " + symbol.name); + } + + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = info; + type.aggregateOffset = info->offset; + type.aggregateIsUnion = info->isUnion; + type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; + type.supported = type.ffiType != nullptr; + + if (count > 0 && args[0].isObject()) { + void* pointer = nullptr; + if (readPointerLikeValue(runtime, args[0], &pointer) && pointer != nullptr) { + return Object::createFromHostObject(runtime, + std::make_shared( + bridge, info, pointer, false, nullptr, + std::make_shared(runtime, args[0]))); + } + } + + std::vector storage(info->size, 0); + if (count > 0) { + NativeApiArgumentFrame frame(1); + convertAggregateArgument(runtime, bridge, type, args[0], storage.data(), frame); + } + return Object::createFromHostObject( + runtime, + std::make_shared(bridge, info, storage.data(), true)); + }); + + constructor.setProperty( + runtime, "kind", + makeString(runtime, symbol.kind == NativeApiSymbolKind::Union ? "union" : "struct")); + constructor.setProperty(runtime, "runtimeName", makeString(runtime, symbol.runtimeName)); + constructor.setProperty(runtime, "metadataOffset", static_cast(symbol.offset)); + constructor.setProperty(runtime, "sizeof", static_cast(info != nullptr ? info->size : 0)); + constructor.setProperty( + runtime, "equals", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "equals"), 2, + [bridge, info](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (info == nullptr || count < 2) { + return false; + } + + NativeApiType type; + type.kind = metagen::mdTypeStruct; + type.aggregateInfo = info; + type.aggregateOffset = info->offset; + type.aggregateIsUnion = info->isUnion; + type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; + type.supported = type.ffiType != nullptr; + + std::vector left(info->size, 0); + std::vector right(info->size, 0); + try { + NativeApiArgumentFrame leftFrame(1); + convertAggregateArgument(runtime, bridge, type, args[0], left.data(), leftFrame); + NativeApiArgumentFrame rightFrame(1); + convertAggregateArgument(runtime, bridge, type, args[1], right.data(), rightFrame); + } catch (const std::exception&) { + return false; + } + + return std::memcmp(left.data(), right.data(), info->size) == 0; + })); + Array fields(runtime, info != nullptr ? info->fields.size() : 0); + if (info != nullptr) { + for (size_t i = 0; i < info->fields.size(); i++) { + fields.setValueAtIndex(runtime, i, makeString(runtime, info->fields[i].name)); + } + } + constructor.setProperty(runtime, "fields", fields); + return constructor; +} + +size_t sizeofInteropType(Runtime& runtime, const std::shared_ptr& bridge, + const Value& value) { + if (auto type = interopTypeFromValue(runtime, bridge, value)) { + return nativeSizeForType(*type); + } + + if (value.isObject()) { + Object object = value.asObject(runtime); + if (object.isHostObject(runtime) || + object.isHostObject(runtime) || + object.isHostObject(runtime) || + nativeClassFromEngineObject(runtime, object) != Nil) { + return sizeof(void*); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return sizeof(void*); + } + Value sizeValue = object.getProperty(runtime, "sizeof"); + if (sizeValue.isNumber()) { + return static_cast(sizeValue.getNumber()); + } + } + + throw JSError(runtime, "Invalid type for interop.sizeof."); +} + +Object createPointer(Runtime& runtime, const std::shared_ptr& bridge, + void* pointer, bool adopted, std::shared_ptr backingValue) { + if (!adopted && bridge != nullptr) { + Value cached = bridge->findPointerValue(runtime, pointer); + if (cached.isObject()) { + Object cachedObject = cached.asObject(runtime); + if (backingValue != nullptr && + cachedObject.isHostObject(runtime)) { + cachedObject.getHostObject(runtime)->setBackingValue( + runtime, *backingValue); + } + return cachedObject; + } + } + + Object result = Object::createFromHostObject( + runtime, std::make_shared(bridge, pointer, "pointer", adopted, + std::move(backingValue))); + if (!adopted && bridge != nullptr) { + bridge->rememberPointerValue(runtime, pointer, Value(runtime, result)); + } + return result; +} + +void installInteropHasInstance(Runtime& runtime, Function& constructor, const char* kind) { + Value symbolCtorValue = runtime.global().getProperty(runtime, "Symbol"); + if (!symbolCtorValue.isObject()) { + return; + } + + Object symbolCtor = symbolCtorValue.asObject(runtime); + Value hasInstanceValue = symbolCtor.getProperty(runtime, "hasInstance"); + if (!hasInstanceValue.isSymbol()) { + return; + } + + try { + Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); + Function defineProperty = objectCtor.getPropertyAsFunction(runtime, "defineProperty"); + Object descriptor(runtime); + descriptor.setProperty(runtime, "configurable", true); + descriptor.setProperty( + runtime, "value", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Symbol.hasInstance"), 1, + [kind = std::string(kind)](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + return false; + } + + Object object = args[0].asObject(runtime); + Value kindValue = object.getProperty(runtime, "kind"); + return kindValue.isString() && kindValue.asString(runtime).utf8(runtime) == kind; + })); + defineProperty.call(runtime, constructor, hasInstanceValue, descriptor); + } catch (const std::exception&) { + } +} + +Class classFromEngineValue(Runtime& runtime, const Value& value) { + if (value.isString()) { + std::string name = value.asString(runtime).utf8(runtime); + return objc_lookUpClass(name.c_str()); + } + if (!value.isObject()) { + return Nil; + } + Object object = value.asObject(runtime); + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return cls; + } + if (stringPropertyOrEmpty(runtime, object, "kind") == "class") { + if (void* pointer = pointerFromSymbolLikeObject(runtime, object)) { + return static_cast(pointer); + } + } + if (object.isHostObject(runtime)) { + id nativeObject = object.getHostObject(runtime)->object(); + return nativeObject != nil ? object_getClass(nativeObject) : Nil; + } + return Nil; +} + +Protocol* protocolFromEngineValue(Runtime& runtime, const Value& value) { + if (value.isString()) { + std::string name = value.asString(runtime).utf8(runtime); + Protocol* protocol = objc_getProtocol(name.c_str()); + if (protocol == nullptr) { + constexpr const char* suffix = "Protocol"; + if (name.size() > std::strlen(suffix) && + name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), suffix) == 0) { + protocol = objc_getProtocol(name.substr(0, name.size() - std::strlen(suffix)).c_str()); + } + } + return protocol; + } + if (!value.isObject()) { + return nullptr; + } + Object object = value.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeProtocol(); + } + if (stringPropertyOrEmpty(runtime, object, "kind") == "protocol") { + return static_cast(pointerFromSymbolLikeObject(runtime, object)); + } + if (object.isHostObject(runtime)) { + return static_cast( + object.getHostObject(runtime)->pointer()); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return static_cast(nativePointer); + } + Value nameValue = object.getProperty(runtime, "name"); + if (nameValue.isString()) { + return protocolFromEngineValue(runtime, nameValue); + } + return nullptr; +} + +Object createInteropObject(Runtime& runtime, const std::shared_ptr& bridge) { + Object interop(runtime); + Object types(runtime); + auto setType = [&](const char* name, MDTypeKind kind) { + Object type(runtime); + double code = static_cast(kind); + type.setProperty(runtime, "__nativeApiTypeCode", code); + type.setProperty( + runtime, "valueOf", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "valueOf"), 0, + [code](Runtime&, const Value&, const Value*, size_t) -> Value { return code; })); + type.setProperty(runtime, "toString", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [code](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + char text[32] = {}; + snprintf(text, sizeof(text), "%d", static_cast(code)); + return makeString(runtime, text); + })); + types.setProperty(runtime, name, type); + }; + setType("void", metagen::mdTypeVoid); + setType("bool", metagen::mdTypeBool); + setType("int8", metagen::mdTypeChar); + setType("uint8", metagen::mdTypeUInt8); + setType("int16", metagen::mdTypeSShort); + setType("uint16", metagen::mdTypeUShort); + setType("int32", metagen::mdTypeSInt); + setType("uint32", metagen::mdTypeUInt); + setType("int64", metagen::mdTypeSInt64); + setType("uint64", metagen::mdTypeUInt64); + setType("float", metagen::mdTypeFloat); + setType("double", metagen::mdTypeDouble); + setType("UTF8CString", metagen::mdTypeString); + setType("unichar", metagen::mdTypeUnichar); + setType("id", metagen::mdTypeAnyObject); + setType("class", metagen::mdTypeClass); + setType("protocol", metagen::mdTypeProtocolObject); + setType("SEL", metagen::mdTypeSelector); + setType("selector", metagen::mdTypeSelector); + setType("pointer", metagen::mdTypePointer); + setType("block", metagen::mdTypeBlock); + setType("functionPointer", metagen::mdTypeFunctionPointer); + interop.setProperty(runtime, "types", types); + + Function pointerConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Pointer"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count > 0 && args[0].isObject()) { + Object object = args[0].asObject(runtime); + if (object.isHostObject(runtime)) { + return Value(runtime, object); + } + } + void* pointer = nullptr; + if (count > 0 && !args[0].isNull() && !args[0].isUndefined()) { + auto readAddress = [&](const Value& value, uintptr_t* address) -> bool { + auto readAddressFromString = [&](const Value& source) -> bool { + try { + Value stringCtorValue = runtime.global().getProperty(runtime, "String"); + if (!stringCtorValue.isObject() || + !stringCtorValue.asObject(runtime).isFunction(runtime)) { + return false; + } + Value stringValue = + stringCtorValue.asObject(runtime).asFunction(runtime).call(runtime, source); + if (!stringValue.isString()) { + return false; + } + return parseIntegerTextToUintptr(stringValue.asString(runtime).utf8(runtime), + address); + } catch (const std::exception&) { + return false; + } + }; + + if (value.isNumber()) { + double number = value.getNumber(); + if (!std::isfinite(number)) { + return false; + } + *address = static_cast(static_cast(number)); + return true; + } + if (value.isBigInt()) { + if (readAddressFromString(value)) { + return true; + } + BigInt bigint = value.getBigInt(runtime); + return parseBigIntToUintptr(runtime, bigint, address); + } + if (value.isObject()) { + Object object = value.asObject(runtime); + Value valueOfValue = object.getProperty(runtime, "valueOf"); + if (valueOfValue.isObject() && valueOfValue.asObject(runtime).isFunction(runtime)) { + Value primitive = valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, object, nullptr, 0); + if (primitive.isNumber()) { + double number = primitive.getNumber(); + if (!std::isfinite(number)) { + return false; + } + *address = static_cast(static_cast(number)); + return true; + } + if (primitive.isBigInt()) { + if (readAddressFromString(primitive)) { + return true; + } + BigInt bigint = primitive.getBigInt(runtime); + return parseBigIntToUintptr(runtime, bigint, address); + } + } + return readAddressFromString(value); + } + return false; + }; + + uintptr_t address = 0; + if (!readAddress(args[0], &address)) { + throw JSError(runtime, "Pointer expects a numeric address."); + } + pointer = reinterpret_cast(address); + } + return createPointer(runtime, bridge, pointer); + }); + Object pointerPrototype(runtime); + pointerPrototype.setProperty(runtime, "constructor", pointerConstructor); + pointerConstructor.setProperty(runtime, "prototype", pointerPrototype); + installInteropHasInstance(runtime, pointerConstructor, "pointer"); + pointerConstructor.setProperty(runtime, "kind", makeString(runtime, "pointer")); + pointerConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "Pointer", pointerConstructor); + + Function blockConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Block"), 2, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + return interopCallbackFromArguments(runtime, "Block", "block", args, count); + }); + Object blockPrototype(runtime); + blockPrototype.setProperty(runtime, "constructor", blockConstructor); + blockConstructor.setProperty(runtime, "prototype", blockPrototype); + installInteropHasInstance(runtime, blockConstructor, "block"); + blockConstructor.setProperty(runtime, "kind", makeString(runtime, "block")); + blockConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "Block", blockConstructor); + + Function functionReferenceConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "FunctionReference"), 2, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + return interopCallbackFromArguments(runtime, "FunctionReference", "functionReference", args, + count); + }); + Object functionReferencePrototype(runtime); + functionReferencePrototype.setProperty(runtime, "constructor", functionReferenceConstructor); + functionReferenceConstructor.setProperty(runtime, "prototype", functionReferencePrototype); + installInteropHasInstance(runtime, functionReferenceConstructor, "functionReference"); + functionReferenceConstructor.setProperty(runtime, "kind", + makeString(runtime, "functionReference")); + functionReferenceConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "FunctionReference", functionReferenceConstructor); + + Function referenceConstructor = Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Reference"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + NativeApiType type = primitiveInteropType(metagen::mdTypePointer); + bool firstArgumentIsType = false; + if (count > 1) { + firstArgumentIsType = true; + } else if (count == 1 && args[0].isObject()) { + Object object = args[0].asObject(runtime); + Value typeCodeValue = object.getProperty(runtime, "__nativeApiTypeCode"); + Value kindValue = object.getProperty(runtime, "kind"); + firstArgumentIsType = + typeCodeValue.isNumber() || object.isFunction(runtime) || + nativeClassFromEngineObject(runtime, object) != Nil || + (kindValue.isString() && (kindValue.asString(runtime).utf8(runtime) == "class" || + kindValue.asString(runtime).utf8(runtime) == "protocol")); + } + std::optional requestedType = + firstArgumentIsType ? interopTypeFromValue(runtime, bridge, args[0]) : std::nullopt; + bool hasType = firstArgumentIsType && requestedType.has_value(); + if (hasType) { + type = *requestedType; + } + + void* data = nullptr; + bool ownsData = false; + size_t byteLength = 0; + std::shared_ptr pendingValue; + std::shared_ptr backingValue; + if (hasType) { + bool usesExternalStorage = false; + Value valueToStore = Value::undefined(); + if (count > 1) { + valueToStore = Value(runtime, args[1]); + if (args[1].isObject()) { + Object object = args[1].asObject(runtime); + if (object.isHostObject(runtime)) { + data = object.getHostObject(runtime)->pointer(); + usesExternalStorage = true; + } else if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + data = reference->data(); + if (data != nullptr) { + usesExternalStorage = true; + } else { + valueToStore = object.getProperty(runtime, "value"); + } + } else if (type.kind == metagen::mdTypeStruct && + object.isHostObject(runtime)) { + data = object.getHostObject(runtime)->data(); + usesExternalStorage = true; + } else if (type.kind == metagen::mdTypePointer || + type.kind == metagen::mdTypeOpaquePointer || + type.kind == metagen::mdTypeBlock || + type.kind == metagen::mdTypeFunctionPointer) { + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + data = nativePointer; + usesExternalStorage = true; + } + } + } + } + if (!usesExternalStorage) { + byteLength = std::max(nativeSizeForType(type), sizeof(void*)); + data = calloc(1, byteLength); + if (data == nullptr) { + throw std::bad_alloc(); + } + ownsData = true; + if (count > 1) { + NativeApiArgumentFrame frame(1); + convertEngineArgument(runtime, bridge, type, valueToStore, data, frame); + if (nativeTypeStoresObjectiveCObject(type) && valueToStore.isObject()) { + backingValue = std::make_shared(runtime, valueToStore); + } + } + } + } else if (count > 0) { + pendingValue = std::make_shared(runtime, args[0]); + } + + if (ownsData && data == nullptr) { + throw std::bad_alloc(); + } + return Object::createFromHostObject( + runtime, std::make_shared( + bridge, type, data, ownsData, byteLength, std::move(pendingValue), + std::move(backingValue))); + }); + Object referencePrototype(runtime); + referencePrototype.setProperty(runtime, "constructor", referenceConstructor); + referenceConstructor.setProperty(runtime, "prototype", referencePrototype); + installInteropHasInstance(runtime, referenceConstructor, "reference"); + referenceConstructor.setProperty(runtime, "kind", makeString(runtime, "reference")); + referenceConstructor.setProperty(runtime, "sizeof", static_cast(sizeof(void*))); + interop.setProperty(runtime, "Reference", referenceConstructor); + + interop.setProperty( + runtime, "sizeof", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "sizeof"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1) { + throw JSError(runtime, "sizeof expects a type."); + } + return static_cast(sizeofInteropType(runtime, bridge, args[0])); + })); + + interop.setProperty( + runtime, "alloc", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "alloc"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isNumber()) { + throw JSError(runtime, "alloc expects a byte size."); + } + size_t size = static_cast(std::max(0, args[0].getNumber())); + return createPointer(runtime, bridge, calloc(1, size), false); + })); + + interop.setProperty( + runtime, "free", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "free"), 1, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + return Value::undefined(); + } + Object object = args[0].asObject(runtime); + if (!object.isHostObject(runtime)) { + return Value::undefined(); + } + auto pointer = object.getHostObject(runtime); + void* raw = pointer->pointer(); + if (raw != nullptr) { + free(raw); + pointer->clearWithoutFree(); + } + return Value::undefined(); + })); + + interop.setProperty( + runtime, "adopt", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "adopt"), 1, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + throw JSError(runtime, "adopt expects a Pointer."); + } + Object object = args[0].asObject(runtime); + if (!object.isHostObject(runtime)) { + throw JSError(runtime, "adopt expects a Pointer."); + } + object.getHostObject(runtime)->adopt(); + return Value(runtime, object); + })); + + interop.setProperty( + runtime, "handleof", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "handleof"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || args[0].isNull() || args[0].isUndefined()) { + return Value::null(); + } + if (args[0].isString()) { + std::string utf8 = args[0].asString(runtime).utf8(runtime); + char* data = strdup(utf8.c_str()); + return createPointer(runtime, bridge, data); + } + if (!args[0].isObject()) { + return Value::null(); + } + Object object = args[0].asObject(runtime); + if (object.isHostObject(runtime)) { + return Value(runtime, object); + } + if (object.isHostObject(runtime)) { + auto reference = object.getHostObject(runtime); + void* data = reference->data(); + if (data == nullptr) { + throw JSError(runtime, "Cannot get handle of empty Reference."); + } + std::shared_ptr backingValue; + if (reference->backingValue() != nullptr && + nativeTypeStoresObjectiveCObject(reference->type())) { + backingValue = reference->backingValue(); + } + return createPointer(runtime, bridge, data, false, std::move(backingValue)); + } + if (object.isHostObject(runtime)) { + auto structObject = object.getHostObject(runtime); + if (structObject->backingValue() != nullptr) { + return Value(runtime, *structObject->backingValue()); + } + return createPointer(runtime, bridge, structObject->data()); + } + if (object.isHostObject(runtime)) { + id nativeObject = object.getHostObject(runtime)->object(); + return createPointer(runtime, bridge, nativeObject, false, + std::make_shared(runtime, args[0])); + } + if (Class cls = nativeClassFromEngineObject(runtime, object)) { + return createPointer(runtime, bridge, cls); + } + if (object.isHostObject(runtime)) { + return createPointer( + runtime, bridge, + object.getHostObject(runtime)->nativeProtocol()); + } + if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { + return createPointer(runtime, bridge, symbolPointer); + } + void* nativePointer = nullptr; + if (readNativePointerProperty(runtime, object, &nativePointer)) { + return createPointer(runtime, bridge, nativePointer); + } + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString()) { + std::string kind = kindValue.asString(runtime).utf8(runtime); + if (kind == "block" || kind == "functionPointer" || kind == "functionReference") { + throw JSError(runtime, "Cannot get handle of uninitialized native callback."); + } + } + Value nativeName = object.getProperty(runtime, "nativeName"); + if (nativeName.isString()) { + std::string name = nativeName.asString(runtime).utf8(runtime); + void* symbol = dlsym(bridge->selfDl(), name.c_str()); + if (symbol != nullptr) { + return createPointer(runtime, bridge, symbol); + } + } + return Value::null(); + })); + + interop.setProperty( + runtime, "object", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "object"), 1, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || args[0].isNull() || args[0].isUndefined()) { + return Value::null(); + } + + void* pointer = nullptr; + if (args[0].isString()) { + uintptr_t address = 0; + if (!parseIntegerTextToUintptr(args[0].asString(runtime).utf8(runtime), + &address)) { + throw JSError(runtime, + "interop.object expects an Objective-C object pointer."); + } + pointer = reinterpret_cast(address); + } else { + NativeApiArgumentFrame frame(1); + pointer = pointerFromEngineValue(runtime, bridge, args[0], frame); + } + + if (pointer == nullptr) { + return Value::null(); + } + + id object = static_cast(pointer); + NativeApiType type = nativeObjectReturnTypeForClass(object_getClass(object)); + return convertNativeReturnValue(runtime, bridge, type, &object); + })); + + interop.setProperty( + runtime, "stringFromCString", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "stringFromCString"), 2, + [bridge](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || args[0].isNull() || args[0].isUndefined()) { + return Value::null(); + } + NativeApiArgumentFrame frame(1); + const char* data = + static_cast(pointerFromEngineValue(runtime, bridge, args[0], frame)); + if (data == nullptr) { + return Value::null(); + } + if (count > 1 && args[1].isNumber()) { + size_t length = static_cast(std::max(0, args[1].getNumber())); + return String::createFromUtf8(runtime, reinterpret_cast(data), + length); + } + return makeString(runtime, data); + })); + + interop.setProperty( + runtime, "bufferFromData", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "bufferFromData"), 1, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isObject()) { + throw JSError(runtime, "Invalid data."); + } + Object object = args[0].asObject(runtime); + if (object.isArrayBuffer(runtime)) { + return Value(runtime, object); + } + id native = nil; + if (object.isHostObject(runtime)) { + native = object.getHostObject(runtime)->object(); + } else if (object.isHostObject(runtime)) { + native = static_cast( + object.getHostObject(runtime)->pointer()); + } + if (native == nil || ![native isKindOfClass:[NSData class]]) { + throw JSError(runtime, "Invalid data."); + } + NSData* data = static_cast(native); + return ArrayBuffer(runtime, std::make_shared( + data.bytes, static_cast(data.length))); + })); + + interop.setProperty( + runtime, "addMethod", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "addMethod"), 2, + [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + throw JSError(runtime, "interop.addMethod requires the Engine class builder layer."); + })); + interop.setProperty( + runtime, "addProtocol", + Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "addProtocol"), 2, + [](Runtime& runtime, const Value&, const Value* args, size_t count) -> Value { + if (count < 2) { + throw JSError(runtime, "interop.addProtocol expects class and protocol."); + } + Class cls = classFromEngineValue(runtime, args[0]); + Protocol* protocol = protocolFromEngineValue(runtime, args[1]); + if (cls == Nil || protocol == nullptr) { + return false; + } + return class_addProtocol(cls, protocol); + })); + + return interop; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm new file mode 100644 index 000000000..e74c4032b --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Class.mm @@ -0,0 +1,501 @@ +class NativeApiClassHostObject final : public HostObject { + public: + NativeApiClassHostObject(std::shared_ptr bridge, + NativeApiSymbol symbol) + : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} + + Class nativeClass() const { + return objc_lookUpClass(symbol_.runtimeName.c_str()); + } + + static Class classRespondingToClassSelector(Class cls, SEL selector) { + for (Class current = cls; current != Nil; + current = class_getSuperclass(current)) { + if (class_getClassMethod(current, selector) != nullptr) { + return current; + } + } + return Nil; + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "class"); + } + if (property == "name") { + return makeString(runtime, symbol_.name); + } + if (property == "runtimeName") { + return makeString(runtime, symbol_.runtimeName); + } + if (property == "available") { + return objc_lookUpClass(symbol_.runtimeName.c_str()) != nil; + } + if (property == "metadataOffset") { + return static_cast(symbol_.offset); + } + if (property == "__superclass") { + if (symbol_.superclassOffset == MD_SECTION_OFFSET_NULL) { + return Value::undefined(); + } + const NativeApiSymbol* superclass = + bridge_->findClassByOffset(symbol_.superclassOffset); + if (superclass == nullptr) { + return Value::undefined(); + } + return makeNativeClassValue(runtime, bridge_, *superclass); + } + if (property == "__runtimeStaticMembers" || + property == "__runtimeInstanceMembers") { + return runtimeMembersArray(runtime, nativeClass(), + property == "__runtimeStaticMembers"); + } + if (property == "__staticMembers" || property == "__instanceMembers") { + bool staticMembers = property == "__staticMembers"; + const auto& members = bridge_->surfaceMembersForClass(symbol_); + Array result(runtime, members.size()); + size_t index = 0; + for (const auto& member : members) { + bool memberIsStatic = + (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic != staticMembers) { + continue; + } + Object descriptor(runtime); + descriptor.setProperty(runtime, "name", makeString(runtime, member.name)); + descriptor.setProperty(runtime, "selectorName", + makeString(runtime, member.selectorName)); + descriptor.setProperty( + runtime, "argumentCount", + static_cast(selectorArgumentCount(member.selectorName))); + descriptor.setProperty(runtime, "property", member.property); + descriptor.setProperty(runtime, "readonly", member.readonly); + descriptor.setProperty(runtime, "signatureOffset", + static_cast(member.signatureOffset)); + descriptor.setProperty( + runtime, "setterSignatureOffset", + static_cast(member.setterSignatureOffset)); + descriptor.setProperty(runtime, "flags", + static_cast(member.flags)); + descriptor.setProperty(runtime, "setterSelectorName", + makeString(runtime, member.setterSelectorName)); + result.setValueAtIndex(runtime, index++, descriptor); + } + Array compact(runtime, index); + for (size_t i = 0; i < index; i++) { + compact.setValueAtIndex(runtime, i, result.getValueAtIndex(runtime, i)); + } + return compact; + } + if (property == "toString") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [symbol = symbol_](Runtime& runtime, const Value&, + const Value*, size_t) -> Value { + return makeString(runtime, + "[NativeApiClass " + symbol.name + "]"); + }); + } + if (property == "construct" || property == "alloc" || property == "new") { + auto bridge = bridge_; + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property), 0, + [bridge, symbol, property](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol.name); + } + + id result = nil; + if (property == "construct" && count == 1) { + void* pointer = nullptr; + if (args[0].isNumber()) { + pointer = reinterpret_cast( + static_cast(args[0].getNumber())); + } else if (args[0].isObject()) { + Object object = args[0].asObject(runtime); + if (object.isHostObject(runtime)) { + auto pointerHost = + object.getHostObject( + runtime); + pointer = pointerHost->pointer(); + if (pointerHost->backingValue() != nullptr) { + Value backingValue(runtime, *pointerHost->backingValue()); + id backingObject = + NativeApiObjectHostObject::nativeObjectFromValue( + runtime, backingValue); + if (backingObject == static_cast(pointer) && + backingObject != nil && + [backingObject isKindOfClass:cls]) { + return backingValue; + } + } + } else if (object.isHostObject( + runtime)) { + auto referenceHost = + object.getHostObject( + runtime); + pointer = referenceHost->data(); + if (referenceHost->backingValue() != nullptr) { + Value backingValue(runtime, *referenceHost->backingValue()); + id backingObject = + NativeApiObjectHostObject::nativeObjectFromValue( + runtime, backingValue); + if (backingObject == static_cast(pointer) && + backingObject != nil && + [backingObject isKindOfClass:cls]) { + return backingValue; + } + } + } else if (object.isHostObject( + runtime)) { + pointer = object + .getHostObject( + runtime) + ->object(); + } + } + return makeNativeObjectValue(runtime, bridge, + static_cast(pointer), false); + } + + if (property == "new") { + if (count != 0) { + throw JSError( + runtime, "new does not take arguments; use invoke for an " + "explicit Objective-C selector."); + } + performDirectObjCInvocation(runtime, + [&]() { result = [[cls alloc] init]; }); + } else { + if (count != 0) { + throw JSError( + runtime, "alloc does not take arguments; call invoke on the " + "allocated object for an explicit init selector."); + } + performDirectObjCInvocation(runtime, + [&]() { result = [cls alloc]; }); + } + + return makeNativeObjectValue(runtime, bridge, result, true); + }); + } + if (property == "invoke" || property == "send") { + auto bridge = bridge_; + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, symbol](Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + std::string selectorName = + readStringArg(runtime, args, count, 0, "selector"); + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol.name); + } + return callObjCSelector(runtime, bridge, static_cast(cls), true, + selectorName, nullptr, args + 1, + count - 1); + }); + } + + Class cls = nativeClass(); + if (cls != Nil) { + Value expando = bridge_->findObjectExpando(runtime, cls, property); + if (!expando.isUndefined()) { + return expando; + } + } + + const auto& members = bridge_->membersForClass(symbol_); + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, property, true)) { + auto bridge = bridge_; + auto symbol = symbol_; + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol.name); + } + SEL selector = sel_getUid(propertyMember->selectorName.c_str()); + Class dispatchClass = classRespondingToClassSelector(cls, selector); + if (dispatchClass != Nil) { + return callObjCSelector(runtime, bridge, static_cast(dispatchClass), true, + propertyMember->selectorName, propertyMember, + nullptr, 0); + } + } + + auto selectors = selectorGroupEntriesForMethod(members, property, true); + if (selectors != nullptr) { + if (cls == Nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol_.name); + } + auto preparedInvocations = std::make_shared>>(selectors->size()); + Value methodFunction = CreateNativeApiSelectorGroupFunction( + runtime, bridge_, cls, true, selectors, preparedInvocations); + bridge_->setObjectExpando(runtime, cls, property, methodFunction); + return methodFunction; + } + + return Value::undefined(); + } + + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override { + std::string property = name.utf8(runtime); + Class cls = objc_lookUpClass(symbol_.runtimeName.c_str()); + if (cls == nil) { + throw JSError( + runtime, "Objective-C class is not available: " + symbol_.name); + } + + const auto& members = bridge_->membersForClass(symbol_); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, true)) { + if (propertyMember->readonly) { + throw JSError( + runtime, "Attempted to assign to readonly property."); + } + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + SEL selector = sel_getUid(setterMember.selectorName.c_str()); + Class dispatchClass = classRespondingToClassSelector(cls, selector); + if (dispatchClass == Nil) { + throw JSError(runtime, + "Objective-C selector is not available: " + + setterMember.selectorName); + } + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, static_cast(dispatchClass), true, + setterMember.selectorName, &setterMember, args, 1); + NATIVE_API_SET_RETURN(true); + } + + throw JSError(runtime, + "No writable native property: " + property); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(8); + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "name"); + addPropertyName(runtime, names, "runtimeName"); + addPropertyName(runtime, names, "available"); + addPropertyName(runtime, names, "metadataOffset"); + addPropertyName(runtime, names, "toString"); + addPropertyName(runtime, names, "construct"); + addPropertyName(runtime, names, "alloc"); + addPropertyName(runtime, names, "new"); + addPropertyName(runtime, names, "invoke"); + addPropertyName(runtime, names, "send"); + return names; + } + + private: + std::shared_ptr bridge_; + NativeApiSymbol symbol_; +}; + +Value makeNativeObjectValue(Runtime& runtime, + const std::shared_ptr& bridge, + id object, bool ownsObject) { + if (object == nil) { + return Value::null(); + } + + Value cached = bridge->findRoundTripValue(runtime, object, nullptr, true); + if (!cached.isUndefined()) { + // A consumed wrapper (e.g. an alloc'd placeholder singleton already passed + // to an initializer) must not be reused: drop the stale entry and re-wrap. + auto cachedHost = + cached.isObject() + ? cached.asObject(runtime).getHostObject(runtime) + : nullptr; + if (cachedHost != nullptr && cachedHost->object() != nil) { + if (ownsObject) { + [object release]; + } + return cached; + } + bridge->forgetRoundTripValue(runtime, object); + } + + Object result = createNativeInstanceHostObject( + runtime, + std::make_shared(bridge, object, ownsObject)); + Value prototypeValue = Value::undefined(); + Value classWrapperValue = + bridge->findObjectExpando(runtime, object, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + Object classWrapper = classWrapperValue.asObject(runtime); + prototypeValue = classWrapper.getProperty(runtime, "prototype"); + } + if (!prototypeValue.isObject()) { + prototypeValue = bridge->findClassPrototype(runtime, object_getClass(object)); + } + if (!prototypeValue.isObject()) { + Value classWrapper = makeNativeClassValue( + runtime, bridge, + nativeApiSymbolForRuntimeClass(bridge, object_getClass(object))); + if (classWrapper.isObject()) { + prototypeValue = + classWrapper.asObject(runtime).getProperty(runtime, "prototype"); + } + } + if (prototypeValue.isObject()) { + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, result, prototype); + } + bridge->rememberScopedRoundTripValue( + runtime, object, Value(runtime, result), + nativeObjectIsStringLike(object)); + return result; +} + +Value globalNativeSymbolValue(Runtime& runtime, const NativeApiSymbol& symbol, + const char* expectedKind) { + Object global = runtime.global(); + Value cacheValue = global.getProperty( + runtime, "__nativeScriptNativeApiGlobalCache"); + if (!cacheValue.isObject()) { + return Value::undefined(); + } + + Object cache = cacheValue.asObject(runtime); + auto readCache = [&](const std::string& name) -> Value { + if (name.empty()) { + return Value::undefined(); + } + + Value value = cache.getProperty(runtime, name.c_str()); + if (!value.isObject()) { + return Value::undefined(); + } + + try { + Object object = value.asObject(runtime); + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString() && + kindValue.asString(runtime).utf8(runtime) == expectedKind) { + return value; + } + } catch (const std::exception&) { + } + + return Value::undefined(); + }; + + Value value = readCache(symbol.name); + if (!value.isUndefined()) { + return value; + } + if (symbol.runtimeName != symbol.name) { + value = readCache(symbol.runtimeName); + if (!value.isUndefined()) { + return value; + } + } + + try { + if (std::strcmp(expectedKind, "class") == 0) { + Value classResolverValue = global.getProperty( + runtime, "__nativeScriptResolveNativeApiClassWrapper"); + if (classResolverValue.isObject() && + classResolverValue.asObject(runtime).isFunction(runtime)) { + Function classResolver = + classResolverValue.asObject(runtime).asFunction(runtime); + auto resolveClassWrapper = [&](const std::string& name) -> Value { + if (name.empty()) { + return Value::undefined(); + } + Value resolved = classResolver.call(runtime, makeString(runtime, name)); + return resolved.isObject() ? std::move(resolved) : Value::undefined(); + }; + + value = resolveClassWrapper(symbol.name); + if (!value.isUndefined()) { + return value; + } + if (symbol.runtimeName != symbol.name) { + value = resolveClassWrapper(symbol.runtimeName); + if (!value.isUndefined()) { + return value; + } + } + } + } + + Value resolverValue = + global.getProperty(runtime, "__nativeScriptResolveNativeApiGlobal"); + if (resolverValue.isObject() && + resolverValue.asObject(runtime).isFunction(runtime)) { + Function resolver = resolverValue.asObject(runtime).asFunction(runtime); + auto resolveGlobal = [&](const std::string& name) -> Value { + if (name.empty()) { + return Value::undefined(); + } + Value resolved = resolver.call(runtime, makeString(runtime, name), + makeString(runtime, expectedKind)); + if (resolved.isObject()) { + return resolved; + } + return Value::undefined(); + }; + + value = resolveGlobal(symbol.name); + if (!value.isUndefined()) { + return value; + } + if (symbol.runtimeName != symbol.name) { + value = resolveGlobal(symbol.runtimeName); + if (!value.isUndefined()) { + return value; + } + } + } + } catch (const std::exception&) { + } + + return Value::undefined(); +} + +Value makeNativeClassValue(Runtime& runtime, + const std::shared_ptr& bridge, + NativeApiSymbol symbol) { + Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); + Value cachedClass = bridge->findClassValue(runtime, cls); + if (!cachedClass.isUndefined()) { + return cachedClass; + } + Value globalValue = globalNativeSymbolValue(runtime, symbol, "class"); + if (!globalValue.isUndefined()) { + return globalValue; + } + return Object::createFromHostObject( + runtime, + std::make_shared(bridge, std::move(symbol))); +} + +Protocol* lookupProtocolByNativeName(const std::string& name) { + Protocol* protocol = objc_getProtocol(name.c_str()); + if (protocol != nullptr) { + return protocol; + } + constexpr const char* suffix = "Protocol"; + size_t suffixLength = std::strlen(suffix); + if (name.size() > suffixLength && + name.compare(name.size() - suffixLength, suffixLength, suffix) == 0) { + protocol = objc_getProtocol( + name.substr(0, name.size() - suffixLength).c_str()); + } + return protocol; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm new file mode 100644 index 000000000..5913da68d --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Interop.mm @@ -0,0 +1,225 @@ +class NativeApiPointerHostObject final + : public HostObject, + public std::enable_shared_from_this { + public: + NativeApiPointerHostObject(std::shared_ptr bridge, + void* pointer, std::string kind = "pointer", + bool adopted = false, + std::shared_ptr backingValue = nullptr) + : bridge_(std::move(bridge)), + pointer_(pointer), + kind_(std::move(kind)), + adopted_(adopted), + backingValue_(std::move(backingValue)) {} + + ~NativeApiPointerHostObject() override { + if (adopted_ && pointer_ != nullptr) { + if (bridge_ != nullptr) { + bridge_->forgetPointerValue(pointer_); + } + free(pointer_); + pointer_ = nullptr; + } + } + + void* pointer() const { return pointer_; } + std::shared_ptr backingValue() const { return backingValue_; } + void setBackingValue(Runtime& runtime, const Value& value) { + backingValue_ = std::make_shared(runtime, value); + } + bool adopted() const { return adopted_; } + void adopt() { adopted_ = true; } + void clearWithoutFree() { + if (bridge_ != nullptr) { + bridge_->forgetPointerValue(pointer_); + } + pointer_ = nullptr; + adopted_ = false; + backingValue_.reset(); + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, kind_); + } + if (property == "address") { + return static_cast(reinterpret_cast(pointer_)); + } + if (property == "adopted") { + return adopted_; + } + if (property == "takeRetainedValue" || property == "takeUnretainedValue") { + bool retained = property == "takeRetainedValue"; + std::weak_ptr weakSelf = shared_from_this(); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [weakSelf, retained](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + auto self = weakSelf.lock(); + if (!self || self->pointer_ == nullptr || self->consumed_) { + throw JSError(runtime, "Unmanaged value has already been consumed."); + } + id object = static_cast(self->pointer_); + self->consumed_ = true; + self->pointer_ = nullptr; + self->adopted_ = false; + self->backingValue_.reset(); + return makeNativeObjectValue(runtime, self->bridge_, object, retained); + }); + } + if (property == "add" || property == "subtract") { + void* pointer = pointer_; + bool add = property == "add"; + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, pointer, add](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + if (count < 1 || !args[0].isNumber()) { + throw JSError(runtime, "Pointer offset must be a number."); + } + intptr_t offset = static_cast(args[0].getNumber()); + intptr_t base = reinterpret_cast(pointer); + void* result = reinterpret_cast(add ? base + offset : base - offset); + return createPointer(runtime, bridge, result); + }); + } + if (property == "toNumber") { + void* pointer = pointer_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toNumber"), 0, + [pointer](Runtime&, const Value&, const Value*, size_t) -> Value { + return static_cast(reinterpret_cast(pointer)); + }); + } + if (property == "toBigInt") { + void* pointer = pointer_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toBigInt"), 0, + [pointer](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return BigInt::fromUint64( + runtime, + static_cast(reinterpret_cast(pointer))); + }); + } + if (property == "toHexString" || property == "toDecimalString") { + void* pointer = pointer_; + bool hex = property == "toHexString"; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [pointer, hex](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + if (hex) { + char text[2 + sizeof(uintptr_t) * 2 + 1] = {}; + snprintf(text, sizeof(text), "0x%llx", + static_cast( + reinterpret_cast(pointer))); + return makeString(runtime, text); + } else { + char text[32] = {}; + snprintf(text, sizeof(text), "%lld", + static_cast(reinterpret_cast(pointer))); + return makeString(runtime, text); + } + }); + } + if (property == "toString") { + void* pointer = pointer_; + std::string kind = kind_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [pointer, kind](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", pointer); + if (kind == "pointer") { + return makeString(runtime, + ""); + } + return makeString(runtime, "[NativeApi " + kind + " " + + std::string(address) + "]"); + }); + } + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(3); + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "address"); + addPropertyName(runtime, names, "adopted"); + addPropertyName(runtime, names, "takeRetainedValue"); + addPropertyName(runtime, names, "takeUnretainedValue"); + addPropertyName(runtime, names, "add"); + addPropertyName(runtime, names, "subtract"); + addPropertyName(runtime, names, "toNumber"); + addPropertyName(runtime, names, "toBigInt"); + addPropertyName(runtime, names, "toHexString"); + addPropertyName(runtime, names, "toDecimalString"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + void* pointer_ = nullptr; + std::string kind_; + bool adopted_ = false; + bool consumed_ = false; + std::shared_ptr backingValue_; +}; + +class NativeApiReferenceHostObject final : public HostObject { + public: + NativeApiReferenceHostObject(std::shared_ptr bridge, + NativeApiType type, void* data, bool ownsData, + size_t byteLength = 0, + std::shared_ptr pendingValue = nullptr, + std::shared_ptr backingValue = nullptr) + : bridge_(std::move(bridge)), + type_(std::move(type)), + data_(data), + ownsData_(ownsData), + byteLength_(byteLength), + pendingValue_(std::move(pendingValue)), + backingValue_(std::move(backingValue)) {} + + ~NativeApiReferenceHostObject() override { + for (id object : retainedObjects_) { + [object release]; + } + if (ownsData_ && data_ != nullptr) { + free(data_); + data_ = nullptr; + } + } + + void* data() const { return data_; } + const NativeApiType& type() const { return type_; } + std::shared_ptr backingValue() const { return backingValue_; } + void ensureStorage(Runtime& runtime, NativeApiType type, + NativeApiArgumentFrame& frame, size_t elements = 1); + void retainObjectSlot(size_t index, id object); + + Value get(Runtime& runtime, const PropNameID& name) override; + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override; + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "value"); + addPropertyName(runtime, names, "address"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + NativeApiType type_; + void* data_ = nullptr; + bool ownsData_ = false; + size_t byteLength_ = 0; + std::shared_ptr pendingValue_; + std::shared_ptr backingValue_; + std::vector retainedObjects_; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm new file mode 100644 index 000000000..2fd5b80be --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Object.mm @@ -0,0 +1,1380 @@ +class NativeApiFastEnumerationIteratorHostObject final : public HostObject { + public: + NativeApiFastEnumerationIteratorHostObject( + std::shared_ptr bridge, id collection) + : bridge_(std::move(bridge)), collection_(collection) { + [(id)collection_ retain]; + } + + ~NativeApiFastEnumerationIteratorHostObject() override { + [(id)collection_ release]; + collection_ = nil; + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "next") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "next"), 0, + [this](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return next(runtime); + }); + } + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "next"); + return names; + } + + private: + Value next(Runtime& runtime) { + Object result(runtime); + if (done_ || collection_ == nil) { + result.setProperty(runtime, "done", true); + return result; + } + + if (stackIndex_ >= stackLength_) { + stackLength_ = [collection_ countByEnumeratingWithState:&state_ + objects:stack_ + count:16]; + stackIndex_ = 0; + if (stackLength_ == 0) { + done_ = true; + result.setProperty(runtime, "done", true); + return result; + } + } + + id value = state_.itemsPtr[stackIndex_++]; + NativeApiType valueType = nativeObjectReturnTypeForClass(object_getClass(value)); + result.setProperty(runtime, "value", + convertNativeReturnValue(runtime, bridge_, valueType, &value)); + result.setProperty(runtime, "done", false); + return result; + } + + std::shared_ptr bridge_; + id collection_ = nil; + NSFastEnumerationState state_ = {}; + id __unsafe_unretained stack_[16] = {}; + NSUInteger stackLength_ = 0; + NSUInteger stackIndex_ = 0; + bool done_ = false; +}; + +NativeApiSymbol nativeApiSymbolForRuntimeClass( + const std::shared_ptr& bridge, Class cls) { + const char* name = cls != Nil ? class_getName(cls) : ""; + if (bridge != nullptr) { + if (const NativeApiSymbol* symbol = bridge->findClassForRuntimePointer(cls)) { + return *symbol; + } + if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { + return *symbol; + } + if (name != nullptr) { + if (const NativeApiSymbol* symbol = bridge->findClass(name)) { + return *symbol; + } + } + } + + return NativeApiSymbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; +} + +std::optional runtimeWritablePropertySetter(id object, + const std::string& property) { + if (object == nil || property.empty()) { + return std::nullopt; + } + + Class current = object_getClass(object); + while (current != Nil) { + objc_property_t prop = class_getProperty(current, property.c_str()); + if (prop != nullptr) { + if (char* readonly = property_copyAttributeValue(prop, "R")) { + free(readonly); + return std::nullopt; + } + + std::string setter = setterSelectorForProperty(property); + if (char* customSetter = property_copyAttributeValue(prop, "S")) { + setter = customSetter; + free(customSetter); + } + + SEL selector = sel_getUid(setter.c_str()); + if ([object respondsToSelector:selector]) { + return setter; + } + } + + current = class_getSuperclass(current); + } + + std::string setter = setterSelectorForProperty(property); + SEL selector = sel_getUid(setter.c_str()); + if ([object respondsToSelector:selector]) { + return setter; + } + + return std::nullopt; +} + +std::optional runtimeReadablePropertyGetter(id object, + const std::string& property) { + if (object == nil || property.empty()) { + return std::nullopt; + } + + Class current = object_getClass(object); + while (current != Nil) { + objc_property_t prop = class_getProperty(current, property.c_str()); + if (prop != nullptr) { + std::string getter = property; + if (char* customGetter = property_copyAttributeValue(prop, "G")) { + getter = customGetter; + free(customGetter); + } + + if (auto selector = + respondingPropertyGetterSelector(object, property, getter)) { + return selector; + } + } + + current = class_getSuperclass(current); + } + + return respondingPropertyGetterSelector(object, property, property); +} + +class NativeApiSuperHostObject final : public HostObject { + public: + NativeApiSuperHostObject(std::shared_ptr bridge, + id receiver, Class dispatchClass) + : bridge_(std::move(bridge)), + receiver_(receiver), + dispatchClass_(dispatchClass) { + if (receiver_ != nil) { + [receiver_ retain]; + } + } + + ~NativeApiSuperHostObject() override { + if (receiver_ != nil) { + [receiver_ release]; + receiver_ = nil; + } + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "super"); + } + if (property == "toString") { + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return makeString(runtime, "[NativeApiSuper]"); + }); + } + if (receiver_ == nil || dispatchClass_ == Nil) { + return Value::undefined(); + } + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(dispatchClass_)) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, false)) { + SEL selector = sel_getUid(propertyMember->selectorName.c_str()); + if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { + return callObjCSelector(runtime, bridge_, receiver_, false, + propertyMember->selectorName, propertyMember, + nullptr, 0, dispatchClass_); + } + } + + if (hasMethodMember(members, property, false)) { + auto bridge = bridge_; + id receiver = receiver_; + Class dispatchClass = dispatchClass_; + std::string memberName = property; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [bridge, receiver, dispatchClass, memberName]( + Runtime& runtime, const Value&, const Value* args, + size_t count) -> Value { + const NativeApiSymbol* symbol = + bridge->findClassForRuntimeClass(dispatchClass); + if (symbol == nullptr) { + throw JSError( + runtime, "Objective-C metadata is not available for super."); + } + const NativeApiMember* selected = selectMethodMember( + bridge->membersForClass(*symbol), memberName, false, count); + if (selected == nullptr) { + throw JSError( + runtime, "Objective-C super selector is not available: " + + memberName); + } + return callObjCSelector(runtime, bridge, receiver, false, + selected->selectorName, selected, args, + count, dispatchClass); + }); + } + } + + return Value::undefined(); + } + + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override { + std::string property = name.utf8(runtime); + if (receiver_ == nil || dispatchClass_ == Nil) { + throw JSError(runtime, "Cannot set property on nil super."); + } + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(dispatchClass_)) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, property, false)) { + if (propertyMember->readonly || + propertyMember->setterSelectorName.empty()) { + throw JSError( + runtime, "Attempted to assign to readonly property."); + } + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, receiver_, false, + setterMember.selectorName, &setterMember, args, 1, + dispatchClass_); + NATIVE_API_SET_RETURN(true); + } + } + + std::string setterSelectorName = setterSelectorForProperty(property); + SEL selector = sel_getUid(setterSelectorName.c_str()); + if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, receiver_, false, setterSelectorName, + nullptr, args, 1, dispatchClass_); + NATIVE_API_SET_RETURN(true); + } + + throw JSError(runtime, + "No writable native super property: " + + property); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + id receiver_ = nil; + Class dispatchClass_ = Nil; +}; + +struct NativeApiRuntimeMember { + std::string name; + std::string selectorName; + size_t argumentCount = 0; +}; + +using NativeApiRuntimeMembers = std::vector; + +struct NativeApiRuntimeMemberIndex { + NativeApiRuntimeMembers members; + std::unordered_set memberNames; + std::unordered_map> + selectorsByNameAndCount; +}; + +struct NativeApiRuntimeMembersCacheKey { + Class cls = Nil; + bool staticMembers = false; + + bool operator==(const NativeApiRuntimeMembersCacheKey& other) const { + return cls == other.cls && staticMembers == other.staticMembers; + } +}; + +struct NativeApiRuntimeMembersCacheKeyHash { + size_t operator()(const NativeApiRuntimeMembersCacheKey& key) const { + size_t classHash = std::hash{}(reinterpret_cast(key.cls)); + return classHash ^ (key.staticMembers ? 0x9e3779b97f4a7c15ULL : 0); + } +}; + +std::mutex& runtimeMembersCacheMutex() { + static std::mutex mutex; + return mutex; +} + +std::unordered_map, + NativeApiRuntimeMembersCacheKeyHash>& +runtimeMembersCache() { + static std::unordered_map, + NativeApiRuntimeMembersCacheKeyHash> + cache; + return cache; +} + +std::shared_ptr emptyRuntimeMembers() { + static auto empty = std::make_shared(); + return empty; +} + +NativeApiRuntimeMemberIndex buildRuntimeMembersForClass(Class cls, + bool staticMembers) { + NativeApiRuntimeMemberIndex index; + if (cls == Nil) { + return index; + } + + std::unordered_set seen; + Class current = staticMembers ? object_getClass(cls) : cls; + while (current != Nil) { + unsigned int methodCount = 0; + Method* methods = class_copyMethodList(current, &methodCount); + for (unsigned int i = 0; i < methodCount; i++) { + SEL selector = method_getName(methods[i]); + const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorName == nullptr || selectorName[0] == '\0') { + continue; + } + + std::string selectorString(selectorName); + std::string name = jsifySelector(selectorString.c_str()); + if (name.empty()) { + continue; + } + + size_t argumentCount = selectorArgumentCount(selectorString); + std::string key = name + "\x1f" + std::to_string(argumentCount); + if (!seen.insert(key).second) { + continue; + } + + index.memberNames.insert(name); + index.selectorsByNameAndCount[name].emplace(argumentCount, selectorString); + index.members.push_back(NativeApiRuntimeMember{ + .name = std::move(name), + .selectorName = std::move(selectorString), + .argumentCount = argumentCount, + }); + } + if (methods != nullptr) { + free(methods); + } + current = class_getSuperclass(current); + } + + return index; +} + +std::shared_ptr runtimeMembersForClass( + Class cls, bool staticMembers) { + if (cls == Nil) { + return emptyRuntimeMembers(); + } + + NativeApiRuntimeMembersCacheKey key{.cls = cls, + .staticMembers = staticMembers}; + + { + std::lock_guard lock(runtimeMembersCacheMutex()); + auto& cache = runtimeMembersCache(); + auto cached = cache.find(key); + if (cached != cache.end()) { + return cached->second; + } + } + + auto members = + std::make_shared( + buildRuntimeMembersForClass(cls, staticMembers)); + + { + std::lock_guard lock(runtimeMembersCacheMutex()); + auto& cache = runtimeMembersCache(); + auto [cached, inserted] = cache.emplace(key, members); + return inserted ? members : cached->second; + } +} + +bool hasRuntimeMemberForName(Class cls, bool staticMembers, + const std::string& name) { + auto index = runtimeMembersForClass(cls, staticMembers); + return index->memberNames.find(name) != index->memberNames.end(); +} + +std::optional selectRuntimeSelectorForName( + Class cls, bool staticMembers, const std::string& name, size_t count) { + auto index = runtimeMembersForClass(cls, staticMembers); + auto selectorsForName = index->selectorsByNameAndCount.find(name); + if (selectorsForName == index->selectorsByNameAndCount.end()) { + return std::nullopt; + } + auto selector = selectorsForName->second.find(count); + if (selector == selectorsForName->second.end()) { + return std::nullopt; + } + return selector->second; +} + +Array runtimeMembersArray(Runtime& runtime, Class cls, bool staticMembers) { + auto index = runtimeMembersForClass(cls, staticMembers); + Array result(runtime, index->members.size()); + for (size_t i = 0; i < index->members.size(); i++) { + const auto& member = index->members[i]; + Object descriptor(runtime); + descriptor.setProperty(runtime, "name", makeString(runtime, member.name)); + descriptor.setProperty(runtime, "selectorName", + makeString(runtime, member.selectorName)); + descriptor.setProperty(runtime, "argumentCount", + static_cast(member.argumentCount)); + descriptor.setProperty(runtime, "property", false); + descriptor.setProperty(runtime, "readonly", false); + descriptor.setProperty(runtime, "setterSelectorName", makeString(runtime, "")); + result.setValueAtIndex(runtime, i, descriptor); + } + return result; +} + +class NativeApiObjectHostObject final + : public HostObject, + public std::enable_shared_from_this { + public: + NativeApiObjectHostObject(std::shared_ptr bridge, + id object, bool ownsObject) + : bridge_(std::move(bridge)), + object_(object), + ownsObject_(ownsObject), + lifetimeState_(std::make_shared(object)) { + if (bridge_ != nullptr && object_ != nil) { + bridge_->retainObjectExpandoOwner(object_); + } + if (object_ != nil && !ownsObject_) { + [object_ retain]; + ownsObject_ = true; + wrapperRetainedObject_ = true; + } + } + + ~NativeApiObjectHostObject() override { + if (bridge_ != nullptr && object_ != nil) { + bridge_->forgetRoundTripValue(object_); + bridge_->releaseObjectExpandoOwner( + object_, class_conformsToProtocol(object_getClass(object_), + @protocol(NativeApiClassBuilderProtocol))); + } + if (lifetimeState_ != nullptr) { + lifetimeState_->clear(); + } + if (ownsObject_ && object_ != nil) { + [object_ release]; + object_ = nil; + } + } + + id object() const { return object_; } + std::shared_ptr lifetimeState() const { + return lifetimeState_; + } + + // Store a JS-owned property as a bridge expando (read back by get()). Used by + // engine adapters whose exotic property storage doesn't fall back to own + // properties when the host set handler defers. + void storeOwnExpando(Runtime& runtime, const std::string& property, + const Value& value) { + if (object_ != nil) { + bridge_->setObjectExpando(runtime, object_, property, value); + } + } + + void disownObject(id expected, bool preserveExpandos = false) { + if (object_ == expected) { + if (bridge_ != nullptr && expected != nil) { + bridge_->forgetRoundTripValue(expected); + bridge_->releaseObjectExpandoOwner(expected, preserveExpandos); + } + ownsObject_ = false; + wrapperRetainedObject_ = false; + object_ = nil; + if (lifetimeState_ != nullptr) { + lifetimeState_->clear(); + } + } + } + + static bool isInitializerSelector(const std::string& selectorName) { + return selectorName.rfind("init", 0) == 0; + } + + static id nativeObjectFromValue(Runtime& runtime, const Value& value) { + if (!value.isObject()) { + return nil; + } + Object object = value.asObject(runtime); + if (!object.isHostObject(runtime)) { + return nil; + } + return object.getHostObject(runtime)->object(); + } + + static Value descriptionString(Runtime& runtime, id object) { + NSString* description = nil; + performDirectObjCInvocation(runtime, [&]() { + description = [(object != nil ? [object description] : @"") copy]; + }); + std::string text = description.UTF8String ?: ""; + [description release]; + return makeString(runtime, text); + } + + Value callObjectSelector(Runtime& runtime, const std::string& selectorName, + const NativeApiMember* member, const Value* args, + size_t count, Class dispatchSuperClass = Nil) { + id receiver = object_; + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + + const bool initializer = isInitializerSelector(selectorName); + std::optional classWrapper; + if (initializer) { + Value classWrapperValue = bridge_->findObjectExpando( + runtime, receiver, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + classWrapper.emplace(classWrapperValue.asObject(runtime)); + } + bridge_->forgetRoundTripValue(runtime, receiver); + } + + Value result = + callObjCSelector(runtime, bridge_, receiver, false, selectorName, member, + args, count, dispatchSuperClass); + if (initializer) { + id resultObject = nativeObjectFromValue(runtime, result); + disownObject(receiver, resultObject == receiver); + if (resultObject != nil) { + // Re-adopt the init result on this host object so that JS overrides + // returning `this` still have a valid native object. + object_ = resultObject; + ownsObject_ = true; + wrapperRetainedObject_ = true; + if (bridge_ != nullptr) { + bridge_->retainObjectExpandoOwner(object_); + } + if (lifetimeState_ != nullptr) { + lifetimeState_->setObject(object_); + } + [object_ retain]; + if (classWrapper) { + bridge_->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *classWrapper)); + if (result.isObject()) { + Value prototypeValue = classWrapper->getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + Object resultValue = result.asObject(runtime); + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, resultValue, prototype); + } + } + } + } + } + return result; + } + + Value callPreparedObjectSelector( + Runtime& runtime, const NativeApiPreparedObjCInvocation& prepared, + const Value* args, size_t count, Class dispatchSuperClass = Nil) { + id receiver = object_; + if (receiver == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + + const bool initializer = preparedObjCInvocationIsInit(prepared); + std::optional classWrapper; + if (initializer) { + Value classWrapperValue = bridge_->findObjectExpando( + runtime, receiver, "__nativeApiClassWrapper"); + if (classWrapperValue.isObject()) { + classWrapper.emplace(classWrapperValue.asObject(runtime)); + } + bridge_->forgetRoundTripValue(runtime, receiver); + } + + Value result = callPreparedObjCSelector( + runtime, bridge_, receiver, false, prepared, args, count, + dispatchSuperClass); + if (initializer) { + id resultObject = nativeObjectFromValue(runtime, result); + disownObject(receiver, resultObject == receiver); + if (resultObject != nil) { + // Re-adopt the init result on this host object so that JS overrides + // returning `this` still have a valid native object. + object_ = resultObject; + ownsObject_ = true; + wrapperRetainedObject_ = true; + if (bridge_ != nullptr) { + bridge_->retainObjectExpandoOwner(object_); + } + if (lifetimeState_ != nullptr) { + lifetimeState_->setObject(object_); + } + [object_ retain]; + if (classWrapper) { + bridge_->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *classWrapper)); + if (result.isObject()) { + Value prototypeValue = classWrapper->getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + Object resultValue = result.asObject(runtime); + Object prototype = prototypeValue.asObject(runtime); + SetNativeApiObjectPrototype(runtime, resultValue, prototype); + } + } + } + } + } + return result; + } + + Value classPrototypeForObject(Runtime& runtime) { + if (object_ == nil) { + return Value::undefined(); + } + + Value classWrapperValue = bridge_->findObjectExpando( + runtime, object_, "__nativeApiClassWrapper"); + if (!classWrapperValue.isObject()) { + classWrapperValue = bridge_->findClassValue(runtime, object_getClass(object_)); + } + if (!classWrapperValue.isObject()) { + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + classWrapperValue = bridge_->findClassValue( + runtime, objc_lookUpClass(symbol->runtimeName.c_str())); + } + } + if (classWrapperValue.isObject()) { + Object classWrapper = classWrapperValue.asObject(runtime); + Value prototypeValue = classWrapper.getProperty(runtime, "prototype"); + if (prototypeValue.isObject()) { + return prototypeValue; + } + } + return bridge_->findClassPrototype(runtime, object_getClass(object_)); + } + + Value engineThisValueForObject(Runtime& runtime) { + Value thisValue = bridge_->findRoundTripValue(runtime, object_, + nullptr, true); + if (thisValue.isObject()) { + return thisValue; + } + return makeNativeObjectValue(runtime, bridge_, object_, false); + } + + Value prototypeFunctionForProperty(Runtime& runtime, + const std::string& property) { + if (property.empty()) { + return Value::undefined(); + } + + Value prototypeValue = classPrototypeForObject(runtime); + if (!prototypeValue.isObject()) { + return Value::undefined(); + } + + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectConstructor.getPropertyAsFunction(runtime, + "getOwnPropertyDescriptor"); + Function getPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "getPrototypeOf"); + Value propertyName = makeString(runtime, property); + Value currentValue(runtime, prototypeValue); + + for (size_t depth = 0; depth < 64 && currentValue.isObject(); depth++) { + Object current = currentValue.asObject(runtime); + Value descriptorValue = + getOwnPropertyDescriptor.call(runtime, Value(runtime, current), + propertyName); + if (descriptorValue.isObject()) { + Value functionValue = + descriptorValue.asObject(runtime).getProperty(runtime, "value"); + if (functionValue.isObject() && + functionValue.asObject(runtime).isFunction(runtime)) { + bridge_->setObjectExpando(runtime, object_, property, functionValue); + return functionValue; + } + return Value::undefined(); + } + currentValue = + getPrototypeOf.call(runtime, Value(runtime, current)); + } + + return Value::undefined(); + } + + // Invoke a JS-prototype getter accessor with this instance as the receiver. + // Sets *found and returns the resolved value. + Value resolveEnginePrototypeGetter(Runtime& runtime, + const std::string& property, bool* found) { + *found = false; + if (object_ == nil || property.empty()) { + return Value::undefined(); + } + Value prototypeValue = classPrototypeForObject(runtime); + if (!prototypeValue.isObject()) { + return Value::undefined(); + } + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectConstructor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); + Function getPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "getPrototypeOf"); + Value propertyName = makeString(runtime, property); + Value currentValue(runtime, prototypeValue); + for (size_t depth = 0; depth < 64 && currentValue.isObject(); depth++) { + Object current = currentValue.asObject(runtime); + Value descriptorValue = getOwnPropertyDescriptor.call( + runtime, Value(runtime, current), propertyName); + if (descriptorValue.isObject()) { + Object descriptor = descriptorValue.asObject(runtime); + Value getterValue = descriptor.getProperty(runtime, "get"); + if (getterValue.isObject() && + getterValue.asObject(runtime).isFunction(runtime)) { + Value thisValue = engineThisValueForObject(runtime); + if (thisValue.isObject()) { + *found = true; + return getterValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, thisValue.asObject(runtime), + static_cast(nullptr), static_cast(0)); + } + } + Value dataValue = descriptor.getProperty(runtime, "value"); + if (!dataValue.isUndefined()) { + *found = true; + return dataValue; + } + return Value::undefined(); + } + currentValue = getPrototypeOf.call(runtime, Value(runtime, current)); + } + return Value::undefined(); + } + + // Invoke a JS-prototype setter accessor with this instance as the receiver. + // Returns true when a setter was found and invoked. + bool invokeEnginePrototypeSetter(Runtime& runtime, const std::string& property, + const Value& value) { + if (object_ == nil || property.empty()) { + return false; + } + Value prototypeValue = classPrototypeForObject(runtime); + if (!prototypeValue.isObject()) { + return false; + } + Object objectConstructor = + runtime.global().getPropertyAsObject(runtime, "Object"); + Function getOwnPropertyDescriptor = + objectConstructor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); + Function getPrototypeOf = + objectConstructor.getPropertyAsFunction(runtime, "getPrototypeOf"); + Value propertyName = makeString(runtime, property); + Value currentValue(runtime, prototypeValue); + for (size_t depth = 0; depth < 64 && currentValue.isObject(); depth++) { + Object current = currentValue.asObject(runtime); + Value descriptorValue = getOwnPropertyDescriptor.call( + runtime, Value(runtime, current), propertyName); + if (descriptorValue.isObject()) { + Value setterValue = + descriptorValue.asObject(runtime).getProperty(runtime, "set"); + if (setterValue.isObject() && + setterValue.asObject(runtime).isFunction(runtime)) { + Value thisValue = engineThisValueForObject(runtime); + if (thisValue.isObject()) { + Value args[] = {Value(runtime, value)}; + setterValue.asObject(runtime).asFunction(runtime).callWithThis( + runtime, thisValue.asObject(runtime), + static_cast(args), static_cast(1)); + return true; + } + } + return false; + } + currentValue = getPrototypeOf.call(runtime, Value(runtime, current)); + } + return false; + } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + + // Fast path: check expando cache first (hot path for method calls). + Value expando = bridge_->findObjectExpando(runtime, object_, property); + if (!expando.isUndefined()) { + return expando; + } + + // Fast path: cached metadata property-getter resolution. Skips the + // special-name chain + per-access metadata discovery for hot getters + // (hash/length/count/...). Only populated for genuine non-extended + // metadata property members below, so a hit is always safe to serve. + if (object_ != nil) { + if (const auto* cached = bridge_->findCachedPropertyGetter( + object_getClass(object_), property)) { + if (cached->preparedInvocation != nullptr) { + return callPreparedObjectSelector(runtime, + *cached->preparedInvocation, + nullptr, 0); + } + return callObjectSelector(runtime, cached->selectorName, cached->member, + nullptr, 0); + } + } + + if (property == "kind") { + return makeString(runtime, "object"); + } + if (property == "className") { + return makeString(runtime, object_ != nil ? object_getClassName(object_) : ""); + } + if (property == "nativeAddress") { + char address[32] = {}; + snprintf(address, sizeof(address), "%p", object_); + return makeString(runtime, address); + } + if (property == "class") { + auto bridge = bridge_; + id object = object_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "class"), 0, + [bridge, object](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + if (object == nil) { + return Value::undefined(); + } + Value classWrapper = bridge->findObjectExpando( + runtime, object, "__nativeApiClassWrapper"); + if (classWrapper.isObject()) { + return classWrapper; + } + NativeApiSymbol symbol = + nativeApiSymbolForRuntimeClass(bridge, object_getClass(object)); + return makeNativeClassValue(runtime, bridge, std::move(symbol)); + }); + } + if (property == "constructor") { + if (object_ == nil) { + return Value::undefined(); + } + // Check class wrapper expando first (set during class setup). + Value classWrapper = bridge_->findObjectExpando( + runtime, object_, "__nativeApiClassWrapper"); + if (classWrapper.isObject()) { + return classWrapper; + } + // Try cached class value. + Class objClass = object_getClass(object_); + Value cached = bridge_->findClassValue(runtime, objClass); + if (!cached.isUndefined()) { + return cached; + } + // Resolve through metadata and global. + NativeApiSymbol symbol = + nativeApiSymbolForRuntimeClass(bridge_, objClass); + // Try the global by the symbol's name (which may be the JS-friendly name + // from metadata, different from the ObjC runtime name for Swift classes). + if (!symbol.name.empty()) { + Object global = runtime.global(); + if (global.hasProperty(runtime, symbol.name.c_str())) { + Value globalClass = global.getProperty(runtime, symbol.name.c_str()); + if (!globalClass.isUndefined() && !globalClass.isNull()) { + return globalClass; + } + } + // Also try the runtime name if different. + if (symbol.runtimeName != symbol.name && + global.hasProperty(runtime, symbol.runtimeName.c_str())) { + Value globalClass = global.getProperty(runtime, symbol.runtimeName.c_str()); + if (!globalClass.isUndefined() && !globalClass.isNull()) { + return globalClass; + } + } + } + // For Swift classes: try findClass by runtime name which checks + // classSymbolsByRuntimeName_ and may return a different JS-friendly name. + if (bridge_ != nullptr) { + const char* runtimeName = class_getName(objClass); + if (runtimeName != nullptr) { + if (const NativeApiSymbol* found = bridge_->findClass(runtimeName)) { + if (found->name != symbol.name) { + Object global = runtime.global(); + if (global.hasProperty(runtime, found->name.c_str())) { + Value globalClass = global.getProperty(runtime, found->name.c_str()); + if (!globalClass.isUndefined() && !globalClass.isNull()) { + return globalClass; + } + } + } + } + } + } + return makeNativeClassValue(runtime, bridge_, std::move(symbol)); + } + if (property == "superclass") { + if (object_ == nil) { + return Value::undefined(); + } + Class superclass = class_getSuperclass(object_getClass(object_)); + if (superclass == Nil) { + return Value::null(); + } + // Try cached class value. + Value cached = bridge_->findClassValue(runtime, superclass); + if (!cached.isUndefined()) { + return cached; + } + // Try global lookup by class name. + const char* name = class_getName(superclass); + if (name != nullptr && name[0] != '\0') { + Object global = runtime.global(); + if (global.hasProperty(runtime, name)) { + Value globalClass = global.getProperty(runtime, name); + if (!globalClass.isUndefined()) { + return globalClass; + } + } + } + NativeApiSymbol symbol = nativeApiSymbolForRuntimeClass(bridge_, superclass); + return makeNativeClassValue(runtime, bridge_, std::move(symbol)); + } + if (property == "super") { + Class dispatchClass = + object_ != nil ? class_getSuperclass(object_getClass(object_)) : Nil; + return Object::createFromHostObject( + runtime, + std::make_shared(bridge_, object_, + dispatchClass)); + } + if (property == "invoke" || property == "send") { + auto bridge = bridge_; + id object = object_; + std::weak_ptr weakSelf = shared_from_this(); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 1, + [bridge, object, weakSelf](Runtime& runtime, const Value&, + const Value* args, + size_t count) -> Value { + std::string selectorName = + readStringArg(runtime, args, count, 0, "selector"); + if (auto self = weakSelf.lock()) { + return self->callObjectSelector(runtime, selectorName, nullptr, + args + 1, count - 1); + } + return callObjCSelector(runtime, bridge, object, false, selectorName, + nullptr, args + 1, count - 1); + }); + } + if (property == "takeRetainedValue" || property == "takeUnretainedValue") { + bool retained = property == "takeRetainedValue"; + std::weak_ptr weakSelf = shared_from_this(); + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [weakSelf, retained](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + auto self = weakSelf.lock(); + if (!self || self->object_ == nil || self->consumed_) { + throw JSError(runtime, "Unmanaged value has already been consumed."); + } + + id object = self->object_; + bool ownsObject = self->ownsObject_; + bool wrapperRetainedObject = self->wrapperRetainedObject_; + if (self->bridge_ != nullptr) { + self->bridge_->forgetRoundTripValue(runtime, object); + self->bridge_->releaseObjectExpandoOwner(object); + } + self->object_ = nil; + self->ownsObject_ = false; + self->wrapperRetainedObject_ = false; + if (self->lifetimeState_ != nullptr) { + self->lifetimeState_->clear(); + } + self->consumed_ = true; + const bool releasePreviousOwnership = + ownsObject && (!retained || wrapperRetainedObject); + try { + Value result = + makeNativeObjectValue(runtime, self->bridge_, object, retained); + if (releasePreviousOwnership) { + [object release]; + } + return result; + } catch (...) { + if (releasePreviousOwnership) { + [object release]; + } + throw; + } + }); + } + if (property == "toString") { + id object = object_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [object](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return NativeApiObjectHostObject::descriptionString(runtime, object); + }); + } + if (property == "description") { + return descriptionString(runtime, object_); + } + if (property == "URL" && object_ != nil && + [object_ respondsToSelector:@selector(URL)]) { + return callObjectSelector(runtime, "URL", nullptr, nullptr, 0); + } + if (property == "Symbol.iterator" || + property == "Symbol(Symbol.iterator)" || + property == "@@iterator") { + auto bridge = bridge_; + id object = object_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "Symbol.iterator"), 0, + [bridge, object](Runtime& runtime, const Value&, const Value*, + size_t) -> Value { + if (object == nil || + ![object conformsToProtocol:@protocol(NSFastEnumeration)]) { + throw JSError( + runtime, "Object does not conform to NSFastEnumeration."); + } + return Object::createFromHostObject( + runtime, + std::make_shared( + bridge, static_cast>(object))); + }); + } + +#if TARGET_OS_OSX + if (property == "initWithRedGreenBlueAlpha") { + Class nsColorClass = NSClassFromString(@"NSColor"); + if (object_ != nil && nsColorClass != Nil && + [object_ isKindOfClass:nsColorClass]) { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 4, + [bridge, nsColorClass](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + const char* selectors[] = { + "colorWithSRGBRed:green:blue:alpha:", + "colorWithCalibratedRed:green:blue:alpha:", + "colorWithDeviceRed:green:blue:alpha:", + }; + for (const char* selectorName : selectors) { + if (class_getClassMethod(nsColorClass, + sel_getUid(selectorName)) != nullptr) { + return callObjCSelector(runtime, bridge, + static_cast(nsColorClass), true, + selectorName, nullptr, args, count); + } + } + throw JSError( + runtime, "NSColor RGB initializer is not available."); + }); + } + } +#endif + + if (property == "initWithFireDateIntervalTargetSelectorUserInfoRepeats") { + Class timerClass = NSClassFromString(@"NSTimer"); + if (object_ != nil && timerClass != Nil && + [object_ isKindOfClass:timerClass]) { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 6, + [bridge, timerClass](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + if (count < 6) { + throw JSError( + runtime, "NSTimer initializer expects six arguments."); + } + return callObjCSelector( + runtime, bridge, static_cast(timerClass), true, + "timerWithTimeInterval:target:selector:userInfo:repeats:", + nullptr, args + 1, count - 1); + }); + } + } + + if (object_ != nil && [object_ isKindOfClass:[NSArray class]]) { + NSArray* array = static_cast(object_); + if (property == "length") { + return static_cast(array.count); + } + if (auto index = parseArrayIndexProperty(property)) { + if (*index >= array.count) { + return Value::undefined(); + } + id element = [array objectAtIndex:*index]; + NativeApiType elementType = nativeObjectReturnType(); + return convertNativeReturnValue(runtime, bridge_, elementType, &element); + } + } + + if (object_ != nil && property == "length" && + ![object_ respondsToSelector:@selector(length)]) { + return Value::undefined(); + } + if (object_ != nil && property == "count" && + ![object_ respondsToSelector:@selector(count)]) { + return Value::undefined(); + } + + // For JS-extended instances, metadata property accessors live on the + // prototype chain (native accessors plus any JS overrides), so defer to the + // engine instead of reading the native property here and shadowing a JS + // override. + bool isEngineExtendedInstance = + object_ != nil && + class_conformsToProtocol(object_getClass(object_), + @protocol(NativeApiClassBuilderProtocol)); + + if (object_ != nil && !isEngineExtendedInstance) { + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, false)) { + if (auto getter = respondingPropertyGetterSelector( + object_, property, propertyMember->selectorName)) { + NativeApiMember getterMember = *propertyMember; + getterMember.selectorName = *getter; + std::shared_ptr preparedGetter; + try { + preparedGetter = prepareNativeApiObjCInvocation( + runtime, bridge_, object_getClass(object_), false, + getterMember.selectorName, &getterMember); + } catch (const std::exception&) { + } + bridge_->cachePropertyGetter(object_getClass(object_), property, + propertyMember, + getterMember.selectorName, + preparedGetter); + if (preparedGetter != nullptr) { + return callPreparedObjectSelector(runtime, *preparedGetter, + nullptr, 0); + } + return callObjectSelector(runtime, getterMember.selectorName, + &getterMember, nullptr, 0); + } + } + + // Resolve metadata methods to a bound selector-group function. The + // bound receiver keeps method-call semantics correct even on engines + // whose host-object interceptor does not preserve `this`, while the + // engine backend can still use its direct selector-group/GSD path. + if (hasMethodMember(members, property, false)) { + auto selectors = + selectorGroupEntriesForMethod(members, property, false); + if (selectors != nullptr) { + auto preparedInvocations = std::make_shared>>( + selectors->size()); + Value methodFunction = CreateNativeApiBoundSelectorGroupFunction( + runtime, bridge_, object_getClass(object_), shared_from_this(), + selectors, preparedInvocations); + // Cache the resolved host function so repeated method access does + // not reallocate it on every call (hot path). + bridge_->setObjectExpando(runtime, object_, property, + methodFunction); + return methodFunction; + } + } + } + } + + Value prototypeFunction = prototypeFunctionForProperty(runtime, property); + if (!prototypeFunction.isUndefined()) { + return prototypeFunction; + } + + // JS-subclassed instances own their members in JS (prototype accessors and + // methods); defer so the engine resolves them instead of the bridge + // returning a registered getter IMP as a raw callable. + if (isEngineExtendedInstance) { +#ifdef NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE + // Engines whose exotic property handler invokes prototype accessors with + // the wrong receiver need the JS-prototype getter resolved here with this + // instance as the receiver. + bool found = false; + Value resolved = resolveEnginePrototypeGetter(runtime, property, &found); + if (found) { + return resolved; + } +#endif + if (auto selector = + runtimeReadablePropertyGetter(object_, property)) { + return callObjectSelector(runtime, *selector, nullptr, nullptr, 0); + } + return Value::undefined(); + } + + if (object_ != nil) { + // A runtime ObjC property (e.g. from a protocol the concrete, non-metadata + // class adopts) must be invoked as a getter, not returned as a callable. + if (objc_property_t prop = + class_getProperty(object_getClass(object_), property.c_str())) { + std::string getter = property; + if (char* customGetter = property_copyAttributeValue(prop, "G")) { + getter = customGetter; + free(customGetter); + } + if (auto selector = + respondingPropertyGetterSelector(object_, property, getter)) { + return callObjectSelector(runtime, *selector, nullptr, nullptr, 0); + } + } + } + + if (object_ != nil && + hasRuntimeMemberForName(object_getClass(object_), false, property)) { + std::weak_ptr weakSelf = shared_from_this(); + std::string memberName = property; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, property.c_str()), 0, + [weakSelf, memberName](Runtime& runtime, const Value&, + const Value* args, size_t count) -> Value { + auto self = weakSelf.lock(); + if (!self || self->object_ == nil) { + throw JSError(runtime, + "Cannot send Objective-C selector to nil."); + } + auto selectorName = selectRuntimeSelectorForName( + object_getClass(self->object_), false, memberName, count); + if (!selectorName) { + throw JSError(runtime, + "Objective-C selector is not available: " + + memberName); + } + return self->callObjectSelector(runtime, *selectorName, nullptr, + args, count); + }); + } + + return Value::undefined(); + } + + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override { + std::string property = name.utf8(runtime); + if (object_ == nil) { + throw JSError(runtime, "Cannot set property on nil object."); + } + + if (const NativeApiSymbol* symbol = + bridge_->findClassForRuntimeClass(object_getClass(object_))) { + const auto& members = bridge_->membersForClass(*symbol); + if (const NativeApiMember* propertyMember = + selectWritablePropertyMember(members, property, false)) { + if (propertyMember->readonly) { + throw JSError( + runtime, "Attempted to assign to readonly property."); + } + NativeApiMember setterMember = *propertyMember; + setterMember.selectorName = propertyMember->setterSelectorName; + setterMember.signatureOffset = propertyMember->setterSignatureOffset; + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, object_, false, + setterMember.selectorName, &setterMember, args, 1); + NATIVE_API_SET_RETURN(true); + } + } + + if (auto setterSelectorName = + runtimeWritablePropertySetter(object_, property)) { + Value args[] = {Value(runtime, value)}; + callObjCSelector(runtime, bridge_, object_, false, + *setterSelectorName, nullptr, args, 1); + NATIVE_API_SET_RETURN(true); + } + + // For JS-subclassed instances, an unknown property is owned by the JS + // prototype (e.g. a JS-defined accessor); defer so the engine runs it instead of + // shadowing it with a bridge expando. + if (class_conformsToProtocol(object_getClass(object_), + @protocol(NativeApiClassBuilderProtocol))) { +#ifdef NATIVESCRIPT_NATIVE_API_HOST_EXPLICIT_OVERRIDE + // Engines whose exotic property storage doesn't fall back to own + // properties need the JS-owned set resolved here: invoke a JS-prototype + // setter if present, otherwise store the value as a bridge expando. + bool invokedPrototypeSetter = + invokeEnginePrototypeSetter(runtime, property, value); + if (!invokedPrototypeSetter) { + storeOwnExpando(runtime, property, value); + } + NATIVE_API_SET_RETURN(true); +#else + NATIVE_API_SET_RETURN(false); +#endif + } + + bridge_->setObjectExpando(runtime, object_, property, value); + NATIVE_API_SET_RETURN(true); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + names.reserve(6); + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "className"); + addPropertyName(runtime, names, "nativeAddress"); + addPropertyName(runtime, names, "constructor"); + addPropertyName(runtime, names, "superclass"); + addPropertyName(runtime, names, "super"); + addPropertyName(runtime, names, "invoke"); + addPropertyName(runtime, names, "send"); + addPropertyName(runtime, names, "takeRetainedValue"); + addPropertyName(runtime, names, "takeUnretainedValue"); + addPropertyName(runtime, names, "toString"); + return names; + } + + private: + std::shared_ptr bridge_; + id object_ = nil; + bool ownsObject_ = false; + bool wrapperRetainedObject_ = false; + bool consumed_ = false; + std::shared_ptr lifetimeState_; +}; diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Protocol.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Protocol.mm new file mode 100644 index 000000000..5c689f627 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Protocol.mm @@ -0,0 +1,318 @@ +class NativeApiProtocolHostObject final : public HostObject { + public: + NativeApiProtocolHostObject(std::shared_ptr bridge, + NativeApiSymbol symbol) + : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} + + Protocol* nativeProtocol() const { + Protocol* protocol = lookupProtocolByNativeName(symbol_.runtimeName); + if (protocol == nullptr && symbol_.runtimeName != symbol_.name) { + protocol = lookupProtocolByNativeName(symbol_.name); + } + return protocol; + } + + const NativeApiSymbol& symbol() const { return symbol_; } + + Value get(Runtime& runtime, const PropNameID& name) override { + std::string property = name.utf8(runtime); + if (property == "kind") { + return makeString(runtime, "protocol"); + } + if (property == "name") { + return makeString(runtime, symbol_.name); + } + if (property == "runtimeName") { + return makeString(runtime, symbol_.runtimeName); + } + if (property == "available") { + return nativeProtocol() != nullptr; + } + if (property == "metadataOffset") { + return static_cast(symbol_.offset); + } + if (property == "nativeAddress") { + return static_cast( + reinterpret_cast(nativeProtocol())); + } + if (property == "prototype") { + Object prototype(runtime); + for (const auto& member : bridge_->membersForProtocol(symbol_)) { + if (prototype.hasProperty(runtime, member.name.c_str())) { + continue; + } + if (member.property) { + defineProtocolProperty(runtime, prototype, member, false); + } else { + prototype.setProperty(runtime, member.name.c_str(), + makeProtocolMemberFunction(runtime, member, + false)); + } + } + return prototype; + } + if (property == "toString") { + auto symbol = symbol_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, "toString"), 0, + [symbol](Runtime& runtime, const Value&, const Value*, size_t) -> Value { + return makeString(runtime, + "[NativeApiProtocol " + symbol.name + "]"); + }); + } + const auto& members = bridge_->membersForProtocol(symbol_); + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, true)) { + return makeProtocolPropertyGetter(runtime, *propertyMember, true); + } + if (const NativeApiMember* propertyMember = + selectPropertyMember(members, property, false)) { + return makeProtocolPropertyGetter(runtime, *propertyMember, true); + } + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (memberIsStatic) { + return makeProtocolMemberFunction(runtime, member, true); + } + } + for (const auto& member : members) { + if (member.property || member.name != property) { + continue; + } + bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; + if (!memberIsStatic) { + return makeProtocolMemberFunction(runtime, member, true); + } + } + return Value::undefined(); + } + + std::vector getPropertyNames(Runtime& runtime) override { + std::vector names; + addPropertyName(runtime, names, "kind"); + addPropertyName(runtime, names, "name"); + addPropertyName(runtime, names, "runtimeName"); + addPropertyName(runtime, names, "available"); + addPropertyName(runtime, names, "metadataOffset"); + addPropertyName(runtime, names, "nativeAddress"); + addPropertyName(runtime, names, "prototype"); + addPropertyName(runtime, names, "toString"); + for (const auto& member : bridge_->membersForProtocol(symbol_)) { + addPropertyName(runtime, names, member.name.c_str()); + } + return names; + } + + private: + static Class classReceiverFromThis(Runtime& runtime, const Value& thisValue) { + if (!thisValue.isObject()) { + return Nil; + } + + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeClass(); + } + + Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); + if (wrappedClass.isObject()) { + Object wrappedObject = wrappedClass.asObject(runtime); + if (wrappedObject.isHostObject(runtime)) { + return wrappedObject.getHostObject(runtime) + ->nativeClass(); + } + } + + Value kindValue = object.getProperty(runtime, "kind"); + if (kindValue.isString() && + kindValue.asString(runtime).utf8(runtime) == "class") { + Value runtimeNameValue = object.getProperty(runtime, "runtimeName"); + if (!runtimeNameValue.isString()) { + runtimeNameValue = object.getProperty(runtime, "name"); + } + if (runtimeNameValue.isString()) { + std::string runtimeName = + runtimeNameValue.asString(runtime).utf8(runtime); + return objc_lookUpClass(runtimeName.c_str()); + } + } + + return Nil; + } + + id objectReceiverFromThis(Runtime& runtime, const Value& thisValue) const { + if (!thisValue.isObject()) { + return nil; + } + + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->object(); + } + + return nil; + } + + Value makeProtocolMemberFunction(Runtime& runtime, NativeApiMember member, + bool receiverIsClass) const { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, + [bridge, member, receiverIsClass](Runtime& runtime, + const Value& thisValue, + const Value* args, + size_t count) -> Value { + id receiver = nil; + if (receiverIsClass) { + receiver = static_cast( + classReceiverFromThis(runtime, thisValue)); + } else if (thisValue.isObject()) { + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + receiver = object.getHostObject(runtime) + ->object(); + } + } + + if (receiver == nil) { + throw JSError( + runtime, "Protocol member requires a native receiver."); + } + return callObjCSelector(runtime, bridge, receiver, receiverIsClass, + member.selectorName, &member, args, count); + }); + } + + Value makeProtocolPropertyGetter(Runtime& runtime, NativeApiMember member, + bool receiverIsClass) const { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, + [bridge, member, receiverIsClass](Runtime& runtime, + const Value& thisValue, + const Value*, size_t) -> Value { + id receiver = nil; + if (receiverIsClass) { + receiver = static_cast( + classReceiverFromThis(runtime, thisValue)); + } else if (thisValue.isObject()) { + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + receiver = object.getHostObject(runtime) + ->object(); + } + } + + if (receiver == nil) { + throw JSError( + runtime, "Protocol property requires a native receiver."); + } + NativeApiMember getterMember = member; + if (auto selector = respondingPropertyGetterSelector( + receiver, member.name, member.selectorName)) { + getterMember.selectorName = *selector; + } + return callObjCSelector(runtime, bridge, receiver, receiverIsClass, + getterMember.selectorName, &getterMember, + nullptr, 0); + }); + } + + Value makeProtocolPropertySetter(Runtime& runtime, NativeApiMember member, + bool receiverIsClass) const { + auto bridge = bridge_; + return Function::createFromHostFunction( + runtime, PropNameID::forAscii(runtime, member.setterSelectorName.c_str()), + 1, + [bridge, member, receiverIsClass](Runtime& runtime, + const Value& thisValue, + const Value* args, + size_t count) -> Value { + id receiver = nil; + if (receiverIsClass) { + receiver = static_cast( + classReceiverFromThis(runtime, thisValue)); + } else if (thisValue.isObject()) { + Object object = thisValue.asObject(runtime); + if (object.isHostObject(runtime)) { + receiver = object.getHostObject(runtime) + ->object(); + } + } + + if (receiver == nil) { + throw JSError( + runtime, "Protocol property requires a native receiver."); + } + if (count < 1) { + throw JSError( + runtime, "Protocol property setter expects a value."); + } + + NativeApiMember setterMember = member; + setterMember.selectorName = member.setterSelectorName; + setterMember.signatureOffset = member.setterSignatureOffset; + return callObjCSelector(runtime, bridge, receiver, receiverIsClass, + setterMember.selectorName, &setterMember, + args, 1); + }); + } + + void defineProtocolProperty(Runtime& runtime, Object& target, + const NativeApiMember& member, + bool receiverIsClass) const { + try { + Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); + Function defineProperty = + objectCtor.getPropertyAsFunction(runtime, "defineProperty"); + Object descriptor(runtime); + descriptor.setProperty(runtime, "configurable", true); + descriptor.setProperty(runtime, "enumerable", true); + descriptor.setProperty(runtime, "get", + makeProtocolPropertyGetter(runtime, member, + receiverIsClass)); + if (!member.readonly && !member.setterSelectorName.empty()) { + descriptor.setProperty(runtime, "set", + makeProtocolPropertySetter(runtime, member, + receiverIsClass)); + } + defineProperty.call(runtime, target, makeString(runtime, member.name), + descriptor); + } catch (const std::exception&) { + } + } + + std::shared_ptr bridge_; + NativeApiSymbol symbol_; +}; + +Value makeNativeProtocolValue(Runtime& runtime, + const std::shared_ptr& bridge, + NativeApiSymbol symbol) { + Value globalValue = globalNativeSymbolValue(runtime, symbol, "protocol"); + if (!globalValue.isUndefined()) { + return globalValue; + } + return Object::createFromHostObject( + runtime, + std::make_shared(bridge, std::move(symbol))); +} + +Class nativeClassFromEngineObject(Runtime& runtime, const Object& object) { + if (object.isHostObject(runtime)) { + return object.getHostObject(runtime)->nativeClass(); + } + + Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); + if (wrappedClass.isObject()) { + Object wrappedObject = wrappedClass.asObject(runtime); + if (wrappedObject.isHostObject(runtime)) { + return wrappedObject.getHostObject(runtime) + ->nativeClass(); + } + } + return Nil; +} diff --git a/NativeScript/ffi/objc/shared/bridge/host_objects/Struct.mm b/NativeScript/ffi/objc/shared/bridge/host_objects/Struct.mm new file mode 100644 index 000000000..a71cea169 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/host_objects/Struct.mm @@ -0,0 +1,47 @@ +class NativeApiStructObjectHostObject final : public HostObject { + public: + NativeApiStructObjectHostObject( + std::shared_ptr bridge, + std::shared_ptr info, + const void* data = nullptr, bool ownsData = true, + std::shared_ptr> storageOwner = nullptr, + std::shared_ptr backingValue = nullptr) + : bridge_(std::move(bridge)), + info_(std::move(info)), + ownedData_(std::move(storageOwner)), + backingValue_(std::move(backingValue)), + ownsData_(ownsData) { + size_t size = info_ != nullptr ? info_->size : 0; + if (ownedData_ != nullptr) { + data_ = const_cast(data); + ownsData_ = false; + } else if (ownsData_) { + ownedData_ = std::make_shared>(size, 0); + if (data != nullptr && size > 0) { + std::memcpy(ownedData_->data(), data, size); + } + data_ = ownedData_->empty() ? nullptr : ownedData_->data(); + } else { + data_ = const_cast(data); + } + } + + void* data() const { return data_; } + std::shared_ptr info() const { return info_; } + std::shared_ptr> storageOwner() const { + return ownedData_; + } + std::shared_ptr backingValue() const { return backingValue_; } + + Value get(Runtime& runtime, const PropNameID& name) override; + NativeApiHostSetResult set(Runtime& runtime, const PropNameID& name, const Value& value) override; + std::vector getPropertyNames(Runtime& runtime) override; + + private: + std::shared_ptr bridge_; + std::shared_ptr info_; + std::shared_ptr> ownedData_; + std::shared_ptr backingValue_; + void* data_ = nullptr; + bool ownsData_ = true; +}; diff --git a/NativeScript/ffi/objc/v8/NativeApiV8.h b/NativeScript/ffi/objc/v8/NativeApiV8.h new file mode 100644 index 000000000..c3d1761f6 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8.h @@ -0,0 +1,22 @@ +#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H +#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H + +#include "ffi/objc/shared/NativeApiBackendConfig.h" +#include "v8.h" + +namespace nativescript { + +using NativeApiScheduler = NativeApiBackendScheduler; +using NativeApiConfig = NativeApiBackendConfig; + +void InstallNativeApi(v8::Isolate* isolate, + v8::Local context, + const NativeApiConfig& config = NativeApiConfig{}); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(v8::Isolate* isolate, + v8::Local context, + const char* metadataPath); + +#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H diff --git a/NativeScript/ffi/objc/v8/NativeApiV8.mm b/NativeScript/ffi/objc/v8/NativeApiV8.mm new file mode 100644 index 000000000..1ed1c20e0 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8.mm @@ -0,0 +1,80 @@ +#include "NativeApiV8.h" + +#ifdef TARGET_ENGINE_V8 + +#include "NativeApiV8Runtime.h" +#include "SignatureDispatch.h" + +namespace nativescript { + +namespace { + +using nativescript::engine::Array; +using nativescript::engine::ArrayBuffer; +using nativescript::engine::BigInt; +using nativescript::engine::Function; +using nativescript::engine::HostObject; +using nativescript::engine::MutableBuffer; +using nativescript::engine::Object; +using nativescript::engine::PropNameID; +using nativescript::engine::Runtime; +using nativescript::engine::String; +using nativescript::engine::StringBuffer; +using nativescript::engine::Value; +using nativescript::engine::JSError; +using metagen::MDMemberFlag; +using metagen::MDMetadataReader; +using metagen::MDSectionOffset; +using metagen::MDTypeKind; + +// clang-format off +#define NATIVESCRIPT_NATIVE_API_BACKEND_NAME "v8" +#include "../shared/bridge/ObjCBridge.mm" +// clang-format on + +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 +#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_SELECTOR_GROUP_FUNCTION 1 +#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 +#define NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE 1 + +#include "NativeApiV8RuntimeSupport.mm" + +// clang-format off +#include "../shared/bridge/HostObjects.mm" +#include "../shared/bridge/Callbacks.mm" +#include "../shared/bridge/TypeConv.mm" +#include "../shared/bridge/Invocation.mm" +#include "../shared/bridge/ClassBuilder.mm" +#include "../shared/bridge/HostObject.mm" +// clang-format on + + +#include "NativeApiV8SelectorGroups.mm" + +} // namespace + +#include "../shared/bridge/Install.mm" + +void InstallNativeApi(v8::Isolate* isolate, v8::Local context, + const NativeApiConfig& config) { + if (isolate == nullptr || context.IsEmpty()) { + return; + } + v8::Locker locker(isolate); + v8::Isolate::Scope isolateScope(isolate); + v8::HandleScope handleScope(isolate); + v8::Context::Scope contextScope(context); + Runtime runtime(isolate, context); + InstallNativeApi(runtime, config); +} + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApi(v8::Isolate* isolate, v8::Local context, + const char* metadataPath) { + nativescript::NativeApiConfig config; + config.metadataPath = metadataPath; + nativescript::InstallNativeApi(isolate, context, config); +} + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Gsd.mm b/NativeScript/ffi/objc/v8/NativeApiV8Gsd.mm new file mode 100644 index 000000000..fb62ddb9d --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Gsd.mm @@ -0,0 +1,216 @@ +// --- GSD (Generated Signature Dispatch) for V8 --- +// GsdObjCContext is the engine-neutral interface the generated invokers use: +// it reads JS arguments and writes the JS return value via the V8 API. The +// readers mirror V8's generic argument/return conversions exactly; any value +// that is not in the fast representation makes a reader return false so the +// invoker falls back to the fully correct generic path. +struct GsdObjCContext; +using ObjCGsdInvoker = bool (*)(GsdObjCContext&); +struct ObjCGsdDispatchEntry { + uint64_t dispatchId; + ObjCGsdInvoker invoker; +}; + +struct GsdObjCContext { + Runtime& runtime; + const std::shared_ptr& bridge; + id self; + SEL selector; + const v8::FunctionCallbackInfo& info; + v8::Isolate* isolate; + v8::Local jsContext; + const NativeApiType& returnType; + + template + void invokeNative(Invocation&& invocation) { + performGeneratedObjCInvocation(runtime, bridge, [&]() { invocation(); }); + } + + v8::Local arg(size_t i) const { + return info[static_cast(i)]; + } + + bool readBool(size_t i, uint8_t* out) { + *out = arg(i)->BooleanValue(isolate) ? 1 : 0; + return true; + } + template + bool readSigned(size_t i, T* out) { + v8::Local v = arg(i); + if (v->IsInt32()) { + *out = static_cast(v.As()->Value()); + return true; + } + if constexpr (sizeof(T) <= 4) { + int32_t tmp = 0; + if (!v->Int32Value(jsContext).To(&tmp)) return false; + *out = static_cast(tmp); + } else { + if (v->IsBigInt()) { + bool lossless = false; + *out = static_cast(v.As()->Int64Value(&lossless)); + } else { + int64_t tmp = 0; + if (!v->IntegerValue(jsContext).To(&tmp)) return false; + *out = static_cast(tmp); + } + } + return true; + } + template + bool readUnsigned(size_t i, T* out) { + v8::Local v = arg(i); + if (v->IsUint32()) { + *out = static_cast(v.As()->Value()); + return true; + } + if (v->IsInt32()) { + *out = static_cast(v.As()->Value()); + return true; + } + if constexpr (sizeof(T) <= 4) { + uint32_t tmp = 0; + if (!v->Uint32Value(jsContext).To(&tmp)) return false; + *out = static_cast(tmp); + } else { + if (v->IsBigInt()) { + bool lossless = false; + *out = static_cast(v.As()->Uint64Value(&lossless)); + } else { + int64_t tmp = 0; + if (!v->IntegerValue(jsContext).To(&tmp)) return false; + *out = static_cast(static_cast(tmp)); + } + } + return true; + } + bool readFloat(size_t i, float* out) { + double tmp = 0.0; + if (!readDouble(i, &tmp)) return false; + *out = static_cast(tmp); + return true; + } + bool readDouble(size_t i, double* out) { + v8::Local v = arg(i); + if (v->IsNumber()) { + *out = v.As()->Value(); + return true; + } + return v->NumberValue(jsContext).To(out); + } + bool readSelector(size_t i, SEL* out) { + return readV8EngineSelectorArgument(runtime, arg(i), out); + } + bool readClass(size_t i, Class* out) { + Class cls = v8NativeClassArgument(runtime, arg(i)); + if (cls == Nil) return false; + *out = cls; + return true; + } + bool readObject(size_t i, id* out) { + v8::Local v = arg(i); + if (v.IsEmpty() || v->IsNullOrUndefined()) { + *out = nil; + return true; + } + if (!v->IsObject()) return false; + if (auto* h = v8HostObjectRaw(v)) { + *out = h->object(); + return true; + } + if (auto* c = v8HostObjectRaw(v)) { + *out = static_cast(c->nativeClass()); + return true; + } + Class cls = v8NativeClassArgument(runtime, v); + if (cls != Nil) { + *out = static_cast(cls); + return true; + } + if (auto* p = v8HostObjectRaw(v)) { + *out = static_cast(p->nativeProtocol()); + return true; + } + return false; + } + + void setVoid() {} + void setBool(bool v) { + info.GetReturnValue().Set(v8::Boolean::New(isolate, v)); + } + void setInt32(int32_t v) { + info.GetReturnValue().Set(v8::Integer::New(isolate, v)); + } + void setUInt32(uint32_t v) { + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, v)); + } + void setUInt16(uint16_t v) { + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, v)); + } + void setInt64(int64_t v) { + info.GetReturnValue().Set(v8Integer64Value(isolate, v)); + } + void setUInt64(uint64_t v) { + info.GetReturnValue().Set(v8UnsignedInteger64Value(isolate, v)); + } + void setDouble(double v) { + info.GetReturnValue().Set(v8::Number::New(isolate, v)); + } + void setSelector(SEL v) { + const char* name = v != nullptr ? sel_getName(v) : nullptr; + if (name == nullptr) { + info.GetReturnValue().Set(v8::Null(isolate)); + } else { + info.GetReturnValue().Set(engine::v8engine::makeV8String(isolate, name)); + } + } + void setClass(Class v) { + if (v == nil) { + info.GetReturnValue().Set(v8::Null(isolate)); + return; + } + const char* name = class_getName(v); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + info.GetReturnValue().Set(result.local(runtime)); + } + void setObject(id obj) { + setV8EngineObjectReturn(runtime, bridge, returnType, obj, info); + } +}; + +// Close the anonymous namespace so the generated dispatch table lives in +// namespace nativescript (visible to lookupObjCGsdInvoker). GsdObjCContext is +// reachable from there via the unnamed namespace's implicit using-directive. +} // namespace (temporary close for GSD .inc) + +#if defined(__has_include) +#if __has_include("GeneratedGsdSignatureDispatch.inc") +#include "GeneratedGsdSignatureDispatch.inc" +#endif +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +inline constexpr ObjCGsdDispatchEntry kGeneratedObjCGsdDispatchEntries[] = { + {0, nullptr}}; +#endif + +ObjCGsdInvoker lookupObjCGsdInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCGsdDispatchEntries, dispatchId); +} + +namespace { // reopen anonymous namespace + +// --- End GSD --- diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm new file mode 100644 index 000000000..1b410907b --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm @@ -0,0 +1,595 @@ +// Included by NativeApiV8SelectorGroups.mm inside the NativeScript anonymous namespace. + +std::string v8StringToUtf8(v8::Isolate* isolate, + v8::Local value) { + v8::String::Utf8Value utf8(isolate, value); + return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); +} + +template +std::shared_ptr v8HostObject(Runtime& runtime, v8::Local value) { + if (value.IsEmpty() || !value->IsObject()) { + return nullptr; + } + v8::Local object = value.As(); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + auto* holder = static_cast( + object->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || + holder->typeToken != engine::v8engine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); +} + +// Fast version that returns raw pointer (no atomic ref count). +// Only safe when the caller guarantees the object stays alive. +template +T* v8HostObjectRaw(v8::Local value) { + if (value.IsEmpty() || !value->IsObject()) { + return nullptr; + } + v8::Local object = value.As(); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + auto* holder = static_cast( + object->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || + holder->typeToken != engine::v8engine::hostObjectTypeToken()) { + return nullptr; + } + return static_cast(holder->hostObject.get()); +} + +id v8NativeObjectArgument(Runtime& runtime, + const std::shared_ptr& bridge, + const NativeApiType& type, + v8::Local value, + NativeApiArgumentFrame& frame) { + v8::Isolate* isolate = runtime.isolate(); + if (value.IsEmpty() || value->IsNullOrUndefined()) { + return nil; + } + if (value->IsString()) { + std::string utf8 = v8StringToUtf8(isolate, value); + id string = type.kind == metagen::mdTypeNSMutableStringObject + ? [[NSMutableString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding] + : [[NSString alloc] initWithBytes:utf8.data() + length:utf8.size() + encoding:NSUTF8StringEncoding]; + if (string != nil) { + frame.addObject(string); + } + return string; + } + if (value->IsBoolean()) { + return [NSNumber numberWithBool:value->BooleanValue(isolate)]; + } + if (value->IsNumber()) { + return [NSNumber numberWithDouble:value->NumberValue(runtime.context()) + .FromMaybe(0)]; + } + if (!value->IsObject()) { + return nil; + } + if (auto objectHost = + v8HostObject(runtime, value)) { + return objectHost->object(); + } + if (auto classHost = v8HostObject(runtime, value)) { + return static_cast(classHost->nativeClass()); + } + if (auto protocolHost = + v8HostObject(runtime, value)) { + return static_cast(protocolHost->nativeProtocol()); + } + if (auto pointerHost = + v8HostObject(runtime, value)) { + return static_cast(pointerHost->pointer()); + } + if (auto referenceHost = + v8HostObject(runtime, value)) { + return static_cast(referenceHost->data()); + } + if (auto structHost = + v8HostObject(runtime, value)) { + return static_cast(structHost->data()); + } + + v8::Local wrappedClassValue; + if (value.As() + ->Get(runtime.context(), + engine::v8engine::makeV8String(isolate, "__nativeApiClass")) + .ToLocal(&wrappedClassValue)) { + if (auto classHost = + v8HostObject(runtime, wrappedClassValue)) { + return static_cast(classHost->nativeClass()); + } + } + + Value wrapped = Value::borrowed(runtime, value); + return objectFromEngineValue(runtime, bridge, wrapped, frame, + type.kind == + metagen::mdTypeNSMutableStringObject); +} + +Class v8NativeClassArgument(Runtime& runtime, v8::Local value) { + if (value.IsEmpty() || value->IsNullOrUndefined()) { + return Nil; + } + auto* state = runtime.rawState(); + if (state != nullptr && value->IsObject()) { + // The cache holds the Class opaquely so the engine layer stays platform + // neutral; cast back on the way out. + if (state->nativeClassArgumentLast.nativeClass != nullptr && + !state->nativeClassArgumentLast.value.IsEmpty() && + state->nativeClassArgumentLast.value.Get(runtime.isolate()) == value) { + return (Class)state->nativeClassArgumentLast.nativeClass; + } + for (auto& entry : state->nativeClassArgumentCache) { + if (entry.nativeClass != nullptr && !entry.value.IsEmpty() && + entry.value.Get(runtime.isolate()) == value) { + state->nativeClassArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeClassArgumentLast.nativeClass = entry.nativeClass; + return (Class)entry.nativeClass; + } + } + } + + Class result = Nil; + if (auto classHost = v8HostObject(runtime, value)) { + result = classHost->nativeClass(); + } else if (value->IsObject()) { + v8::Local wrappedClassValue; + if (value.As() + ->Get(runtime.context(), + engine::v8engine::makeV8String(runtime.isolate(), + "__nativeApiClass")) + .ToLocal(&wrappedClassValue)) { + if (auto classHost = + v8HostObject(runtime, + wrappedClassValue)) { + result = classHost->nativeClass(); + } + } + } + + if (result == Nil) { + Value wrapped = Value::borrowed(runtime, value); + result = classFromEngineValue(runtime, wrapped); + } + + if (result != Nil && state != nullptr && value->IsObject()) { + constexpr size_t cacheSize = + sizeof(state->nativeClassArgumentCache) / + sizeof(state->nativeClassArgumentCache[0]); + auto& entry = state->nativeClassArgumentCache[ + state->nativeClassArgumentCacheNext++ % cacheSize]; + entry.value.Reset(runtime.isolate(), value); + entry.nativeClass = result; + state->nativeClassArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeClassArgumentLast.nativeClass = result; + } + return result; +} + +bool readV8EngineSelectorArgument(Runtime& runtime, v8::Local value, + SEL* result) { + if (result == nullptr) { + return false; + } + if (value.IsEmpty() || value->IsNullOrUndefined()) { + *result = nullptr; + return true; + } + if (!value->IsString()) { + return false; + } + auto* state = runtime.rawState(); + if (state != nullptr) { + if (state->nativeSelectorArgumentLast.selector != nullptr && + !state->nativeSelectorArgumentLast.value.IsEmpty() && + state->nativeSelectorArgumentLast.value.Get(runtime.isolate()) == + value) { + *result = (SEL)state->nativeSelectorArgumentLast.selector; + return true; + } + for (auto& entry : state->nativeSelectorArgumentCache) { + if (entry.selector != nullptr && !entry.value.IsEmpty() && + entry.value.Get(runtime.isolate()) == value) { + *result = (SEL)entry.selector; + state->nativeSelectorArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeSelectorArgumentLast.selector = entry.selector; + return true; + } + } + } + + v8::Isolate* isolate = runtime.isolate(); + v8::Local string = value.As(); + char stackBuffer[128]; + if (string->Utf8LengthV2(isolate) + 1 <= sizeof(stackBuffer)) { + string->WriteUtf8V2(isolate, stackBuffer, sizeof(stackBuffer), + v8::String::WriteFlags::kNullTerminate); + *result = sel_registerName(stackBuffer); + } else { + std::string selectorName = v8StringToUtf8(isolate, value); + *result = sel_registerName(selectorName.c_str()); + } + if (*result != nullptr && state != nullptr) { + constexpr size_t cacheSize = + sizeof(state->nativeSelectorArgumentCache) / + sizeof(state->nativeSelectorArgumentCache[0]); + auto& entry = state->nativeSelectorArgumentCache[ + state->nativeSelectorArgumentCacheNext++ % cacheSize]; + entry.value.Reset(isolate, value); + entry.selector = *result; + state->nativeSelectorArgumentLast.value.Reset(isolate, value); + state->nativeSelectorArgumentLast.selector = *result; + } + return true; +} + +bool prepareV8EngineArgument( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, v8::Local value, + NativeApiArgumentFrame& frame, size_t index) { + ffi_type* ffiType = ffiTypeForEngineArgument(type); + size_t size = + ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); + void* target = frame.storageAt(index, size); + + switch (type.kind) { + case metagen::mdTypeBool: + if (!value->IsBoolean()) { + return false; + } + *static_cast(target) = + value->BooleanValue(runtime.isolate()) ? 1 : 0; + return true; + case metagen::mdTypeChar: { + int32_t converted = 0; + if (!value->Int32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: { + uint32_t converted = 0; + if (!value->Uint32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeSShort: { + int32_t converted = 0; + if (!value->Int32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeUShort: + case metagen::mdTypeUnichar: { + if (value->IsString()) { + std::string text = v8StringToUtf8(runtime.isolate(), value); + if (text.size() != 1) { + return false; + } + *static_cast(target) = + static_cast(static_cast(text[0])); + return true; + } + uint32_t converted = 0; + if (!value->Uint32Value(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeSInt: + return value->Int32Value(runtime.context()).To( + static_cast(target)); + case metagen::mdTypeUInt: + return value->Uint32Value(runtime.context()).To( + static_cast(target)); + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: { + if (value->IsBigInt()) { + bool lossless = false; + *static_cast(target) = + value.As()->Int64Value(&lossless); + return true; + } + return value->IntegerValue(runtime.context()).To( + static_cast(target)); + } + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: { + if (value->IsBigInt()) { + bool lossless = false; + *static_cast(target) = + value.As()->Uint64Value(&lossless); + return true; + } + int64_t converted = 0; + if (!value->IntegerValue(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeFloat: { + double converted = 0; + if (!value->NumberValue(runtime.context()).To(&converted)) { + return false; + } + *static_cast(target) = static_cast(converted); + return true; + } + case metagen::mdTypeDouble: + return value->NumberValue(runtime.context()).To( + static_cast(target)); + case metagen::mdTypeSelector: + return readV8EngineSelectorArgument(runtime, value, + static_cast(target)); + case metagen::mdTypeClass: { + Class cls = v8NativeClassArgument(runtime, value); + if (cls == Nil) { + return false; + } + *static_cast(target) = cls; + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + *static_cast(target) = + v8NativeObjectArgument(runtime, bridge, type, value, frame); + return true; + default: + break; + } + + Value wrapped = Value::borrowed(runtime, value); + convertEngineFfiArgument(runtime, bridge, type, wrapped, target, frame); + return true; +} + +v8::Local v8Integer64Value(v8::Isolate* isolate, int64_t value) { + constexpr int64_t maxSafeInteger = 9007199254740991LL; + constexpr int64_t minSafeInteger = -9007199254740991LL; + if (value >= minSafeInteger && value <= maxSafeInteger) { + return v8::Number::New(isolate, static_cast(value)); + } + return v8::BigInt::New(isolate, value); +} + +v8::Local v8UnsignedInteger64Value(v8::Isolate* isolate, + uint64_t value) { + constexpr uint64_t maxSafeInteger = 9007199254740991ULL; + if (value <= maxSafeInteger) { + return v8::Number::New(isolate, static_cast(value)); + } + return v8::BigInt::NewFromUnsigned(isolate, value); +} + +bool setV8EngineObjectReturn( + Runtime& runtime, const std::shared_ptr& bridge, + const NativeApiType& type, id object, + const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = runtime.isolate(); + if (object == nil) { + info.GetReturnValue().Set(v8::Null(isolate)); + return true; + } + Value roundTrip = + findCachedNativeObjectReturn(runtime, bridge, type, object); + if (!roundTrip.isUndefined()) { + info.GetReturnValue().Set(roundTrip.local(runtime)); + if (type.returnOwned) { + [object release]; + } + return true; + } + if (nativeObjectReturnMayCoerceToString(type) && + nativeObjectIsStringLike(object)) { + std::string utf8 = utf8StringFromNSString(static_cast(object)); + if (type.returnOwned) { + [object release]; + } + info.GetReturnValue().Set(engine::v8engine::makeV8String(isolate, utf8)); + return true; + } + if ([object isKindOfClass:[NSNull class]]) { + if (type.returnOwned) { + [object release]; + } + info.GetReturnValue().Set(v8::Null(isolate)); + return true; + } + if ([object isKindOfClass:[NSNumber class]] && + ![object isKindOfClass:[NSDecimalNumber class]]) { + NSNumber* number = static_cast(object); + const char* objCType = [number objCType]; + bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == + CFBooleanGetTypeID() || + (objCType != nullptr && + std::strcmp(objCType, @encode(BOOL)) == 0); + if (isBool) { + info.GetReturnValue().Set(v8::Boolean::New(isolate, [number boolValue])); + } else { + info.GetReturnValue().Set(v8::Number::New(isolate, [number doubleValue])); + } + if (type.returnOwned) { + [object release]; + } + return true; + } + + if (const NativeApiSymbol* classSymbol = + bridge->findClassForRuntimePointer((void*)object)) { + Value result = makeNativeClassValue(runtime, bridge, *classSymbol); + info.GetReturnValue().Set(result.local(runtime)); + if (type.returnOwned) { + [object release]; + } + return true; + } + if (const NativeApiSymbol* protocolSymbol = + bridge->findProtocolForRuntimePointer((void*)object)) { + Value result = makeNativeProtocolValue(runtime, bridge, *protocolSymbol); + info.GetReturnValue().Set(result.local(runtime)); + if (type.returnOwned) { + [object release]; + } + return true; + } + Value result = makeNativeObjectValue(runtime, bridge, object, type.returnOwned); + info.GetReturnValue().Set(result.local(runtime)); + return true; +} + +bool setV8EngineReturnValue( + Runtime& runtime, const std::shared_ptr& bridge, + NativeApiType type, void* value, const std::string& selectorName, + const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = runtime.isolate(); + switch (type.kind) { + case metagen::mdTypeVoid: + info.GetReturnValue().Set(v8::Undefined(isolate)); + return true; + case metagen::mdTypeBool: + info.GetReturnValue().Set( + v8::Boolean::New(isolate, *static_cast(value) != 0)); + return true; + case metagen::mdTypeChar: + info.GetReturnValue().Set( + v8::Integer::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUChar: + case metagen::mdTypeUInt8: + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned( + isolate, *static_cast(value))); + return true; + case metagen::mdTypeSShort: + info.GetReturnValue().Set( + v8::Integer::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUShort: + info.GetReturnValue().Set( + v8::Integer::NewFromUnsigned(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUnichar: { + const char16_t unit = *static_cast(value); + // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates + // fall back to U+FFFD). + char buffer[4] = {0}; + size_t length = 0; + if (unit < 0x80) { + buffer[length++] = static_cast(unit); + } else if (unit < 0x800) { + buffer[length++] = static_cast(0xC0 | (unit >> 6)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } else if (unit >= 0xD800 && unit <= 0xDFFF) { + buffer[length++] = static_cast(0xEF); + buffer[length++] = static_cast(0xBF); + buffer[length++] = static_cast(0xBD); + } else { + buffer[length++] = static_cast(0xE0 | (unit >> 12)); + buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); + buffer[length++] = static_cast(0x80 | (unit & 0x3F)); + } + info.GetReturnValue().Set( + engine::v8engine::makeV8String(isolate, std::string(buffer, length))); + return true; + } + case metagen::mdTypeSInt: + info.GetReturnValue().Set( + v8::Integer::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeUInt: + info.GetReturnValue().Set(v8::Integer::NewFromUnsigned( + isolate, *static_cast(value))); + return true; + case metagen::mdTypeSLong: + case metagen::mdTypeSInt64: + info.GetReturnValue().Set( + v8Integer64Value(isolate, *static_cast(value))); + return true; + case metagen::mdTypeULong: + case metagen::mdTypeUInt64: + info.GetReturnValue().Set( + v8UnsignedInteger64Value(isolate, *static_cast(value))); + return true; + case metagen::mdTypeFloat: + info.GetReturnValue().Set( + v8::Number::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeDouble: + info.GetReturnValue().Set( + v8::Number::New(isolate, *static_cast(value))); + return true; + case metagen::mdTypeClass: { + Class cls = *static_cast(value); + if (cls == nil) { + info.GetReturnValue().Set(v8::Null(isolate)); + return true; + } + const char* name = class_getName(cls); + NativeApiSymbol symbol{ + .kind = NativeApiSymbolKind::Class, + .offset = MD_SECTION_OFFSET_NULL, + .name = name != nullptr ? name : "", + .runtimeName = name != nullptr ? name : "", + }; + if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { + symbol = *found; + } + Value result = makeNativeClassValue(runtime, bridge, std::move(symbol)); + info.GetReturnValue().Set(result.local(runtime)); + return true; + } + case metagen::mdTypeAnyObject: + case metagen::mdTypeProtocolObject: + case metagen::mdTypeClassObject: + case metagen::mdTypeInstanceObject: + case metagen::mdTypeNSStringObject: + case metagen::mdTypeNSMutableStringObject: + if ((selectorName == "valueForKey:" || + selectorName == "valueForKeyPath:") && + isObjectiveCObjectType(type)) { + type.kind = metagen::mdTypeAnyObject; + } + return setV8EngineObjectReturn(runtime, bridge, type, + *static_cast(value), info); + case metagen::mdTypeSelector: { + SEL selector = *static_cast(value); + const char* selectorNameValue = + selector != nullptr ? sel_getName(selector) : nullptr; + if (selectorNameValue == nullptr) { + info.GetReturnValue().Set(v8::Null(isolate)); + } else { + info.GetReturnValue().Set( + engine::v8engine::makeV8String(isolate, selectorNameValue)); + } + return true; + } + default: + break; + } + Value result = convertNativeReturnValue(runtime, bridge, type, value); + info.GetReturnValue().Set(result.local(runtime)); + return true; +} diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h new file mode 100644 index 000000000..bc28ab2a3 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h @@ -0,0 +1,40 @@ +#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H +#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H + +// Apple-side entry point for the V8 engine layer. +// +// The engine layer itself -- the `nativescript::engine` classes wrapping V8 -- +// moved to jsi/v8/V8Runtime.h so the Android runtime can compile it too. What +// stays here is exactly what the Apple ObjC bridge needs on top of it and +// Android has no use for: Foundation, the Objective-C runtime, the Mach-O +// metadata section lookup, and the metadata reader. +// +// Apple sources include this path, unchanged, and get both halves. + +#ifdef TARGET_ENGINE_V8 + +#import +#include +#include +#include +#include +#include +#include + +#include "Metadata.h" +#include "MetadataReader.h" +#include "ffi.h" +#include "ffi/objc/shared/EnumMemberAliases.h" + +#include "jsi/v8/V8Runtime.h" + +@protocol NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +#endif // TARGET_ENGINE_V8 + +#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H diff --git a/NativeScript/ffi/objc/v8/NativeApiV8RuntimeSupport.mm b/NativeScript/ffi/objc/v8/NativeApiV8RuntimeSupport.mm new file mode 100644 index 000000000..121029e89 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8RuntimeSupport.mm @@ -0,0 +1,121 @@ +// Included by NativeApiV8.mm inside the NativeScript anonymous namespace. + +struct NativeApiLazyGlobalData { + NativeApiLazyGlobalData(v8::Isolate* isolate, const std::string& name, + const std::string& kind) { + nameValue.Reset(isolate, engine::v8engine::makeV8String(isolate, name)); + kindValue.Reset(isolate, engine::v8engine::makeV8String(isolate, kind)); + } + + ~NativeApiLazyGlobalData() { + nameValue.Reset(); + kindValue.Reset(); + } + + v8::Global nameValue; + v8::Global kindValue; +}; + +std::shared_ptr retainNativeApiRuntime(Runtime& runtime) { + return std::make_shared(runtime.state()); +} + +class NativeApiRuntimeScope final { + public: + explicit NativeApiRuntimeScope(Runtime& runtime) + : locker_(runtime.isolate()), + isolateScope_(runtime.isolate()), + handleScope_(runtime.isolate()), + context_(runtime.context()), + contextScope_(context_) {} + + private: + v8::Locker locker_; + v8::Isolate::Scope isolateScope_; + v8::HandleScope handleScope_; + v8::Local context_; + v8::Context::Scope contextScope_; +}; + +void NativeApiLazyGlobalGetter(v8::Local, + const v8::PropertyCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::HandleScope handleScope(isolate); + v8::Local context = isolate->GetCurrentContext(); + if (!info.Data()->IsExternal()) { + return; + } + + auto* data = static_cast(info.Data().As()->Value(v8::kExternalPointerTypeTagDefault)); + if (data == nullptr) { + return; + } + v8::Local nameValue = data->nameValue.Get(isolate); + v8::Local kindValue = data->kindValue.Get(isolate); + + v8::Local global = context->Global(); + v8::Local resolverValue; + if (!global + ->Get(context, engine::v8engine::makeV8String( + isolate, "__nativeScriptResolveNativeApiLazyGlobal")) + .ToLocal(&resolverValue) || + !resolverValue->IsFunction()) { + return; + } + + v8::TryCatch tryCatch(isolate); + v8::Local args[] = {nameValue, kindValue}; + v8::Local result; + if (!resolverValue.As()->Call(context, global, 2, args).ToLocal(&result)) { + if (tryCatch.HasCaught()) { + isolate->ThrowException(tryCatch.Exception()); + } + return; + } + if (global->Delete(context, nameValue).FromMaybe(false)) { + global->DefineOwnProperty(context, nameValue, result, v8::DontEnum).FromMaybe(false); + } + info.GetReturnValue().Set(result); +} + +bool InstallNativeApiLazyGlobal(Runtime& runtime, std::shared_ptr, + const std::string& name, const std::string& kind, + bool force) { + if (name.empty() || kind.empty()) { + return false; + } + + v8::Isolate* isolate = runtime.isolate(); + v8::EscapableHandleScope handleScope(isolate); + v8::Local context = runtime.context(); + v8::Local global = context->Global(); + v8::Local property = engine::v8engine::makeV8String(isolate, name); + if (!force && global->HasOwnProperty(context, property).FromMaybe(false)) { + return false; + } + + auto data = std::make_shared(isolate, name, kind); + v8::Local external = v8::External::New(isolate, data.get(), v8::kExternalPointerTypeTagDefault); + + bool installed = global + ->SetNativeDataProperty(context, property, NativeApiLazyGlobalGetter, + nullptr, external, v8::DontEnum) + .FromMaybe(false); + if (installed) { + runtime.state()->retainedNativeData.push_back(std::move(data)); + } + return installed; +} + +void SetNativeApiObjectPrototype(Runtime& runtime, Object& object, + const Object& prototype) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!object.local(runtime) + ->SetPrototypeV2(runtime.context(), prototype.local(runtime)) + .FromMaybe(false)) { + throw JSError(runtime, + engine::v8engine::currentExceptionMessage(runtime.isolate(), + tryCatch)); + } +} + diff --git a/NativeScript/ffi/objc/v8/NativeApiV8SelectorGroups.mm b/NativeScript/ffi/objc/v8/NativeApiV8SelectorGroups.mm new file mode 100644 index 000000000..83116c270 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8SelectorGroups.mm @@ -0,0 +1,260 @@ +// Included by NativeApiV8.mm inside the NativeScript anonymous namespace. + +#include "../shared/bridge/SelectorGroupData.h" + +#include "NativeApiV8Marshalling.mm" + +#include "NativeApiV8Gsd.mm" + +#include "../shared/bridge/SelectorGroupCall.h" + + +void* lookupGeneratedEngineObjCGsdInvoker(uint64_t dispatchId) { + return reinterpret_cast(lookupObjCGsdInvoker(dispatchId)); +} + +bool tryCallGeneratedEngineObjCSelector( + Runtime&, const std::shared_ptr&, id, + const NativeApiPreparedObjCInvocation&, const Value*, size_t, Class, + Value*) { + return false; +} + +void setV8EnginePreparedObjCResult( + Runtime& runtime, const std::shared_ptr& bridge, + id receiver, const NativeApiPreparedObjCInvocation& prepared, + const std::shared_ptr& receiverHostObject, + const std::optional& initializerClassWrapper, + const v8::FunctionCallbackInfo& info, + Class dispatchSuperClass) { + const NativeApiSignature& signature = prepared.signature; + if (receiver == nil || signature.variadic || + unsupportedEngineType(signature.returnType)) { + throw JSError(runtime, + "Objective-C selector is not supported by V8 engine: " + + prepared.selectorName); + } + + const bool isNSErrorOutMethod = prepared.isNSErrorOutMethod; + const size_t providedCount = static_cast(info.Length()); + if (isNSErrorOutMethod) { + size_t expected = signature.argumentTypes.size(); + if (providedCount > expected || providedCount + 1 < expected) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + std::to_string(expected) + "\"."); + } + } else if (providedCount != signature.argumentTypes.size()) { + throw JSError( + runtime, "Actual arguments count: \"" + std::to_string(providedCount) + + "\". Expected: \"" + + std::to_string(signature.argumentTypes.size()) + "\"."); + } + + // GSD fast path: the generated invoker reads args directly from + // FunctionCallbackInfo, calls objc_msgSend with a typed cast, and sets the + // return via the V8 API — all in one generated function. Bypasses all + // generic marshalling. + if (prepared.gsdEngineCallable && dispatchSuperClass == Nil && + providedCount == prepared.gsdEngineArgumentCount && + !initializerClassWrapper && !isNSErrorOutMethod) { + auto invoker = reinterpret_cast(prepared.engineInvoker); + GsdObjCContext ctx{runtime, + bridge, + receiver, + prepared.selector, + info, + runtime.isolate(), + runtime.context(), + signature.returnType}; + if (invoker(ctx)) { + return; + } + } + + if (dispatchSuperClass == Nil && !initializerClassWrapper && + providedCount <= 2) { + Value fastArgs[2]; + for (size_t i = 0; i < providedCount; i++) { + fastArgs[i] = Value::borrowed(runtime, info[static_cast(i)]); + } + Value fastResult; + if (tryCallFastEngineObjCSelector(runtime, bridge, receiver, prepared, + fastArgs, providedCount, Nil, + &fastResult)) { + info.GetReturnValue().Set(fastResult.local(runtime)); + return; + } + } + + NativeApiArgumentFrame frame(signature.argumentTypes.size()); + for (size_t i = 0; i < providedCount; i++) { + if (!prepareV8EngineArgument(runtime, bridge, signature.argumentTypes[i], + info[static_cast(i)], frame, i)) { + throw JSError(runtime, + "Objective-C argument is not supported by V8 engine: " + + prepared.selectorName); + } + } + + const bool hasImplicitNSErrorOutArg = + isNSErrorOutMethod && providedCount + 1 == signature.argumentTypes.size(); + NSError* implicitNSError = nil; + if (hasImplicitNSErrorOutArg) { + size_t outArgIndex = signature.argumentTypes.size() - 1; + void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); + NSError** implicitNSErrorOutArg = &implicitNSError; + *static_cast(target) = implicitNSErrorOutArg; + } + + NativeApiPointerFrame values(signature.argumentTypes.size() + 2); + size_t valueIndex = 0; + struct objc_super superReceiver = {receiver, dispatchSuperClass}; + struct objc_super* superReceiverPtr = &superReceiver; + if (dispatchSuperClass != Nil) { + values.set(valueIndex++, &superReceiverPtr); + } else { + values.set(valueIndex++, &receiver); + } + values.set(valueIndex++, const_cast(&prepared.selector)); + for (size_t i = 0; i < signature.argumentTypes.size(); i++) { + values.set(valueIndex++, frame.values()[i]); + } + + NativeApiReturnStorage returnStorage( + nativeSizeForType(signature.returnType)); + performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { + if (prepared.preparedInvoker != nullptr && dispatchSuperClass == Nil) { + prepared.preparedInvoker(reinterpret_cast(objc_msgSend), + values.data(), returnStorage.data()); + } else { +#if defined(__x86_64__) + bool isStret = signature.returnType.ffiType->size > 16 && + signature.returnType.ffiType->type == FFI_TYPE_STRUCT; + void (*target)(void) = + dispatchSuperClass != Nil + ? (isStret ? FFI_FN(objc_msgSendSuper_stret) + : FFI_FN(objc_msgSendSuper)) + : (isStret ? FFI_FN(objc_msgSend_stret) : FFI_FN(objc_msgSend)); + ffi_call(const_cast(&signature.cif), target, + returnStorage.data(), values.data()); +#else + ffi_call(const_cast(&signature.cif), + dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) + : FFI_FN(objc_msgSend), + returnStorage.data(), values.data()); +#endif + } + }); + + NativeApiType returnType = signature.returnType; + if (hasImplicitNSErrorOutArg && implicitNSError != nil) { + const char* errorMessage = [[implicitNSError description] UTF8String]; + throw JSError( + runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); + } + if (initializerClassWrapper) { + id resultObject = nil; + if (isObjectiveCObjectType(returnType)) { + resultObject = *static_cast(returnStorage.data()); + } + if (receiverHostObject != nullptr && resultObject != receiver) { + receiverHostObject->disownObject(receiver); + } + if (resultObject != nil) { + bridge->setObjectExpando(runtime, resultObject, + "__nativeApiClassWrapper", + Value(runtime, *initializerClassWrapper)); + } + } + setV8EngineReturnValue(runtime, bridge, returnType, returnStorage.data(), + prepared.selectorName, info); +} + +void NativeApiSelectorGroupCallback( + const v8::FunctionCallbackInfo& info) { + auto* data = static_cast( + info.Data().As()->Value(v8::kExternalPointerTypeTagDefault)); + if (data == nullptr || data->selectors == nullptr || + data->preparedInvocations == nullptr) { + return; + } + + Runtime& runtime = data->runtime; + v8::HandleScope handleScope(runtime.isolate()); + try { + NativeApiRoundTripCacheFrameGuard roundTripFrame(data->bridge); + size_t count = static_cast(info.Length()); + auto call = resolveNativeApiSelectorGroupCall( + runtime, *data, count, + [&]() -> id { + // The V8 handle keeps this raw host object alive for the call. + auto* host = + v8HostObjectRaw(info.This()); + return host != nullptr ? host->object() : nil; + }, + [&]() { + return v8HostObject(runtime, info.This()); + }, + [](uint64_t dispatchId) { return lookupObjCGsdInvoker(dispatchId); }); + if (call.hasImmediateResult) { + info.GetReturnValue().Set(call.immediateResult.local(runtime)); + return; + } + // Inline GSD fast path: skip the setV8EnginePreparedObjCResult call and its + // argument-count/NSError preamble entirely for the common case. The + // generated invoker reads args, calls objc_msgSend, and sets the return. + if (call.prepared->gsdEngineCallable && call.dispatchClass == Nil && + !call.prepared->isInitMethod && + count == call.prepared->gsdEngineArgumentCount) { + auto invoker = + reinterpret_cast(call.prepared->engineInvoker); + GsdObjCContext ctx{runtime, + data->bridge, + call.receiver, + call.prepared->selector, + info, + runtime.isolate(), + runtime.context(), + call.prepared->signature.returnType}; + if (invoker(ctx)) { + return; + } + } + setV8EnginePreparedObjCResult( + runtime, data->bridge, call.receiver, *call.prepared, + call.receiverHostObject, call.initializerClassWrapper, info, + call.dispatchClass); + } catch (const std::exception& exception) { + engine::v8engine::throwV8Exception(info.GetIsolate(), exception); + } +} + +Function CreateNativeApiSelectorGroupFunctionImpl( + Runtime& runtime, std::shared_ptr bridge, + Class lookupClass, bool receiverIsClass, + std::shared_ptr> selectors, + std::shared_ptr< + std::vector>> + preparedInvocations, + std::weak_ptr boundReceiver, + std::shared_ptr boundReceiverState) { + auto data = std::make_shared( + runtime.state(), std::move(bridge), lookupClass, receiverIsClass, + std::move(selectors), std::move(preparedInvocations), + std::move(boundReceiver), std::move(boundReceiverState)); + auto* rawData = data.get(); + runtime.state()->retainedNativeData.push_back(std::move(data)); + + v8::Local external = + v8::External::New(runtime.isolate(), rawData, v8::kExternalPointerTypeTagDefault); + v8::Local functionTemplate = + v8::FunctionTemplate::New(runtime.isolate(), + NativeApiSelectorGroupCallback, external); + v8::Local function = + functionTemplate->GetFunction(runtime.context()).ToLocalChecked(); + function->SetName( + engine::v8engine::makeV8String(runtime.isolate(), "__nativeSelectorGroup")); + Value functionValue(runtime, function); + return functionValue.asObject(runtime).asFunction(runtime); +} diff --git a/NativeScript/ffi/objc/v8/SignatureDispatch.h b/NativeScript/ffi/objc/v8/SignatureDispatch.h new file mode 100644 index 000000000..7fd3b8533 --- /dev/null +++ b/NativeScript/ffi/objc/v8/SignatureDispatch.h @@ -0,0 +1,48 @@ +#ifndef NATIVESCRIPT_FFI_V8_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_V8_SIGNATURE_DISPATCH_H + +#include + +#include "ffi/objc/shared/SignatureDispatchCore.h" + +// Engine-neutral GSD (Generated Signature Dispatch). The GsdObjCContext struct, +// the ObjCGsdInvoker/ObjCGsdDispatchEntry types, the generated dispatch table, +// and lookupObjCGsdInvoker are all defined in NativeApiV8SelectorGroups.mm, +// which NativeApiV8.mm includes after the host object helpers the context +// relies on. Nothing GSD-related is declared here to avoid creating an +// ambiguous second GsdObjCContext. + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 1 +#endif + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH 0 +#endif + +// NOTE: GeneratedGsdSignatureDispatch.inc is included from +// NativeApiV8SelectorGroups.mm after GsdObjCContext is defined (avoids +// namespace ordering issues). + +// The main .inc (prepared invokers + tables) is included here. +#if defined(__has_include) +#if __has_include("GeneratedSignatureDispatch.inc") +#include "GeneratedSignatureDispatch.inc" +#endif +#endif + +#include "ffi/objc/shared/PreparedSignatureDispatch.h" + +#endif // NATIVESCRIPT_FFI_V8_SIGNATURE_DISPATCH_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJS.h b/NativeScript/ffi/quickjs/NativeApiQuickJS.h deleted file mode 100644 index 73c31984d..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJS.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H -#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H - -#include "ffi/shared/direct/NativeApiDirect.h" -#include "quickjs.h" - -namespace nativescript { - -using NativeApiQuickJSConfig = NativeApiDirectConfig; - -void InstallNativeApiQuickJS(JSContext* context, - const NativeApiQuickJSConfig& config = - NativeApiQuickJSConfig{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiQuickJS(JSContext* context, - const char* metadataPath); - -#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJS.mm b/NativeScript/ffi/quickjs/NativeApiQuickJS.mm deleted file mode 100644 index b72dc695a..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJS.mm +++ /dev/null @@ -1,178 +0,0 @@ -#include "NativeApiQuickJS.h" - -#ifdef TARGET_ENGINE_QUICKJS - -#include "NativeApiQuickJSRuntime.h" - -namespace nativescript { - -using NativeApiJsiConfig = NativeApiDirectConfig; -using NativeApiJsiScheduler = NativeApiDirectScheduler; - -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -// clang-format on - -#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 -#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 - -static JSValue NativeApiQuickJSLazyGlobalGetter(JSContext* context, JSValueConst, int, - JSValueConst*, int, JSValueConst* data) { - JSValue global = JS_GetGlobalObject(context); - JSValue resolver = JS_GetPropertyStr(context, global, "__nativeScriptResolveNativeApiLazyGlobal"); - if (!JS_IsFunction(context, resolver)) { - JS_FreeValue(context, resolver); - JS_FreeValue(context, global); - return JS_UNDEFINED; - } - - JSValueConst args[] = {data[0], data[1]}; - JSValue result = JS_Call(context, resolver, global, 2, args); - JS_FreeValue(context, resolver); - if (JS_IsException(result)) { - JS_FreeValue(context, global); - return result; - } - - JSAtom atom = JS_ValueToAtom(context, data[0]); - if (atom != JS_ATOM_NULL) { - JS_DefinePropertyValue(context, global, atom, JS_DupValue(context, result), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - JS_FreeAtom(context, atom); - } - JS_FreeValue(context, global); - return result; -} - -// Assigning over a lazy global must behave like a plain global assignment -// (@nativescript/core writes shims such as global.System); replace the -// accessor with an ordinary writable property instead of throwing -// "no setter for property". -static JSValue NativeApiQuickJSLazyGlobalSetter(JSContext* context, JSValueConst, int argc, - JSValueConst* argv, int, JSValueConst* data) { - JSValue global = JS_GetGlobalObject(context); - JSAtom atom = JS_ValueToAtom(context, data[0]); - if (atom != JS_ATOM_NULL) { - JSValue value = argc > 0 ? JS_DupValue(context, argv[0]) : JS_UNDEFINED; - JS_DefinePropertyValue(context, global, atom, value, JS_PROP_C_W_E); - JS_FreeAtom(context, atom); - } - JS_FreeValue(context, global); - return JS_UNDEFINED; -} - -bool InstallNativeApiEngineLazyGlobal(Runtime& runtime, std::shared_ptr, - const std::string& name, const std::string& kind, - bool force) { - if (name.empty() || kind.empty()) { - return false; - } - - JSContext* context = runtime.context(); - JSValue global = JS_GetGlobalObject(context); - JSAtom atom = JS_NewAtomLen(context, name.data(), name.size()); - if (atom == JS_ATOM_NULL) { - JS_FreeValue(context, global); - return false; - } - - int hasProperty = JS_HasProperty(context, global, atom); - if (!force && hasProperty > 0) { - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - if (hasProperty < 0) { - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - - JSValue data[] = { - JS_NewStringLen(context, name.data(), name.size()), - JS_NewStringLen(context, kind.data(), kind.size()), - }; - if (JS_IsException(data[0]) || JS_IsException(data[1])) { - JS_FreeValue(context, data[0]); - JS_FreeValue(context, data[1]); - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - - JSValue getter = JS_NewCFunctionData(context, NativeApiQuickJSLazyGlobalGetter, 0, 0, 2, data); - JSValue setter = JS_NewCFunctionData(context, NativeApiQuickJSLazyGlobalSetter, 1, 0, 2, data); - JS_FreeValue(context, data[0]); - JS_FreeValue(context, data[1]); - if (JS_IsException(getter) || JS_IsException(setter)) { - JS_FreeValue(context, getter); - JS_FreeValue(context, setter); - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return false; - } - - int status = JS_DefinePropertyGetSet(context, global, atom, getter, setter, JS_PROP_CONFIGURABLE); - JS_FreeAtom(context, atom); - JS_FreeValue(context, global); - return status >= 0; -} - -// clang-format off -#include "jsi/NativeApiJsiHostObjects.h" -// clang-format on - -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::make_shared(runtime.state()); -} - -// clang-format off -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -void InstallNativeApiQuickJS(JSContext* context, const NativeApiQuickJSConfig& config) { - if (context == nullptr) { - return; - } - auto state = facebook::jsi::quickjsdirect::stateForContext(context); - facebook::jsi::Runtime runtime(state); - facebook::jsi::quickjsdirect::ensureClasses(runtime); - InstallNativeApiJSI(runtime, config); -} - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiQuickJS(JSContext* context, const char* metadataPath) { - nativescript::NativeApiQuickJSConfig config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiQuickJS(context, config); -} - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSHostObjects.mm b/NativeScript/ffi/quickjs/NativeApiQuickJSHostObjects.mm deleted file mode 100644 index 1cd706a30..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSHostObjects.mm +++ /dev/null @@ -1,239 +0,0 @@ -#include "NativeApiQuickJSRuntime.h" - -#ifdef TARGET_ENGINE_QUICKJS - -namespace facebook { -namespace jsi { - -namespace quickjsdirect { - -JSClassID gHostClassId = 0; -JSClassID gFunctionClassId = 0; - -namespace { -std::mutex& runtimeStatesMutex() { - static auto* mutex = new std::mutex(); - return *mutex; -} - -std::unordered_map>& runtimeStates() { - static auto* states = new std::unordered_map>(); - return *states; -} -} // namespace - -std::shared_ptr stateForContext(JSContext* context) { - std::lock_guard lock(runtimeStatesMutex()); - auto& states = runtimeStates(); - auto it = states.find(context); - if (it != states.end()) { - return it->second; - } - auto state = std::make_shared(context); - states[context] = state; - return state; -} - -static JSValue nativeHostGet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - (void)receiver; - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - return JS_UNDEFINED; - } - try { - Value result = holder->hostObject->get(runtime, PropNameID(atomToUtf8(ctx, atom))); - return result.local(runtime); - } catch (const std::exception& error) { - return throwError(ctx, error); - } -} - -static int nativeHostSet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, - JSValueConst, int) { - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - return 0; - } - try { - holder->hostObject->set(runtime, PropNameID(atomToUtf8(ctx, atom)), Value(runtime, value)); - return 1; - } catch (const std::exception& error) { - throwError(ctx, error); - return -1; - } -} - -static int nativeHostHas(JSContext* ctx, JSValueConst obj, JSAtom atom) { - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - return 0; - } - try { - auto names = holder->hostObject->getPropertyNames(runtime); - std::string requested = atomToUtf8(ctx, atom); - for (const auto& name : names) { - if (name.utf8(runtime) == requested) { - return 1; - } - } - } catch (const std::exception&) { - } - return 0; -} - -static int nativeHostOwnNames(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, - JSValueConst obj) { - Runtime runtime(stateForContext(ctx)); - auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); - if (holder == nullptr || holder->hostObject == nullptr) { - *ptab = nullptr; - *plen = 0; - return 0; - } - auto names = holder->hostObject->getPropertyNames(runtime); - *plen = static_cast(names.size()); - *ptab = static_cast(js_mallocz(ctx, sizeof(JSPropertyEnum) * names.size())); - for (uint32_t i = 0; i < *plen; i++) { - (*ptab)[i].is_enumerable = true; - (*ptab)[i].atom = JS_NewAtom(ctx, names[i].utf8(runtime).c_str()); - } - return 0; -} - -static void nativeHostFinalize(JSRuntime*, JSValue value) { - auto* holder = static_cast(JS_GetOpaque(value, gHostClassId)); - delete holder; -} - -static JSValue invokeFunctionHolder(JSContext* ctx, FunctionHolder* holder, JSValueConst thisValue, - int argc, JSValueConst* argv) { - Runtime runtime(stateForContext(ctx)); - if (holder == nullptr || !holder->callback) { - return JS_UNDEFINED; - } - std::vector args; - args.reserve(argc); - for (int i = 0; i < argc; i++) { - args.emplace_back(runtime, argv[i]); - } - try { - Value self(runtime, thisValue); - Value result = - holder->callback(runtime, self, args.empty() ? nullptr : args.data(), args.size()); - return result.local(runtime); - } catch (const std::exception& error) { - return throwError(ctx, error); - } -} - -static JSValue nativeFunctionCall(JSContext* ctx, JSValue function, JSValue thisValue, int argc, - JSValue* argv, int) { - auto* holder = static_cast(JS_GetOpaque(function, gFunctionClassId)); - return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); -} - -static JSValue nativeFunctionCallData(JSContext* ctx, JSValue thisValue, int argc, JSValue* argv, - int, JSValue* data) { - auto* holder = static_cast(JS_GetOpaque(data[0], gFunctionClassId)); - return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); -} - -static void nativeFunctionFinalize(JSRuntime*, JSValue value) { - auto* holder = static_cast(JS_GetOpaque(value, gFunctionClassId)); - delete holder; -} - -static JSClassExoticMethods hostExoticMethods = { - .get_own_property = nullptr, - .get_own_property_names = nativeHostOwnNames, - .delete_property = nullptr, - .define_own_property = nullptr, - .has_property = nativeHostHas, - .get_property = nativeHostGet, - .set_property = nativeHostSet, -}; - -void ensureClasses(Runtime& runtime) { - auto state = runtime.state(); - JSRuntime* rt = JS_GetRuntime(runtime.context()); - if (gHostClassId == 0) { - JS_NewClassID(rt, &gHostClassId); - } - if (!state->hostClassRegistered) { - JSClassDef def = {}; - def.class_name = "NativeScriptDirectHostObject"; - def.exotic = &hostExoticMethods; - def.finalizer = nativeHostFinalize; - JS_NewClass(rt, gHostClassId, &def); - JS_SetClassProto(runtime.context(), gHostClassId, JS_NewObject(runtime.context())); - state->hostClassRegistered = true; - } - if (gFunctionClassId == 0) { - JS_NewClassID(rt, &gFunctionClassId); - } - if (!state->functionClassRegistered) { - JSClassDef def = {}; - def.class_name = "NativeScriptDirectFunction"; - def.call = nativeFunctionCall; - def.finalizer = nativeFunctionFinalize; - JS_NewClass(rt, gFunctionClassId, &def); - JS_SetClassProto(runtime.context(), gFunctionClassId, JS_NewObject(runtime.context())); - state->functionClassRegistered = true; - } -} - -} // namespace quickjsdirect - -quickjsdirect::HostObjectHolder* Object::hostObjectHolder(Runtime& runtime) const { - quickjsdirect::ensureClasses(runtime); - JSValue object = local(runtime); - auto* holder = static_cast( - JS_GetOpaque(object, quickjsdirect::gHostClassId)); - JS_FreeValue(runtime.context(), object); - return holder; -} - -Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken) { - quickjsdirect::ensureClasses(runtime); - auto* holder = new quickjsdirect::HostObjectHolder(runtime.state(), std::move(host), typeToken); - JSValue object = JS_NewObjectClass(runtime.context(), quickjsdirect::gHostClassId); - JS_SetOpaque(object, holder); - Object result = Object::fromValueStorage(Value(runtime, object).storage_); - JS_FreeValue(runtime.context(), object); - return result; -} - -Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, - unsigned int parameterCount, HostFunctionType callback) { - quickjsdirect::ensureClasses(runtime); - auto* holder = new quickjsdirect::FunctionHolder(runtime.state(), std::move(callback)); - JSValue data = JS_NewObjectClass(runtime.context(), quickjsdirect::gFunctionClassId); - if (JS_IsException(data)) { - delete holder; - throw JSError(runtime, "QuickJS host function data allocation failed."); - } - JS_SetOpaque(data, holder); - - JSValue function = JS_NewCFunctionData(runtime.context(), quickjsdirect::nativeFunctionCallData, - static_cast(parameterCount), 0, 1, &data); - JS_FreeValue(runtime.context(), data); - if (JS_IsException(function)) { - throw JSError(runtime, "QuickJS host function allocation failed."); - } - - std::string functionName = name.utf8(runtime); - JSValue nameValue = JS_NewStringLen(runtime.context(), functionName.data(), functionName.size()); - JS_DefinePropertyValueStr(runtime.context(), function, "name", nameValue, JS_PROP_CONFIGURABLE); - Function result = Function(Object::fromValueStorage(Value(runtime, function).storage_)); - JS_FreeValue(runtime.context(), function); - return result; -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.h b/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.h deleted file mode 100644 index 438a78166..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.h +++ /dev/null @@ -1,745 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H -#define NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H - -#ifdef TARGET_ENGINE_QUICKJS - -#import -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" -#include "quickjs.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace facebook { -namespace jsi { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual void set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace quickjsdirect { - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct RuntimeState { - explicit RuntimeState(JSContext* context) : context(context) {} - JSContext* context = nullptr; - bool hostClassRegistered = false; - bool functionClassRegistered = false; -}; - -extern JSClassID gHostClassId; -extern JSClassID gFunctionClassId; - -std::shared_ptr stateForContext(JSContext* context); - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - QuickJS, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - ~ValueStorage() { - if (context != nullptr && !JS_IsUninitialized(value)) { - JS_FreeValue(context, value); - } - } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - JSContext* context = nullptr; - JSValue value = JS_UNINITIALIZED; -}; - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - std::shared_ptr state; - HostFunctionType callback; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - std::shared_ptr buffer; -}; - -inline std::string valueToUtf8(JSContext* context, JSValueConst value) { - size_t length = 0; - const char* cString = JS_ToCStringLen(context, &length, value); - if (cString == nullptr) { - return {}; - } - std::string result(cString, length); - JS_FreeCString(context, cString); - return result; -} - -inline std::string atomToUtf8(JSContext* context, JSAtom atom) { - const char* cString = JS_AtomToCString(context, atom); - if (cString == nullptr) { - return {}; - } - std::string result(cString); - JS_FreeCString(context, cString); - return result; -} - -inline JSValue throwError(JSContext* context, const std::exception& error) { - return JS_ThrowTypeError(context, "%s", error.what()); -} - -void ensureClasses(Runtime& runtime); - -} // namespace quickjsdirect - -class Runtime { - public: - explicit Runtime(JSContext* context) : state_(quickjsdirect::stateForContext(context)) {} - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - JSContext* context() const { return state_->context; } - std::shared_ptr state() const { return state_; } - Object global(); - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - void drainMicrotasks() { - JSContext* ctx = context(); - JSRuntime* rt = JS_GetRuntime(ctx); - JSContext* jobCtx = nullptr; - while (JS_ExecutePendingJob(rt, &jobCtx) > 0) { - } - } - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, JSValue value); - static String createFromUtf8(Runtime& runtime, const char* value) { - return String(runtime, JS_NewString(runtime.context(), value != nullptr ? value : "")); - } - static String createFromUtf8(Runtime& runtime, const std::string& value) { - return String(runtime, JS_NewStringLen(runtime.context(), value.data(), value.size())); - } - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - return String(runtime, - JS_NewStringLen(runtime.context(), reinterpret_cast(value), length)); - } - std::string utf8(Runtime& runtime) const; - JSValue local(Runtime& runtime) const; - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::Undefined)) {} - Value(bool value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::Bool)) { - storage_->boolValue = value; - } - Value(double value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::Number)) { - storage_->numberValue = value; - } - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) : storage_(value.storage_) {} - Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) {} - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - Value(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); - } - - static Value undefined() { return Value(); } - static Value null() { - Value value; - value.storage_ = - std::make_shared(quickjsdirect::ValueStorage::Kind::Null); - return value; - } - bool isUndefined() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Undefined || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsUndefined(storage_->value)); - } - bool isNull() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Null || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsNull(storage_->value)); - } - bool isBool() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Bool || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsBool(storage_->value)); - } - bool getBool() const { - if (storage_->kind == quickjsdirect::ValueStorage::Kind::Bool) { - return storage_->boolValue; - } - if (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS) { - return JS_ToBool(storage_->context, storage_->value) != 0; - } - return false; - } - bool isNumber() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::Number || - (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsNumber(storage_->value)); - } - double getNumber() const { - if (storage_->kind == quickjsdirect::ValueStorage::Kind::Number) { - return storage_->numberValue; - } - if (storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS) { - double value = 0; - JS_ToFloat64(storage_->context, &value, storage_->value); - return value; - } - return 0; - } - bool isObject() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsObject(storage_->value); - } - bool isString() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsString(storage_->value); - } - bool isBigInt() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsBigInt(storage_->context, storage_->value); - } - bool isSymbol() const { - return storage_->kind == quickjsdirect::ValueStorage::Kind::QuickJS && - JS_IsSymbol(storage_->value); - } - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - JSValue local(Runtime& runtime) const { - switch (storage_->kind) { - case quickjsdirect::ValueStorage::Kind::Undefined: - return JS_UNDEFINED; - case quickjsdirect::ValueStorage::Kind::Null: - return JS_NULL; - case quickjsdirect::ValueStorage::Kind::Bool: - return JS_NewBool(runtime.context(), storage_->boolValue); - case quickjsdirect::ValueStorage::Kind::Number: - return JS_NewFloat64(runtime.context(), storage_->numberValue); - case quickjsdirect::ValueStorage::Kind::QuickJS: - return JS_DupValue(runtime.context(), storage_->value); - } - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_NewObject(runtime.context()); - } - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - quickjsdirect::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - JSValue object = local(runtime); - JSValue result = JS_GetPropertyStr(runtime.context(), object, name != nullptr ? name : ""); - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS property get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - Value getProperty(Runtime& runtime, const Value& key) const { - JSValue object = local(runtime); - JSValue keyValue = key.local(runtime); - JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); - JS_FreeValue(runtime.context(), keyValue); - JSValue result = - atom == JS_ATOM_NULL ? JS_UNDEFINED : JS_GetProperty(runtime.context(), object, atom); - if (atom != JS_ATOM_NULL) { - JS_FreeAtom(runtime.context(), atom); - } - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS property get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - JSValue object = local(runtime); - JSValue localValue = value.local(runtime); - int status = - JS_SetPropertyStr(runtime.context(), object, name != nullptr ? name : "", localValue); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property set failed."); - } - } - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - JSValue object = local(runtime); - JSValue keyValue = key.local(runtime); - JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); - JS_FreeValue(runtime.context(), keyValue); - JSValue localValue = value.local(runtime); - int status = - atom == JS_ATOM_NULL ? -1 : JS_SetProperty(runtime.context(), object, atom, localValue); - if (atom != JS_ATOM_NULL) { - JS_FreeAtom(runtime.context(), atom); - } - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property set failed."); - } - } - bool hasProperty(Runtime& runtime, const char* name) const { - JSValue object = local(runtime); - JSAtom atom = JS_NewAtom(runtime.context(), name != nullptr ? name : ""); - int result = JS_HasProperty(runtime.context(), object, atom); - JS_FreeAtom(runtime.context(), atom); - JS_FreeValue(runtime.context(), object); - return result > 0; - } - bool isFunction(Runtime& runtime) const { - JSValue object = local(runtime); - bool result = JS_IsFunction(runtime.context(), object); - JS_FreeValue(runtime.context(), object); - return result; - } - bool isArray(Runtime& runtime) const { - JSValue object = local(runtime); - int result = JS_IsArray(runtime.context(), object); - JS_FreeValue(runtime.context(), object); - return result > 0; - } - bool isArrayBuffer(Runtime& runtime) const { - JSValue object = local(runtime); - bool result = JS_IsArrayBuffer(object); - JS_FreeValue(runtime.context(), object); - return result; - } - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == quickjsdirect::hostObjectTypeToken(); - } - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != quickjsdirect::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - explicit Object(std::shared_ptr storage) - : storage_(std::move(storage)) {} - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - quickjsdirect::HostObjectHolder* hostObjectHolder(Runtime& runtime) const; - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - Value call(Runtime& runtime, const Value* args, size_t count) const { - JSValue function = local(runtime); - JSValue global = JS_GetGlobalObject(runtime.context()); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_Call(runtime.context(), function, global, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), global); - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS function call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - JSValue function = local(runtime); - JSValue thisValue = thisObject.local(runtime); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_Call(runtime.context(), function, thisValue, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), thisValue); - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS function call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - JSValue function = local(runtime); - std::vector argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - JSValue result = JS_CallConstructor(runtime.context(), function, static_cast(argv.size()), - argv.empty() ? nullptr : argv.data()); - for (auto& arg : argv) { - JS_FreeValue(runtime.context(), arg); - } - JS_FreeValue(runtime.context(), function); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS constructor call failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_NewArray(runtime.context()); - JS_SetPropertyStr(runtime.context(), storage_->value, "length", - JS_NewUint32(runtime.context(), static_cast(size))); - } - explicit Array(Object object) : Object(std::move(object.storage_)) {} - size_t size(Runtime& runtime) const { - Value length = getProperty(runtime, "length"); - return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; - } - Value getValueAtIndex(Runtime& runtime, size_t index) const { - JSValue object = local(runtime); - JSValue result = JS_GetPropertyUint32(runtime.context(), object, static_cast(index)); - JS_FreeValue(runtime.context(), object); - if (JS_IsException(result)) { - throw JSError(runtime, "QuickJS array get failed."); - } - Value value(runtime, result); - JS_FreeValue(runtime.context(), result); - return value; - } - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - JSValue object = local(runtime); - JSValue localValue = value.local(runtime); - int status = - JS_SetPropertyUint32(runtime.context(), object, static_cast(index), localValue); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS array set failed."); - } - } - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); - } - static BigInt fromInt64(Runtime& runtime, int64_t value) { - JSValue result = JS_NewBigInt64(runtime.context(), value); - BigInt bigint(runtime, result); - JS_FreeValue(runtime.context(), result); - return bigint; - } - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - JSValue result = JS_NewBigUint64(runtime.context(), value); - BigInt bigint(runtime, result); - JS_FreeValue(runtime.context(), result); - return bigint; - } - String toString(Runtime& runtime, int) const; - JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - auto* holder = new quickjsdirect::ArrayBufferHolder(std::move(buffer)); - storage_->context = runtime.context(); - storage_->value = JS_NewArrayBuffer( - runtime.context(), holder->buffer->data(), holder->buffer->size(), - [](JSRuntime*, void* opaque, void*) { - delete static_cast(opaque); - }, - holder, false); - } - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - size_t size(Runtime& runtime) const { - JSValue object = local(runtime); - size_t size = 0; - JS_GetArrayBuffer(runtime.context(), &size, object); - JS_FreeValue(runtime.context(), object); - return size; - } - uint8_t* data(Runtime& runtime) const { - JSValue object = local(runtime); - size_t size = 0; - uint8_t* data = JS_GetArrayBuffer(runtime.context(), &size, object); - JS_FreeValue(runtime.context(), object); - return data; - } -}; -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS - -#endif // NATIVESCRIPT_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm b/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm deleted file mode 100644 index 6a64b8e57..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm +++ /dev/null @@ -1,40 +0,0 @@ -#include "NativeApiQuickJSRuntime.h" - -#ifdef TARGET_ENGINE_QUICKJS - -namespace facebook { -namespace jsi { - -String BigInt::toString(Runtime& runtime, int) const { - JSValue value = local(runtime); - JSValue stringValue = JS_ToString(runtime.context(), value); - JS_FreeValue(runtime.context(), value); - String result(runtime, stringValue); - JS_FreeValue(runtime.context(), stringValue); - return result; -} - -Object Runtime::global() { - JSValue global = JS_GetGlobalObject(context()); - Object result = Object::fromValueStorage(Value(*this, global).storage_); - JS_FreeValue(context(), global); - return result; -} - -Value Runtime::evaluateJavaScript(std::shared_ptr buffer, - const std::string& sourceURL) { - JSValue result = - JS_Eval(context(), buffer != nullptr ? buffer->data() : "", - buffer != nullptr ? buffer->size() : 0, sourceURL.c_str(), JS_EVAL_TYPE_GLOBAL); - if (JS_IsException(result)) { - throw JSError(*this, "QuickJS script evaluation failed."); - } - Value value(*this, result); - JS_FreeValue(context(), result); - return value; -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSValue.mm b/NativeScript/ffi/quickjs/NativeApiQuickJSValue.mm deleted file mode 100644 index 94ae05e0e..000000000 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSValue.mm +++ /dev/null @@ -1,88 +0,0 @@ -#include "NativeApiQuickJSRuntime.h" - -#ifdef TARGET_ENGINE_QUICKJS - -namespace facebook { -namespace jsi { - -Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } -void HostObject::set(Runtime&, const PropNameID&, const Value&) {} -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } -String::String(Runtime& runtime, JSValue value) - : storage_(std::make_shared( - quickjsdirect::ValueStorage::Kind::QuickJS)) { - storage_->context = runtime.context(); - storage_->value = JS_DupValue(runtime.context(), value); -} -std::string String::utf8(Runtime& runtime) const { - JSValue value = local(runtime); - std::string result = quickjsdirect::valueToUtf8(runtime.context(), value); - JS_FreeValue(runtime.context(), value); - return result; -} -JSValue String::local(Runtime& runtime) const { - return JS_DupValue(runtime.context(), storage_->value); -} -String::operator Value() const { - Value value; - value.storage_ = storage_; - return value; -} -Value::Value(Runtime&, const Object& object) : storage_(object.storage_) {} -Value::Value(Runtime&, const Function& function) : storage_(function.storage_) {} -Value::Value(Runtime&, const Array& array) : storage_(array.storage_) {} -Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) : storage_(arrayBuffer.storage_) {} -Value::Value(Runtime&, const BigInt& bigint) : storage_(bigint.storage_) {} -Object Value::asObject(Runtime&) const { return Object::fromValueStorage(storage_); } -String Value::asString(Runtime& runtime) const { - JSValue value = local(runtime); - String result(runtime, value); - JS_FreeValue(runtime.context(), value); - return result; -} -BigInt Value::getBigInt(Runtime& runtime) const { - JSValue value = local(runtime); - BigInt result(runtime, value); - JS_FreeValue(runtime.context(), value); - return result; -} -Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime).asFunction(runtime); -} -Function Object::asFunction(Runtime&) const { return Function(*this); } -Array Object::getArray(Runtime&) const { return Array(*this); } -ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } -Array Object::getPropertyNames(Runtime& runtime) const { - JSValue object = local(runtime); - JSPropertyEnum* properties = nullptr; - uint32_t count = 0; - int status = JS_GetOwnPropertyNames(runtime.context(), &properties, &count, object, - JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY); - JS_FreeValue(runtime.context(), object); - if (status < 0) { - throw JSError(runtime, "QuickJS property names failed."); - } - Array result(runtime, count); - for (uint32_t i = 0; i < count; i++) { - JSValue nameValue = JS_AtomToValue(runtime.context(), properties[i].atom); - result.setValueAtIndex(runtime, i, Value(runtime, nameValue)); - JS_FreeValue(runtime.context(), nameValue); - JS_FreeAtom(runtime.context(), properties[i].atom); - } - js_free(runtime.context(), properties); - return result; -} -void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { - setProperty(runtime, name, Value(runtime, value)); -} -void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/ffi/shared/direct/NativeApiDirect.h b/NativeScript/ffi/shared/direct/NativeApiDirect.h deleted file mode 100644 index ef55cda4f..000000000 --- a/NativeScript/ffi/shared/direct/NativeApiDirect.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_SHARED_DIRECT_NATIVE_API_DIRECT_H -#define NATIVESCRIPT_FFI_SHARED_DIRECT_NATIVE_API_DIRECT_H - -#include -#include - -namespace nativescript { - -class NativeApiDirectScheduler { - public: - virtual ~NativeApiDirectScheduler() = default; - virtual void invokeOnJS(std::function task) = 0; - virtual void invokeOnUI(std::function task) = 0; -}; - -struct NativeApiDirectConfig { - const char* metadataPath = nullptr; - const void* metadataPtr = nullptr; - const char* globalName = "__nativeScriptNativeApi"; - std::shared_ptr scheduler = nullptr; - std::function)> nativeInvocationInvoker = nullptr; - std::function)> nativeCallbackInvoker = nullptr; - std::function)> jsThreadCallbackInvoker = nullptr; - bool invokeCallbacksOnNativeCallerThread = false; - bool installGlobalSymbols = false; -}; - -} // namespace nativescript - -#endif // NATIVESCRIPT_FFI_SHARED_DIRECT_NATIVE_API_DIRECT_H diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiBridge.h b/NativeScript/ffi/shared/jsi/NativeApiJsiBridge.h deleted file mode 100644 index d543b1ec3..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiBridge.h +++ /dev/null @@ -1,1705 +0,0 @@ -thread_local bool gDispatchNativeCallsToUI = false; -thread_local bool gExecutingDispatchedUINativeCall = false; -thread_local int gSynchronousNativeInvocationDepth = 0; -thread_local int gNativeCallerThreadJsiCallbackDepth = 0; -thread_local std::vector gNativeCallbackExceptionCaptureStack; -std::atomic gActiveSynchronousNativeInvocationDepth{0}; -static char gNativeApiJsiExtendedClassKey; - -void markNativeApiJsiExtendedClass(Class cls) { - if (cls == Nil) { - return; - } - objc_setAssociatedObject(cls, &gNativeApiJsiExtendedClassKey, @YES, - OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -bool isNativeApiJsiExtendedClass(Class cls) { - Class current = cls; - while (current != Nil) { - if (objc_getAssociatedObject(current, &gNativeApiJsiExtendedClassKey) != nil) { - return true; - } - current = class_getSuperclass(current); - } - return false; -} - -class ScopedNativeApiUINativeCallDispatch final { - public: - ScopedNativeApiUINativeCallDispatch() - : previous_(gDispatchNativeCallsToUI) { - gDispatchNativeCallsToUI = true; - } - - ~ScopedNativeApiUINativeCallDispatch() { - gDispatchNativeCallsToUI = previous_; - } - - private: - bool previous_ = false; -}; - -bool shouldDispatchNativeCallToUI() { - return gDispatchNativeCallsToUI && ![NSThread isMainThread]; -} - -class ScopedNativeApiSynchronousInvocation final { - public: - ScopedNativeApiSynchronousInvocation() { - gSynchronousNativeInvocationDepth += 1; - gActiveSynchronousNativeInvocationDepth.fetch_add(1, - std::memory_order_acq_rel); - } - - ~ScopedNativeApiSynchronousInvocation() { - gSynchronousNativeInvocationDepth -= 1; - gActiveSynchronousNativeInvocationDepth.fetch_sub(1, - std::memory_order_acq_rel); - } -}; - -class ScopedNativeCallerThreadJsiCallback final { - public: - ScopedNativeCallerThreadJsiCallback() { - gNativeCallerThreadJsiCallbackDepth += 1; - } - - ~ScopedNativeCallerThreadJsiCallback() { - gNativeCallerThreadJsiCallbackDepth -= 1; - } - - ScopedNativeCallerThreadJsiCallback( - const ScopedNativeCallerThreadJsiCallback&) = delete; - ScopedNativeCallerThreadJsiCallback& operator=( - const ScopedNativeCallerThreadJsiCallback&) = delete; -}; - -class ScopedNativeCallbackExceptionCapture final { - public: - explicit ScopedNativeCallbackExceptionCapture(std::string* message) - : message_(message) { - gNativeCallbackExceptionCaptureStack.push_back(message_); - } - - ~ScopedNativeCallbackExceptionCapture() { - if (!gNativeCallbackExceptionCaptureStack.empty() && - gNativeCallbackExceptionCaptureStack.back() == message_) { - gNativeCallbackExceptionCaptureStack.pop_back(); - } - } - - ScopedNativeCallbackExceptionCapture( - const ScopedNativeCallbackExceptionCapture&) = delete; - ScopedNativeCallbackExceptionCapture& operator=( - const ScopedNativeCallbackExceptionCapture&) = delete; - - private: - std::string* message_ = nullptr; -}; - -bool recordNativeCallbackException(const std::string& message) { - if (gNativeCallbackExceptionCaptureStack.empty()) { - return false; - } - - std::string* captured = gNativeCallbackExceptionCaptureStack.back(); - if (captured == nullptr) { - return false; - } - - if (captured->empty()) { - *captured = message; - } - return true; -} - -template -void performNativeInvocation(Runtime& runtime, - const std::function)>& - invoker, - Invocation&& invocation) { - NSString* exceptionDescription = nil; - std::string callbackException; - auto run = [&]() { - ScopedNativeApiSynchronousInvocation synchronousInvocation; - ScopedNativeCallbackExceptionCapture callbackExceptionCapture( - &callbackException); - @try { - invocation(); - } @catch (NSException* exception) { - exceptionDescription = [exception.description copy]; - } - }; - - bool skipInvoker = gNativeCallerThreadJsiCallbackDepth > 0; - if (shouldDispatchNativeCallToUI()) { - dispatch_sync(dispatch_get_main_queue(), ^{ - bool previous = gExecutingDispatchedUINativeCall; - gExecutingDispatchedUINativeCall = true; - if (invoker && !skipInvoker) { - invoker(run); - } else { - run(); - } - gExecutingDispatchedUINativeCall = previous; - }); - } else if (invoker && !skipInvoker) { - invoker(run); - } else { - run(); - } - - if (exceptionDescription != nil) { - std::string message = exceptionDescription.UTF8String ?: ""; - [exceptionDescription release]; - throw facebook::jsi::JSError(runtime, message); - } - if (!callbackException.empty()) { - throw facebook::jsi::JSError(runtime, callbackException); - } -} - -enum class NativeApiSymbolKind { - Class, - Function, - Constant, - Protocol, - Enum, - Struct, - Union, -}; - -struct NativeApiSymbol { - NativeApiSymbolKind kind; - MDSectionOffset offset = 0; - MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL; - std::string name; - std::string runtimeName; -}; - -struct NativeApiMember { - std::string name; - std::string selectorName; - std::string setterSelectorName; - MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; - MDSectionOffset setterSignatureOffset = MD_SECTION_OFFSET_NULL; - MDMemberFlag flags = metagen::mdMemberFlagNull; - bool property = false; - bool readonly = false; -}; - -struct NativeApiJsiAggregateInfo; - -struct NativeApiJsiFfiType { - ffi_type type = {}; - std::vector elements; - - NativeApiJsiFfiType() { - type.type = FFI_TYPE_STRUCT; - type.size = 0; - type.alignment = 0; - type.elements = nullptr; - } - - void finalize() { - elements.push_back(nullptr); - type.elements = elements.data(); - } -}; - -struct NativeApiJsiType { - MDTypeKind kind = metagen::mdTypeVoid; - ffi_type* ffiType = &ffi_type_void; - bool supported = true; - bool returnOwned = false; - MDSectionOffset signatureOffset = MD_SECTION_OFFSET_NULL; - MDSectionOffset aggregateOffset = MD_SECTION_OFFSET_NULL; - bool aggregateIsUnion = false; - uint16_t arraySize = 0; - std::shared_ptr elementType; - std::shared_ptr aggregateInfo; - std::shared_ptr ownedFfiType; -}; - -struct NativeApiJsiAggregateField { - std::string name; - uint16_t offset = 0; - NativeApiJsiType type; -}; - -struct NativeApiJsiAggregateInfo { - std::string name; - uint16_t size = 0; - bool isUnion = false; - MDSectionOffset offset = MD_SECTION_OFFSET_NULL; - std::vector fields; - std::shared_ptr ffi; -}; - -std::string jsifySelector(const char* selector) { - std::string jsifiedSelector; - bool nextUpper = false; - for (const char* c = selector; c != nullptr && *c != '\0'; c++) { - if (*c == ':') { - nextUpper = true; - } else if (nextUpper) { - jsifiedSelector += static_cast(toupper(*c)); - nextUpper = false; - } else { - jsifiedSelector += *c; - } - } - return jsifiedSelector; -} - -std::string booleanGetterSelectorForProperty(const std::string& property) { - if (property.empty()) { - return property; - } - - std::string selector = "is"; - selector += static_cast(toupper(property[0])); - selector += property.substr(1); - return selector; -} - -std::optional runtimeBooleanGetterSelectorForProperty( - Class cls, bool staticMethod, const std::string& property) { - if (cls == nil || property.empty()) { - return std::nullopt; - } - - std::string selectorName = booleanGetterSelectorForProperty(property); - SEL selector = sel_getUid(selectorName.c_str()); - if ((!staticMethod && class_getInstanceMethod(cls, selector) != nullptr) || - (staticMethod && class_getClassMethod(cls, selector) != nullptr)) { - return selectorName; - } - return std::nullopt; -} - -std::optional runtimeSelectorNameForProperty( - Class cls, bool staticMethod, const std::string& property) { - if (cls == nil || property.empty()) { - return std::nullopt; - } - -#if TARGET_OS_OSX - if (property == "initWithRedGreenBlueAlpha") { - const char* candidates[] = { - "initWithSRGBRed:green:blue:alpha:", - "initWithCalibratedRed:green:blue:alpha:", - }; - for (const char* candidate : candidates) { - SEL selector = sel_getUid(candidate); - if ((!staticMethod && class_getInstanceMethod(cls, selector) != nullptr) || - (staticMethod && class_getClassMethod(cls, selector) != nullptr)) { - return std::string(candidate); - } - } - } else if (property == "colorWithRedGreenBlueAlpha") { - const char* candidates[] = { - "colorWithSRGBRed:green:blue:alpha:", - "colorWithCalibratedRed:green:blue:alpha:", - }; - for (const char* candidate : candidates) { - SEL selector = sel_getUid(candidate); - if ((!staticMethod && class_getInstanceMethod(cls, selector) != nullptr) || - (staticMethod && class_getClassMethod(cls, selector) != nullptr)) { - return std::string(candidate); - } - } - } -#endif - - if (auto selectorName = - runtimeBooleanGetterSelectorForProperty(cls, staticMethod, property)) { - return selectorName; - } - - Class scan = staticMethod ? object_getClass(cls) : cls; - while (scan != Nil) { - unsigned int methodCount = 0; - Method* methods = class_copyMethodList(scan, &methodCount); - for (unsigned int i = 0; i < methodCount; i++) { - SEL selector = method_getName(methods[i]); - const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; - if (selectorName != nullptr && - (property == selectorName || jsifySelector(selectorName) == property)) { - std::string result(selectorName); - free(methods); - return result; - } - } - free(methods); - scan = class_getSuperclass(scan); - } - - return std::nullopt; -} - -std::string setterSelectorForProperty(const std::string& property) { - if (property.empty()) { - return property; - } - - std::string selector = "set"; - selector += static_cast(toupper(property[0])); - selector += property.substr(1); - selector += ":"; - return selector; -} - -bool hasRuntimeSetterForProperty(Class cls, bool staticMethod, - const std::string& property) { - if (cls == nil || property.empty()) { - return false; - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - return staticMethod ? class_getClassMethod(cls, selector) != nullptr - : class_getInstanceMethod(cls, selector) != nullptr; -} - -size_t selectorArgumentCount(const std::string& selector) { - return static_cast( - std::count(selector.begin(), selector.end(), ':')); -} - -const NativeApiMember* selectMethodMember( - const std::vector& members, const std::string& property, - bool staticMethod, size_t argumentCount) { - const NativeApiMember* fallback = nullptr; - for (const auto& member : members) { - if (member.property || member.name != property) { - continue; - } - - bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic != staticMethod) { - continue; - } - - if (fallback == nullptr) { - fallback = &member; - } - if (selectorArgumentCount(member.selectorName) == argumentCount) { - return &member; - } - } - return fallback; -} - -const NativeApiMember* selectPropertyMember( - const std::vector& members, const std::string& property, - bool staticMethod) { - for (const auto& member : members) { - if (!member.property || member.name != property) { - continue; - } - - bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic == staticMethod) { - return &member; - } - } - return nullptr; -} - -const NativeApiMember* selectWritablePropertyMember( - const std::vector& members, const std::string& property, - bool staticMethod) { - const NativeApiMember* fallback = nullptr; - for (const auto& member : members) { - if (!member.property || member.name != property) { - continue; - } - - bool memberIsStatic = (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic != staticMethod) { - continue; - } - - if (fallback == nullptr) { - fallback = &member; - } - if (!member.readonly && !member.setterSelectorName.empty()) { - return &member; - } - } - return fallback; -} - -void skipMetadataJsiType(MDMetadataReader* metadata, MDSectionOffset* offset); -Protocol* lookupProtocolByNativeName(const std::string& name); - -inline uintptr_t normalizeRuntimePointer(uintptr_t pointer) { -#if INTPTR_MAX == INT64_MAX - return pointer & 0x0000FFFFFFFFFFFFULL; -#else - return pointer; -#endif -} - -class NativeApiJsiBridge { - public: - explicit NativeApiJsiBridge(const NativeApiJsiConfig& config) - : metadata_(loadMetadata(config)), - scheduler_(config.scheduler), - nativeInvocationInvoker_(config.nativeInvocationInvoker), - nativeCallbackInvoker_(config.nativeCallbackInvoker), - jsThreadCallbackInvoker_(config.jsThreadCallbackInvoker), - invokeCallbacksOnNativeCallerThread_( - config.invokeCallbacksOnNativeCallerThread) { - selfDl_ = dlopen(nullptr, RTLD_NOW); - buildSymbolIndexes(); - } - - ~NativeApiJsiBridge() { - if (selfDl_ != nullptr) { - dlclose(selfDl_); - } - } - - MDMetadataReader* metadata() const { return metadata_.get(); } - - void* selfDl() const { return selfDl_; } - - const NativeApiSymbol* find(const std::string& name) const { - auto it = symbolsByName_.find(name); - return it != symbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findClass(const std::string& name) const { - const NativeApiSymbol* symbol = find(name); - if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Class) { - return symbol; - } - auto it = classSymbolsByRuntimeName_.find(name); - return it != classSymbolsByRuntimeName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findClassByOffset(MDSectionOffset offset) const { - auto it = classSymbolsByOffset_.find(offset); - return it != classSymbolsByOffset_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findClassForRuntimeClass(Class cls) const { - Class current = cls; - while (current != Nil) { - const char* name = class_getName(current); - if (name != nullptr) { - if (const NativeApiSymbol* symbol = findClass(name)) { - return symbol; - } - } - current = class_getSuperclass(current); - } - return nullptr; - } - - const NativeApiSymbol* findClassForRuntimePointer(void* pointer) const { - if (pointer == nullptr) { - return nullptr; - } - - auto it = classSymbolsByRuntimePointer_.find( - normalizeRuntimePointer(reinterpret_cast(pointer))); - return it != classSymbolsByRuntimePointer_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findProtocolForRuntimePointer(void* pointer) const { - if (pointer == nullptr) { - return nullptr; - } - - auto it = protocolSymbolsByRuntimePointer_.find( - normalizeRuntimePointer(reinterpret_cast(pointer))); - return it != protocolSymbolsByRuntimePointer_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findFunction(const std::string& name) const { - auto it = functionSymbolsByName_.find(name); - return it != functionSymbolsByName_.end() ? &it->second : nullptr; - } - - void rememberRoundTripValue(Runtime& runtime, const void* native, - const Value& value) { - if (native == nullptr) { - return; - } - std::lock_guard lock(roundTripValuesMutex_); - roundTripValues_[normalizeRuntimePointer( - reinterpret_cast(native))] = - std::make_shared(runtime, value); - } - - Value findRoundTripValue(Runtime& runtime, const void* native) const { - if (native == nullptr) { - return Value::undefined(); - } - std::lock_guard lock(roundTripValuesMutex_); - auto it = roundTripValues_.find( - normalizeRuntimePointer(reinterpret_cast(native))); - if (it == roundTripValues_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void forgetRoundTripValue(const void* native) { - if (native == nullptr) { - return; - } - std::lock_guard lock(roundTripValuesMutex_); - roundTripValues_.erase( - normalizeRuntimePointer(reinterpret_cast(native))); - } - - void rememberClassValue(Runtime& runtime, Class cls, const Value& value) { - if (cls == Nil) { - return; - } - classValues_[normalizeRuntimePointer(reinterpret_cast(cls))] = - std::make_shared(runtime, value); - } - - Value findClassValue(Runtime& runtime, Class cls) const { - if (cls == Nil) { - return Value::undefined(); - } - auto it = classValues_.find( - normalizeRuntimePointer(reinterpret_cast(cls))); - if (it == classValues_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void rememberClassPrototype(Runtime& runtime, Class cls, const Value& value) { - if (cls == Nil) { - return; - } - classPrototypes_[normalizeRuntimePointer(reinterpret_cast(cls))] = - std::make_shared(runtime, value); - } - - Value findClassPrototype(Runtime& runtime, Class cls) const { - if (cls == Nil) { - return Value::undefined(); - } - auto it = classPrototypes_.find( - normalizeRuntimePointer(reinterpret_cast(cls))); - if (it == classPrototypes_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void setObjectExpando(Runtime& runtime, const void* native, - const std::string& property, const Value& value) { - if (native == nullptr || property.empty()) { - return; - } - objectExpandos_[normalizeRuntimePointer(reinterpret_cast(native))] - [property] = std::make_shared(runtime, value); - } - - Value findObjectExpando(Runtime& runtime, const void* native, - const std::string& property) const { - if (native == nullptr || property.empty()) { - return Value::undefined(); - } - auto objectIt = objectExpandos_.find( - normalizeRuntimePointer(reinterpret_cast(native))); - if (objectIt == objectExpandos_.end()) { - return Value::undefined(); - } - auto propertyIt = objectIt->second.find(property); - if (propertyIt == objectIt->second.end() || propertyIt->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *propertyIt->second); - } - - void forgetObjectExpandos(const void* native) { - if (native == nullptr) { - return; - } - objectExpandos_.erase( - normalizeRuntimePointer(reinterpret_cast(native))); - } - - void rememberPointerValue(Runtime& runtime, const void* native, - const Value& value) { - pointerValues_[reinterpret_cast(native)] = - std::make_shared(runtime, value); - } - - Value findPointerValue(Runtime& runtime, const void* native) const { - auto it = pointerValues_.find(reinterpret_cast(native)); - if (it == pointerValues_.end() || it->second == nullptr) { - return Value::undefined(); - } - return Value(runtime, *it->second); - } - - void forgetPointerValue(const void* native) { - if (native == nullptr) { - return; - } - pointerValues_.erase(reinterpret_cast(native)); - } - - const NativeApiSymbol* findConstant(const std::string& name) const { - auto it = constantSymbolsByName_.find(name); - return it != constantSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findProtocol(const std::string& name) const { - const NativeApiSymbol* symbol = find(name); - if (symbol != nullptr && symbol->kind == NativeApiSymbolKind::Protocol) { - return symbol; - } - auto it = protocolSymbolsByRuntimeName_.find(name); - return it != protocolSymbolsByRuntimeName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findEnum(const std::string& name) const { - auto it = enumSymbolsByName_.find(name); - return it != enumSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findStruct(const std::string& name) const { - auto it = structSymbolsByName_.find(name); - return it != structSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findUnion(const std::string& name) const { - auto it = unionSymbolsByName_.find(name); - return it != unionSymbolsByName_.end() ? &it->second : nullptr; - } - - const NativeApiSymbol* findAggregate(const std::string& name) const { - const NativeApiSymbol* symbol = findStruct(name); - if (symbol != nullptr) { - return symbol; - } - return findUnion(name); - } - - size_t classCount() const { return classNames_.size(); } - size_t functionCount() const { return functionNames_.size(); } - size_t constantCount() const { return constantNames_.size(); } - size_t protocolCount() const { return protocolNames_.size(); } - size_t enumCount() const { return enumNames_.size(); } - size_t structCount() const { return structNames_.size(); } - size_t unionCount() const { return unionNames_.size(); } - - const std::vector& classNames() const { return classNames_; } - const std::vector& functionNames() const { return functionNames_; } - const std::vector& constantNames() const { return constantNames_; } - const std::vector& protocolNames() const { return protocolNames_; } - const std::vector& enumNames() const { return enumNames_; } - const std::vector& structNames() const { return structNames_; } - const std::vector& unionNames() const { return unionNames_; } - std::shared_ptr scheduler() const { return scheduler_; } - const std::function)>& nativeInvocationInvoker() - const { - return nativeInvocationInvoker_; - } - const std::function)>& nativeCallbackInvoker() - const { - return nativeCallbackInvoker_; - } - const std::function)>& jsThreadCallbackInvoker() - const { - return jsThreadCallbackInvoker_; - } - bool invokeCallbacksOnNativeCallerThread() const { - return invokeCallbacksOnNativeCallerThread_; - } - std::thread::id jsThreadId() const { return jsThreadId_; } - - void retainJsiLifetime(std::shared_ptr lifetime) { - if (lifetime == nullptr) { - return; - } - std::lock_guard lock(retainedLifetimesMutex_); - retainedLifetimes_.push_back(std::move(lifetime)); - } - - const std::vector& membersForClass( - const NativeApiSymbol& symbol) const { - auto cached = membersByClassOffset_.find(symbol.offset); - if (cached != membersByClassOffset_.end()) { - return cached->second; - } - - auto inserted = membersByClassOffset_.emplace( - symbol.offset, readMembersForClassHierarchy(symbol)); - return inserted.first->second; - } - - const std::vector& surfaceMembersForClass( - const NativeApiSymbol& symbol) const { - auto cached = surfaceMembersByClassOffset_.find(symbol.offset); - if (cached != surfaceMembersByClassOffset_.end()) { - return cached->second; - } - - auto inserted = surfaceMembersByClassOffset_.emplace( - symbol.offset, readSurfaceMembersForClass(symbol)); - return inserted.first->second; - } - - const std::vector& membersForProtocol( - const NativeApiSymbol& symbol) const { - auto cached = membersByProtocolOffset_.find(symbol.offset); - if (cached != membersByProtocolOffset_.end()) { - return cached->second; - } - - auto inserted = membersByProtocolOffset_.emplace( - symbol.offset, readMembersForProtocolHierarchy(symbol.offset)); - return inserted.first->second; - } - - std::shared_ptr aggregateInfoFor( - MDSectionOffset aggregateOffset, bool isUnion); - - std::shared_ptr aggregateInfoFor( - const NativeApiSymbol& symbol) { - return aggregateInfoFor(symbol.offset, - symbol.kind == NativeApiSymbolKind::Union); - } - - private: - static std::unique_ptr loadMetadataFromFile( - const char* metadataPath) { - const char* path = metadataPath != nullptr ? metadataPath : "metadata.nsmd"; - FILE* file = fopen(path, "rb"); - if (file == nullptr) { - throw std::runtime_error(std::string("metadata.nsmd not found: ") + path); - } - - fseek(file, 0, SEEK_END); - long size = ftell(file); - fseek(file, 0, SEEK_SET); - if (size <= 0) { - fclose(file); - throw std::runtime_error(std::string("metadata.nsmd is empty: ") + path); - } - - void* buffer = malloc(static_cast(size)); - if (buffer == nullptr) { - fclose(file); - throw std::bad_alloc(); - } - - size_t read = fread(buffer, 1, static_cast(size), file); - fclose(file); - if (read != static_cast(size)) { - free(buffer); - throw std::runtime_error(std::string("failed to read metadata: ") + path); - } - - return std::make_unique(buffer, true); - } - - static std::unique_ptr loadMetadata( - const NativeApiJsiConfig& config) { - if (config.metadataPtr != nullptr && - *static_cast(config.metadataPtr) != '\0') { -#ifdef EMBED_METADATA_SIZE - return std::make_unique((void*)embedded_metadata); -#else - return std::make_unique( - const_cast(config.metadataPtr)); -#endif - } - -#ifdef EMBED_METADATA_SIZE - if (config.metadataPath == nullptr) { - return std::make_unique((void*)embedded_metadata); - } -#endif - - unsigned long segmentSize = 0; - auto segmentData = getsegmentdata( - reinterpret_cast(_dyld_get_image_header(0)), - "__objc_metadata", &segmentSize); - if (segmentData != nullptr && segmentSize > 0) { - return std::make_unique(segmentData); - } - - return loadMetadataFromFile(config.metadataPath); - } - - void addSymbol(NativeApiSymbolKind kind, MDSectionOffset offset, - const char* name, const char* runtimeName = nullptr, - MDSectionOffset superclassOffset = MD_SECTION_OFFSET_NULL) { - if (name == nullptr || name[0] == '\0') { - return; - } - - NativeApiSymbol symbol{ - .kind = kind, - .offset = offset, - .superclassOffset = superclassOffset, - .name = name, - .runtimeName = runtimeName != nullptr ? runtimeName : name, - }; - - switch (kind) { - case NativeApiSymbolKind::Class: - classNames_.push_back(symbol.name); - break; - case NativeApiSymbolKind::Function: - functionNames_.push_back(symbol.name); - functionSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Constant: - constantNames_.push_back(symbol.name); - constantSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Protocol: - protocolNames_.push_back(symbol.name); - break; - case NativeApiSymbolKind::Enum: - enumNames_.push_back(symbol.name); - enumSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Struct: - structNames_.push_back(symbol.name); - structSymbolsByName_[symbol.name] = symbol; - break; - case NativeApiSymbolKind::Union: - unionNames_.push_back(symbol.name); - unionSymbolsByName_[symbol.name] = symbol; - break; - } - - symbolsByName_[symbol.name] = symbol; - if (kind == NativeApiSymbolKind::Class) { - classSymbolsByOffset_[symbol.offset] = symbol; - classSymbolsByRuntimeName_[symbol.runtimeName] = symbol; - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls != Nil) { - classSymbolsByRuntimePointer_[normalizeRuntimePointer( - reinterpret_cast(cls))] = symbol; - } - } else if (kind == NativeApiSymbolKind::Protocol) { - protocolSymbolsByOffset_[symbol.offset] = symbol; - protocolSymbolsByRuntimeName_[symbol.runtimeName] = symbol; - auto rememberProtocolRuntimeName = [&](const std::string& runtimeName) { - if (runtimeName.empty()) { - return; - } - protocolSymbolsByRuntimeName_[runtimeName] = symbol; - Protocol* runtimeProtocol = lookupProtocolByNativeName(runtimeName); - if (runtimeProtocol != nullptr) { - protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( - reinterpret_cast(runtimeProtocol))] = symbol; - } - }; - if (symbol.name.size() > 9 && - std::isdigit(static_cast(symbol.name.back()))) { - size_t digitsStart = symbol.name.size(); - while (digitsStart > 0 && - std::isdigit(static_cast(symbol.name[digitsStart - 1]))) { - digitsStart--; - } - constexpr const char* protocolSuffix = "Protocol"; - size_t protocolSuffixLength = std::strlen(protocolSuffix); - if (digitsStart > protocolSuffixLength && - symbol.name.compare(digitsStart - protocolSuffixLength, - protocolSuffixLength, protocolSuffix) == 0) { - rememberProtocolRuntimeName( - symbol.name.substr(0, digitsStart - protocolSuffixLength)); - } - } - Protocol* protocol = lookupProtocolByNativeName(symbol.runtimeName); - if (protocol == nullptr && symbol.runtimeName != symbol.name) { - protocol = lookupProtocolByNativeName(symbol.name); - } - if (protocol != nullptr) { - protocolSymbolsByRuntimePointer_[normalizeRuntimePointer( - reinterpret_cast(protocol))] = symbol; - } - } else if (kind == NativeApiSymbolKind::Struct) { - structSymbolsByOffset_[symbol.offset] = symbol; - } else if (kind == NativeApiSymbolKind::Union) { - unionSymbolsByOffset_[symbol.offset] = symbol; - } - } - - void addAggregateAliases(NativeApiSymbolKind kind, MDSectionOffset offset, - const std::string& name) { - if (name.empty()) { - return; - } - - if (!name.empty() && name[0] == '_') { - std::string alias = name.substr(1); - if (!alias.empty() && symbolsByName_.find(alias) == symbolsByName_.end()) { - addSymbol(kind, offset, alias.c_str(), name.c_str()); - } - } - - constexpr const char* suffix = "Struct"; - if (name.size() < std::strlen(suffix) || - name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), - suffix) != 0) { - std::string alias = name + suffix; - if (symbolsByName_.find(alias) == symbolsByName_.end()) { - addSymbol(kind, offset, alias.c_str(), name.c_str()); - } - } - } - - void buildSymbolIndexes() { - if (metadata_ == nullptr) { - return; - } - - indexConstants(); - indexEnums(); - indexFunctions(); - indexProtocols(); - indexClasses(); - indexStructs(); - indexUnions(); - } - - static void skipConstantValue(MDMetadataReader* metadata, - MDSectionOffset& offset, - metagen::MDVariableEvalKind evalKind) { - switch (evalKind) { - case metagen::mdEvalNone: - skipMetadataJsiType(metadata, &offset); - break; - case metagen::mdEvalInt64: - offset += sizeof(int64_t); - break; - case metagen::mdEvalDouble: - offset += sizeof(double); - break; - case metagen::mdEvalString: - offset += sizeof(MDSectionOffset); - break; - } - } - - void indexConstants() { - MDSectionOffset offset = metadata_->constantsOffset; - while (offset < metadata_->enumsOffset) { - MDSectionOffset originalOffset = offset; - addSymbol(NativeApiSymbolKind::Constant, originalOffset, - metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - auto evalKind = metadata_->getVariableEvalKind(offset); - offset += sizeof(metagen::MDVariableEvalKind); - skipConstantValue(metadata_.get(), offset, evalKind); - } - } - - void indexEnums() { - MDSectionOffset offset = metadata_->enumsOffset; - while (offset < metadata_->signaturesOffset) { - MDSectionOffset originalOffset = offset; - addSymbol(NativeApiSymbolKind::Enum, originalOffset, - metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - - bool next = true; - while (next) { - auto nameOffset = metadata_->getOffset(offset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - offset += sizeof(MDSectionOffset); - offset += sizeof(int64_t); - } - } - } - - void indexFunctions() { - MDSectionOffset offset = metadata_->functionsOffset; - while (offset < metadata_->protocolsOffset) { - MDSectionOffset originalOffset = offset; - addSymbol(NativeApiSymbolKind::Function, originalOffset, - metadata_->getString(offset)); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - offset += sizeof(metagen::MDFunctionFlag); - } - } - - void indexProtocols() { - MDSectionOffset offset = metadata_->protocolsOffset; - while (offset < metadata_->classesOffset) { - MDSectionOffset originalOffset = offset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - addSymbol(NativeApiSymbolKind::Protocol, originalOffset, - metadata_->resolveString(nameOffset)); - - while (next) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - - next = true; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - if (flags == metagen::mdMemberFlagNull) { - break; - } - - skipMember(flags, offset); - } - } - } - - void indexClasses() { - MDSectionOffset offset = metadata_->classesOffset; - while (offset < metadata_->structsOffset) { - MDSectionOffset originalOffset = offset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - auto runtimeNameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - - auto name = metadata_->resolveString(nameOffset); - const char* runtimeName = name; - if (runtimeNameOffset != MD_SECTION_OFFSET_NULL) { - runtimeName = metadata_->resolveString(runtimeNameOffset); - } - - while (hasProtocols) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - - auto superclass = metadata_->getOffset(offset); - offset += sizeof(superclass); - MDSectionOffset superclassOffset = - superclass & ~metagen::mdSectionOffsetNext; - if (superclassOffset != MD_SECTION_OFFSET_NULL) { - superclassOffset += metadata_->classesOffset; - } - - addSymbol(NativeApiSymbolKind::Class, originalOffset, name, runtimeName, - superclassOffset); - - bool next = (superclass & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - skipMember(flags, offset); - } - } - } - - void skipAggregateFields(MDSectionOffset& offset, bool isUnion) const { - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - if (!isUnion) { - offset += sizeof(uint16_t); - } - skipMetadataJsiType(metadata_.get(), &offset); - } - } - - void indexStructs() { - MDSectionOffset offset = metadata_->structsOffset; - while (offset < metadata_->unionsOffset) { - if (metadata_->getOffset(offset) == 0) { - break; - } - MDSectionOffset originalOffset = offset; - const char* name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(uint16_t); - addSymbol(NativeApiSymbolKind::Struct, originalOffset, name); - addAggregateAliases(NativeApiSymbolKind::Struct, originalOffset, - name != nullptr ? name : ""); - skipAggregateFields(offset, false); - } - } - - void indexUnions() { - MDSectionOffset offset = metadata_->unionsOffset; - while (metadata_->getOffset(offset) != 0) { - MDSectionOffset originalOffset = offset; - const char* name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(uint16_t); - addSymbol(NativeApiSymbolKind::Union, originalOffset, name); - addAggregateAliases(NativeApiSymbolKind::Union, originalOffset, - name != nullptr ? name : ""); - skipAggregateFields(offset, true); - } - } - - void skipMember(MDMemberFlag flags, MDSectionOffset& offset) const { - if ((flags & metagen::mdMemberProperty) != 0) { - bool readonly = (flags & metagen::mdMemberReadonly) != 0; - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - if (!readonly) { - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - } - return; - } - - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - } - - std::vector readProtocolOffsetsForClass( - MDSectionOffset classOffset, MDSectionOffset* memberOffset = nullptr, - MDSectionOffset* superclassOffsetOut = nullptr) const { - std::vector protocols; - if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { - return protocols; - } - - MDSectionOffset offset = classOffset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - - while (hasProtocols) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - protocolOffset &= ~metagen::mdSectionOffsetNext; - if (protocolOffset != MD_SECTION_OFFSET_NULL) { - protocols.push_back(protocolOffset + metadata_->protocolsOffset); - } - } - - auto superclass = metadata_->getOffset(offset); - offset += sizeof(superclass); - const bool hasMembers = (superclass & metagen::mdSectionOffsetNext) != 0; - if (superclassOffsetOut != nullptr) { - MDSectionOffset superclassOffset = - superclass & ~metagen::mdSectionOffsetNext; - *superclassOffsetOut = - superclassOffset != MD_SECTION_OFFSET_NULL - ? superclassOffset + metadata_->classesOffset - : MD_SECTION_OFFSET_NULL; - } - if (memberOffset != nullptr) { - *memberOffset = hasMembers ? offset : MD_SECTION_OFFSET_NULL; - } - return protocols; - } - - std::vector readOwnMembersForClass( - MDSectionOffset classOffset) const { - std::vector members; - if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - MDSectionOffset memberOffset = MD_SECTION_OFFSET_NULL; - for (MDSectionOffset protocolOffset : - readProtocolOffsetsForClass(classOffset, &memberOffset)) { - auto protocol = protocolSymbolsByOffset_.find(protocolOffset); - if (protocol == protocolSymbolsByOffset_.end()) { - continue; - } - const auto& protocolMembers = membersForProtocol(protocol->second); - members.insert(members.end(), protocolMembers.begin(), - protocolMembers.end()); - } - - if (memberOffset != MD_SECTION_OFFSET_NULL) { - std::vector ownMembers = - readMembersAtOffset(memberOffset); - members.insert(members.end(), ownMembers.begin(), ownMembers.end()); - } - return members; - } - - std::vector readMembersForClass( - MDSectionOffset classOffset) const { - std::vector members; - if (metadata_ == nullptr || classOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - MDSectionOffset offset = classOffset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - - while (hasProtocols) { - auto protocolOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - - auto superclass = metadata_->getOffset(offset); - offset += sizeof(superclass); - - bool next = (superclass & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - if (flags == metagen::mdMemberFlagNull) { - break; - } - - NativeApiMember member; - member.flags = flags; - if ((flags & metagen::mdMemberProperty) != 0) { - member.property = true; - member.readonly = (flags & metagen::mdMemberReadonly) != 0; - member.name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - - if (!member.readonly) { - member.setterSelectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.setterSignatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - } - } else { - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - member.name = jsifySelector(member.selectorName.c_str()); - } - members.push_back(std::move(member)); - } - - return members; - } - - static bool memberIsStatic(const NativeApiMember& member) { - return (member.flags & metagen::mdMemberStatic) != 0; - } - - static bool sameMemberSlot(const NativeApiMember& lhs, - const NativeApiMember& rhs) { - return lhs.name == rhs.name && lhs.property == rhs.property && - memberIsStatic(lhs) == memberIsStatic(rhs); - } - - static bool sameMethodSelector(const NativeApiMember& lhs, - const NativeApiMember& rhs) { - return !lhs.property && !rhs.property && sameMemberSlot(lhs, rhs) && - lhs.selectorName == rhs.selectorName; - } - - static const NativeApiMember* findPropertyMember( - const std::vector& members, - const NativeApiMember& candidate) { - for (const auto& member : members) { - if (member.property && sameMemberSlot(member, candidate)) { - return &member; - } - } - return nullptr; - } - - static bool selectorExistsInMembers( - const std::vector& members, - const NativeApiMember& candidate) { - for (const auto& member : members) { - if (sameMethodSelector(member, candidate)) { - return true; - } - } - return false; - } - - static bool shouldSkipPropertyOverride( - const NativeApiMember* inherited, const NativeApiMember& member) { - if (inherited == nullptr || !inherited->property) { - return false; - } - - bool sameGetter = inherited->selectorName == member.selectorName; - bool sameSetter = - inherited->setterSelectorName == member.setterSelectorName; - if ((!inherited->readonly && member.readonly) || - (inherited->readonly == member.readonly && sameGetter && - (member.readonly || sameSetter))) { - return true; - } - return false; - } - - static void appendSurfaceMember( - std::vector& surface, - const std::vector& inheritedMembers, - const NativeApiMember& member) { - if (member.name.empty()) { - return; - } - - if (member.property) { - const NativeApiMember* inherited = - findPropertyMember(inheritedMembers, member); - if (shouldSkipPropertyOverride(inherited, member)) { - return; - } - - for (auto& existing : surface) { - if (!existing.property || !sameMemberSlot(existing, member)) { - continue; - } - if (existing.readonly && !member.readonly) { - existing = member; - } - return; - } - surface.push_back(member); - return; - } - - const bool keepInheritedMethod = - member.name == "alloc" || member.name == "toString" || - member.name == "superclass"; - if (!keepInheritedMethod && - selectorExistsInMembers(inheritedMembers, member)) { - return; - } - if (selectorExistsInMembers(surface, member)) { - return; - } - surface.push_back(member); - } - - std::vector readSurfaceMembersForClass( - const NativeApiSymbol& symbol) const { - std::vector inheritedMembers; - if (symbol.superclassOffset != MD_SECTION_OFFSET_NULL) { - auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); - if (superclass != classSymbolsByOffset_.end()) { - const auto& inherited = surfaceMembersForClass(superclass->second); - inheritedMembers.insert(inheritedMembers.end(), inherited.begin(), - inherited.end()); - } - } - - std::vector surface; - for (const auto& member : readOwnMembersForClass(symbol.offset)) { - appendSurfaceMember(surface, inheritedMembers, member); - } - return surface; - } - - std::vector readMembersAtOffset( - MDSectionOffset& offset) const { - std::vector members; - bool next = true; - while (next) { - auto flags = metadata_->getMemberFlag(offset); - next = (flags & metagen::mdMemberNext) != 0; - offset += sizeof(flags); - if (flags == metagen::mdMemberFlagNull) { - break; - } - - NativeApiMember member; - member.flags = flags; - if ((flags & metagen::mdMemberProperty) != 0) { - member.property = true; - member.readonly = (flags & metagen::mdMemberReadonly) != 0; - member.name = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - - if (!member.readonly) { - member.setterSelectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.setterSignatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - } - } else { - member.selectorName = metadata_->getString(offset); - offset += sizeof(MDSectionOffset); - member.signatureOffset = - metadata_->signaturesOffset + metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - member.name = jsifySelector(member.selectorName.c_str()); - } - members.push_back(std::move(member)); - } - return members; - } - - std::vector readMembersForProtocolHierarchy( - MDSectionOffset protocolOffset) const { - std::vector members; - if (metadata_ == nullptr || protocolOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - MDSectionOffset offset = protocolOffset; - auto nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - bool hasProtocols = (nameOffset & metagen::mdSectionOffsetNext) != 0; - - while (hasProtocols) { - auto inheritedOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - hasProtocols = (inheritedOffset & metagen::mdSectionOffsetNext) != 0; - inheritedOffset &= ~metagen::mdSectionOffsetNext; - if (inheritedOffset == MD_SECTION_OFFSET_NULL) { - continue; - } - - MDSectionOffset absoluteOffset = - inheritedOffset + metadata_->protocolsOffset; - auto inheritedSymbol = protocolSymbolsByOffset_.find(absoluteOffset); - if (inheritedSymbol != protocolSymbolsByOffset_.end()) { - const auto& inheritedMembers = - membersForProtocol(inheritedSymbol->second); - members.insert(members.end(), inheritedMembers.begin(), - inheritedMembers.end()); - } - } - - std::vector ownMembers = readMembersAtOffset(offset); - members.insert(members.end(), ownMembers.begin(), ownMembers.end()); - return members; - } - - std::vector readMembersForClassHierarchy( - const NativeApiSymbol& symbol) const { - std::vector members = readOwnMembersForClass(symbol.offset); - if (symbol.superclassOffset == MD_SECTION_OFFSET_NULL) { - return members; - } - - auto superclass = classSymbolsByOffset_.find(symbol.superclassOffset); - if (superclass != classSymbolsByOffset_.end()) { - const auto& inheritedMembers = membersForClass(superclass->second); - members.insert(members.end(), inheritedMembers.begin(), - inheritedMembers.end()); - } - return members; - } - - std::unique_ptr metadata_; - void* selfDl_ = nullptr; - std::unordered_map symbolsByName_; - std::unordered_map functionSymbolsByName_; - std::unordered_map constantSymbolsByName_; - std::unordered_map enumSymbolsByName_; - std::unordered_map structSymbolsByName_; - std::unordered_map unionSymbolsByName_; - std::unordered_map classSymbolsByRuntimeName_; - std::unordered_map protocolSymbolsByRuntimeName_; - std::unordered_map classSymbolsByRuntimePointer_; - std::unordered_map protocolSymbolsByRuntimePointer_; - mutable std::mutex roundTripValuesMutex_; - std::unordered_map> roundTripValues_; - std::unordered_map> classValues_; - std::unordered_map> classPrototypes_; - std::unordered_map> pointerValues_; - std::unordered_map>> - objectExpandos_; - std::unordered_map classSymbolsByOffset_; - std::unordered_map protocolSymbolsByOffset_; - std::vector classNames_; - std::vector functionNames_; - std::vector constantNames_; - std::vector protocolNames_; - std::vector enumNames_; - std::vector structNames_; - std::vector unionNames_; - std::shared_ptr scheduler_; - std::function)> nativeInvocationInvoker_; - std::function)> nativeCallbackInvoker_; - std::function)> jsThreadCallbackInvoker_; - bool invokeCallbacksOnNativeCallerThread_ = false; - mutable std::unordered_map> - membersByClassOffset_; - mutable std::unordered_map> - surfaceMembersByClassOffset_; - mutable std::unordered_map> - membersByProtocolOffset_; - std::unordered_map structSymbolsByOffset_; - std::unordered_map unionSymbolsByOffset_; - std::unordered_map> - aggregateInfoByOffset_; - std::unordered_set aggregateInfoInProgress_; - std::thread::id jsThreadId_ = std::this_thread::get_id(); - std::mutex retainedLifetimesMutex_; - std::vector> retainedLifetimes_; -}; - -Value makeString(Runtime& runtime, const std::string& value) { - return String::createFromUtf8(runtime, value); -} - -std::string readStringArg(Runtime& runtime, const Value* args, size_t count, - size_t index, const char* argumentName) { - if (index >= count || !args[index].isString()) { - throw facebook::jsi::JSError( - runtime, std::string(argumentName) + " must be a string."); - } - return args[index].asString(runtime).utf8(runtime); -} - -const char* kindName(NativeApiSymbolKind kind) { - switch (kind) { - case NativeApiSymbolKind::Class: - return "class"; - case NativeApiSymbolKind::Function: - return "function"; - case NativeApiSymbolKind::Constant: - return "constant"; - case NativeApiSymbolKind::Protocol: - return "protocol"; - case NativeApiSymbolKind::Enum: - return "enum"; - case NativeApiSymbolKind::Struct: - return "struct"; - case NativeApiSymbolKind::Union: - return "union"; - } - return "unknown"; -} - -Array namesToArray(Runtime& runtime, const std::vector& names) { - Array result(runtime, names.size()); - for (size_t i = 0; i < names.size(); i++) { - result.setValueAtIndex(runtime, i, makeString(runtime, names[i])); - } - return result; -} - -void addPropertyName(Runtime& runtime, std::vector& names, - const char* name) { - names.push_back(PropNameID::forAscii(runtime, name)); -} - -class NativeApiPointerHostObject; -class NativeApiObjectHostObject; -class NativeApiClassHostObject; -class NativeApiProtocolHostObject; -class NativeApiJsiArgumentFrame; - -Value callCFunction(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol, const Value* args, - size_t count); - -Value callObjCSelector(Runtime& runtime, - const std::shared_ptr& bridge, - id receiver, bool receiverIsClass, - const std::string& selectorName, - const NativeApiMember* member, - const Value* args, size_t count, - Class dispatchSuperClass = Nil); - -Value makeNativeObjectValue(Runtime& runtime, - const std::shared_ptr& bridge, - id object, bool ownsObject); - -Value makeNativeClassValue(Runtime& runtime, - const std::shared_ptr& bridge, - NativeApiSymbol symbol); - -Object symbolToObject(Runtime& runtime, const NativeApiSymbol& symbol) { - Object result(runtime); - result.setProperty(runtime, "kind", makeString(runtime, kindName(symbol.kind))); - result.setProperty(runtime, "name", makeString(runtime, symbol.name)); - result.setProperty(runtime, "runtimeName", - makeString(runtime, symbol.runtimeName)); - result.setProperty(runtime, "metadataOffset", - static_cast(symbol.offset)); - - if (symbol.kind == NativeApiSymbolKind::Class) { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - result.setProperty(runtime, "available", cls != nil); - if (cls != nil) { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", cls); - result.setProperty(runtime, "nativeAddress", makeString(runtime, address)); - } - } else if (symbol.kind == NativeApiSymbolKind::Struct || - symbol.kind == NativeApiSymbolKind::Union) { - result.setProperty(runtime, "available", true); - } - - return result; -} - -size_t nativeSizeForType(const NativeApiJsiType& type); -std::optional parseArrayIndexProperty(const std::string& property); - -NativeApiJsiType nativeObjectReturnType( - MDTypeKind kind = metagen::mdTypeAnyObject) { - NativeApiJsiType type; - type.kind = kind; - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; -} - -NativeApiJsiType nativeObjectReturnTypeForClass(Class cls) { - if (cls != Nil) { - const char* name = class_getName(cls); - if (name != nullptr && std::strcmp(name, "NSString") == 0) { - return nativeObjectReturnType(metagen::mdTypeNSStringObject); - } - if (name != nullptr && std::strcmp(name, "NSMutableString") == 0) { - return nativeObjectReturnType(metagen::mdTypeNSMutableStringObject); - } - } - return nativeObjectReturnType(metagen::mdTypeInstanceObject); -} - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value); -Object createPointer(Runtime& runtime, - const std::shared_ptr& bridge, - void* pointer, bool adopted = false); - -NativeApiJsiType primitiveInteropType(MDTypeKind kind); diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiCallbacks.h b/NativeScript/ffi/shared/jsi/NativeApiJsiCallbacks.h deleted file mode 100644 index cad9a54d1..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiCallbacks.h +++ /dev/null @@ -1,1974 +0,0 @@ -bool isObjectiveCObjectType(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: - return true; - default: - return false; - } -} - -#ifndef NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::shared_ptr(&runtime, [](Runtime*) {}); -} -#endif - -#ifndef NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE -class NativeApiJsiRuntimeScope final { - public: - explicit NativeApiJsiRuntimeScope(Runtime&) {} -}; -#endif - -struct NativeApiJsiSignature { - ffi_cif cif = {}; - NativeApiJsiType returnType; - std::vector argumentTypes; - std::vector ffiTypes; - std::string selectorName; - bool variadic = false; - bool prepared = false; - unsigned int implicitArgumentCount = 0; -}; - -enum class NativeApiJsiCallbackThreadPolicy { - Default, - UI, - JS, -}; - -NativeApiJsiCallbackThreadPolicy readJsiCallbackThreadPolicy( - Runtime& runtime, Object& functionObject) { - constexpr const char* propertyName = "__nativeScriptCallbackThread"; - try { - if (!functionObject.hasProperty(runtime, propertyName)) { - return NativeApiJsiCallbackThreadPolicy::Default; - } - Value policyValue = functionObject.getProperty(runtime, propertyName); - if (!policyValue.isString()) { - return NativeApiJsiCallbackThreadPolicy::Default; - } - std::string policy = policyValue.asString(runtime).utf8(runtime); - if (policy == "ui") { - return NativeApiJsiCallbackThreadPolicy::UI; - } - if (policy == "js") { - return NativeApiJsiCallbackThreadPolicy::JS; - } - } catch (const std::exception&) { - } - return NativeApiJsiCallbackThreadPolicy::Default; -} - -bool selectorEndsWithNSErrorParam(const std::string& selectorName) { - constexpr const char* suffix = "error:"; - size_t suffixLength = std::strlen(suffix); - return selectorName.size() >= suffixLength && - selectorName.compare(selectorName.size() - suffixLength, suffixLength, - suffix) == 0; -} - -bool isNSErrorOutJsiMethodSignature(const NativeApiJsiSignature& signature) { - if (signature.argumentTypes.empty() || signature.variadic || - !selectorEndsWithNSErrorParam(signature.selectorName)) { - return false; - } - - return signature.argumentTypes.back().kind == metagen::mdTypePointer; -} - -bool isNSErrorOutJsiMethodCallback(const NativeApiJsiSignature& signature) { - return signature.returnType.kind == metagen::mdTypeBool && - signature.implicitArgumentCount >= 2 && - isNSErrorOutJsiMethodSignature(signature); -} - -class NativeApiJsiArgumentFrame { - public: - explicit NativeApiJsiArgumentFrame(size_t count) : storage_(count), values_(count) {} - - ~NativeApiJsiArgumentFrame() { - for (char* string : ownedCStrings_) { - free(string); - } - for (void* buffer : ownedBuffers_) { - free(buffer); - } - for (id object : ownedObjects_) { - [object release]; - } - for (const auto& entry : temporaryRoundTripValues_) { - if (entry.first != nullptr) { - entry.first->forgetRoundTripValue(entry.second); - } - } - ownedLifetimes_.clear(); - } - - void* storageAt(size_t index, size_t size) { - storage_[index].assign(std::max(size, sizeof(void*)), 0); - values_[index] = storage_[index].data(); - return values_[index]; - } - - void addCString(char* value) { ownedCStrings_.push_back(value); } - void* addBuffer(size_t size) { - void* buffer = calloc(1, std::max(size, 1)); - if (buffer == nullptr) { - throw std::bad_alloc(); - } - ownedBuffers_.push_back(buffer); - return buffer; - } - void addObject(id value) { ownedObjects_.push_back(value); } - void addLifetime(std::shared_ptr value) { - if (value != nullptr) { - ownedLifetimes_.push_back(std::move(value)); - } - } - void rememberRoundTripValue( - const std::shared_ptr& bridge, Runtime& runtime, - const void* native, const Value& value) { - if (bridge == nullptr || native == nullptr) { - return; - } - bridge->rememberRoundTripValue(runtime, native, value); - temporaryRoundTripValues_.push_back({bridge, native}); - } - void** values() { return values_.empty() ? nullptr : values_.data(); } - - private: - std::vector> storage_; - std::vector values_; - std::vector ownedCStrings_; - std::vector ownedBuffers_; - std::vector ownedObjects_; - std::vector> ownedLifetimes_; - std::vector, const void*>> - temporaryRoundTripValues_; -}; - -class NativeApiMutableBuffer final : public MutableBuffer { - public: - explicit NativeApiMutableBuffer(size_t size) : data_(size) {} - NativeApiMutableBuffer(const void* data, size_t size) : data_(size) { - if (data != nullptr && size > 0) { - std::memcpy(data_.data(), data, size); - } - } - - size_t size() const override { return data_.size(); } - uint8_t* data() override { return data_.empty() ? nullptr : data_.data(); } - - private: - std::vector data_; -}; - -void convertJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame); - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value); - -Value wrapNativeFunctionPointer(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* pointer, - bool block); - -bool isObjectiveCObjectType(const NativeApiJsiType& type); - -struct NativeApiJsiBlockDescriptor { - unsigned long reserved = 0; - unsigned long size = 0; - void (*copyHelper)(void*, void*) = nullptr; - void (*disposeHelper)(void*) = nullptr; - const char* signature = nullptr; -}; - -struct NativeApiJsiBlockLiteral { - void* isa = nullptr; - int flags = 0; - int reserved = 0; - void* invoke = nullptr; - NativeApiJsiBlockDescriptor* descriptor = nullptr; - void* callback = nullptr; -}; - -constexpr int kNativeApiJsiBlockNeedsFree = (1 << 24); -constexpr int kNativeApiJsiBlockHasCopyDispose = (1 << 25); -constexpr int kNativeApiJsiBlockRefCountOne = (1 << 1); -constexpr int kNativeApiJsiBlockHasSignature = (1 << 30); - -void* nativeApiJsiStackBlockIsa() { - static void* isa = dlsym(RTLD_DEFAULT, "_NSConcreteStackBlock"); - return isa; -} - -void nativeApiJsiBlockCopy(void* dst, void* src); -void nativeApiJsiBlockDispose(void* src); - -std::string objcEncodingForJsiType(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeVoid: - return "v"; - case metagen::mdTypeBool: - return "B"; - case metagen::mdTypeChar: - return "c"; - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - return "C"; - case metagen::mdTypeSShort: - return "s"; - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - return "S"; - case metagen::mdTypeSInt: - return "i"; - case metagen::mdTypeUInt: - return "I"; - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - return "q"; - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - return "Q"; - case metagen::mdTypeFloat: - return "f"; - case metagen::mdTypeDouble: - return "d"; - case metagen::mdTypeString: - return "*"; - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: - return "@"; - case metagen::mdTypeClass: - return "#"; - case metagen::mdTypeSelector: - return ":"; - case metagen::mdTypeBlock: - return "@?"; - case metagen::mdTypeFunctionPointer: - return "^?"; - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: - if (type.elementType != nullptr && - type.elementType->kind != metagen::mdTypeVoid) { - return "^" + objcEncodingForJsiType(*type.elementType); - } - return "^v"; - case metagen::mdTypeStruct: - return "{" + - (type.aggregateInfo != nullptr ? type.aggregateInfo->name - : std::string("?")) + - "=}"; - case metagen::mdTypeArray: - return "[" + std::to_string(type.arraySize) + - (type.elementType != nullptr ? objcEncodingForJsiType(*type.elementType) - : std::string("?")) + - "]"; - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - return type.elementType != nullptr ? objcEncodingForJsiType(*type.elementType) - : "?"; - default: - return "?"; - } -} - -std::string objcBlockSignatureForJsiSignature( - const NativeApiJsiSignature& signature) { - std::string encoding = objcEncodingForJsiType(signature.returnType); - encoding += "@?"; - for (const auto& argType : signature.argumentTypes) { - encoding += objcEncodingForJsiType(argType); - } - return encoding; -} - -std::string objcMethodSignatureForJsiSignature( - const NativeApiJsiSignature& signature) { - std::string encoding = objcEncodingForJsiType(signature.returnType); - encoding += "@:"; - for (const auto& argType : signature.argumentTypes) { - encoding += objcEncodingForJsiType(argType); - } - return encoding; -} - -[[noreturn]] void throwNativeApiJsiCallbackException( - const std::string& message) { - NSString* reason = [NSString stringWithUTF8String:message.c_str()]; - @throw [NSException exceptionWithName:@"NativeScriptJSICallbackException" - reason:reason - userInfo:nil]; -} - -class NativeApiJsiCallback; - -void nativeApiJsiCallbackTrampoline(ffi_cif* cif, void* ret, void* args[], - void* data); - -std::atomic gActiveNativeThreadJsiCallbacks{0}; - -class NativeApiJsiCallback final - : public std::enable_shared_from_this { - public: - NativeApiJsiCallback(Runtime& runtime, - std::shared_ptr bridge, - std::shared_ptr signature, - Function function, bool block, - NativeApiJsiCallbackThreadPolicy threadPolicy = - NativeApiJsiCallbackThreadPolicy::Default, - bool bindThis = false) - : runtimeOwner_(retainNativeApiJsiRuntime(runtime)), - runtime_(runtimeOwner_.get()), - bridge_(std::move(bridge)), - signature_(std::move(signature)), - function_(std::make_shared(std::move(function))), - block_(block), - threadPolicy_(threadPolicy), - bindThis_(bindThis) { - closure_ = static_cast( - ffi_closure_alloc(sizeof(ffi_closure), &executable_)); - if (closure_ == nullptr || executable_ == nullptr || - signature_ == nullptr || !signature_->prepared) { - throw facebook::jsi::JSError(runtime, - "Unable to allocate native JSI callback."); - } - - ffi_status status = ffi_prep_closure_loc( - closure_, &signature_->cif, nativeApiJsiCallbackTrampoline, this, - executable_); - if (status != FFI_OK) { - ffi_closure_free(closure_); - closure_ = nullptr; - executable_ = nullptr; - throw facebook::jsi::JSError(runtime, - "Unable to prepare native JSI callback."); - } - - if (block_) { - blockSignature_ = objcBlockSignatureForJsiSignature(*signature_); - descriptor_ = std::make_unique(); - descriptor_->reserved = 0; - descriptor_->size = sizeof(NativeApiJsiBlockLiteral); - descriptor_->copyHelper = nativeApiJsiBlockCopy; - descriptor_->disposeHelper = nativeApiJsiBlockDispose; - descriptor_->signature = blockSignature_.c_str(); - - blockLiteral_ = std::make_unique(); - blockLiteral_->isa = nativeApiJsiStackBlockIsa(); - blockLiteral_->flags = kNativeApiJsiBlockHasCopyDispose | - kNativeApiJsiBlockHasSignature; - blockLiteral_->invoke = executable_; - blockLiteral_->descriptor = descriptor_.get(); - blockLiteral_->callback = this; - } - } - - ~NativeApiJsiCallback() { - if (closure_ != nullptr) { - ffi_closure_free(closure_); - closure_ = nullptr; - executable_ = nullptr; - } - } - - void* functionPointer() const { - return block_ && blockLiteral_ != nullptr - ? static_cast(blockLiteral_.get()) - : executable_; - } - - const NativeApiJsiSignature& signature() const { return *signature_; } - - void retainBlockCopy(const void* blockPointer) { - if (!block_) { - return; - } - auto self = shared_from_this(); - if (bridge_ != nullptr && runtime_ != nullptr && function_ != nullptr && - blockPointer != nullptr) { - bridge_->rememberRoundTripValue(*runtime_, blockPointer, - Value(*runtime_, *function_)); - } - std::lock_guard lock(retainedBlockCopiesMutex_); - retainedBlockCopies_.push_back({blockPointer, std::move(self)}); - } - - bool releaseBlockCopy(const void* blockPointer) { - if (!block_) { - return false; - } - std::shared_ptr keepAlive; - try { - keepAlive = shared_from_this(); - } catch (const std::bad_weak_ptr&) { - return false; - } - std::lock_guard lock(retainedBlockCopiesMutex_); - auto it = retainedBlockCopies_.end(); - if (blockPointer != nullptr) { - it = std::find_if( - retainedBlockCopies_.begin(), retainedBlockCopies_.end(), - [blockPointer](const RetainedBlockCopy& retained) { - return retained.blockPointer == blockPointer; - }); - } - if (it != retainedBlockCopies_.end()) { - if (bridge_ != nullptr && it->blockPointer != nullptr) { - bridge_->forgetRoundTripValue(it->blockPointer); - } - retainedBlockCopies_.erase(it); - return true; - } - return false; - } - - void invoke(void* ret, void* args[]) { - if (runtime_ == nullptr || function_ == nullptr || signature_ == nullptr) { - throwNativeApiJsiCallbackException("Invalid JSI callback."); - } - - std::string error; - auto call = [&]() { invokeOnCurrentThread(ret, args, &error); }; - const auto& nativeCallbackInvoker = bridge_->nativeCallbackInvoker(); - const auto& jsThreadCallbackInvoker = bridge_->jsThreadCallbackInvoker(); - bool currentThreadIsJs = - std::this_thread::get_id() == bridge_->jsThreadId(); - - auto callOnNativeCallerThread = [&]() { - ScopedNativeCallerThreadJsiCallback callbackScope; - if (nativeCallbackInvoker) { - nativeCallbackInvoker(call); - } else { - call(); - } - }; - auto callOnUIThread = [&]() { - auto runOnUIThread = [&]() { - bool previous = gExecutingDispatchedUINativeCall; - gExecutingDispatchedUINativeCall = true; - callOnNativeCallerThread(); - gExecutingDispatchedUINativeCall = previous; - }; - if ([NSThread isMainThread]) { - runOnUIThread(); - } else { - dispatch_sync(dispatch_get_main_queue(), ^{ - runOnUIThread(); - }); - } - }; - auto callOnJSThread = [&]() { - if (currentThreadIsJs) { - call(); - return; - } - if (jsThreadCallbackInvoker) { - jsThreadCallbackInvoker(call); - return; - } - if (auto scheduler = bridge_->scheduler()) { - dispatch_semaphore_t done = dispatch_semaphore_create(0); - scheduler->invokeOnJS([call, done]() mutable { - call(); - dispatch_semaphore_signal(done); - }); - dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); - return; - } - error = "Native callback was invoked off the JS thread without a JS scheduler."; - }; - - if (threadPolicy_ == NativeApiJsiCallbackThreadPolicy::UI) { - callOnUIThread(); - if (!error.empty()) { - if (!recordNativeCallbackException(error)) { - throwNativeApiJsiCallbackException(error); - } - } - return; - } - if (threadPolicy_ == NativeApiJsiCallbackThreadPolicy::JS) { - callOnJSThread(); - if (!error.empty()) { - if (!recordNativeCallbackException(error)) { - throwNativeApiJsiCallbackException(error); - } - } - return; - } - - bool returnsVoid = signature_->returnType.kind == metagen::mdTypeVoid; - bool activeSynchronousNativeInvocation = - gActiveSynchronousNativeInvocationDepth.load( - std::memory_order_acquire) > 0; - bool nativeCallerThreadCallbacks = - bridge_->invokeCallbacksOnNativeCallerThread(); - bool nativeCallerThreadCallback = - nativeCallerThreadCallbacks && !currentThreadIsJs && - (block_ || bindThis_ || - (activeSynchronousNativeInvocation && !returnsVoid)); - bool direct = currentThreadIsJs || - gExecutingDispatchedUINativeCall || - gSynchronousNativeInvocationDepth > 0 || - nativeCallerThreadCallback || - (nativeCallerThreadCallbacks && !nativeCallbackInvoker && - activeSynchronousNativeInvocation); - bool waitForNativeThreadCallback = - currentThreadIsJs && nativeCallbackInvoker && - gActiveNativeThreadJsiCallbacks.load(std::memory_order_acquire) > 0; - if (direct && !waitForNativeThreadCallback) { - if (nativeCallerThreadCallback) { - callOnNativeCallerThread(); - } else { - call(); - } - } else if (!currentThreadIsJs && !nativeCallerThreadCallbacks) { - callOnJSThread(); - } else if (!currentThreadIsJs && returnsVoid && block_ && - jsThreadCallbackInvoker) { - jsThreadCallbackInvoker(call); - } else if (nativeCallbackInvoker) { - bool nativeThreadCallback = !currentThreadIsJs; - if (nativeThreadCallback) { - gActiveNativeThreadJsiCallbacks.fetch_add(1, - std::memory_order_acq_rel); - } - try { - nativeCallbackInvoker(call); - } catch (...) { - if (nativeThreadCallback) { - gActiveNativeThreadJsiCallbacks.fetch_sub( - 1, std::memory_order_acq_rel); - } - throw; - } - if (nativeThreadCallback) { - gActiveNativeThreadJsiCallbacks.fetch_sub(1, - std::memory_order_acq_rel); - } - } else if (auto scheduler = bridge_->scheduler()) { - dispatch_semaphore_t done = dispatch_semaphore_create(0); - scheduler->invokeOnJS([call, done]() mutable { - call(); - dispatch_semaphore_signal(done); - }); - dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); - } else { - error = "Native callback was invoked off the JS thread without a JS scheduler."; - } - - if (!error.empty()) { - if (!recordNativeCallbackException(error)) { - throwNativeApiJsiCallbackException(error); - } - } - } - - private: - void invokeOnCurrentThread(void* ret, void* args[], std::string* error) { - try { - NativeApiJsiRuntimeScope runtimeScope(*runtime_); - size_t nativeArgOffset = signature_->implicitArgumentCount; - std::vector jsArgs; - jsArgs.reserve(signature_->argumentTypes.size()); - for (size_t i = 0; i < signature_->argumentTypes.size(); i++) { - jsArgs.emplace_back(convertNativeReturnValue( - *runtime_, bridge_, signature_->argumentTypes[i], - args[i + nativeArgOffset])); - } - - Value result = Value::undefined(); - if (bindThis_ && nativeArgOffset >= 1) { - id self = *static_cast(args[0]); - Value thisValue = - makeNativeObjectValue(*runtime_, bridge_, self, false); - Object thisObject = thisValue.isObject() - ? thisValue.asObject(*runtime_) - : Object(*runtime_); - result = - jsArgs.empty() - ? function_->callWithThis(*runtime_, thisObject) - : function_->callWithThis( - *runtime_, thisObject, - static_cast(jsArgs.data()), - static_cast(jsArgs.size())); - } else { - result = - jsArgs.empty() - ? function_->call(*runtime_) - : function_->call(*runtime_, - static_cast(jsArgs.data()), - static_cast(jsArgs.size())); - } - storeReturnValue(result, ret); - if (std::this_thread::get_id() == bridge_->jsThreadId()) { - runtime_->drainMicrotasks(); - } - } catch (const std::exception& exception) { - if (isNSErrorOutJsiMethodCallback(*signature_)) { - zeroReturnValue(ret); - populateNSErrorOutArgument(args, exception.what()); - return; - } - if (error != nullptr) { - *error = exception.what(); - } - zeroReturnValue(ret); - } catch (...) { - if (isNSErrorOutJsiMethodCallback(*signature_)) { - zeroReturnValue(ret); - populateNSErrorOutArgument(args, "Unknown exception in native JSI callback."); - return; - } - if (error != nullptr) { - *error = "Unknown exception in native JSI callback."; - } - zeroReturnValue(ret); - } - } - - void populateNSErrorOutArgument(void* args[], const char* message) { - if (args == nullptr || signature_ == nullptr || - signature_->argumentTypes.empty()) { - return; - } - - size_t outArgIndex = signature_->implicitArgumentCount + - signature_->argumentTypes.size() - 1; - void* outArgValue = args[outArgIndex]; - NSError** outError = - outArgValue != nullptr ? *reinterpret_cast(outArgValue) - : nullptr; - if (outError == nullptr) { - return; - } - - NSString* nsMessage = - message != nullptr ? [NSString stringWithUTF8String:message] : nil; - if (nsMessage == nil) { - nsMessage = @"JS error"; - } - NSDictionary* userInfo = @{NSLocalizedDescriptionKey : nsMessage}; - *outError = [NSError errorWithDomain:@"TNSErrorDomain" - code:1 - userInfo:userInfo]; - } - - void zeroReturnValue(void* ret) { - if (ret == nullptr || signature_ == nullptr || - signature_->returnType.kind == metagen::mdTypeVoid) { - return; - } - size_t size = nativeSizeForType(signature_->returnType); - if (size > 0) { - std::memset(ret, 0, size); - } - } - - void storeReturnValue(const Value& result, void* ret) { - if (ret == nullptr || - signature_->returnType.kind == metagen::mdTypeVoid) { - return; - } - - zeroReturnValue(ret); - if (result.isUndefined() || result.isNull()) { - return; - } - const auto& returnType = signature_->returnType; - if (returnType.kind == metagen::mdTypeString && result.isString()) { - std::string utf8 = result.asString(*runtime_).utf8(*runtime_); - *static_cast(ret) = strdup(utf8.c_str()); - return; - } - if ((returnType.kind == metagen::mdTypePointer || - returnType.kind == metagen::mdTypeOpaquePointer) && - result.isString()) { - std::string utf8 = result.asString(*runtime_).utf8(*runtime_); - *static_cast(ret) = strdup(utf8.c_str()); - return; - } - - NativeApiJsiArgumentFrame frame(1); - convertJsiArgument(*runtime_, bridge_, returnType, result, ret, frame); - if (isObjectiveCObjectType(returnType)) { - id object = *static_cast(ret); - if (object != nil) { - [object retain]; - [object autorelease]; - } - } - } - - std::shared_ptr runtimeOwner_; - Runtime* runtime_ = nullptr; - std::shared_ptr bridge_; - std::shared_ptr signature_; - std::shared_ptr function_; - bool block_ = false; - NativeApiJsiCallbackThreadPolicy threadPolicy_ = - NativeApiJsiCallbackThreadPolicy::Default; - bool bindThis_ = false; - ffi_closure* closure_ = nullptr; - void* executable_ = nullptr; - std::string blockSignature_; - std::unique_ptr descriptor_; - std::unique_ptr blockLiteral_; - struct RetainedBlockCopy { - const void* blockPointer = nullptr; - std::shared_ptr lifetime; - }; - std::mutex retainedBlockCopiesMutex_; - std::vector retainedBlockCopies_; -}; - -void nativeApiJsiBlockCopy(void* dst, void* src) { - auto* dstBlock = static_cast(dst); - auto* srcBlock = static_cast(src); - if (dstBlock == nullptr || srcBlock == nullptr || - srcBlock->callback == nullptr) { - return; - } - dstBlock->callback = srcBlock->callback; - static_cast(srcBlock->callback) - ->retainBlockCopy(dstBlock); -} - -void nativeApiJsiBlockDispose(void* src) { - auto* block = static_cast(src); - if (block == nullptr || block->callback == nullptr) { - return; - } - bool released = - static_cast(block->callback)->releaseBlockCopy(block); - if (released) { - block->callback = nullptr; - } -} - -void nativeApiJsiCallbackTrampoline(ffi_cif*, void* ret, void* args[], - void* data) { - auto callback = static_cast(data); - if (callback == nullptr) { - return; - } - @try { - callback->invoke(ret, args); - } @catch (NSException* exception) { - const char* description = - exception.description != nil ? exception.description.UTF8String : nullptr; - std::string message = description != nullptr - ? description - : "Objective-C exception in native JSI callback."; - if (!recordNativeCallbackException(message)) { - @throw; - } - } -} - -size_t nativeSizeForType(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeStruct: - if (type.aggregateInfo != nullptr) { - return type.aggregateInfo->size; - } - break; - case metagen::mdTypeArray: - if (type.elementType != nullptr) { - return nativeSizeForType(*type.elementType) * - static_cast(type.arraySize); - } - break; - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - if (type.elementType != nullptr) { - size_t lanes = std::max(type.arraySize, 1); - size_t abiLanes = lanes == 3 ? 4 : lanes; - return nativeSizeForType(*type.elementType) * abiLanes; - } - break; - default: - break; - } - - if (type.ffiType != nullptr && type.ffiType->size > 0) { - return type.ffiType->size; - } - if (type.ffiType == &ffi_type_void) { - return 0; - } - return sizeof(void*); -} - -Value signedInteger64ToJsiValue(Runtime& runtime, int64_t value) { - constexpr int64_t maxSafeInteger = 9007199254740991LL; - constexpr int64_t minSafeInteger = -9007199254740991LL; - if (value >= minSafeInteger && value <= maxSafeInteger) { - return static_cast(value); - } - return BigInt::fromInt64(runtime, value); -} - -Value unsignedInteger64ToJsiValue(Runtime& runtime, uint64_t value) { - constexpr uint64_t maxSafeInteger = 9007199254740991ULL; - if (value <= maxSafeInteger) { - return static_cast(value); - } - return BigInt::fromUint64(runtime, value); -} - -bool parseIntegerTextToUintptr(const std::string& text, uintptr_t* address) { - if (address == nullptr) { - return false; - } - if (text.empty()) { - return false; - } - - char* end = nullptr; - if (text[0] == '-') { - long long signedValue = std::strtoll(text.c_str(), &end, 10); - if (end == nullptr || *end != '\0') { - return false; - } - *address = static_cast(static_cast(signedValue)); - return true; - } - - int base = 10; - const char* start = text.c_str(); - if (text.size() > 2 && text[0] == '0' && - (text[1] == 'x' || text[1] == 'X')) { - base = 16; - } - unsigned long long unsignedValue = std::strtoull(start, &end, base); - if (end == nullptr || *end != '\0') { - return false; - } - *address = static_cast(unsignedValue); - return true; -} - -bool parseBigIntToUintptr(Runtime& runtime, const BigInt& bigint, - uintptr_t* address) { - return parseIntegerTextToUintptr(bigint.toString(runtime, 10).utf8(runtime), - address); -} - -bool readJsiBuffer(Runtime& runtime, const Object& object, const uint8_t** data, - size_t* byteLength) { - if (data == nullptr || byteLength == nullptr) { - return false; - } - - if (object.isArrayBuffer(runtime)) { - ArrayBuffer buffer = object.getArrayBuffer(runtime); - *data = buffer.data(runtime); - *byteLength = buffer.size(runtime); - return true; - } - - Value bufferValue = object.getProperty(runtime, "buffer"); - if (!bufferValue.isObject()) { - return false; - } - Object bufferObject = bufferValue.asObject(runtime); - if (!bufferObject.isArrayBuffer(runtime)) { - return false; - } - - size_t byteOffset = 0; - size_t viewByteLength = 0; - Value offsetValue = object.getProperty(runtime, "byteOffset"); - if (offsetValue.isNumber()) { - byteOffset = static_cast(std::max(0, offsetValue.getNumber())); - } - Value lengthValue = object.getProperty(runtime, "byteLength"); - if (lengthValue.isNumber()) { - viewByteLength = static_cast(std::max(0, lengthValue.getNumber())); - } - - ArrayBuffer buffer = bufferObject.getArrayBuffer(runtime); - if (byteOffset > buffer.size(runtime)) { - return false; - } - if (viewByteLength == 0 || byteOffset + viewByteLength > buffer.size(runtime)) { - viewByteLength = buffer.size(runtime) - byteOffset; - } - *data = buffer.data(runtime) + byteOffset; - *byteLength = viewByteLength; - return true; -} - -uint32_t rawTypeKind(MDTypeKind kind) { - return static_cast(kind); -} - -MDTypeKind stripTypeFlags(MDTypeKind kind) { - uint32_t raw = rawTypeKind(kind); - raw &= ~static_cast(metagen::mdTypeFlagNext); - raw &= ~static_cast(metagen::mdTypeFlagVariadic); - return static_cast(raw); -} - -size_t alignUp(size_t value, size_t alignment) { - if (alignment == 0) { - return value; - } - return ((value + alignment - 1) / alignment) * alignment; -} - -ffi_type* ffiTypeForJsiKind(MDTypeKind kind) { - switch (kind) { - case metagen::mdTypeChar: - return &ffi_type_sint8; - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - case metagen::mdTypeBool: - return &ffi_type_uint8; - case metagen::mdTypeSShort: - return &ffi_type_sint16; - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - return &ffi_type_uint16; - case metagen::mdTypeSInt: - return &ffi_type_sint32; - case metagen::mdTypeUInt: - return &ffi_type_uint32; - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - return &ffi_type_sint64; - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - return &ffi_type_uint64; - case metagen::mdTypeFloat: - return &ffi_type_float; - case metagen::mdTypeDouble: - return &ffi_type_double; - case metagen::mdTypeVoid: - return &ffi_type_void; - case metagen::mdTypeString: - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: - case metagen::mdTypeClass: - case metagen::mdTypeSelector: - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - return &ffi_type_pointer; - default: - return nullptr; - } -} - -bool isSupportedJsiKind(MDTypeKind kind) { - switch (kind) { - default: - return ffiTypeForJsiKind(kind) != nullptr; - } -} - -void skipMetadataJsiTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, - MDTypeKind kind); - -void skipMetadataJsiType(MDMetadataReader* metadata, MDSectionOffset* offset) { - MDTypeKind kind = stripTypeFlags(metadata->getTypeKind(*offset)); - *offset += sizeof(MDTypeKind); - skipMetadataJsiTypePayload(metadata, offset, kind); -} - -void skipMetadataJsiTypePayload(MDMetadataReader* metadata, MDSectionOffset* offset, - MDTypeKind kind) { - switch (kind) { - case metagen::mdTypeClassObject: { - auto classOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - case metagen::mdTypeProtocolObject: { - bool next = true; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - case metagen::mdTypeArray: - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - *offset += sizeof(uint16_t); - skipMetadataJsiType(metadata, offset); - break; - case metagen::mdTypeStruct: - *offset += sizeof(MDSectionOffset); - break; - case metagen::mdTypePointer: - skipMetadataJsiType(metadata, offset); - break; - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - *offset += sizeof(MDSectionOffset); - break; - default: - break; - } -} - -NativeApiJsiType parseMetadataJsiType(MDMetadataReader* metadata, - MDSectionOffset* offset, - NativeApiJsiBridge* bridge) { - MDTypeKind rawKind = metadata->getTypeKind(*offset); - MDTypeKind kind = stripTypeFlags(rawKind); - *offset += sizeof(MDTypeKind); - - NativeApiJsiType type; - type.kind = kind; - - switch (kind) { - case metagen::mdTypeArray: { - type.arraySize = metadata->getArraySize(*offset); - *offset += sizeof(uint16_t); - type.elementType = - std::make_shared( - parseMetadataJsiType(metadata, offset, bridge)); - auto ffiOwner = std::make_shared(); - ffiOwner->elements.reserve(static_cast(type.arraySize) + 1); - ffi_type* elementFfiType = type.elementType->ffiType != nullptr - ? type.elementType->ffiType - : &ffi_type_pointer; - for (uint16_t i = 0; i < type.arraySize; i++) { - ffiOwner->elements.push_back(elementFfiType); - } - ffiOwner->finalize(); - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = type.elementType->supported; - return type; - } - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: { - type.arraySize = metadata->getArraySize(*offset); - *offset += sizeof(uint16_t); - type.elementType = - std::make_shared( - parseMetadataJsiType(metadata, offset, bridge)); - auto ffiOwner = std::make_shared(); -#if defined(FFI_TYPE_EXT_VECTOR) - ffiOwner->type.type = - kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_EXT_VECTOR; -#else - ffiOwner->type.type = - kind == metagen::mdTypeComplex ? FFI_TYPE_COMPLEX : FFI_TYPE_STRUCT; -#endif - ffi_type* elementFfiType = type.elementType->ffiType != nullptr - ? type.elementType->ffiType - : &ffi_type_float; - size_t lanes = std::max(type.arraySize, 1); - size_t abiLanes = lanes == 3 ? 4 : lanes; - size_t elementSize = std::max(elementFfiType->size, sizeof(float)); - size_t elementAlignment = - std::max(elementFfiType->alignment, static_cast(1)); - ffiOwner->elements.reserve(abiLanes + 1); - for (size_t i = 0; i < abiLanes; i++) { - ffiOwner->elements.push_back(elementFfiType); - } - ffiOwner->finalize(); - size_t vectorAlignment = elementAlignment; - if (kind != metagen::mdTypeComplex) { - size_t packedSize = abiLanes * elementSize; - size_t preferredAlignment = packedSize >= 16 ? 16 : packedSize; - vectorAlignment = std::max(vectorAlignment, preferredAlignment); - } - vectorAlignment = std::min(vectorAlignment, 16); - ffiOwner->type.alignment = static_cast(vectorAlignment); - ffiOwner->type.size = alignUp(abiLanes * elementSize, vectorAlignment); - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = type.elementType->supported; - return type; - } - case metagen::mdTypeStruct: { - auto structOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool isUnion = (structOffset & metagen::mdSectionOffsetNext) != 0; - structOffset &= ~metagen::mdSectionOffsetNext; - if (structOffset == MD_SECTION_OFFSET_NULL || bridge == nullptr) { - type.kind = metagen::mdTypePointer; - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; - } - - MDSectionOffset absoluteOffset = - structOffset + (isUnion ? metadata->unionsOffset : metadata->structsOffset); - type.aggregateOffset = absoluteOffset; - type.aggregateIsUnion = isUnion; - type.aggregateInfo = bridge->aggregateInfoFor(absoluteOffset, isUnion); - type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr - ? &type.aggregateInfo->ffi->type - : nullptr; - type.supported = type.ffiType != nullptr; - return type; - } - case metagen::mdTypePointer: - type.elementType = - std::make_shared( - parseMetadataJsiType(metadata, offset, bridge)); - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - type.signatureOffset = metadata->getOffset(*offset) + metadata->signaturesOffset; - *offset += sizeof(MDSectionOffset); - type.ffiType = &ffi_type_pointer; - type.supported = true; - return type; - case metagen::mdTypeClassObject: { - auto classOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - bool next = (classOffset & metagen::mdSectionOffsetNext) != 0; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - case metagen::mdTypeProtocolObject: { - bool next = true; - while (next) { - auto protocolOffset = metadata->getOffset(*offset); - *offset += sizeof(MDSectionOffset); - next = (protocolOffset & metagen::mdSectionOffsetNext) != 0; - } - break; - } - default: - break; - } - - type.ffiType = ffiTypeForJsiKind(kind); - type.supported = type.ffiType != nullptr && isSupportedJsiKind(kind); - return type; -} - -std::shared_ptr NativeApiJsiBridge::aggregateInfoFor( - MDSectionOffset aggregateOffset, bool isUnion) { - if (metadata_ == nullptr || aggregateOffset == MD_SECTION_OFFSET_NULL) { - return nullptr; - } - - auto cached = aggregateInfoByOffset_.find(aggregateOffset); - if (cached != aggregateInfoByOffset_.end()) { - return cached->second; - } - - auto info = std::make_shared(); - info->offset = aggregateOffset; - info->isUnion = isUnion; - aggregateInfoByOffset_[aggregateOffset] = info; - - if (aggregateInfoInProgress_.find(aggregateOffset) != - aggregateInfoInProgress_.end()) { - auto ffiOwner = std::make_shared(); - ffiOwner->elements.push_back(&ffi_type_pointer); - ffiOwner->finalize(); - info->ffi = ffiOwner; - return info; - } - - aggregateInfoInProgress_.insert(aggregateOffset); - - MDSectionOffset offset = aggregateOffset; - const char* name = metadata_->getString(offset); - info->name = name != nullptr ? name : ""; - offset += sizeof(MDSectionOffset); - info->size = metadata_->getArraySize(offset); - offset += sizeof(uint16_t); - - bool next = true; - while (next) { - MDSectionOffset nameOffset = metadata_->getOffset(offset); - offset += sizeof(MDSectionOffset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - if (nameOffset == MD_SECTION_OFFSET_NULL) { - break; - } - - NativeApiJsiAggregateField field; - const char* fieldName = metadata_->resolveString(nameOffset); - field.name = fieldName != nullptr ? fieldName : ""; - if (!isUnion) { - field.offset = metadata_->getArraySize(offset); - offset += sizeof(uint16_t); - } - field.type = parseMetadataJsiType(metadata_.get(), &offset, this); - info->fields.push_back(std::move(field)); - } - - auto ffiOwner = std::make_shared(); - if (isUnion) { - ffi_type* largest = &ffi_type_uint8; - size_t largestSize = 0; - for (const auto& field : info->fields) { - size_t fieldSize = nativeSizeForType(field.type); - if (field.type.ffiType != nullptr && fieldSize >= largestSize) { - largest = field.type.ffiType; - largestSize = fieldSize; - } - } - ffiOwner->elements.push_back(largest); - } else { - for (const auto& field : info->fields) { - ffiOwner->elements.push_back(field.type.ffiType != nullptr - ? field.type.ffiType - : &ffi_type_pointer); - } - if (ffiOwner->elements.empty()) { - ffiOwner->elements.push_back(&ffi_type_uint8); - } - } - ffiOwner->finalize(); - info->ffi = ffiOwner; - aggregateInfoInProgress_.erase(aggregateOffset); - return info; -} - -ffi_type* ffiTypeForJsiArgument(const NativeApiJsiType& type) { - switch (type.kind) { - case metagen::mdTypeArray: - return &ffi_type_pointer; - default: - return type.ffiType != nullptr ? type.ffiType : &ffi_type_pointer; - } -} - -std::optional parseMetadataJsiSignature( - MDMetadataReader* metadata, MDSectionOffset signatureOffset, - unsigned int implicitArgumentCount, NativeApiJsiBridge* bridge, - bool returnOwned = false) { - if (metadata == nullptr || signatureOffset == MD_SECTION_OFFSET_NULL) { - return std::nullopt; - } - - NativeApiJsiSignature signature; - signature.implicitArgumentCount = implicitArgumentCount; - - MDSectionOffset offset = signatureOffset; - MDTypeKind returnKind = metadata->getTypeKind(offset); - uint32_t returnKindRaw = rawTypeKind(returnKind); - bool next = - (returnKindRaw & static_cast(metagen::mdTypeFlagNext)) != 0; - signature.variadic = - (returnKindRaw & static_cast(metagen::mdTypeFlagVariadic)) != 0; - signature.returnType = parseMetadataJsiType(metadata, &offset, bridge); - signature.returnType.returnOwned = returnOwned; - - while (next) { - MDTypeKind argKind = metadata->getTypeKind(offset); - next = (rawTypeKind(argKind) & - static_cast(metagen::mdTypeFlagNext)) != 0; - signature.argumentTypes.push_back(parseMetadataJsiType(metadata, &offset, bridge)); - } - - signature.ffiTypes.reserve(signature.argumentTypes.size() + - implicitArgumentCount); - for (unsigned int i = 0; i < implicitArgumentCount; i++) { - signature.ffiTypes.push_back(&ffi_type_pointer); - } - for (const auto& argType : signature.argumentTypes) { - signature.ffiTypes.push_back(ffiTypeForJsiArgument(argType)); - } - - ffi_status status = ffi_prep_cif( - &signature.cif, FFI_DEFAULT_ABI, - static_cast(signature.ffiTypes.size()), - signature.returnType.ffiType != nullptr ? signature.returnType.ffiType - : &ffi_type_void, - signature.ffiTypes.empty() ? nullptr : signature.ffiTypes.data()); - signature.prepared = status == FFI_OK; - return signature; -} - -const char* skipObjCTypeQualifiers(const char* encoding) { - while (encoding != nullptr && *encoding != '\0' && - std::strchr("rnNoORV", *encoding) != nullptr) { - encoding++; - } - return encoding; -} - -const char* skipObjCTypeFieldName(const char* encoding, std::string* name) { - if (encoding == nullptr || *encoding != '"') { - return encoding; - } - - encoding++; - const char* start = encoding; - while (*encoding != '\0' && *encoding != '"') { - encoding++; - } - if (name != nullptr) { - *name = std::string(start, static_cast(encoding - start)); - } - return *encoding == '"' ? encoding + 1 : encoding; -} - -std::string normalizedObjCAggregateName(std::string name) { - if (!name.empty() && name.front() == '_') { - name.erase(name.begin()); - } - return name; -} - -std::vector knownObjCAggregateFieldNames( - const std::string& aggregateName, size_t fieldCount) { - std::string name = normalizedObjCAggregateName(aggregateName); - std::vector fields; - if (name == "CGPoint" || name == "NSPoint") { - fields = {"x", "y"}; - } else if (name == "CGSize" || name == "NSSize") { - fields = {"width", "height"}; - } else if (name == "CGRect" || name == "NSRect") { - fields = {"origin", "size"}; - } else if (name == "CGVector") { - fields = {"dx", "dy"}; - } else if (name == "UIEdgeInsets" || name == "NSEdgeInsets") { - fields = {"top", "left", "bottom", "right"}; - } else if (name == "NSDirectionalEdgeInsets") { - fields = {"top", "leading", "bottom", "trailing"}; - } else if (name == "NSRange" || name == "CFRange") { - fields = {"location", "length"}; - } else if (name == "CGAffineTransform") { - fields = {"a", "b", "c", "d", "tx", "ty"}; - } else if (name == "CATransform3D") { - fields = {"m11", "m12", "m13", "m14", "m21", "m22", "m23", "m24", - "m31", "m32", "m33", "m34", "m41", "m42", "m43", "m44"}; - } - - if (fields.size() != fieldCount) { - fields.clear(); - } - return fields; -} - -const NativeApiSymbol* findObjCAggregateSymbol( - NativeApiJsiBridge* bridge, const std::string& name, bool isUnion) { - if (bridge == nullptr || name.empty()) { - return nullptr; - } - - std::vector candidates; - candidates.push_back(name); - std::string normalized = normalizedObjCAggregateName(name); - if (normalized != name) { - candidates.push_back(normalized); - } else { - candidates.push_back("_" + name); - } - constexpr const char* suffix = "Struct"; - if (normalized.size() > std::strlen(suffix) && - normalized.compare(normalized.size() - std::strlen(suffix), - std::strlen(suffix), suffix) == 0) { - candidates.push_back( - normalized.substr(0, normalized.size() - std::strlen(suffix))); - } else { - candidates.push_back(normalized + suffix); - } - - for (const auto& candidate : candidates) { - const NativeApiSymbol* symbol = - isUnion ? bridge->findUnion(candidate) : bridge->findStruct(candidate); - if (symbol == nullptr) { - symbol = bridge->findAggregate(candidate); - } - if (symbol != nullptr) { - return symbol; - } - } - - return nullptr; -} - -void applyObjCEncodingSizeAndAlignment(const char* encoding, - NativeApiJsiFfiType* ffiType, - uint16_t* sizeOut = nullptr) { - if (encoding == nullptr || ffiType == nullptr) { - return; - } - - NSUInteger size = 0; - NSUInteger alignment = 0; - NSGetSizeAndAlignment(encoding, &size, &alignment); - if (size > 0) { - ffiType->type.size = static_cast(size); - if (sizeOut != nullptr) { - *sizeOut = static_cast(std::min( - size, static_cast(std::numeric_limits::max()))); - } - } - if (alignment > 0) { - ffiType->type.alignment = static_cast(alignment); - } -} - -NativeApiJsiType parseObjCEncodedJsiType( - const char* encoding, NativeApiJsiBridge* bridge = nullptr, - const char** endEncoding = nullptr); - -bool unsupportedJsiType(const NativeApiJsiType& type); - -NativeApiJsiType parseObjCEncodedAggregateJsiType( - const char* encoding, NativeApiJsiBridge* bridge, const char** endEncoding) { - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - - const bool isUnion = *encoding == '('; - const char close = isUnion ? ')' : '}'; - const char* cursor = encoding + 1; - const char* nameStart = cursor; - while (*cursor != '\0' && *cursor != '=' && *cursor != close) { - cursor++; - } - std::string aggregateName(nameStart, static_cast(cursor - nameStart)); - - if (const NativeApiSymbol* symbol = - findObjCAggregateSymbol(bridge, aggregateName, isUnion)) { - type.aggregateOffset = symbol->offset; - type.aggregateIsUnion = symbol->kind == NativeApiSymbolKind::Union; - type.aggregateInfo = bridge->aggregateInfoFor(*symbol); - type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr - ? &type.aggregateInfo->ffi->type - : nullptr; - type.supported = type.ffiType != nullptr; - - int depth = 0; - const char* end = encoding; - do { - if (*end == *encoding) { - depth++; - } else if (*end == close) { - depth--; - } - end++; - } while (*end != '\0' && depth > 0); - if (endEncoding != nullptr) { - *endEncoding = end; - } - return type; - } - - auto info = std::make_shared(); - info->name = aggregateName; - info->isUnion = isUnion; - info->offset = MD_SECTION_OFFSET_NULL; - - if (*cursor == '=') { - cursor++; - } - - size_t computedOffset = 0; - size_t maxFieldSize = 0; - size_t fieldIndex = 0; - while (*cursor != '\0' && *cursor != close) { - NativeApiJsiAggregateField field; - std::string encodedFieldName; - cursor = skipObjCTypeFieldName(cursor, &encodedFieldName); - const char* fieldStart = cursor; - const char* fieldEnd = cursor; - field.type = parseObjCEncodedJsiType(cursor, bridge, &fieldEnd); - if (fieldEnd == fieldStart || unsupportedJsiType(field.type)) { - type.supported = false; - type.ffiType = nullptr; - if (endEncoding != nullptr) { - *endEncoding = fieldEnd; - } - return type; - } - - NSUInteger fieldSize = 0; - NSUInteger fieldAlignment = 0; - NSGetSizeAndAlignment(fieldStart, &fieldSize, &fieldAlignment); - size_t nativeFieldSize = - fieldSize > 0 ? static_cast(fieldSize) - : nativeSizeForType(field.type); - size_t nativeFieldAlignment = - fieldAlignment > 0 ? static_cast(fieldAlignment) - : std::max(1, field.type.ffiType != nullptr - ? field.type.ffiType->alignment - : 1); - if (isUnion) { - field.offset = 0; - maxFieldSize = std::max(maxFieldSize, nativeFieldSize); - } else { - computedOffset = alignUp(computedOffset, nativeFieldAlignment); - field.offset = static_cast(std::min( - computedOffset, std::numeric_limits::max())); - computedOffset += nativeFieldSize; - } - field.name = !encodedFieldName.empty() - ? encodedFieldName - : "field" + std::to_string(fieldIndex); - info->fields.push_back(std::move(field)); - fieldIndex++; - cursor = fieldEnd; - } - - if (*cursor == close) { - cursor++; - } - if (endEncoding != nullptr) { - *endEncoding = cursor; - } - - auto knownNames = knownObjCAggregateFieldNames(aggregateName, info->fields.size()); - for (size_t i = 0; i < knownNames.size(); i++) { - info->fields[i].name = knownNames[i]; - } - - auto ffiOwner = std::make_shared(); - if (isUnion) { - ffi_type* largest = &ffi_type_uint8; - size_t largestSize = 0; - for (const auto& field : info->fields) { - size_t fieldSize = nativeSizeForType(field.type); - if (field.type.ffiType != nullptr && fieldSize >= largestSize) { - largest = field.type.ffiType; - largestSize = fieldSize; - } - } - ffiOwner->elements.push_back(largest); - } else { - for (const auto& field : info->fields) { - ffiOwner->elements.push_back(field.type.ffiType != nullptr - ? field.type.ffiType - : &ffi_type_pointer); - } - } - if (ffiOwner->elements.empty()) { - ffiOwner->elements.push_back(&ffi_type_uint8); - } - ffiOwner->finalize(); - applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get(), &info->size); - if (info->size == 0) { - info->size = static_cast(std::min( - isUnion ? maxFieldSize : computedOffset, - std::numeric_limits::max())); - } - - info->ffi = ffiOwner; - type.aggregateInfo = info; - type.aggregateOffset = MD_SECTION_OFFSET_NULL; - type.aggregateIsUnion = isUnion; - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = true; - return type; -} - -NativeApiJsiType parseObjCEncodedArrayJsiType( - const char* encoding, NativeApiJsiBridge* bridge, const char** endEncoding) { - NativeApiJsiType type; - type.kind = metagen::mdTypeArray; - - const char* cursor = encoding + 1; - uint16_t count = 0; - while (*cursor >= '0' && *cursor <= '9') { - count = static_cast( - std::min(std::numeric_limits::max(), - (count * 10) + (*cursor - '0'))); - cursor++; - } - type.arraySize = count; - - const char* elementEnd = cursor; - type.elementType = std::make_shared( - parseObjCEncodedJsiType(cursor, bridge, &elementEnd)); - cursor = elementEnd; - if (*cursor == ']') { - cursor++; - } - if (endEncoding != nullptr) { - *endEncoding = cursor; - } - - auto ffiOwner = std::make_shared(); - ffi_type* elementFfiType = - type.elementType != nullptr && type.elementType->ffiType != nullptr - ? type.elementType->ffiType - : &ffi_type_pointer; - for (uint16_t i = 0; i < count; i++) { - ffiOwner->elements.push_back(elementFfiType); - } - if (ffiOwner->elements.empty()) { - ffiOwner->elements.push_back(&ffi_type_uint8); - } - ffiOwner->finalize(); - applyObjCEncodingSizeAndAlignment(encoding, ffiOwner.get()); - - type.ownedFfiType = ffiOwner; - type.ffiType = &ffiOwner->type; - type.supported = type.elementType != nullptr && type.elementType->supported; - return type; -} - -NativeApiJsiType parseObjCEncodedJsiType( - const char* encoding, NativeApiJsiBridge* bridge, const char** endEncoding) { - encoding = skipObjCTypeQualifiers(encoding); - NativeApiJsiType type; - - if (encoding == nullptr || *encoding == '\0') { - type.kind = metagen::mdTypePointer; - type.ffiType = &ffi_type_pointer; - if (endEncoding != nullptr) { - *endEncoding = encoding; - } - return type; - } - - auto finishPrimitive = [&](const char* end) { - type.ffiType = ffiTypeForJsiKind(type.kind); - type.supported = type.ffiType != nullptr; - if (endEncoding != nullptr) { - *endEncoding = end; - } - return type; - }; - - switch (*encoding) { - case 'c': - type.kind = metagen::mdTypeChar; - break; - case 'i': - type.kind = metagen::mdTypeSInt; - break; - case 's': - type.kind = metagen::mdTypeSShort; - break; - case 'l': - case 'q': - type.kind = metagen::mdTypeSInt64; - break; - case 'C': - type.kind = metagen::mdTypeUInt8; - break; - case 'I': - type.kind = metagen::mdTypeUInt; - break; - case 'S': - type.kind = metagen::mdTypeUShort; - break; - case 'L': - case 'Q': - type.kind = metagen::mdTypeUInt64; - break; - case 'f': - type.kind = metagen::mdTypeFloat; - break; - case 'd': - type.kind = metagen::mdTypeDouble; - break; - case 'B': - type.kind = metagen::mdTypeBool; - break; - case 'v': - type.kind = metagen::mdTypeVoid; - break; - case '*': - type.kind = metagen::mdTypeString; - break; - case '@': - if (encoding[1] == '?') { - type.kind = metagen::mdTypeBlock; - return finishPrimitive(encoding + 2); - } - { - const char* objectEnd = encoding + 1; - if (*objectEnd == '"') { - objectEnd++; - while (*objectEnd != '\0' && *objectEnd != '"') { - objectEnd++; - } - if (*objectEnd == '"') { - objectEnd++; - } - } - if (std::strncmp(encoding, "@\"NSString\"", 11) == 0) { - type.kind = metagen::mdTypeNSStringObject; - } else if (std::strncmp(encoding, "@\"NSMutableString\"", 18) == 0) { - type.kind = metagen::mdTypeNSMutableStringObject; - } else { - type.kind = metagen::mdTypeAnyObject; - } - return finishPrimitive(objectEnd); - } - case '#': - type.kind = metagen::mdTypeClass; - break; - case ':': - type.kind = metagen::mdTypeSelector; - break; - case '^': - type.kind = metagen::mdTypePointer; - { - const char* elementEnd = encoding + 1; - type.elementType = std::make_shared( - parseObjCEncodedJsiType(encoding + 1, bridge, &elementEnd)); - type.ffiType = &ffi_type_pointer; - type.supported = true; - if (elementEnd == encoding + 1 && encoding[1] != '\0') { - elementEnd = encoding + 2; - } - if (endEncoding != nullptr) { - *endEncoding = elementEnd; - } - } - return type; - case '{': - case '(': - return parseObjCEncodedAggregateJsiType(encoding, bridge, endEncoding); - case '[': - return parseObjCEncodedArrayJsiType(encoding, bridge, endEncoding); - case 'b': { - type.kind = metagen::mdTypeUInt; - const char* cursor = encoding + 1; - while (*cursor >= '0' && *cursor <= '9') { - cursor++; - } - return finishPrimitive(cursor); - } - case '?': - type.kind = metagen::mdTypeOpaquePointer; - break; - default: - type.kind = metagen::mdTypePointer; - break; - } - - return finishPrimitive(encoding + 1); -} - -std::optional parseObjCMethodJsiSignature( - Method method, NativeApiJsiBridge* bridge = nullptr) { - if (method == nullptr) { - return std::nullopt; - } - - NativeApiJsiSignature signature; - signature.implicitArgumentCount = 2; - - char* returnEncoding = method_copyReturnType(method); - signature.returnType = parseObjCEncodedJsiType(returnEncoding, bridge); - if (returnEncoding != nullptr) { - free(returnEncoding); - } - - unsigned int totalArgc = method_getNumberOfArguments(method); - for (unsigned int i = 2; i < totalArgc; i++) { - char* argEncoding = method_copyArgumentType(method, i); - signature.argumentTypes.push_back(parseObjCEncodedJsiType(argEncoding, bridge)); - if (argEncoding != nullptr) { - free(argEncoding); - } - } - - signature.ffiTypes.reserve(totalArgc); - signature.ffiTypes.push_back(&ffi_type_pointer); - signature.ffiTypes.push_back(&ffi_type_pointer); - for (const auto& argType : signature.argumentTypes) { - signature.ffiTypes.push_back(ffiTypeForJsiArgument(argType)); - } - - ffi_status status = ffi_prep_cif( - &signature.cif, FFI_DEFAULT_ABI, - static_cast(signature.ffiTypes.size()), - signature.returnType.ffiType != nullptr ? signature.returnType.ffiType - : &ffi_type_void, - signature.ffiTypes.data()); - signature.prepared = status == FFI_OK; - return signature; -} - -bool prepareJsiMethodSignature(NativeApiJsiSignature* signature) { - if (signature == nullptr) { - return false; - } - signature->implicitArgumentCount = 2; - signature->ffiTypes.clear(); - signature->ffiTypes.reserve(signature->argumentTypes.size() + 2); - signature->ffiTypes.push_back(&ffi_type_pointer); - signature->ffiTypes.push_back(&ffi_type_pointer); - for (const auto& argType : signature->argumentTypes) { - ffi_type* ffiType = ffiTypeForJsiArgument(argType); - if (ffiType == nullptr) { - signature->prepared = false; - return false; - } - signature->ffiTypes.push_back(ffiType); - } - ffi_type* returnFfiType = - signature->returnType.ffiType != nullptr ? signature->returnType.ffiType - : &ffi_type_void; - signature->prepared = - ffi_prep_cif(&signature->cif, FFI_DEFAULT_ABI, - static_cast(signature->ffiTypes.size()), - returnFfiType, signature->ffiTypes.data()) == FFI_OK; - return signature->prepared; -} - -bool reconcileObjCMethodRuntimeSignature(NativeApiJsiSignature* signature, - const NativeApiJsiSignature& runtime) { - if (signature == nullptr || - signature->argumentTypes.size() != runtime.argumentTypes.size()) { - return false; - } - - bool changed = false; - for (size_t i = 0; i < signature->argumentTypes.size(); i++) { - NativeApiJsiType& metadataType = signature->argumentTypes[i]; - const NativeApiJsiType& runtimeType = runtime.argumentTypes[i]; - if (runtimeType.kind == metagen::mdTypeBlock && - metadataType.kind == metagen::mdTypeFunctionPointer) { - metadataType.kind = metagen::mdTypeBlock; - metadataType.ffiType = runtimeType.ffiType; - metadataType.supported = runtimeType.supported; - changed = true; - } - } - - return !changed || prepareJsiMethodSignature(signature); -} - -bool unsupportedJsiType(const NativeApiJsiType& type) { - if (type.kind == metagen::mdTypeStruct && type.aggregateInfo != nullptr && - type.aggregateInfo->ffi != nullptr) { - return false; - } - return !type.supported || type.ffiType == nullptr; -} - -bool signatureSupportedForJsiCallback(const NativeApiJsiSignature& signature) { - if (!signature.prepared || signature.variadic || - unsupportedJsiType(signature.returnType)) { - return false; - } - for (const auto& argType : signature.argumentTypes) { - if (unsupportedJsiType(argType)) { - return false; - } - } - return true; -} - -std::shared_ptr createJsiCallback( - Runtime& runtime, const std::shared_ptr& bridge, - const NativeApiJsiType& type, Function function, bool block, - NativeApiJsiCallbackThreadPolicy threadPolicy = - NativeApiJsiCallbackThreadPolicy::Default) { - if (bridge == nullptr || bridge->metadata() == nullptr || - type.signatureOffset == MD_SECTION_OFFSET_NULL) { - throw facebook::jsi::JSError( - runtime, "Native callback metadata is unavailable."); - } - - auto parsed = parseMetadataJsiSignature( - bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); - if (!parsed || !signatureSupportedForJsiCallback(*parsed)) { - throw facebook::jsi::JSError( - runtime, "Native callback signature is not supported by pure JSI."); - } - - auto signature = - std::make_shared(std::move(*parsed)); - auto callback = std::make_shared( - runtime, bridge, std::move(signature), std::move(function), block, - threadPolicy); - if (!block) { - bridge->retainJsiLifetime(callback); - } - return callback; -} - -std::shared_ptr createJsiMethodCallback( - Runtime& runtime, const std::shared_ptr& bridge, - const std::string& selectorName, MDSectionOffset signatureOffset, - Function function, bool returnOwned) { - if (bridge == nullptr || bridge->metadata() == nullptr || - signatureOffset == MD_SECTION_OFFSET_NULL) { - throw facebook::jsi::JSError( - runtime, "Native method callback metadata is unavailable."); - } - - auto parsed = parseMetadataJsiSignature( - bridge->metadata(), signatureOffset, 2, bridge.get(), returnOwned); - if (!parsed || !signatureSupportedForJsiCallback(*parsed)) { - throw facebook::jsi::JSError( - runtime, "Native method callback signature is not supported by pure JSI."); - } - parsed->selectorName = selectorName; - - auto signature = - std::make_shared(std::move(*parsed)); - auto threadPolicy = readJsiCallbackThreadPolicy(runtime, function); - auto callback = std::make_shared( - runtime, bridge, std::move(signature), std::move(function), false, - threadPolicy, true); - bridge->retainJsiLifetime(callback); - return callback; -} - -std::shared_ptr createJsiMethodCallback( - Runtime& runtime, const std::shared_ptr& bridge, - const std::string& selectorName, NativeApiJsiSignature signature, - Function function) { - signature.selectorName = selectorName; - prepareJsiMethodSignature(&signature); - if (!signatureSupportedForJsiCallback(signature)) { - throw facebook::jsi::JSError( - runtime, "Native method callback signature is not supported by pure JSI."); - } - - auto sharedSignature = - std::make_shared(std::move(signature)); - auto threadPolicy = readJsiCallbackThreadPolicy(runtime, function); - auto callback = std::make_shared( - runtime, bridge, std::move(sharedSignature), std::move(function), false, - threadPolicy, true); - bridge->retainJsiLifetime(callback); - return callback; -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h b/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h deleted file mode 100644 index 4a29b60f0..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiClassBuilder.h +++ /dev/null @@ -1,733 +0,0 @@ -std::string readOptionalStringProperty(Runtime& runtime, const Object& object, const char* name) { - if (name == nullptr || !object.hasProperty(runtime, name)) { - return ""; - } - Value value = object.getProperty(runtime, name); - return value.isString() ? value.asString(runtime).utf8(runtime) : ""; -} - -struct NativeApiJsiClassBuilderRegistration { - std::shared_ptr runtimeOwner; - Runtime* runtime = nullptr; - std::shared_ptr bridge; -}; - -std::mutex gNativeApiJsiClassBuilderMutex; -std::unordered_map gNativeApiJsiClassBuilders; -struct NativeApiJsiKnownExposedMethod { - std::string selectorName; - NativeApiJsiSignature signature; -}; -std::mutex gNativeApiJsiKnownExposedMethodsMutex; -std::unordered_map gNativeApiJsiKnownExposedMethods; - -void rememberNativeApiJsiClassBuilder(Runtime& runtime, - const std::shared_ptr& bridge, - Class cls) { - if (cls == Nil) { - return; - } - std::lock_guard lock(gNativeApiJsiClassBuilderMutex); - auto runtimeOwner = retainNativeApiJsiRuntime(runtime); - gNativeApiJsiClassBuilders[cls] = NativeApiJsiClassBuilderRegistration{ - .runtimeOwner = runtimeOwner, - .runtime = runtimeOwner.get(), - .bridge = bridge, - }; -} - -void rememberNativeApiJsiKnownExposedMethod(const std::string& selectorName, - const NativeApiJsiSignature& signature) { - if (selectorName.empty()) { - return; - } - NativeApiJsiKnownExposedMethod method{ - .selectorName = selectorName, - .signature = signature, - }; - std::lock_guard lock(gNativeApiJsiKnownExposedMethodsMutex); - gNativeApiJsiKnownExposedMethods[selectorName] = method; - gNativeApiJsiKnownExposedMethods[jsifySelector(selectorName.c_str())] = std::move(method); -} - -std::optional knownNativeApiJsiExposedMethod( - const std::string& name) { - std::lock_guard lock(gNativeApiJsiKnownExposedMethodsMutex); - auto it = gNativeApiJsiKnownExposedMethods.find(name); - if (it == gNativeApiJsiKnownExposedMethods.end()) { - return std::nullopt; - } - NativeApiJsiKnownExposedMethod method = it->second; - prepareJsiMethodSignature(&method.signature); - return method; -} - -std::optional findNativeApiJsiClassBuilder(id object) { - Class cls = object != nil ? object_getClass(object) : Nil; - std::lock_guard lock(gNativeApiJsiClassBuilderMutex); - while (cls != Nil) { - auto it = gNativeApiJsiClassBuilders.find(cls); - if (it != gNativeApiJsiClassBuilders.end()) { - return it->second; - } - cls = class_getSuperclass(cls); - } - return std::nullopt; -} - -const char* nativeApiJsiFastEnumerationEncoding() { - static const char* encoding = nullptr; - if (encoding == nullptr) { - struct objc_method_description desc = protocol_getMethodDescription( - @protocol(NSFastEnumeration), @selector(countByEnumeratingWithState:objects:count:), YES, - YES); - encoding = desc.types; - } - return encoding; -} - -NSUInteger nativeApiJsiSymbolIteratorCountByEnumerating(id self, SEL, NSFastEnumerationState* state, - id __unsafe_unretained stackbuf[], - NSUInteger len) { - if (len == 0 || state == nullptr || stackbuf == nullptr) { - return 0; - } - - auto registration = findNativeApiJsiClassBuilder(self); - if (!registration || registration->runtime == nullptr || registration->bridge == nullptr) { - return 0; - } - - Runtime& runtime = *registration->runtime; - NativeApiJsiRuntimeScope runtimeScope(runtime); - auto bridge = registration->bridge; - try { - Value receiver = makeNativeObjectValue(runtime, bridge, self, false); - if (!receiver.isObject()) { - return 0; - } - - Value iteratorFactoryValue = - runtime.global().getProperty(runtime, "__nativeScriptCreateNativeApiIterator"); - if (!iteratorFactoryValue.isObject() || - !iteratorFactoryValue.asObject(runtime).isFunction(runtime)) { - return 0; - } - - Function iteratorFactory = iteratorFactoryValue.asObject(runtime).asFunction(runtime); - Value prototype = bridge->findClassPrototype(runtime, object_getClass(self)); - Value iteratorValue = - prototype.isObject() - ? iteratorFactory.call(runtime, Value(runtime, receiver), Value(runtime, prototype)) - : iteratorFactory.call(runtime, Value(runtime, receiver)); - if (!iteratorValue.isObject()) { - return 0; - } - Object iterator = iteratorValue.asObject(runtime); - Value nextValue = iterator.getProperty(runtime, "next"); - if (!nextValue.isObject() || !nextValue.asObject(runtime).isFunction(runtime)) { - return 0; - } - Function next = nextValue.asObject(runtime).asFunction(runtime); - - auto callNext = [&]() -> Value { return next.callWithThis(runtime, iterator); }; - - for (unsigned long skipped = 0; skipped < state->state; skipped++) { - Value skippedResult = callNext(); - if (!skippedResult.isObject()) { - return 0; - } - Value doneValue = skippedResult.asObject(runtime).getProperty(runtime, "done"); - if (doneValue.isBool() && doneValue.getBool()) { - return 0; - } - } - - NSUInteger count = 0; - while (count < len) { - Value nextResult = callNext(); - if (!nextResult.isObject()) { - break; - } - Object nextObject = nextResult.asObject(runtime); - Value doneValue = nextObject.getProperty(runtime, "done"); - if (doneValue.isBool() && doneValue.getBool()) { - break; - } - - Value value = nextObject.getProperty(runtime, "value"); - NativeApiJsiArgumentFrame frame(1); - id nativeValue = objectFromJsiValue(runtime, bridge, value, frame, false); - if (nativeValue != nil) { - [nativeValue retain]; - [nativeValue autorelease]; - } - stackbuf[count++] = nativeValue; - } - - state->itemsPtr = stackbuf; - state->mutationsPtr = &state->extra[0]; - state->extra[0] = 0; - state->state += count; - return count; - } catch (const std::exception&) { - return 0; - } -} - -NativeApiSymbol runtimeSymbolForClass(const std::shared_ptr& bridge, - Class cls) { - if (bridge != nullptr) { - if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { - return *symbol; - } - } - - const char* name = cls != Nil ? class_getName(cls) : ""; - return NativeApiSymbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = name != nullptr ? name : "", - .runtimeName = name != nullptr ? name : "", - }; -} - -std::string nextAvailableJsiClassName(const std::string& requestedName) { - if (requestedName.empty()) { - return ""; - } - if (objc_lookUpClass(requestedName.c_str()) == Nil) { - return requestedName; - } - - size_t suffix = 1; - std::string candidate; - do { - candidate = requestedName + std::to_string(suffix++); - } while (objc_lookUpClass(candidate.c_str()) != Nil); - return candidate; -} - -std::vector methodOverridesForName(const std::vector& members, - const std::string& name) { - std::vector result; - std::unordered_set selectors; - for (const auto& member : members) { - if (member.property || member.name != name || (member.flags & metagen::mdMemberStatic) != 0 || - member.selectorName.empty()) { - continue; - } - if (selectors.insert(member.selectorName).second) { - result.push_back(member); - } - } - return result; -} - -const NativeApiMember* propertyOverrideForName(const std::vector& members, - const std::string& name) { - const NativeApiMember* fallback = nullptr; - for (const auto& member : members) { - if (member.property && member.name == name && (member.flags & metagen::mdMemberStatic) == 0) { - if (fallback == nullptr) { - fallback = &member; - } - if (!member.readonly && !member.setterSelectorName.empty()) { - return &member; - } - } - } - return fallback; -} - -void addJsiOverrideMethod(Runtime& runtime, const std::shared_ptr& bridge, - Class nativeClass, Class baseClass, const std::string& selectorName, - MDSectionOffset signatureOffset, bool returnOwned, Function function) { - if (selectorName.empty() || signatureOffset == MD_SECTION_OFFSET_NULL) { - return; - } - - auto callback = createJsiMethodCallback(runtime, bridge, selectorName, signatureOffset, - std::move(function), returnOwned); - SEL selector = sel_registerName(selectorName.c_str()); - std::string metadataEncoding = objcMethodSignatureForJsiSignature(callback->signature()); - class_replaceMethod(nativeClass, selector, reinterpret_cast(callback->functionPointer()), - metadataEncoding.c_str()); -} - -Value getObjectPropertyOrUndefined(Runtime& runtime, const Object& object, - const std::string& name) { - return object.hasProperty(runtime, name.c_str()) ? object.getProperty(runtime, name.c_str()) - : Value::undefined(); -} - -Class dispatchSuperclassForJsiDerivedReceiver(id receiver, Class fallback) { - if (receiver == nil) { - return Nil; - } - - Class receiverClass = object_getClass(receiver); - if (receiverClass == Nil || - !class_conformsToProtocol(receiverClass, @protocol(NativeApiJsiClassBuilderProtocol))) { - return Nil; - } - - Class superclass = class_getSuperclass(receiverClass); - return superclass != Nil ? superclass : fallback; -} - -std::optional functionForSelector(Runtime& runtime, const Object& methods, - const std::string& selectorName) { - Value value = getObjectPropertyOrUndefined(runtime, methods, selectorName); - if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { - std::string jsName = jsifySelector(selectorName.c_str()); - if (jsName != selectorName) { - value = getObjectPropertyOrUndefined(runtime, methods, jsName); - } - } - if (!value.isObject() || !value.asObject(runtime).isFunction(runtime)) { - return std::nullopt; - } - return value.asObject(runtime).asFunction(runtime); -} - -std::optional readExposedType(Runtime& runtime, - const std::shared_ptr& bridge, - const Object& descriptor, - const char* propertyName) { - if (!descriptor.hasProperty(runtime, propertyName)) { - return std::nullopt; - } - return interopTypeFromValue(runtime, bridge, descriptor.getProperty(runtime, propertyName)); -} - -std::optional exposedMethodSignature( - Runtime& runtime, const std::shared_ptr& bridge, - const std::string& selectorName, const Object& descriptor) { - NativeApiJsiSignature signature; - if (auto returnType = readExposedType(runtime, bridge, descriptor, "returns")) { - signature.returnType = *returnType; - } else { - signature.returnType = primitiveInteropType(metagen::mdTypeVoid); - } - - Value paramsValue = getObjectPropertyOrUndefined(runtime, descriptor, "params"); - if (!paramsValue.isUndefined() && !paramsValue.isNull()) { - if (!paramsValue.isObject() || !paramsValue.asObject(runtime).isArray(runtime)) { - throw facebook::jsi::JSError(runtime, "exposedMethods params must be an array."); - } - Array params = paramsValue.asObject(runtime).getArray(runtime); - for (size_t i = 0; i < params.size(runtime); i++) { - Value typeValue = params.getValueAtIndex(runtime, i); - auto type = interopTypeFromValue(runtime, bridge, typeValue); - if (!type) { - throw facebook::jsi::JSError(runtime, - "exposedMethods contains an unsupported parameter type."); - } - signature.argumentTypes.push_back(*type); - } - } - - // A colon-less selector may still declare params (@nativescript/core - // exposes onReceive with one NSNotification param); the Objective-C runtime - // accepts such methods and callers like NSNotificationCenter invoke them - // with the argument. Only reject a mismatch when the selector explicitly - // declares argument slots. - size_t selectorArguments = selectorArgumentCount(selectorName); - if (selectorArguments != signature.argumentTypes.size() && selectorArguments != 0) { - throw facebook::jsi::JSError(runtime, - "exposedMethods selector argument count does not match params."); - } - - prepareJsiMethodSignature(&signature); - return signature; -} - -std::optional runtimeProtocolMethodSignature(const char* types) { - if (types == nullptr) { - return std::nullopt; - } - - NSMethodSignature* methodSignature = [NSMethodSignature signatureWithObjCTypes:types]; - if (methodSignature == nil || methodSignature.numberOfArguments < 2) { - return std::nullopt; - } - - NativeApiJsiSignature signature; - signature.implicitArgumentCount = 2; - signature.returnType = parseObjCEncodedJsiType(methodSignature.methodReturnType); - for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) { - signature.argumentTypes.push_back( - parseObjCEncodedJsiType([methodSignature getArgumentTypeAtIndex:i])); - } - if (unsupportedJsiType(signature.returnType)) { - return std::nullopt; - } - for (const auto& argumentType : signature.argumentTypes) { - if (unsupportedJsiType(argumentType)) { - return std::nullopt; - } - } - return signature; -} - -std::optional protocolSymbolFromJsiValue( - Runtime& runtime, const std::shared_ptr& bridge, const Value& value) { - if (value.isString()) { - std::string name = value.asString(runtime).utf8(runtime); - if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { - return *symbol; - } - return std::nullopt; - } - if (!value.isObject()) { - return std::nullopt; - } - - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->symbol(); - } - - if (stringPropertyOrEmpty(runtime, object, "kind") != "protocol") { - return std::nullopt; - } - - std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); - if (!runtimeName.empty()) { - if (const NativeApiSymbol* symbol = bridge->findProtocol(runtimeName)) { - return *symbol; - } - } - - std::string name = stringPropertyOrEmpty(runtime, object, "name"); - if (!name.empty()) { - if (const NativeApiSymbol* symbol = bridge->findProtocol(name)) { - return *symbol; - } - } - - return std::nullopt; -} - -void addJsiExposedMethod(Runtime& runtime, const std::shared_ptr& bridge, - Class nativeClass, const std::string& selectorName, - NativeApiJsiSignature signature, Function function) { - if (selectorName.empty()) { - return; - } - auto callback = createJsiMethodCallback(runtime, bridge, selectorName, std::move(signature), - std::move(function)); - std::string encoding = objcMethodSignatureForJsiSignature(callback->signature()); - class_replaceMethod(nativeClass, sel_registerName(selectorName.c_str()), - reinterpret_cast(callback->functionPointer()), encoding.c_str()); -} - -bool addRuntimeProtocolOverrideForName(Runtime& runtime, - const std::shared_ptr& bridge, - Class nativeClass, const std::vector& protocols, - const std::string& propertyName, Function function) { - std::unordered_set visited; - std::function visit = [&](Protocol* protocol) -> bool { - if (protocol == nullptr || !visited.insert(protocol).second) { - return false; - } - - Protocol** inherited = protocol_copyProtocolList(protocol, nullptr); - if (inherited != nullptr) { - unsigned int inheritedCount = 0; - free(inherited); - inherited = protocol_copyProtocolList(protocol, &inheritedCount); - for (unsigned int i = 0; i < inheritedCount; i++) { - if (visit(inherited[i])) { - free(inherited); - return true; - } - } - free(inherited); - } - - for (BOOL required : {YES, NO}) { - unsigned int count = 0; - objc_method_description* descriptions = - protocol_copyMethodDescriptionList(protocol, required, YES, &count); - for (unsigned int i = 0; i < count; i++) { - SEL selector = descriptions[i].name; - const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; - if (selectorName == nullptr || jsifySelector(selectorName) != propertyName) { - continue; - } - auto signature = runtimeProtocolMethodSignature(descriptions[i].types); - if (signature) { - addJsiExposedMethod(runtime, bridge, nativeClass, selectorName, std::move(*signature), - std::move(function)); - free(descriptions); - return true; - } - } - free(descriptions); - } - return false; - }; - - for (Protocol* protocol : protocols) { - if (visit(protocol)) { - return true; - } - } - return false; -} - -Object getOwnPropertyDescriptor(Runtime& runtime, const Object& object, const std::string& name) { - Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); - Function getOwnPropertyDescriptor = - objectCtor.getPropertyAsFunction(runtime, "getOwnPropertyDescriptor"); - Value args[] = {Value(runtime, object), makeString(runtime, name)}; - Value descriptorValue = getOwnPropertyDescriptor.call(runtime, static_cast(args), - static_cast(2)); - return descriptorValue.isObject() ? descriptorValue.asObject(runtime) : Object(runtime); -} - -Value extendNativeApiJsiClass(Runtime& runtime, const std::shared_ptr& bridge, - const Value* args, size_t count) { - if (count < 2 || !args[0].isObject() || !args[1].isObject()) { - throw facebook::jsi::JSError(runtime, "extendClass expects a native class and method object."); - } - - Class baseClass = classFromJsiValue(runtime, args[0]); - if (baseClass == Nil) { - throw facebook::jsi::JSError(runtime, "extendClass can only extend native class constructors."); - } - if (class_conformsToProtocol(baseClass, @protocol(NativeApiJsiClassBuilderProtocol))) { - throw facebook::jsi::JSError(runtime, "Cannot extend an already extended class."); - } - - Object methods = args[1].asObject(runtime); - Object options = count >= 3 && args[2].isObject() ? args[2].asObject(runtime) : Object(runtime); - std::string requestedName = readOptionalStringProperty(runtime, options, "name"); - if (requestedName.empty()) { - const char* baseName = class_getName(baseClass); - requestedName = std::string(baseName != nullptr ? baseName : "NSObject") + "_Extended_" + - std::to_string(rand()); - } - - std::string className = nextAvailableJsiClassName(requestedName); - Class nativeClass = objc_allocateClassPair(baseClass, className.c_str(), 0); - if (nativeClass == Nil) { - throw facebook::jsi::JSError(runtime, "Failed to allocate Objective-C class."); - } - - markNativeApiJsiExtendedClass(nativeClass); - class_addProtocol(nativeClass, @protocol(NativeApiJsiClassBuilderProtocol)); - rememberNativeApiJsiClassBuilder(runtime, bridge, nativeClass); - - NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); - std::vector extensionMembers = bridge->membersForClass(baseSymbol); - std::vector optionProtocols; - Value protocolsValue = getObjectPropertyOrUndefined(runtime, options, "protocols"); - if (protocolsValue.isObject() && protocolsValue.asObject(runtime).isArray(runtime)) { - Array protocols = protocolsValue.asObject(runtime).getArray(runtime); - for (size_t i = 0; i < protocols.size(runtime); i++) { - Value protocolValue = protocols.getValueAtIndex(runtime, i); - Protocol* protocol = protocolFromJsiValue(runtime, protocolValue); - std::optional protocolSymbol = - protocolSymbolFromJsiValue(runtime, bridge, protocolValue); - if (protocol != nullptr) { - optionProtocols.push_back(protocol); - class_addProtocol(nativeClass, protocol); - if (!protocolSymbol) { - if (const NativeApiSymbol* runtimeSymbol = - bridge->findProtocolForRuntimePointer(protocol)) { - protocolSymbol = *runtimeSymbol; - } - } - } - if (protocolSymbol) { - const auto& protocolMembers = bridge->membersForProtocol(*protocolSymbol); - extensionMembers.insert(extensionMembers.begin(), protocolMembers.begin(), - protocolMembers.end()); - } - } - } - const auto& members = extensionMembers; - Array propertyNames = methods.getPropertyNames(runtime); - for (size_t i = 0; i < propertyNames.size(runtime); i++) { - Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); - if (!propertyNameValue.isString()) { - continue; - } - - std::string propertyName = propertyNameValue.asString(runtime).utf8(runtime); - Object descriptor = getOwnPropertyDescriptor(runtime, methods, propertyName); - - Value value = descriptor.getProperty(runtime, "value"); - if (value.isObject() && value.asObject(runtime).isFunction(runtime)) { - auto overrides = methodOverridesForName(members, propertyName); - bool addedOverride = false; - for (const auto& member : overrides) { - if (member.selectorName.empty() || member.signatureOffset == MD_SECTION_OFFSET_NULL || - member.signatureOffset == 0) { - continue; - } - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, member.selectorName, - member.signatureOffset, - (member.flags & metagen::mdMemberReturnOwned) != 0, - value.asObject(runtime).asFunction(runtime)); - addedOverride = true; - } - if (!addedOverride) { - bool addedRuntimeProtocolOverride = addRuntimeProtocolOverrideForName( - runtime, bridge, nativeClass, optionProtocols, propertyName, - value.asObject(runtime).asFunction(runtime)); - if (!addedRuntimeProtocolOverride) { - if (auto known = knownNativeApiJsiExposedMethod(propertyName)) { - addJsiExposedMethod(runtime, bridge, nativeClass, known->selectorName, - std::move(known->signature), - value.asObject(runtime).asFunction(runtime)); - } - } - } - } - - const NativeApiMember* propertyMember = propertyOverrideForName(members, propertyName); - - Value getter = descriptor.getProperty(runtime, "get"); - if (propertyMember != nullptr && getter.isObject() && - getter.asObject(runtime).isFunction(runtime)) { - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, propertyMember->selectorName, - propertyMember->signatureOffset, - (propertyMember->flags & metagen::mdMemberReturnOwned) != 0, - getter.asObject(runtime).asFunction(runtime)); - } else if (propertyMember == nullptr && getter.isObject() && - getter.asObject(runtime).isFunction(runtime)) { - auto overrides = methodOverridesForName(members, propertyName); - for (const auto& member : overrides) { - if (selectorArgumentCount(member.selectorName) != 0) { - continue; - } - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, member.selectorName, - member.signatureOffset, - (member.flags & metagen::mdMemberReturnOwned) != 0, - getter.asObject(runtime).asFunction(runtime)); - } - } - - Value setter = descriptor.getProperty(runtime, "set"); - if (propertyMember != nullptr && setter.isObject() && - setter.asObject(runtime).isFunction(runtime) && - !propertyMember->setterSelectorName.empty()) { - addJsiOverrideMethod(runtime, bridge, nativeClass, baseClass, - propertyMember->setterSelectorName, - propertyMember->setterSignatureOffset, false, - setter.asObject(runtime).asFunction(runtime)); - } - } - - Value exposedMethodsValue = getObjectPropertyOrUndefined(runtime, options, "exposedMethods"); - if (!exposedMethodsValue.isObject()) { - exposedMethodsValue = getObjectPropertyOrUndefined(runtime, methods, "ObjCExposedMethods"); - } - if (exposedMethodsValue.isObject()) { - Object exposedMethods = exposedMethodsValue.asObject(runtime); - Array exposedNames = exposedMethods.getPropertyNames(runtime); - for (size_t i = 0; i < exposedNames.size(runtime); i++) { - Value selectorValue = exposedNames.getValueAtIndex(runtime, i); - if (!selectorValue.isString()) { - continue; - } - std::string selectorName = selectorValue.asString(runtime).utf8(runtime); - Value descriptorValue = getObjectPropertyOrUndefined(runtime, exposedMethods, selectorName); - if (!descriptorValue.isObject()) { - continue; - } - auto function = functionForSelector(runtime, methods, selectorName); - if (!function) { - continue; - } - auto signature = - exposedMethodSignature(runtime, bridge, selectorName, descriptorValue.asObject(runtime)); - if (signature) { - rememberNativeApiJsiKnownExposedMethod(selectorName, *signature); - addJsiExposedMethod(runtime, bridge, nativeClass, selectorName, std::move(*signature), - std::move(*function)); - } - } - } - - Value hasIteratorValue = getObjectPropertyOrUndefined(runtime, options, "__hasIterator"); - if (hasIteratorValue.isBool() && hasIteratorValue.getBool()) { - class_addProtocol(nativeClass, @protocol(NSFastEnumeration)); - if (const char* encoding = nativeApiJsiFastEnumerationEncoding()) { - class_replaceMethod(nativeClass, @selector(countByEnumeratingWithState:objects:count:), - reinterpret_cast(nativeApiJsiSymbolIteratorCountByEnumerating), - encoding); - } - } - - objc_registerClassPair(nativeClass); - - NativeApiSymbol newSymbol = baseSymbol; - newSymbol.name = className; - newSymbol.runtimeName = className; - newSymbol.superclassOffset = baseSymbol.offset; - return makeNativeClassValue(runtime, bridge, std::move(newSymbol)); -} - -Value invokeNativeApiJsiBaseMethod(Runtime& runtime, - const std::shared_ptr& bridge, - const Value* args, size_t count) { - if (count < 3 || !args[0].isObject() || !args[1].isObject() || !args[2].isString()) { - throw facebook::jsi::JSError(runtime, - "__invokeBase expects base class, receiver, and member name."); - } - - Class baseClass = classFromJsiValue(runtime, args[0]); - if (baseClass == Nil) { - throw facebook::jsi::JSError(runtime, "__invokeBase base class is invalid."); - } - - Object receiverObject = args[1].asObject(runtime); - if (!receiverObject.isHostObject(runtime)) { - throw facebook::jsi::JSError(runtime, "__invokeBase receiver is not native."); - } - - // Dispatch through the host object so initializer selectors get their - // receiver-consumption bookkeeping (disown + round-trip/expando forget); - // calling the raw selector path here leaves dangling ownership of the - // placeholder an init consumes (e.g. UIColor.alloc().initWith...). - auto hostObject = receiverObject.getHostObject(runtime); - id receiver = hostObject->object(); - std::string memberName = args[2].asString(runtime).utf8(runtime); - size_t actualArgc = count - 3; - - NativeApiSymbol baseSymbol = runtimeSymbolForClass(bridge, baseClass); - const auto& members = bridge->membersForClass(baseSymbol); - const NativeApiMember* member = selectMethodMember(members, memberName, false, actualArgc); - if (member == nullptr) { - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, memberName, false)) { - if (actualArgc == 0) { - Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - return hostObject->callObjectSelector(runtime, propertyMember->selectorName, - propertyMember, nullptr, 0, dispatchClass); - } - if (actualArgc == 1 && !propertyMember->setterSelectorName.empty() && - !propertyMember->readonly) { - Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - return hostObject->callObjectSelector(runtime, setterMember.selectorName, &setterMember, - args + 3, actualArgc, dispatchClass); - } - } - } - if (member == nullptr) { - throw facebook::jsi::JSError(runtime, - "Objective-C base selector is not available: " + memberName); - } - - Class dispatchClass = dispatchSuperclassForJsiDerivedReceiver(receiver, baseClass); - return hostObject->callObjectSelector(runtime, member->selectorName, member, args + 3, - actualArgc, dispatchClass); -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiConversion.h b/NativeScript/ffi/shared/jsi/NativeApiJsiConversion.h deleted file mode 100644 index 6ed83df9c..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiConversion.h +++ /dev/null @@ -1,2122 +0,0 @@ -std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, - const char* name); -void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object); - -id objectFromJsiValue(Runtime& runtime, - const std::shared_ptr& bridge, - const Value& value, NativeApiJsiArgumentFrame& frame, - bool mutableString) { - if (value.isNull() || value.isUndefined()) { - return nil; - } - if (value.isString()) { - std::string utf8 = value.asString(runtime).utf8(runtime); - id string = mutableString - ? [[NSMutableString alloc] initWithBytes:utf8.data() - length:utf8.size() - encoding:NSUTF8StringEncoding] - : [[NSString alloc] initWithBytes:utf8.data() - length:utf8.size() - encoding:NSUTF8StringEncoding]; - frame.addObject(string); - return string; - } - if (value.isBool()) { - return [NSNumber numberWithBool:value.getBool()]; - } - if (value.isNumber()) { - return [NSNumber numberWithDouble:value.getNumber()]; - } - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->object(); - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return static_cast(cls); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime) - ->nativeProtocol()); - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - return static_cast(symbolPointer); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->pointer()); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->data()); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->data()); - } - - Value getTimeValue = object.getProperty(runtime, "getTime"); - Value toISOStringValue = object.getProperty(runtime, "toISOString"); - if (getTimeValue.isObject() && - getTimeValue.asObject(runtime).isFunction(runtime) && - toISOStringValue.isObject() && - toISOStringValue.asObject(runtime).isFunction(runtime)) { - Value millisValue = getTimeValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (millisValue.isNumber()) { - NSDate* date = [NSDate dateWithTimeIntervalSince1970:millisValue.getNumber() / 1000.0]; - bridge->rememberRoundTripValue(runtime, date, value); - return date; - } - } - - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitiveValue = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (primitiveValue.isString() || primitiveValue.isBool() || - primitiveValue.isNumber()) { - return objectFromJsiValue(runtime, bridge, primitiveValue, frame, - mutableString); - } - } - - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - NSData* data = [NSData dataWithBytes:bytes length:byteLength]; - bridge->rememberRoundTripValue(runtime, data, value); - return data; - } - - if (object.isArray(runtime)) { - Array array = object.getArray(runtime); - NSMutableArray* nativeArray = - [NSMutableArray arrayWithCapacity:array.size(runtime)]; - for (size_t i = 0; i < array.size(runtime); i++) { - id element = objectFromJsiValue(runtime, bridge, - array.getValueAtIndex(runtime, i), - frame, false); - [nativeArray addObject:element != nil ? element : [NSNull null]]; - } - bridge->rememberRoundTripValue(runtime, nativeArray, value); - return nativeArray; - } - - Value lengthValue = object.getProperty(runtime, "length"); - if (lengthValue.isNumber() && std::isfinite(lengthValue.getNumber()) && - lengthValue.getNumber() >= 0) { - size_t length = static_cast(std::floor(lengthValue.getNumber())); - NSMutableArray* nativeArray = [NSMutableArray arrayWithCapacity:length]; - for (size_t i = 0; i < length; i++) { - std::string key = std::to_string(i); - id element = objectFromJsiValue( - runtime, bridge, object.getProperty(runtime, key.c_str()), frame, - false); - [nativeArray addObject:element != nil ? element : [NSNull null]]; - } - bridge->rememberRoundTripValue(runtime, nativeArray, value); - return nativeArray; - } - - Value entriesValue = object.getProperty(runtime, "entries"); - Value sizeValue = object.getProperty(runtime, "size"); - Value getValue = object.getProperty(runtime, "get"); - if (entriesValue.isObject() && - entriesValue.asObject(runtime).isFunction(runtime) && - sizeValue.isNumber() && getValue.isObject() && - getValue.asObject(runtime).isFunction(runtime)) { - Object arrayCtor = runtime.global().getPropertyAsObject(runtime, "Array"); - Function arrayFrom = arrayCtor.getPropertyAsFunction(runtime, "from"); - Value iterator = entriesValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - Value pairsValue = arrayFrom.call(runtime, iterator); - if (pairsValue.isObject() && pairsValue.asObject(runtime).isArray(runtime)) { - Array pairs = pairsValue.asObject(runtime).getArray(runtime); - NSMutableDictionary* nativeMap = - [NSMutableDictionary dictionaryWithCapacity:pairs.size(runtime)]; - for (size_t i = 0; i < pairs.size(runtime); i++) { - Value pairValue = pairs.getValueAtIndex(runtime, i); - if (!pairValue.isObject() || - !pairValue.asObject(runtime).isArray(runtime)) { - continue; - } - Array pair = pairValue.asObject(runtime).getArray(runtime); - if (pair.size(runtime) < 2) { - continue; - } - id key = objectFromJsiValue(runtime, bridge, - pair.getValueAtIndex(runtime, 0), - frame, false); - id nativeValue = objectFromJsiValue(runtime, bridge, - pair.getValueAtIndex(runtime, 1), - frame, false); - if (key != nil) { - [nativeMap setObject:nativeValue != nil ? nativeValue : [NSNull null] - forKey:key]; - } - } - bridge->rememberRoundTripValue(runtime, nativeMap, value); - return nativeMap; - } - } - - NSMutableDictionary* dictionary = [NSMutableDictionary dictionary]; - Array propertyNames = object.getPropertyNames(runtime); - for (size_t i = 0; i < propertyNames.size(runtime); i++) { - Value propertyNameValue = propertyNames.getValueAtIndex(runtime, i); - if (!propertyNameValue.isString()) { - continue; - } - std::string key = propertyNameValue.asString(runtime).utf8(runtime); - Value propertyValue = object.getProperty(runtime, key.c_str()); - if (propertyValue.isUndefined()) { - continue; - } - id nativeValue = - objectFromJsiValue(runtime, bridge, propertyValue, frame, false); - NSString* nativeKey = [NSString stringWithUTF8String:key.c_str()]; - if (nativeKey != nil) { - [dictionary setObject:nativeValue != nil ? nativeValue : [NSNull null] - forKey:nativeKey]; - } - } - bridge->rememberRoundTripValue(runtime, dictionary, value); - return dictionary; - } - throw facebook::jsi::JSError(runtime, - "Value cannot be converted to Objective-C object."); -} - -std::string utf8StringFromNSString(NSString* string) { - if (string == nil) { - return ""; - } - NSUInteger length = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; - std::string result(length, '\0'); - NSUInteger usedLength = 0; - NSRange remainingRange = NSMakeRange(0, 0); - BOOL ok = [string getBytes:result.data() - maxLength:length - usedLength:&usedLength - encoding:NSUTF8StringEncoding - options:0 - range:NSMakeRange(0, string.length) - remainingRange:&remainingRange]; - if (!ok) { - return string.UTF8String ?: ""; - } - result.resize(usedLength); - return result; -} - -bool readNativePointerProperty(Runtime& runtime, const Object& object, - void** pointer) { - if (pointer == nullptr) { - return false; - } - - Value nativePointerObjectValue = - object.getProperty(runtime, "__nativeApiPointerObject"); - if (nativePointerObjectValue.isObject()) { - Object nativePointerObject = nativePointerObjectValue.asObject(runtime); - if (nativePointerObject.isHostObject( - runtime)) { - *pointer = nativePointerObject - .getHostObject(runtime) - ->pointer(); - return true; - } - } - - Value nativePointerValue = - object.getProperty(runtime, "__nativeApiPointer"); - if (nativePointerValue.isNumber()) { - *pointer = reinterpret_cast( - static_cast(nativePointerValue.getNumber())); - return true; - } - - Value nativeAddressValue = object.getProperty(runtime, "nativeAddress"); - if (nativeAddressValue.isNumber()) { - *pointer = reinterpret_cast( - static_cast(nativeAddressValue.getNumber())); - return true; - } - - return false; -} - -std::string stringPropertyOrEmpty(Runtime& runtime, const Object& object, - const char* name) { - if (name == nullptr || !object.hasProperty(runtime, name)) { - return ""; - } - Value value = object.getProperty(runtime, name); - return value.isString() ? value.asString(runtime).utf8(runtime) : ""; -} - -void* pointerFromSymbolLikeObject(Runtime& runtime, const Object& object) { - std::string kind = stringPropertyOrEmpty(runtime, object, "kind"); - if (kind != "class" && kind != "protocol") { - return nullptr; - } - - std::string runtimeName = stringPropertyOrEmpty(runtime, object, "runtimeName"); - if (runtimeName.empty()) { - runtimeName = stringPropertyOrEmpty(runtime, object, "name"); - } - if (runtimeName.empty()) { - return nullptr; - } - - if (kind == "class") { - return objc_lookUpClass(runtimeName.c_str()); - } - return lookupProtocolByNativeName(runtimeName); -} - -void* pointerFromJsiValue(Runtime& runtime, const Value& value, - NativeApiJsiArgumentFrame& frame) { - if (value.isNull() || value.isUndefined()) { - return nullptr; - } - if (value.isNumber()) { - return reinterpret_cast(static_cast(value.getNumber())); - } - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->pointer(); - } - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->object(); - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return cls; - } - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime) - ->nativeProtocol(); - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - return symbolPointer; - } - if (object.isHostObject(runtime)) { - auto reference = - object.getHostObject(runtime); - if (reference->data() == nullptr) { - reference->ensureStorage(runtime, reference->type(), frame); - } - return reference->data(); - } - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->data(); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return nativePointer; - } - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - return const_cast(bytes); - } - } - if (value.isString()) { - std::string utf8 = value.asString(runtime).utf8(runtime); - char* string = strdup(utf8.c_str()); - return string; - } - throw facebook::jsi::JSError(runtime, "Value cannot be converted to pointer."); -} - -bool readPointerLikeValue(Runtime& runtime, const Value& value, void** pointer) { - if (pointer == nullptr || !value.isObject()) { - return false; - } - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->pointer(); - return true; - } - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->data(); - return true; - } - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->data(); - return true; - } - if (object.isHostObject(runtime)) { - *pointer = object.getHostObject(runtime)->object(); - return true; - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - *pointer = cls; - return true; - } - if (object.isHostObject(runtime)) { - *pointer = - object.getHostObject(runtime)->nativeProtocol(); - return true; - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - *pointer = symbolPointer; - return true; - } - return readNativePointerProperty(runtime, object, pointer); -} - -template -void writeNumericArgument(Runtime& runtime, const Value& value, void* target, - const char* typeName) { - const Value* numericValue = &value; - Value primitiveValue = Value::undefined(); - if (value.isObject()) { - Object object = value.asObject(runtime); - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - primitiveValue = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - numericValue = &primitiveValue; - } - } - - if (!numericValue->isNumber() && !numericValue->isBool()) { - throw facebook::jsi::JSError(runtime, - std::string("Expected numeric ") + typeName + - " argument."); - } - double number = numericValue->isBool() ? (numericValue->getBool() ? 1.0 : 0.0) - : numericValue->getNumber(); - *static_cast(target) = static_cast(number); -} - -void convertJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame); - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value); - -Class classFromJsiValue(Runtime& runtime, const Value& value); -Protocol* protocolFromJsiValue(Runtime& runtime, const Value& value); - -std::optional parseArrayIndexProperty(const std::string& property) { - if (property.empty()) { - return std::nullopt; - } - size_t index = 0; - for (char c : property) { - if (!std::isdigit(static_cast(c))) { - return std::nullopt; - } - size_t digit = static_cast(c - '0'); - if (index > (std::numeric_limits::max() - digit) / 10) { - return std::nullopt; - } - index = (index * 10) + digit; - } - return index; -} - -size_t referenceElementStride(const NativeApiJsiType& type) { - return std::max(nativeSizeForType(type), 1); -} - -void convertAggregateArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame) { - size_t size = nativeSizeForType(type); - if (size == 0) { - return; - } - - std::memset(target, 0, size); - if (value.isNull() || value.isUndefined()) { - return; - } - - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - auto structObject = object.getHostObject(runtime); - if (structObject->data() != nullptr) { - std::memcpy(target, structObject->data(), - std::min(size, static_cast(structObject->info()->size))); - } - return; - } - if (object.isHostObject(runtime)) { - void* data = object.getHostObject(runtime)->data(); - if (data != nullptr) { - std::memcpy(target, data, size); - } - return; - } - if (object.isHostObject(runtime)) { - void* data = object.getHostObject(runtime)->pointer(); - if (data != nullptr) { - std::memcpy(target, data, size); - } - return; - } - - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - if (bytes != nullptr) { - std::memcpy(target, bytes, std::min(byteLength, size)); - } - return; - } - } - - if (type.aggregateInfo == nullptr) { - throw facebook::jsi::JSError(runtime, "Missing native struct metadata."); - } - if (!value.isObject()) { - throw facebook::jsi::JSError(runtime, "Expected struct descriptor object."); - } - - Object object = value.asObject(runtime); - for (const auto& field : type.aggregateInfo->fields) { - bool hasField = object.hasProperty(runtime, field.name.c_str()); - if (!hasField) { - continue; - } - Value fieldValue = object.getProperty(runtime, field.name.c_str()); - void* fieldTarget = static_cast(target) + field.offset; - convertJsiArgument(runtime, bridge, field.type, fieldValue, fieldTarget, - frame); - } -} - -void convertIndexedAggregateArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame) { - size_t size = nativeSizeForType(type); - std::memset(target, 0, size); - if (value.isNull() || value.isUndefined()) { - return; - } - if (value.isObject()) { - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, value.asObject(runtime), &bytes, &byteLength)) { - if (bytes != nullptr) { - std::memcpy(target, bytes, std::min(byteLength, size)); - } - return; - } - } - if (!value.isObject() || !value.asObject(runtime).isArray(runtime)) { - throw facebook::jsi::JSError(runtime, "Expected array, ArrayBuffer, or typed array."); - } - - Array array = value.asObject(runtime).getArray(runtime); - size_t elementSize = type.elementType != nullptr ? nativeSizeForType(*type.elementType) : 0; - if (elementSize == 0 || type.elementType == nullptr) { - throw facebook::jsi::JSError(runtime, "Invalid native array element type."); - } - size_t count = std::min(type.arraySize, array.size(runtime)); - for (size_t i = 0; i < count; i++) { - void* slot = static_cast(target) + (i * elementSize); - convertJsiArgument(runtime, bridge, *type.elementType, - array.getValueAtIndex(runtime, i), slot, frame); - } -} - -void convertJsiFfiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, const Value& value, - void* target, NativeApiJsiArgumentFrame& frame) { - if (type.kind != metagen::mdTypeArray) { - convertJsiArgument(runtime, bridge, type, value, target, frame); - return; - } - - void* pointer = nullptr; - if (!value.isNull() && !value.isUndefined()) { - if (value.isObject()) { - Object object = value.asObject(runtime); - if (!readPointerLikeValue(runtime, value, &pointer)) { - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - pointer = const_cast(bytes); - } - } - } - - if (pointer == nullptr) { - size_t byteLength = nativeSizeForType(type); - void* buffer = frame.addBuffer(byteLength); - convertIndexedAggregateArgument(runtime, bridge, type, value, buffer, - frame); - pointer = buffer; - } - } - - *static_cast(target) = pointer; -} - -void convertJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, - const Value& value, void* target, - NativeApiJsiArgumentFrame& frame) { - if (unsupportedJsiType(type)) { - throw facebook::jsi::JSError(runtime, - "This native signature is not supported by " - "the pure JSI bridge yet."); - } - - switch (type.kind) { - case metagen::mdTypeBool: - if (!value.isNumber() && !value.isBool()) { - throw facebook::jsi::JSError(runtime, - "Expected boolean or numeric argument."); - } - *static_cast(target) = - value.isBool() ? static_cast(value.getBool()) - : static_cast(value.getNumber() != 0); - break; - case metagen::mdTypeChar: - writeNumericArgument(runtime, value, target, "int8"); - break; - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - writeNumericArgument(runtime, value, target, "uint8"); - break; - case metagen::mdTypeSShort: - writeNumericArgument(runtime, value, target, "int16"); - break; - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - if (value.isString()) { - std::string text = value.asString(runtime).utf8(runtime); - if (text.size() != 1) { - throw facebook::jsi::JSError( - runtime, "Expected a single-character string."); - } - *static_cast(target) = - static_cast(static_cast(text[0])); - } else { - writeNumericArgument(runtime, value, target, "uint16"); - } - break; - case metagen::mdTypeSInt: - writeNumericArgument(runtime, value, target, "int32"); - break; - case metagen::mdTypeUInt: - writeNumericArgument(runtime, value, target, "uint32"); - break; - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - writeNumericArgument(runtime, value, target, "int64"); - break; - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - writeNumericArgument(runtime, value, target, "uint64"); - break; - case metagen::mdTypeFloat: - writeNumericArgument(runtime, value, target, "float"); - break; - case metagen::mdTypeDouble: - writeNumericArgument(runtime, value, target, "double"); - break; - case metagen::mdTypeString: { - if (value.isNull() || value.isUndefined()) { - *static_cast(target) = nullptr; - break; - } - if (value.isObject()) { - Object object = value.asObject(runtime); - void* pointer = nullptr; - if (readPointerLikeValue(runtime, value, &pointer)) { - *static_cast(target) = static_cast(pointer); - break; - } - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - *static_cast(target) = - reinterpret_cast(const_cast(bytes)); - break; - } - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitive = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (primitive.isString()) { - std::string utf8 = primitive.asString(runtime).utf8(runtime); - char* string = strdup(utf8.c_str()); - *static_cast(target) = string; - break; - } - } - } - if (!value.isString()) { - throw facebook::jsi::JSError(runtime, "Expected string argument."); - } - std::string utf8 = value.asString(runtime).utf8(runtime); - char* string = strdup(utf8.c_str()); - *static_cast(target) = string; - break; - } - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: { - id object = objectFromJsiValue( - runtime, bridge, value, frame, - type.kind == metagen::mdTypeNSMutableStringObject); - *static_cast(target) = object; - break; - } - case metagen::mdTypeClass: { - *static_cast(target) = classFromJsiValue(runtime, value); - break; - } - case metagen::mdTypeSelector: { - if (value.isNull() || value.isUndefined()) { - *static_cast(target) = nullptr; - break; - } - if (!value.isString()) { - throw facebook::jsi::JSError(runtime, "Expected selector string."); - } - std::string selectorName = value.asString(runtime).utf8(runtime); - *static_cast(target) = sel_registerName(selectorName.c_str()); - break; - } - case metagen::mdTypePointer: - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - auto reference = object.getHostObject(runtime); - if (reference->data() == nullptr && type.elementType != nullptr) { - reference->ensureStorage(runtime, *type.elementType, frame); - } else if (reference->data() == nullptr) { - reference->ensureStorage(runtime, reference->type(), frame); - } - void* pointer = reference->data(); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - *static_cast(target) = pointer; - break; - } - if (object.isHostObject(runtime)) { - void* pointer = - object.getHostObject(runtime) - ->data(); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - *static_cast(target) = pointer; - break; - } - const uint8_t* bytes = nullptr; - size_t byteLength = 0; - if (readJsiBuffer(runtime, object, &bytes, &byteLength)) { - void* pointer = const_cast(bytes); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - *static_cast(target) = pointer; - break; - } - } - *static_cast(target) = pointerFromJsiValue(runtime, value, frame); - break; - case metagen::mdTypeOpaquePointer: - *static_cast(target) = pointerFromJsiValue(runtime, value, frame); - break; - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: { - if (value.isObject()) { - Object object = value.asObject(runtime); - void* nativePointer = nullptr; - if (object.isFunction(runtime)) { - std::string functionKind = stringPropertyOrEmpty(runtime, object, "kind"); - if (functionKind == "block" || functionKind == "functionPointer" || - functionKind == "functionReference") { - if (readNativePointerProperty(runtime, object, &nativePointer)) { - *static_cast(target) = nativePointer; - break; - } - } - - auto threadPolicy = readJsiCallbackThreadPolicy(runtime, object); - auto callback = - createJsiCallback(runtime, bridge, type, object.asFunction(runtime), - type.kind == metagen::mdTypeBlock, threadPolicy); - void* pointer = callback->functionPointer(); - if (type.kind == metagen::mdTypeBlock) { - frame.addLifetime(callback); - frame.rememberRoundTripValue(bridge, runtime, pointer, value); - } else { - bridge->rememberRoundTripValue(runtime, pointer, value); - } - try { - object.setProperty(runtime, "__nativeApiPointerObject", - createPointer(runtime, bridge, pointer)); - object.setProperty( - runtime, "__nativeApiPointer", - static_cast(reinterpret_cast(pointer))); - } catch (const std::exception&) { - } - *static_cast(target) = pointer; - break; - } - } - *static_cast(target) = pointerFromJsiValue(runtime, value, frame); - break; - } - case metagen::mdTypeStruct: - convertAggregateArgument(runtime, bridge, type, value, target, frame); - break; - case metagen::mdTypeArray: - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: - convertIndexedAggregateArgument(runtime, bridge, type, value, target, - frame); - break; - default: - throw facebook::jsi::JSError(runtime, "Unsupported JSI argument type."); - } -} - -Value convertNativeReturnValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* value) { - if (unsupportedJsiType(type)) { - throw facebook::jsi::JSError(runtime, - "This native return type is not supported by " - "the pure JSI bridge yet."); - } - - switch (type.kind) { - case metagen::mdTypeVoid: - return Value::undefined(); - case metagen::mdTypeBool: - return *static_cast(value) != 0; - case metagen::mdTypeChar: - return static_cast(*static_cast(value)); - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - return static_cast(*static_cast(value)); - case metagen::mdTypeSShort: - return static_cast(*static_cast(value)); - case metagen::mdTypeUShort: - return static_cast(*static_cast(value)); - case metagen::mdTypeUnichar: { - const char16_t unit = *static_cast(value); - // UTF-8 encode one UTF-16 code unit (1-3 bytes; unpaired surrogates - // fall back to U+FFFD). - char buffer[4] = {0}; - size_t length = 0; - if (unit < 0x80) { - buffer[length++] = static_cast(unit); - } else if (unit < 0x800) { - buffer[length++] = static_cast(0xC0 | (unit >> 6)); - buffer[length++] = static_cast(0x80 | (unit & 0x3F)); - } else if (unit >= 0xD800 && unit <= 0xDFFF) { - buffer[length++] = static_cast(0xEF); - buffer[length++] = static_cast(0xBF); - buffer[length++] = static_cast(0xBD); - } else { - buffer[length++] = static_cast(0xE0 | (unit >> 12)); - buffer[length++] = static_cast(0x80 | ((unit >> 6) & 0x3F)); - buffer[length++] = static_cast(0x80 | (unit & 0x3F)); - } - return String::createFromUtf8( - runtime, reinterpret_cast(buffer), length); - } - case metagen::mdTypeSInt: - return static_cast(*static_cast(value)); - case metagen::mdTypeUInt: - return static_cast(*static_cast(value)); - case metagen::mdTypeSLong: - case metagen::mdTypeSInt64: - return signedInteger64ToJsiValue(runtime, *static_cast(value)); - case metagen::mdTypeULong: - case metagen::mdTypeUInt64: - return unsignedInteger64ToJsiValue(runtime, - *static_cast(value)); - case metagen::mdTypeFloat: - return static_cast(*static_cast(value)); - case metagen::mdTypeDouble: - return *static_cast(value); - case metagen::mdTypeString: { - const char* string = *static_cast(value); - if (string == nullptr) { - return Value::null(); - } - NativeApiJsiType cStringType = - primitiveInteropType(metagen::mdTypeChar); - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, cStringType, const_cast(string), false)); - } - case metagen::mdTypeClass: { - Class cls = *static_cast(value); - if (cls == nil) { - return Value::null(); - } - const char* name = class_getName(cls); - NativeApiSymbol symbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = name != nullptr ? name : "", - .runtimeName = name != nullptr ? name : "", - }; - if (const NativeApiSymbol* found = bridge->findClass(symbol.name)) { - symbol = *found; - } - return makeNativeClassValue(runtime, bridge, std::move(symbol)); - } - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClassObject: - case metagen::mdTypeInstanceObject: - case metagen::mdTypeNSStringObject: - case metagen::mdTypeNSMutableStringObject: { - id object = *static_cast(value); - if (object == nil) { - return Value::null(); - } - if ([object isKindOfClass:[NSNull class]]) { - if (type.returnOwned) { - [object release]; - } - return Value::null(); - } - if ([object respondsToSelector:@selector(UTF8String)]) { - bool untypedObject = type.kind == metagen::mdTypeAnyObject; - bool explicitNSString = type.kind == metagen::mdTypeNSStringObject; - if (untypedObject || explicitNSString) { - std::string utf8 = utf8StringFromNSString(static_cast(object)); - if (type.returnOwned) { - [object release]; - } - return makeString(runtime, utf8); - } - } - if ([object isKindOfClass:[NSNumber class]] && - ![object isKindOfClass:[NSDecimalNumber class]]) { - NSNumber* number = static_cast(object); - const char* objCType = [number objCType]; - bool isBool = CFGetTypeID((__bridge CFTypeRef)number) == - CFBooleanGetTypeID() || - (objCType != nullptr && - std::strcmp(objCType, @encode(BOOL)) == 0); - Value result = isBool ? Value(static_cast([number boolValue])) - : Value([number doubleValue]); - if (type.returnOwned) { - [object release]; - } - return result; - } - Value roundTrip = bridge->findRoundTripValue(runtime, object); - if (!roundTrip.isUndefined()) { - if (type.returnOwned) { - [object release]; - } - return roundTrip; - } - if (const NativeApiSymbol* classSymbol = - bridge->findClassForRuntimePointer((void*)object)) { - return makeNativeClassValue(runtime, bridge, *classSymbol); - } - if (const NativeApiSymbol* protocolSymbol = - bridge->findProtocolForRuntimePointer((void*)object)) { - return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); - } - return makeNativeObjectValue(runtime, bridge, object, type.returnOwned); - } - case metagen::mdTypeSelector: { - SEL selector = *static_cast(value); - const char* selectorName = selector != nullptr ? sel_getName(selector) : nullptr; - return selectorName != nullptr ? makeString(runtime, selectorName) - : Value::null(); - } - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: { - void* pointer = *static_cast(value); - if (pointer == nullptr) { - return Value::null(); - } - if (const NativeApiSymbol* classSymbol = - bridge->findClassForRuntimePointer(pointer)) { - return makeNativeClassValue(runtime, bridge, *classSymbol); - } - if (const NativeApiSymbol* protocolSymbol = - bridge->findProtocolForRuntimePointer(pointer)) { - return makeNativeProtocolValue(runtime, bridge, *protocolSymbol); - } - if (type.kind == metagen::mdTypePointer && type.elementType != nullptr) { - std::shared_ptr backingValue; - Value roundTrip = bridge->findRoundTripValue(runtime, pointer); - if (!roundTrip.isUndefined()) { - backingValue = std::make_shared(runtime, roundTrip); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, *type.elementType, pointer, false, 0, nullptr, - std::move(backingValue))); - } - return createPointer(runtime, bridge, pointer); - } - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: { - void* pointer = *static_cast(value); - if (pointer == nullptr) { - return Value::null(); - } - Value roundTrip = bridge->findRoundTripValue(runtime, pointer); - if (!roundTrip.isUndefined()) { - return roundTrip; - } - return wrapNativeFunctionPointer(runtime, bridge, type, pointer, - type.kind == metagen::mdTypeBlock); - } - case metagen::mdTypeStruct: - if (type.aggregateInfo == nullptr) { - return ArrayBuffer( - runtime, std::make_shared( - value, nativeSizeForType(type))); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, type.aggregateInfo, value, true)); - case metagen::mdTypeArray: - case metagen::mdTypeVector: - case metagen::mdTypeExtVector: - case metagen::mdTypeComplex: { - Array result(runtime, type.arraySize); - if (type.elementType == nullptr) { - return result; - } - size_t elementSize = nativeSizeForType(*type.elementType); - auto base = static_cast(value); - for (uint16_t i = 0; i < type.arraySize; i++) { - result.setValueAtIndex( - runtime, i, - convertNativeReturnValue(runtime, bridge, *type.elementType, - base + (static_cast(i) * elementSize))); - } - return result; - } - default: - throw facebook::jsi::JSError(runtime, "Unsupported JSI return type."); - } -} - -void NativeApiReferenceHostObject::ensureStorage( - Runtime& runtime, NativeApiJsiType type, NativeApiJsiArgumentFrame& frame, - size_t elements) { - size_t elementCount = std::max(elements, 1); - NativeApiJsiType storageType = std::move(type); - size_t stride = std::max(nativeSizeForType(storageType), 1); - size_t required = std::max(stride * elementCount, sizeof(void*)); - type_ = std::move(storageType); - - if (data_ == nullptr) { - data_ = calloc(1, required); - ownsData_ = true; - byteLength_ = required; - } else if (ownsData_ && byteLength_ < required) { - void* expanded = realloc(data_, required); - if (expanded == nullptr) { - throw std::bad_alloc(); - } - std::memset(static_cast(expanded) + byteLength_, 0, - required - byteLength_); - data_ = expanded; - byteLength_ = required; - } - - if (data_ != nullptr && pendingValue_ != nullptr) { - Value pending(runtime, *pendingValue_); - convertJsiArgument(runtime, bridge_, type_, pending, data_, frame); - pendingValue_.reset(); - } -} - -Value NativeApiReferenceHostObject::get(Runtime& runtime, - const PropNameID& name) { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "reference"); - } - if (property == "address") { - return static_cast(reinterpret_cast(data_)); - } - if (property == "value") { - if (data_ == nullptr) { - if (pendingValue_ != nullptr) { - return Value(runtime, *pendingValue_); - } - return Value::undefined(); - } - return convertNativeReturnValue(runtime, bridge_, type_, data_); - } - if (auto index = parseArrayIndexProperty(property)) { - if (data_ == nullptr) { - return Value::undefined(); - } - void* slot = static_cast(data_) + - (*index * referenceElementStride(type_)); - return convertNativeReturnValue(runtime, bridge_, type_, slot); - } - if (property == "toString") { - void* data = data_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [data](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", data); - return makeString(runtime, - ""); - }); - } - return Value::undefined(); -} - -void NativeApiReferenceHostObject::set(Runtime& runtime, - const PropNameID& name, - const Value& value) { - std::string property = name.utf8(runtime); - auto index = parseArrayIndexProperty(property); - if (property != "value" && !index) { - return; - } - size_t slotIndex = index.value_or(0); - NativeApiJsiArgumentFrame frame(1); - if (data_ == nullptr) { - if (slotIndex == 0) { - pendingValue_ = std::make_shared(runtime, value); - return; - } - ensureStorage(runtime, type_, frame, slotIndex + 1); - } - pendingValue_.reset(); - void* slot = static_cast(data_) + - (slotIndex * referenceElementStride(type_)); - convertJsiArgument(runtime, bridge_, type_, value, slot, frame); -} - -Value NativeApiStructObjectHostObject::get(Runtime& runtime, - const PropNameID& name) { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, info_ != nullptr && info_->isUnion ? "union" : "struct"); - } - if (property == "name") { - return makeString(runtime, info_ != nullptr ? info_->name : ""); - } - if (property == "sizeof") { - return static_cast(info_ != nullptr ? info_->size : 0); - } - if (property == "address") { - return static_cast(reinterpret_cast(data_)); - } - if (property == "toString") { - auto info = info_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [info](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return makeString(runtime, - std::string("[NativeApiJsi ") + - (info != nullptr && info->isUnion ? "Union " : "Struct ") + - (info != nullptr ? info->name : "") + "]"); - }); - } - - if (info_ != nullptr && data_ != nullptr) { - for (const auto& field : info_->fields) { - if (field.name != property) { - continue; - } - void* fieldData = static_cast(data_) + field.offset; - if (field.type.kind == metagen::mdTypeStruct && - field.type.aggregateInfo != nullptr) { - return Object::createFromHostObject( - runtime, std::make_shared( - bridge_, field.type.aggregateInfo, fieldData, false, - ownedData_, backingValue_)); - } - return convertNativeReturnValue(runtime, bridge_, field.type, fieldData); - } - } - return Value::undefined(); -} - -void NativeApiStructObjectHostObject::set(Runtime& runtime, - const PropNameID& name, - const Value& value) { - std::string property = name.utf8(runtime); - if (info_ == nullptr || data_ == nullptr) { - throw facebook::jsi::JSError(runtime, "Struct is not initialized."); - } - for (const auto& field : info_->fields) { - if (field.name != property) { - continue; - } - NativeApiJsiArgumentFrame frame(1); - convertJsiArgument(runtime, bridge_, field.type, value, - static_cast(data_) + field.offset, frame); - return; - } - throw facebook::jsi::JSError(runtime, "No native struct field: " + property); -} - -std::vector NativeApiStructObjectHostObject::getPropertyNames( - Runtime& runtime) { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "name"); - addPropertyName(runtime, names, "sizeof"); - addPropertyName(runtime, names, "address"); - addPropertyName(runtime, names, "toString"); - if (info_ != nullptr) { - for (const auto& field : info_->fields) { - addPropertyName(runtime, names, field.name.c_str()); - } - } - return names; -} - -NativeApiJsiType primitiveInteropType(MDTypeKind kind) { - NativeApiJsiType type; - type.kind = kind; - type.ffiType = ffiTypeForJsiKind(kind); - type.supported = type.ffiType != nullptr; - return type; -} - -std::optional primitiveInteropTypeFromCode(int32_t code) { - MDTypeKind kind = static_cast(code); - switch (kind) { - case metagen::mdTypeVoid: - case metagen::mdTypeBool: - case metagen::mdTypeChar: - case metagen::mdTypeUChar: - case metagen::mdTypeUInt8: - case metagen::mdTypeSShort: - case metagen::mdTypeUShort: - case metagen::mdTypeUnichar: - case metagen::mdTypeSInt: - case metagen::mdTypeUInt: - case metagen::mdTypeSLong: - case metagen::mdTypeULong: - case metagen::mdTypeSInt64: - case metagen::mdTypeUInt64: - case metagen::mdTypeFloat: - case metagen::mdTypeDouble: - case metagen::mdTypeString: - case metagen::mdTypeAnyObject: - case metagen::mdTypeProtocolObject: - case metagen::mdTypeClass: - case metagen::mdTypeSelector: - case metagen::mdTypePointer: - case metagen::mdTypeOpaquePointer: - case metagen::mdTypeBlock: - case metagen::mdTypeFunctionPointer: - return primitiveInteropType(kind); - default: - return std::nullopt; - } -} - -std::optional interopTypeFromValue( - Runtime& runtime, const std::shared_ptr& bridge, - const Value& value) { - if (value.isNumber()) { - return primitiveInteropTypeFromCode(static_cast(value.getNumber())); - } - - if (!value.isObject()) { - return std::nullopt; - } - - Object object = value.asObject(runtime); - Value typeCodeValue = object.getProperty(runtime, "__nativeApiTypeCode"); - if (typeCodeValue.isNumber()) { - return primitiveInteropTypeFromCode( - static_cast(typeCodeValue.getNumber())); - } - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitive = - valueOfValue.asObject(runtime).asFunction(runtime).callWithThis( - runtime, object, nullptr, 0); - if (primitive.isNumber()) { - return primitiveInteropTypeFromCode( - static_cast(primitive.getNumber())); - } - } - - Class descriptorClass = nativeClassFromJsiObject(runtime, object); - if (descriptorClass == Nil && - stringPropertyOrEmpty(runtime, object, "kind") == "class") { - descriptorClass = - static_cast(pointerFromSymbolLikeObject(runtime, object)); - } - if (descriptorClass != Nil) { - return nativeObjectReturnTypeForClass(descriptorClass); - } - - if (object.isHostObject(runtime)) { - auto structObject = object.getHostObject(runtime); - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = structObject->info(); - type.aggregateOffset = type.aggregateInfo != nullptr - ? type.aggregateInfo->offset - : MD_SECTION_OFFSET_NULL; - type.aggregateIsUnion = type.aggregateInfo != nullptr && - type.aggregateInfo->isUnion; - type.ffiType = type.aggregateInfo != nullptr && type.aggregateInfo->ffi != nullptr - ? &type.aggregateInfo->ffi->type - : nullptr; - type.supported = type.ffiType != nullptr; - return type; - } - - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString()) { - std::string kindName = kindValue.asString(runtime).utf8(runtime); - if (kindName == "pointer") { - return primitiveInteropType(metagen::mdTypePointer); - } - if (kindName == "reference") { - return primitiveInteropType(metagen::mdTypePointer); - } - if (kindName == "class") { - return nativeObjectReturnType(metagen::mdTypeInstanceObject); - } - if (kindName == "selector") { - return primitiveInteropType(metagen::mdTypeSelector); - } - if (kindName == "protocol") { - return primitiveInteropType(metagen::mdTypeProtocolObject); - } - if (kindName == "block") { - return primitiveInteropType(metagen::mdTypeBlock); - } - if (kindName == "functionPointer") { - return primitiveInteropType(metagen::mdTypeFunctionPointer); - } - if (kindName == "functionReference") { - return primitiveInteropType(metagen::mdTypeFunctionPointer); - } - } - Value offsetValue = object.getProperty(runtime, "metadataOffset"); - if (kindValue.isString() && offsetValue.isNumber()) { - std::string kindName = kindValue.asString(runtime).utf8(runtime); - if (kindName == "struct" || kindName == "union") { - bool isUnion = kindName == "union"; - auto info = bridge->aggregateInfoFor( - static_cast(offsetValue.getNumber()), isUnion); - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = info; - type.aggregateOffset = info != nullptr ? info->offset : MD_SECTION_OFFSET_NULL; - type.aggregateIsUnion = isUnion; - type.ffiType = info != nullptr && info->ffi != nullptr ? &info->ffi->type : nullptr; - type.supported = type.ffiType != nullptr; - return type; - } - } - - return std::nullopt; -} - -Value makeAggregateConstructor(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol) { - auto info = bridge->aggregateInfoFor(symbol); - auto constructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, symbol.name.c_str()), 1, - [bridge, symbol, info](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (info == nullptr) { - throw facebook::jsi::JSError(runtime, - "Native aggregate metadata is unavailable: " + - symbol.name); - } - - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = info; - type.aggregateOffset = info->offset; - type.aggregateIsUnion = info->isUnion; - type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; - type.supported = type.ffiType != nullptr; - - if (count > 0 && args[0].isObject()) { - void* pointer = nullptr; - if (readPointerLikeValue(runtime, args[0], &pointer) && pointer != nullptr) { - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, info, pointer, false, nullptr, - std::make_shared(runtime, args[0]))); - } - } - - std::vector storage(info->size, 0); - if (count > 0) { - NativeApiJsiArgumentFrame frame(1); - convertAggregateArgument(runtime, bridge, type, args[0], - storage.data(), frame); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, info, storage.data(), true)); - }); - - constructor.setProperty(runtime, "kind", - makeString(runtime, symbol.kind == NativeApiSymbolKind::Union - ? "union" - : "struct")); - constructor.setProperty(runtime, "runtimeName", makeString(runtime, symbol.runtimeName)); - constructor.setProperty(runtime, "metadataOffset", static_cast(symbol.offset)); - constructor.setProperty(runtime, "sizeof", - static_cast(info != nullptr ? info->size : 0)); - constructor.setProperty( - runtime, "equals", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "equals"), 2, - [bridge, info](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (info == nullptr || count < 2) { - return false; - } - - NativeApiJsiType type; - type.kind = metagen::mdTypeStruct; - type.aggregateInfo = info; - type.aggregateOffset = info->offset; - type.aggregateIsUnion = info->isUnion; - type.ffiType = info->ffi != nullptr ? &info->ffi->type : nullptr; - type.supported = type.ffiType != nullptr; - - std::vector left(info->size, 0); - std::vector right(info->size, 0); - try { - NativeApiJsiArgumentFrame leftFrame(1); - convertAggregateArgument(runtime, bridge, type, args[0], - left.data(), leftFrame); - NativeApiJsiArgumentFrame rightFrame(1); - convertAggregateArgument(runtime, bridge, type, args[1], - right.data(), rightFrame); - } catch (const std::exception&) { - return false; - } - - return std::memcmp(left.data(), right.data(), info->size) == 0; - })); - Array fields(runtime, info != nullptr ? info->fields.size() : 0); - if (info != nullptr) { - for (size_t i = 0; i < info->fields.size(); i++) { - fields.setValueAtIndex(runtime, i, makeString(runtime, info->fields[i].name)); - } - } - constructor.setProperty(runtime, "fields", fields); - return constructor; -} - -size_t sizeofInteropType(Runtime& runtime, - const std::shared_ptr& bridge, - const Value& value) { - if (auto type = interopTypeFromValue(runtime, bridge, value)) { - return nativeSizeForType(*type); - } - - if (value.isObject()) { - Object object = value.asObject(runtime); - if (object.isHostObject(runtime) || - object.isHostObject(runtime) || - object.isHostObject(runtime) || - nativeClassFromJsiObject(runtime, object) != Nil) { - return sizeof(void*); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return sizeof(void*); - } - Value sizeValue = object.getProperty(runtime, "sizeof"); - if (sizeValue.isNumber()) { - return static_cast(sizeValue.getNumber()); - } - } - - throw facebook::jsi::JSError(runtime, "Invalid type for interop.sizeof."); -} - -Object createPointer(Runtime& runtime, - const std::shared_ptr& bridge, - void* pointer, bool adopted) { - if (!adopted && bridge != nullptr) { - Value cached = bridge->findPointerValue(runtime, pointer); - if (cached.isObject()) { - return cached.asObject(runtime); - } - } - - Object result = Object::createFromHostObject( - runtime, - std::make_shared(bridge, pointer, "pointer", - adopted)); - if (!adopted && bridge != nullptr) { - bridge->rememberPointerValue(runtime, pointer, Value(runtime, result)); - } - return result; -} - -void installInteropHasInstance(Runtime& runtime, Function& constructor, - const char* kind) { - Value symbolCtorValue = runtime.global().getProperty(runtime, "Symbol"); - if (!symbolCtorValue.isObject()) { - return; - } - - Object symbolCtor = symbolCtorValue.asObject(runtime); - Value hasInstanceValue = symbolCtor.getProperty(runtime, "hasInstance"); - if (!hasInstanceValue.isSymbol()) { - return; - } - - try { - Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); - Function defineProperty = - objectCtor.getPropertyAsFunction(runtime, "defineProperty"); - Object descriptor(runtime); - descriptor.setProperty(runtime, "configurable", true); - descriptor.setProperty( - runtime, "value", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Symbol.hasInstance"), 1, - [kind = std::string(kind)](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - return false; - } - - Object object = args[0].asObject(runtime); - Value kindValue = object.getProperty(runtime, "kind"); - return kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == kind; - })); - defineProperty.call(runtime, constructor, hasInstanceValue, descriptor); - } catch (const std::exception&) { - } -} - -Class classFromJsiValue(Runtime& runtime, const Value& value) { - if (value.isString()) { - std::string name = value.asString(runtime).utf8(runtime); - return objc_lookUpClass(name.c_str()); - } - if (!value.isObject()) { - return Nil; - } - Object object = value.asObject(runtime); - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return cls; - } - if (stringPropertyOrEmpty(runtime, object, "kind") == "class") { - if (void* pointer = pointerFromSymbolLikeObject(runtime, object)) { - return static_cast(pointer); - } - } - if (object.isHostObject(runtime)) { - id nativeObject = object.getHostObject(runtime)->object(); - return nativeObject != nil ? object_getClass(nativeObject) : Nil; - } - return Nil; -} - -Protocol* protocolFromJsiValue(Runtime& runtime, const Value& value) { - if (value.isString()) { - std::string name = value.asString(runtime).utf8(runtime); - Protocol* protocol = objc_getProtocol(name.c_str()); - if (protocol == nullptr) { - constexpr const char* suffix = "Protocol"; - if (name.size() > std::strlen(suffix) && - name.compare(name.size() - std::strlen(suffix), std::strlen(suffix), - suffix) == 0) { - protocol = objc_getProtocol( - name.substr(0, name.size() - std::strlen(suffix)).c_str()); - } - } - return protocol; - } - if (!value.isObject()) { - return nullptr; - } - Object object = value.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime) - ->nativeProtocol(); - } - if (stringPropertyOrEmpty(runtime, object, "kind") == "protocol") { - return static_cast(pointerFromSymbolLikeObject(runtime, object)); - } - if (object.isHostObject(runtime)) { - return static_cast( - object.getHostObject(runtime)->pointer()); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return static_cast(nativePointer); - } - Value nameValue = object.getProperty(runtime, "name"); - if (nameValue.isString()) { - return protocolFromJsiValue(runtime, nameValue); - } - return nullptr; -} - -Object createInteropObject(Runtime& runtime, - const std::shared_ptr& bridge) { - Object interop(runtime); - Object types(runtime); - auto setType = [&](const char* name, MDTypeKind kind) { - Object type(runtime); - double code = static_cast(kind); - type.setProperty(runtime, "__nativeApiTypeCode", code); - type.setProperty( - runtime, "valueOf", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "valueOf"), 0, - [code](Runtime&, const Value&, const Value*, size_t) -> Value { - return code; - })); - type.setProperty( - runtime, "toString", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [code](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - char text[32] = {}; - snprintf(text, sizeof(text), "%d", static_cast(code)); - return makeString(runtime, text); - })); - types.setProperty(runtime, name, type); - }; - setType("void", metagen::mdTypeVoid); - setType("bool", metagen::mdTypeBool); - setType("int8", metagen::mdTypeChar); - setType("uint8", metagen::mdTypeUInt8); - setType("int16", metagen::mdTypeSShort); - setType("uint16", metagen::mdTypeUShort); - setType("int32", metagen::mdTypeSInt); - setType("uint32", metagen::mdTypeUInt); - setType("int64", metagen::mdTypeSInt64); - setType("uint64", metagen::mdTypeUInt64); - setType("float", metagen::mdTypeFloat); - setType("double", metagen::mdTypeDouble); - setType("UTF8CString", metagen::mdTypeString); - setType("unichar", metagen::mdTypeUnichar); - setType("id", metagen::mdTypeAnyObject); - setType("class", metagen::mdTypeClass); - setType("protocol", metagen::mdTypeProtocolObject); - setType("SEL", metagen::mdTypeSelector); - setType("selector", metagen::mdTypeSelector); - setType("pointer", metagen::mdTypePointer); - setType("block", metagen::mdTypeBlock); - setType("functionPointer", metagen::mdTypeFunctionPointer); - interop.setProperty(runtime, "types", types); - - Function pointerConstructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Pointer"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count > 0 && args[0].isObject()) { - Object object = args[0].asObject(runtime); - if (object.isHostObject(runtime)) { - return Value(runtime, object); - } - } - void* pointer = nullptr; - if (count > 0 && !args[0].isNull() && !args[0].isUndefined()) { - auto readAddress = [&](const Value& value, - uintptr_t* address) -> bool { - auto readAddressFromString = [&](const Value& source) -> bool { - try { - Value stringCtorValue = - runtime.global().getProperty(runtime, "String"); - if (!stringCtorValue.isObject() || - !stringCtorValue.asObject(runtime).isFunction(runtime)) { - return false; - } - Value stringValue = - stringCtorValue.asObject(runtime).asFunction(runtime) - .call(runtime, source); - if (!stringValue.isString()) { - return false; - } - return parseIntegerTextToUintptr( - stringValue.asString(runtime).utf8(runtime), address); - } catch (const std::exception&) { - return false; - } - }; - - if (value.isNumber()) { - double number = value.getNumber(); - if (!std::isfinite(number)) { - return false; - } - *address = static_cast( - static_cast(number)); - return true; - } - if (value.isBigInt()) { - if (readAddressFromString(value)) { - return true; - } - BigInt bigint = value.getBigInt(runtime); - return parseBigIntToUintptr(runtime, bigint, address); - } - if (value.isObject()) { - Object object = value.asObject(runtime); - Value valueOfValue = object.getProperty(runtime, "valueOf"); - if (valueOfValue.isObject() && - valueOfValue.asObject(runtime).isFunction(runtime)) { - Value primitive = valueOfValue.asObject(runtime) - .asFunction(runtime) - .callWithThis(runtime, object, nullptr, 0); - if (primitive.isNumber()) { - double number = primitive.getNumber(); - if (!std::isfinite(number)) { - return false; - } - *address = static_cast( - static_cast(number)); - return true; - } - if (primitive.isBigInt()) { - if (readAddressFromString(primitive)) { - return true; - } - BigInt bigint = primitive.getBigInt(runtime); - return parseBigIntToUintptr(runtime, bigint, address); - } - } - return readAddressFromString(value); - } - return false; - }; - - uintptr_t address = 0; - if (!readAddress(args[0], &address)) { - throw facebook::jsi::JSError(runtime, - "Pointer expects a numeric address."); - } - pointer = reinterpret_cast(address); - } - return createPointer(runtime, bridge, pointer); - }); - Object pointerPrototype(runtime); - pointerPrototype.setProperty(runtime, "constructor", pointerConstructor); - pointerConstructor.setProperty(runtime, "prototype", pointerPrototype); - installInteropHasInstance(runtime, pointerConstructor, "pointer"); - pointerConstructor.setProperty(runtime, "kind", makeString(runtime, "pointer")); - pointerConstructor.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - interop.setProperty(runtime, "Pointer", pointerConstructor); - - Function functionReferenceConstructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "FunctionReference"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError( - runtime, "FunctionReference expects a function."); - } - - Object object = args[0].asObject(runtime); - if (!object.isFunction(runtime)) { - throw facebook::jsi::JSError( - runtime, "FunctionReference expects a function."); - } - - Function function = object.asFunction(runtime); - function.setProperty(runtime, "kind", - makeString(runtime, "functionReference")); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - return function; - }); - Object functionReferencePrototype(runtime); - functionReferencePrototype.setProperty(runtime, "constructor", - functionReferenceConstructor); - functionReferenceConstructor.setProperty(runtime, "prototype", - functionReferencePrototype); - installInteropHasInstance(runtime, functionReferenceConstructor, - "functionReference"); - functionReferenceConstructor.setProperty(runtime, "kind", - makeString(runtime, - "functionReference")); - functionReferenceConstructor.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - interop.setProperty(runtime, "FunctionReference", - functionReferenceConstructor); - - Function referenceConstructor = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Reference"), 2, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - NativeApiJsiType type = primitiveInteropType(metagen::mdTypePointer); - bool firstArgumentIsType = false; - if (count > 1) { - firstArgumentIsType = true; - } else if (count == 1 && args[0].isObject()) { - Object object = args[0].asObject(runtime); - Value typeCodeValue = - object.getProperty(runtime, "__nativeApiTypeCode"); - Value kindValue = object.getProperty(runtime, "kind"); - firstArgumentIsType = - typeCodeValue.isNumber() || object.isFunction(runtime) || - nativeClassFromJsiObject(runtime, object) != Nil || - (kindValue.isString() && - (kindValue.asString(runtime).utf8(runtime) == "class" || - kindValue.asString(runtime).utf8(runtime) == "protocol")); - } - std::optional requestedType = - firstArgumentIsType - ? interopTypeFromValue(runtime, bridge, args[0]) - : std::nullopt; - bool hasType = firstArgumentIsType && requestedType.has_value(); - if (hasType) { - type = *requestedType; - } - - void* data = nullptr; - bool ownsData = false; - size_t byteLength = 0; - std::shared_ptr pendingValue; - if (hasType) { - bool usesExternalStorage = false; - Value valueToStore = Value::undefined(); - if (count > 1) { - valueToStore = Value(runtime, args[1]); - if (args[1].isObject()) { - Object object = args[1].asObject(runtime); - if (object.isHostObject(runtime)) { - data = object - .getHostObject( - runtime) - ->pointer(); - usesExternalStorage = true; - } else if (object.isHostObject( - runtime)) { - auto reference = - object.getHostObject( - runtime); - data = reference->data(); - if (data != nullptr) { - usesExternalStorage = true; - } else { - valueToStore = object.getProperty(runtime, "value"); - } - } else if (type.kind == metagen::mdTypeStruct && - object.isHostObject< - NativeApiStructObjectHostObject>(runtime)) { - data = object - .getHostObject< - NativeApiStructObjectHostObject>(runtime) - ->data(); - usesExternalStorage = true; - } else if (type.kind == metagen::mdTypePointer || - type.kind == metagen::mdTypeOpaquePointer || - type.kind == metagen::mdTypeBlock || - type.kind == metagen::mdTypeFunctionPointer) { - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, - &nativePointer)) { - data = nativePointer; - usesExternalStorage = true; - } - } - } - } - if (!usesExternalStorage) { - byteLength = std::max(nativeSizeForType(type), - sizeof(void*)); - data = calloc(1, byteLength); - if (data == nullptr) { - throw std::bad_alloc(); - } - ownsData = true; - if (count > 1) { - NativeApiJsiArgumentFrame frame(1); - convertJsiArgument(runtime, bridge, type, valueToStore, data, - frame); - } - } - } else if (count > 0) { - pendingValue = std::make_shared(runtime, args[0]); - } - - if (ownsData && data == nullptr) { - throw std::bad_alloc(); - } - return Object::createFromHostObject( - runtime, std::make_shared( - bridge, type, data, ownsData, byteLength, - std::move(pendingValue))); - }); - Object referencePrototype(runtime); - referencePrototype.setProperty(runtime, "constructor", referenceConstructor); - referenceConstructor.setProperty(runtime, "prototype", referencePrototype); - installInteropHasInstance(runtime, referenceConstructor, "reference"); - referenceConstructor.setProperty(runtime, "kind", - makeString(runtime, "reference")); - referenceConstructor.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - interop.setProperty(runtime, "Reference", referenceConstructor); - - interop.setProperty( - runtime, "sizeof", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "sizeof"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1) { - throw facebook::jsi::JSError(runtime, "sizeof expects a type."); - } - return static_cast(sizeofInteropType(runtime, bridge, args[0])); - })); - - interop.setProperty( - runtime, "alloc", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "alloc"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isNumber()) { - throw facebook::jsi::JSError(runtime, "alloc expects a byte size."); - } - size_t size = static_cast(std::max(0, args[0].getNumber())); - return createPointer(runtime, bridge, calloc(1, size), false); - })); - - interop.setProperty( - runtime, "free", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "free"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - return Value::undefined(); - } - Object object = args[0].asObject(runtime); - if (!object.isHostObject(runtime)) { - return Value::undefined(); - } - auto pointer = object.getHostObject(runtime); - void* raw = pointer->pointer(); - if (raw != nullptr) { - free(raw); - pointer->clearWithoutFree(); - } - return Value::undefined(); - })); - - interop.setProperty( - runtime, "adopt", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "adopt"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError(runtime, "adopt expects a Pointer."); - } - Object object = args[0].asObject(runtime); - if (!object.isHostObject(runtime)) { - throw facebook::jsi::JSError(runtime, "adopt expects a Pointer."); - } - object.getHostObject(runtime)->adopt(); - return Value(runtime, object); - })); - - interop.setProperty( - runtime, "handleof", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "handleof"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || args[0].isNull() || args[0].isUndefined()) { - return Value::null(); - } - if (args[0].isString()) { - std::string utf8 = args[0].asString(runtime).utf8(runtime); - char* data = strdup(utf8.c_str()); - return createPointer(runtime, bridge, data); - } - if (!args[0].isObject()) { - return Value::null(); - } - Object object = args[0].asObject(runtime); - if (object.isHostObject(runtime)) { - return Value(runtime, object); - } - if (object.isHostObject(runtime)) { - void* data = - object.getHostObject(runtime)->data(); - if (data == nullptr) { - throw facebook::jsi::JSError( - runtime, "Cannot get handle of empty Reference."); - } - return createPointer(runtime, bridge, data); - } - if (object.isHostObject(runtime)) { - auto structObject = - object.getHostObject(runtime); - if (structObject->backingValue() != nullptr) { - return Value(runtime, *structObject->backingValue()); - } - return createPointer(runtime, bridge, structObject->data()); - } - if (object.isHostObject(runtime)) { - return createPointer( - runtime, bridge, - object.getHostObject(runtime) - ->object()); - } - if (Class cls = nativeClassFromJsiObject(runtime, object)) { - return createPointer(runtime, bridge, cls); - } - if (object.isHostObject(runtime)) { - return createPointer( - runtime, bridge, - object.getHostObject(runtime) - ->nativeProtocol()); - } - if (void* symbolPointer = pointerFromSymbolLikeObject(runtime, object)) { - return createPointer(runtime, bridge, symbolPointer); - } - void* nativePointer = nullptr; - if (readNativePointerProperty(runtime, object, &nativePointer)) { - return createPointer(runtime, bridge, nativePointer); - } - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == "functionReference") { - throw facebook::jsi::JSError( - runtime, "Cannot get handle of uninitialized FunctionReference."); - } - Value nativeName = object.getProperty(runtime, "nativeName"); - if (nativeName.isString()) { - std::string name = nativeName.asString(runtime).utf8(runtime); - void* symbol = dlsym(bridge->selfDl(), name.c_str()); - if (symbol != nullptr) { - return createPointer(runtime, bridge, symbol); - } - } - return Value::null(); - })); - - interop.setProperty( - runtime, "stringFromCString", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "stringFromCString"), 2, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || args[0].isNull() || args[0].isUndefined()) { - return Value::null(); - } - NativeApiJsiArgumentFrame frame(1); - const char* data = - static_cast(pointerFromJsiValue(runtime, args[0], frame)); - if (data == nullptr) { - return Value::null(); - } - if (count > 1 && args[1].isNumber()) { - size_t length = static_cast(std::max(0, args[1].getNumber())); - return String::createFromUtf8(runtime, - reinterpret_cast(data), - length); - } - return makeString(runtime, data); - })); - - interop.setProperty( - runtime, "bufferFromData", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "bufferFromData"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError(runtime, "Invalid data."); - } - Object object = args[0].asObject(runtime); - if (object.isArrayBuffer(runtime)) { - return Value(runtime, object); - } - id native = nil; - if (object.isHostObject(runtime)) { - native = object.getHostObject(runtime)->object(); - } else if (object.isHostObject(runtime)) { - native = static_cast( - object.getHostObject(runtime)->pointer()); - } - if (native == nil || ![native isKindOfClass:[NSData class]]) { - throw facebook::jsi::JSError(runtime, "Invalid data."); - } - NSData* data = static_cast(native); - return ArrayBuffer( - runtime, std::make_shared( - data.bytes, static_cast(data.length))); - })); - - interop.setProperty( - runtime, "addMethod", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "addMethod"), 2, - [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - throw facebook::jsi::JSError( - runtime, - "interop.addMethod requires the JSI class builder layer."); - })); - interop.setProperty( - runtime, "addProtocol", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "addProtocol"), 2, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 2) { - throw facebook::jsi::JSError( - runtime, "interop.addProtocol expects class and protocol."); - } - Class cls = classFromJsiValue(runtime, args[0]); - Protocol* protocol = protocolFromJsiValue(runtime, args[1]); - if (cls == Nil || protocol == nullptr) { - return false; - } - return class_addProtocol(cls, protocol); - })); - - return interop; -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObject.h b/NativeScript/ffi/shared/jsi/NativeApiJsiHostObject.h deleted file mode 100644 index 06b82774d..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObject.h +++ /dev/null @@ -1,560 +0,0 @@ -#ifndef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS -inline bool InstallNativeApiEngineLazyGlobal( - Runtime&, std::shared_ptr, const std::string&, - const std::string&, bool) { - return false; -} -#endif - -class NativeApiHostObject final : public HostObject { - public: - explicit NativeApiHostObject(std::shared_ptr bridge) - : bridge_(std::move(bridge)) {} - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "runtime") { - return makeString(runtime, "jsi"); - } - if (property == "backend") { - return makeString(runtime, "hermes"); - } - if (property == "metadata") { - return metadataObject(runtime); - } - if (property == "hasScheduler") { - return bridge_->scheduler() != nullptr; - } - if (property == "interop") { - return createInteropObject(runtime, bridge_); - } -#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS - if (property == "__defineLazyGlobal") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__defineLazyGlobal"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string name = readStringArg(runtime, args, count, 0, "name"); - std::string kind = readStringArg(runtime, args, count, 1, "kind"); - bool force = count > 2 && args[2].isBool() && args[2].getBool(); - return InstallNativeApiEngineLazyGlobal(runtime, bridge, name, kind, - force); - }); - } -#endif - if (property == "__fastEnumeration") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__fastEnumeration"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 1 || !args[0].isObject()) { - throw facebook::jsi::JSError( - runtime, "Fast enumeration expects a native object."); - } - id object = NativeApiObjectHostObject::nativeObjectFromValue(runtime, args[0]); - if (object == nil) { - throw facebook::jsi::JSError( - runtime, "Fast enumeration expects a native object."); - } - if (![object conformsToProtocol:@protocol(NSFastEnumeration)]) { - throw facebook::jsi::JSError( - runtime, "Object does not conform to NSFastEnumeration."); - } - return Object::createFromHostObject( - runtime, - std::make_shared( - bridge, static_cast>(object))); - }); - } - if (property == "runOnUI") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "runOnUI"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - auto scheduler = bridge->scheduler(); - if (scheduler == nullptr) { - throw facebook::jsi::JSError( - runtime, - "NativeApiJsi was installed without a UI scheduler."); - } - - std::shared_ptr callback; - if (count > 0 && !args[0].isNull() && !args[0].isUndefined()) { - if (!args[0].isObject()) { - throw facebook::jsi::JSError( - runtime, "runOnUI expects a function callback."); - } - - Object callbackObject = args[0].asObject(runtime); - if (!callbackObject.isFunction(runtime)) { - throw facebook::jsi::JSError( - runtime, "runOnUI expects a function callback."); - } - callback = std::make_shared( - callbackObject.asFunction(runtime)); - } - - Runtime* runtimePtr = &runtime; - auto promiseCtor = - runtime.global().getPropertyAsFunction(runtime, "Promise"); - return promiseCtor.callAsConstructor( - runtime, - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "runOnUIPromise"), - 2, - [scheduler, runtimePtr, callback]( - Runtime& promiseRuntime, const Value&, - const Value* promiseArgs, - size_t promiseArgc) -> Value { - if (promiseArgc < 2 || !promiseArgs[0].isObject() || - !promiseArgs[1].isObject()) { - return Value::undefined(); - } - - auto resolve = std::make_shared( - promiseArgs[0].asObject(promiseRuntime) - .asFunction(promiseRuntime)); - auto reject = std::make_shared( - promiseArgs[1].asObject(promiseRuntime) - .asFunction(promiseRuntime)); - if (callback == nullptr) { - scheduler->invokeOnUI([scheduler, runtimePtr, resolve]() { - scheduler->invokeOnJS([runtimePtr, resolve]() { - resolve->call(*runtimePtr); - }); - }); - return Value::undefined(); - } - - scheduler->invokeOnJS([runtimePtr, callback, resolve, reject]() { - try { - { - ScopedNativeApiUINativeCallDispatch uiDispatch; - callback->call(*runtimePtr); - } - resolve->call(*runtimePtr); - } catch (const std::exception& error) { - reject->call( - *runtimePtr, - String::createFromUtf8(*runtimePtr, error.what())); - } - }); - - return Value::undefined(); - })); - }); - } - if (property == "import") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "import"), 1, - [](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string path = readStringArg(runtime, args, count, 0, "path"); - std::string frameworkPath = path; - if (!frameworkPath.empty() && frameworkPath[0] != '/') { - frameworkPath = "/System/Library/Frameworks/" + frameworkPath + - ".framework"; - } - - NSBundle* bundle = [NSBundle - bundleWithPath:[NSString stringWithUTF8String:frameworkPath.c_str()]]; - if (bundle == nil || ![bundle load]) { - throw facebook::jsi::JSError( - runtime, "Could not load bundle: " + frameworkPath); - } - return true; - }); - } - if (property == "lookup") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "lookup"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string symbolName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->find(symbolName); - if (symbol == nullptr) { - return Value::null(); - } - return symbolToObject(runtime, *symbol); - }); - } - if (property == "getClass") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getClass"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string className = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findClass(className); - if (symbol == nullptr) { - Class cls = objc_lookUpClass(className.c_str()); - if (cls == nil) { - return Value::null(); - } - NativeApiSymbol runtimeSymbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = className, - .runtimeName = className, - }; - return makeNativeClassValue(runtime, bridge, - std::move(runtimeSymbol)); - } - - return makeNativeClassValue(runtime, bridge, *symbol); - }); - } - if (property == "__extendClass") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__extendClass"), 2, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return extendNativeApiJsiClass(runtime, bridge, args, count); - }); - } - if (property == "__invokeBase") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__invokeBase"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return invokeNativeApiJsiBaseMethod(runtime, bridge, args, count); - }); - } - if (property == "__rememberClassWrapper") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__rememberClassWrapper"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 2) { - return Value::undefined(); - } - Class cls = classFromJsiValue(runtime, args[0]); - if (cls == Nil) { - return Value::undefined(); - } - bridge->rememberClassValue(runtime, cls, args[1]); - if (count >= 3 && args[2].isObject()) { - bridge->rememberClassPrototype(runtime, cls, args[2]); - } - return Value::undefined(); - }); - } - if (property == "__rememberObjectClassWrapper") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "__rememberObjectClassWrapper"), - 2, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 2) { - return Value::undefined(); - } - id object = NativeApiObjectHostObject::nativeObjectFromValue( - runtime, args[0]); - if (object == nil) { - return Value::undefined(); - } - bridge->setObjectExpando(runtime, object, - "__nativeApiClassWrapper", args[1]); - return Value::undefined(); - }); - } - if (property == "CC_SHA256") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "CC_SHA256"), 3, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (count < 3 || !args[1].isNumber()) { - throw facebook::jsi::JSError( - runtime, "CC_SHA256 expects data, length, and output."); - } - void* commonCrypto = - dlopen("/usr/lib/system/libcommonCrypto.dylib", - RTLD_NOW | RTLD_LOCAL); - void* symbol = commonCrypto != nullptr - ? dlsym(commonCrypto, "CC_SHA256") - : nullptr; - if (symbol == nullptr && commonCrypto != nullptr) { - symbol = dlsym(commonCrypto, "_CC_SHA256"); - } - if (symbol == nullptr) { - throw facebook::jsi::JSError(runtime, - "CC_SHA256 is not available."); - } - NativeApiJsiArgumentFrame frame(3); - void* data = pointerFromJsiValue(runtime, args[0], frame); - void* output = pointerFromJsiValue(runtime, args[2], frame); - using CC_SHA256_Fn = unsigned char* (*)(const void*, unsigned long, - unsigned char*); - auto fn = reinterpret_cast(symbol); - unsigned char* result = - fn(data, static_cast(args[1].getNumber()), - static_cast(output)); - return createPointer(runtime, bridge, result); - }); - } - if (property == "getFunction") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getFunction"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string functionName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findFunction(functionName); - if (symbol == nullptr) { - return Value::null(); - } - auto function = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, symbol->name), 0, - [bridge, symbol = *symbol](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - return callCFunction(runtime, bridge, symbol, args, count); - }); - function.setProperty(runtime, "kind", makeString(runtime, "function")); - function.setProperty(runtime, "nativeName", - makeString(runtime, symbol->name)); - function.setProperty(runtime, "metadataOffset", - static_cast(symbol->offset)); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - return function; - }); - } - if (property == "getConstant") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getConstant"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string constantName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findConstant(constantName); - if (symbol == nullptr) { - return Value::undefined(); - } - return constantToValue(runtime, bridge, *symbol); - }); - } - if (property == "getEnum") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getEnum"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string enumName = readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findEnum(enumName); - if (symbol == nullptr) { - return Value::undefined(); - } - return enumToObject(runtime, bridge->metadata(), *symbol); - }); - } - if (property == "getProtocol") { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "getProtocol"), 1, - [bridge](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string protocolName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = bridge->findProtocol(protocolName); - if (symbol == nullptr) { - Protocol* protocol = lookupProtocolByNativeName(protocolName); - if (protocol == nullptr) { - return Value::null(); - } - const char* runtimeName = protocol_getName(protocol); - NativeApiSymbol runtimeSymbol{ - .kind = NativeApiSymbolKind::Protocol, - .offset = MD_SECTION_OFFSET_NULL, - .name = protocolName, - .runtimeName = runtimeName != nullptr ? runtimeName : protocolName, - }; - return makeNativeProtocolValue(runtime, bridge, - std::move(runtimeSymbol)); - } - return makeNativeProtocolValue(runtime, bridge, *symbol); - }); - } - if (property == "getStruct" || property == "getUnion") { - auto bridge = bridge_; - bool isUnion = property == "getUnion"; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, isUnion](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string aggregateName = - readStringArg(runtime, args, count, 0, "name"); - const NativeApiSymbol* symbol = - isUnion ? bridge->findUnion(aggregateName) - : bridge->findStruct(aggregateName); - if (symbol == nullptr) { - return Value::undefined(); - } - return makeAggregateConstructor(runtime, bridge, *symbol); - }); - } - - if (const NativeApiSymbol* classSymbol = bridge_->findClass(property)) { - return makeNativeClassValue(runtime, bridge_, *classSymbol); - } - - if (const NativeApiSymbol* functionSymbol = bridge_->findFunction(property)) { - auto bridge = bridge_; - Function function = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, symbol = *functionSymbol](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - return callCFunction(runtime, bridge, symbol, args, count); - }); - function.setProperty(runtime, "kind", makeString(runtime, "function")); - function.setProperty(runtime, "nativeName", - makeString(runtime, functionSymbol->name)); - function.setProperty(runtime, "metadataOffset", - static_cast(functionSymbol->offset)); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - return function; - } - - if (const NativeApiSymbol* constantSymbol = bridge_->findConstant(property)) { - return constantToValue(runtime, bridge_, *constantSymbol); - } - - if (const NativeApiSymbol* enumSymbol = bridge_->findEnum(property)) { - return enumToObject(runtime, bridge_->metadata(), *enumSymbol); - } - - if (const NativeApiSymbol* protocolSymbol = - bridge_->findProtocol(property)) { - return makeNativeProtocolValue(runtime, bridge_, *protocolSymbol); - } - - if (const NativeApiSymbol* aggregateSymbol = - bridge_->findAggregate(property)) { - return makeAggregateConstructor(runtime, bridge_, *aggregateSymbol); - } - - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(11); - addPropertyName(runtime, names, "runtime"); - addPropertyName(runtime, names, "backend"); - addPropertyName(runtime, names, "metadata"); - addPropertyName(runtime, names, "hasScheduler"); - addPropertyName(runtime, names, "interop"); -#ifdef NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS - addPropertyName(runtime, names, "__defineLazyGlobal"); -#endif - addPropertyName(runtime, names, "runOnUI"); - addPropertyName(runtime, names, "import"); - addPropertyName(runtime, names, "lookup"); - addPropertyName(runtime, names, "getClass"); - addPropertyName(runtime, names, "__extendClass"); - addPropertyName(runtime, names, "__invokeBase"); - addPropertyName(runtime, names, "__rememberClassWrapper"); - addPropertyName(runtime, names, "__rememberObjectClassWrapper"); - addPropertyName(runtime, names, "getFunction"); - addPropertyName(runtime, names, "getConstant"); - addPropertyName(runtime, names, "getEnum"); - addPropertyName(runtime, names, "getProtocol"); - addPropertyName(runtime, names, "getStruct"); - addPropertyName(runtime, names, "getUnion"); - return names; - } - - private: - Object metadataObject(Runtime& runtime) const { - Object metadata(runtime); - metadata.setProperty(runtime, "classes", - static_cast(bridge_->classCount())); - metadata.setProperty(runtime, "functions", - static_cast(bridge_->functionCount())); - metadata.setProperty(runtime, "constants", - static_cast(bridge_->constantCount())); - metadata.setProperty(runtime, "protocols", - static_cast(bridge_->protocolCount())); - metadata.setProperty(runtime, "enums", - static_cast(bridge_->enumCount())); - metadata.setProperty(runtime, "structs", - static_cast(bridge_->structCount())); - metadata.setProperty(runtime, "unions", - static_cast(bridge_->unionCount())); - - metadata.setProperty( - runtime, "classNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "classNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->classNames()); - })); - metadata.setProperty( - runtime, "functionNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "functionNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->functionNames()); - })); - metadata.setProperty( - runtime, "constantNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "constantNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->constantNames()); - })); - metadata.setProperty( - runtime, "protocolNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "protocolNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->protocolNames()); - })); - metadata.setProperty( - runtime, "enumNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "enumNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->enumNames()); - })); - metadata.setProperty( - runtime, "structNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "structNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->structNames()); - })); - metadata.setProperty( - runtime, "unionNames", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "unionNames"), 0, - [bridge = bridge_](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - return namesToArray(runtime, bridge->unionNames()); - })); - return metadata; - } - - std::shared_ptr bridge_; -}; diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObjects.h b/NativeScript/ffi/shared/jsi/NativeApiJsiHostObjects.h deleted file mode 100644 index a1dbf289d..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiHostObjects.h +++ /dev/null @@ -1,1759 +0,0 @@ -class NativeApiPointerHostObject final - : public HostObject, - public std::enable_shared_from_this { - public: - NativeApiPointerHostObject(std::shared_ptr bridge, - void* pointer, std::string kind = "pointer", - bool adopted = false) - : bridge_(std::move(bridge)), - pointer_(pointer), - kind_(std::move(kind)), - adopted_(adopted) {} - - ~NativeApiPointerHostObject() override { - if (adopted_ && pointer_ != nullptr) { - if (bridge_ != nullptr) { - bridge_->forgetPointerValue(pointer_); - } - free(pointer_); - pointer_ = nullptr; - } - } - - void* pointer() const { return pointer_; } - bool adopted() const { return adopted_; } - void adopt() { adopted_ = true; } - void clearWithoutFree() { - if (bridge_ != nullptr) { - bridge_->forgetPointerValue(pointer_); - } - pointer_ = nullptr; - adopted_ = false; - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, kind_); - } - if (property == "address") { - return static_cast(reinterpret_cast(pointer_)); - } - if (property == "adopted") { - return adopted_; - } - if (property == "takeRetainedValue" || property == "takeUnretainedValue") { - bool retained = property == "takeRetainedValue"; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [weakSelf, retained](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - auto self = weakSelf.lock(); - if (!self || self->pointer_ == nullptr || self->consumed_) { - throw facebook::jsi::JSError(runtime, "Unmanaged value has already been consumed."); - } - id object = static_cast(self->pointer_); - self->consumed_ = true; - self->pointer_ = nullptr; - self->adopted_ = false; - return makeNativeObjectValue(runtime, self->bridge_, object, retained); - }); - } - if (property == "add" || property == "subtract") { - void* pointer = pointer_; - bool add = property == "add"; - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, pointer, add](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - if (count < 1 || !args[0].isNumber()) { - throw facebook::jsi::JSError(runtime, "Pointer offset must be a number."); - } - intptr_t offset = static_cast(args[0].getNumber()); - intptr_t base = reinterpret_cast(pointer); - void* result = reinterpret_cast(add ? base + offset : base - offset); - return createPointer(runtime, bridge, result); - }); - } - if (property == "toNumber") { - void* pointer = pointer_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toNumber"), 0, - [pointer](Runtime&, const Value&, const Value*, size_t) -> Value { - return static_cast(reinterpret_cast(pointer)); - }); - } - if (property == "toBigInt") { - void* pointer = pointer_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toBigInt"), 0, - [pointer](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return BigInt::fromUint64( - runtime, - static_cast(reinterpret_cast(pointer))); - }); - } - if (property == "toHexString" || property == "toDecimalString") { - void* pointer = pointer_; - bool hex = property == "toHexString"; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [pointer, hex](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - if (hex) { - char text[2 + sizeof(uintptr_t) * 2 + 1] = {}; - snprintf(text, sizeof(text), "0x%llx", - static_cast( - reinterpret_cast(pointer))); - return makeString(runtime, text); - } else { - char text[32] = {}; - snprintf(text, sizeof(text), "%lld", - static_cast(reinterpret_cast(pointer))); - return makeString(runtime, text); - } - }); - } - if (property == "toString") { - void* pointer = pointer_; - std::string kind = kind_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [pointer, kind](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", pointer); - if (kind == "pointer") { - return makeString(runtime, - ""); - } - return makeString(runtime, "[NativeApiJsi " + kind + " " + - std::string(address) + "]"); - }); - } - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(3); - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "address"); - addPropertyName(runtime, names, "adopted"); - addPropertyName(runtime, names, "takeRetainedValue"); - addPropertyName(runtime, names, "takeUnretainedValue"); - addPropertyName(runtime, names, "add"); - addPropertyName(runtime, names, "subtract"); - addPropertyName(runtime, names, "toNumber"); - addPropertyName(runtime, names, "toBigInt"); - addPropertyName(runtime, names, "toHexString"); - addPropertyName(runtime, names, "toDecimalString"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - void* pointer_ = nullptr; - std::string kind_; - bool adopted_ = false; - bool consumed_ = false; -}; - -class NativeApiReferenceHostObject final : public HostObject { - public: - NativeApiReferenceHostObject(std::shared_ptr bridge, - NativeApiJsiType type, void* data, bool ownsData, - size_t byteLength = 0, - std::shared_ptr pendingValue = nullptr, - std::shared_ptr backingValue = nullptr) - : bridge_(std::move(bridge)), - type_(std::move(type)), - data_(data), - ownsData_(ownsData), - byteLength_(byteLength), - pendingValue_(std::move(pendingValue)), - backingValue_(std::move(backingValue)) {} - - ~NativeApiReferenceHostObject() override { - if (ownsData_ && data_ != nullptr) { - free(data_); - data_ = nullptr; - } - } - - void* data() const { return data_; } - const NativeApiJsiType& type() const { return type_; } - void ensureStorage(Runtime& runtime, NativeApiJsiType type, - NativeApiJsiArgumentFrame& frame, size_t elements = 1); - - Value get(Runtime& runtime, const PropNameID& name) override; - void set(Runtime& runtime, const PropNameID& name, const Value& value) override; - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "value"); - addPropertyName(runtime, names, "address"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - NativeApiJsiType type_; - void* data_ = nullptr; - bool ownsData_ = false; - size_t byteLength_ = 0; - std::shared_ptr pendingValue_; - std::shared_ptr backingValue_; -}; - -class NativeApiStructObjectHostObject final : public HostObject { - public: - NativeApiStructObjectHostObject( - std::shared_ptr bridge, - std::shared_ptr info, - const void* data = nullptr, bool ownsData = true, - std::shared_ptr> storageOwner = nullptr, - std::shared_ptr backingValue = nullptr) - : bridge_(std::move(bridge)), - info_(std::move(info)), - ownedData_(std::move(storageOwner)), - backingValue_(std::move(backingValue)), - ownsData_(ownsData) { - size_t size = info_ != nullptr ? info_->size : 0; - if (ownedData_ != nullptr) { - data_ = const_cast(data); - ownsData_ = false; - } else if (ownsData_) { - ownedData_ = std::make_shared>(size, 0); - if (data != nullptr && size > 0) { - std::memcpy(ownedData_->data(), data, size); - } - data_ = ownedData_->empty() ? nullptr : ownedData_->data(); - } else { - data_ = const_cast(data); - } - } - - void* data() const { return data_; } - std::shared_ptr info() const { return info_; } - std::shared_ptr> storageOwner() const { - return ownedData_; - } - std::shared_ptr backingValue() const { return backingValue_; } - - Value get(Runtime& runtime, const PropNameID& name) override; - void set(Runtime& runtime, const PropNameID& name, const Value& value) override; - std::vector getPropertyNames(Runtime& runtime) override; - - private: - std::shared_ptr bridge_; - std::shared_ptr info_; - std::shared_ptr> ownedData_; - std::shared_ptr backingValue_; - void* data_ = nullptr; - bool ownsData_ = true; -}; - -class NativeApiFastEnumerationIteratorHostObject final : public HostObject { - public: - NativeApiFastEnumerationIteratorHostObject( - std::shared_ptr bridge, id collection) - : bridge_(std::move(bridge)), collection_(collection) { - [(id)collection_ retain]; - } - - ~NativeApiFastEnumerationIteratorHostObject() override { - [(id)collection_ release]; - collection_ = nil; - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "next") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "next"), 0, - [this](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return next(runtime); - }); - } - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "next"); - return names; - } - - private: - Value next(Runtime& runtime) { - Object result(runtime); - if (done_ || collection_ == nil) { - result.setProperty(runtime, "done", true); - return result; - } - - if (stackIndex_ >= stackLength_) { - stackLength_ = [collection_ countByEnumeratingWithState:&state_ - objects:stack_ - count:16]; - stackIndex_ = 0; - if (stackLength_ == 0) { - done_ = true; - result.setProperty(runtime, "done", true); - return result; - } - } - - id value = state_.itemsPtr[stackIndex_++]; - NativeApiJsiType valueType = nativeObjectReturnTypeForClass(object_getClass(value)); - result.setProperty(runtime, "value", - convertNativeReturnValue(runtime, bridge_, valueType, &value)); - result.setProperty(runtime, "done", false); - return result; - } - - std::shared_ptr bridge_; - id collection_ = nil; - NSFastEnumerationState state_ = {}; - id __unsafe_unretained stack_[16] = {}; - NSUInteger stackLength_ = 0; - NSUInteger stackIndex_ = 0; - bool done_ = false; -}; - -NativeApiSymbol nativeApiSymbolForRuntimeClass( - const std::shared_ptr& bridge, Class cls) { - const char* name = cls != Nil ? class_getName(cls) : ""; - if (bridge != nullptr) { - if (const NativeApiSymbol* symbol = bridge->findClassForRuntimePointer(cls)) { - return *symbol; - } - if (const NativeApiSymbol* symbol = bridge->findClassForRuntimeClass(cls)) { - return *symbol; - } - if (name != nullptr) { - if (const NativeApiSymbol* symbol = bridge->findClass(name)) { - return *symbol; - } - } - } - - return NativeApiSymbol{ - .kind = NativeApiSymbolKind::Class, - .offset = MD_SECTION_OFFSET_NULL, - .name = name != nullptr ? name : "", - .runtimeName = name != nullptr ? name : "", - }; -} - -class NativeApiSuperHostObject final : public HostObject { - public: - NativeApiSuperHostObject(std::shared_ptr bridge, - id receiver, Class dispatchClass) - : bridge_(std::move(bridge)), - receiver_(receiver), - dispatchClass_(dispatchClass) { - if (receiver_ != nil) { - [receiver_ retain]; - } - } - - ~NativeApiSuperHostObject() override { - if (receiver_ != nil) { - [receiver_ release]; - receiver_ = nil; - } - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "super"); - } - if (property == "toString") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return makeString(runtime, "[NativeApiJsiSuper]"); - }); - } - if (receiver_ == nil || dispatchClass_ == Nil) { - return Value::undefined(); - } - - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(dispatchClass_)) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, false)) { - SEL selector = sel_getUid(propertyMember->selectorName.c_str()); - if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { - return callObjCSelector(runtime, bridge_, receiver_, false, - propertyMember->selectorName, propertyMember, - nullptr, 0, dispatchClass_); - } - } - - if (selectMethodMember(members, property, false, 0) != nullptr) { - auto bridge = bridge_; - id receiver = receiver_; - Class dispatchClass = dispatchClass_; - std::string memberName = property; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, receiver, dispatchClass, memberName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - const NativeApiSymbol* symbol = - bridge->findClassForRuntimeClass(dispatchClass); - if (symbol == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C metadata is not available for super."); - } - const NativeApiMember* selected = selectMethodMember( - bridge->membersForClass(*symbol), memberName, false, count); - if (selected == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C super selector is not available: " + - memberName); - } - return callObjCSelector(runtime, bridge, receiver, false, - selected->selectorName, selected, args, - count, dispatchClass); - }); - } - } - - if (auto selectorName = - runtimeSelectorNameForProperty(dispatchClass_, false, property)) { - auto bridge = bridge_; - id receiver = receiver_; - Class dispatchClass = dispatchClass_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, receiver, dispatchClass, selectorName = *selectorName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return callObjCSelector(runtime, bridge, receiver, false, - selectorName, nullptr, args, count, - dispatchClass); - }); - } - - return Value::undefined(); - } - - void set(Runtime& runtime, const PropNameID& name, const Value& value) override { - std::string property = name.utf8(runtime); - if (receiver_ == nil || dispatchClass_ == Nil) { - throw facebook::jsi::JSError(runtime, "Cannot set property on nil super."); - } - - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(dispatchClass_)) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, property, false)) { - if (propertyMember->readonly || - propertyMember->setterSelectorName.empty()) { - throw facebook::jsi::JSError( - runtime, "Attempted to assign to readonly property."); - } - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, receiver_, false, - setterMember.selectorName, &setterMember, args, 1, - dispatchClass_); - return; - } - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - if (class_getInstanceMethod(dispatchClass_, selector) != nullptr) { - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, receiver_, false, setterSelectorName, - nullptr, args, 1, dispatchClass_); - return; - } - - throw facebook::jsi::JSError(runtime, - "No writable native super property: " + - property); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - id receiver_ = nil; - Class dispatchClass_ = Nil; -}; - -class NativeApiObjectHostObject final - : public HostObject, - public std::enable_shared_from_this { - public: - NativeApiObjectHostObject(std::shared_ptr bridge, - id object, bool ownsObject) - : bridge_(std::move(bridge)), object_(object), ownsObject_(ownsObject) { - if (object_ != nil && !ownsObject_) { - [object_ retain]; - ownsObject_ = true; - } - } - - ~NativeApiObjectHostObject() override { - if (ownsObject_ && object_ != nil) { - [object_ release]; - object_ = nil; - } - } - - id object() const { return object_; } - - void disownObject(id expected) { - if (object_ == expected) { - ownsObject_ = false; - object_ = nil; - } - } - - static bool isInitializerSelector(const std::string& selectorName) { - return selectorName.rfind("init", 0) == 0; - } - - static id nativeObjectFromValue(Runtime& runtime, const Value& value) { - if (!value.isObject()) { - return nil; - } - Object object = value.asObject(runtime); - if (!object.isHostObject(runtime)) { - return nil; - } - return object.getHostObject(runtime)->object(); - } - - Value callObjectSelector(Runtime& runtime, const std::string& selectorName, - const NativeApiMember* member, const Value* args, - size_t count, Class dispatchSuperClass = Nil) { - id receiver = object_; - if (receiver == nil) { - throw facebook::jsi::JSError(runtime, - "Cannot send Objective-C selector to nil."); - } - - const bool initializer = isInitializerSelector(selectorName); - std::optional classWrapper; - if (initializer) { - Value classWrapperValue = bridge_->findObjectExpando( - runtime, receiver, "__nativeApiClassWrapper"); - if (classWrapperValue.isObject()) { - classWrapper.emplace(classWrapperValue.asObject(runtime)); - } - bridge_->forgetRoundTripValue(receiver); - bridge_->forgetObjectExpandos(receiver); - } - - Value result = - callObjCSelector(runtime, bridge_, receiver, false, selectorName, member, - args, count, dispatchSuperClass); - if (initializer) { - if (nativeObjectFromValue(runtime, result) != receiver) { - disownObject(receiver); - } else if (classWrapper) { - bridge_->setObjectExpando(runtime, receiver, "__nativeApiClassWrapper", - Value(runtime, *classWrapper)); - } - } - return result; - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "object"); - } - if (property == "className") { - return makeString(runtime, object_ != nil ? object_getClassName(object_) : ""); - } - if (property == "nativeAddress") { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", object_); - return makeString(runtime, address); - } - if (property == "class") { - auto bridge = bridge_; - id object = object_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "class"), 0, - [bridge, object](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - if (object == nil) { - return Value::undefined(); - } - Value classWrapper = bridge->findObjectExpando( - runtime, object, "__nativeApiClassWrapper"); - if (classWrapper.isObject()) { - return classWrapper; - } - NativeApiSymbol symbol = - nativeApiSymbolForRuntimeClass(bridge, object_getClass(object)); - return makeNativeClassValue(runtime, bridge, std::move(symbol)); - }); - } - if (property == "constructor") { - if (object_ == nil) { - return Value::undefined(); - } - Value classWrapper = bridge_->findObjectExpando( - runtime, object_, "__nativeApiClassWrapper"); - if (classWrapper.isObject()) { - return classWrapper; - } - NativeApiSymbol symbol = - nativeApiSymbolForRuntimeClass(bridge_, object_getClass(object_)); - return makeNativeClassValue(runtime, bridge_, std::move(symbol)); - } - if (property == "superclass") { - if (object_ == nil) { - return Value::undefined(); - } - Class superclass = class_getSuperclass(object_getClass(object_)); - if (superclass == Nil) { - return Value::null(); - } - NativeApiSymbol symbol = nativeApiSymbolForRuntimeClass(bridge_, superclass); - return makeNativeClassValue(runtime, bridge_, std::move(symbol)); - } - if (property == "super") { - Class dispatchClass = - object_ != nil ? class_getSuperclass(object_getClass(object_)) : Nil; - return Object::createFromHostObject( - runtime, - std::make_shared(bridge_, object_, - dispatchClass)); - } - if (property == "invoke" || property == "send") { - auto bridge = bridge_; - id object = object_; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, object, weakSelf](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - std::string selectorName = - readStringArg(runtime, args, count, 0, "selector"); - if (auto self = weakSelf.lock()) { - return self->callObjectSelector(runtime, selectorName, nullptr, - args + 1, count - 1); - } - return callObjCSelector(runtime, bridge, object, false, selectorName, - nullptr, args + 1, count - 1); - }); - } - if (property == "takeRetainedValue" || property == "takeUnretainedValue") { - bool retained = property == "takeRetainedValue"; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [weakSelf, retained](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - auto self = weakSelf.lock(); - if (!self || self->object_ == nil || self->consumed_) { - throw facebook::jsi::JSError(runtime, "Unmanaged value has already been consumed."); - } - - id object = self->object_; - if (self->bridge_ != nullptr) { - self->bridge_->forgetRoundTripValue(object); - } - if (self->ownsObject_) { - [object release]; - } - self->object_ = nil; - self->ownsObject_ = false; - self->consumed_ = true; - return makeNativeObjectValue(runtime, self->bridge_, object, retained); - }); - } - if (property == "toString") { - id object = object_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [object](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - NSString* description = - object != nil ? [object description] : @""; - return makeString(runtime, description.UTF8String ?: ""); - }); - } - if (property == "URL" && object_ != nil && - [object_ respondsToSelector:@selector(URL)]) { - return callObjectSelector(runtime, "URL", nullptr, nullptr, 0); - } - if (property == "Symbol.iterator" || - property == "Symbol(Symbol.iterator)" || - property == "@@iterator") { - auto bridge = bridge_; - id object = object_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "Symbol.iterator"), 0, - [bridge, object](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - if (object == nil || - ![object conformsToProtocol:@protocol(NSFastEnumeration)]) { - throw facebook::jsi::JSError( - runtime, "Object does not conform to NSFastEnumeration."); - } - return Object::createFromHostObject( - runtime, - std::make_shared( - bridge, static_cast>(object))); - }); - } - -#if TARGET_OS_OSX - if (property == "initWithRedGreenBlueAlpha") { - Class nsColorClass = NSClassFromString(@"NSColor"); - if (object_ != nil && nsColorClass != Nil && - [object_ isKindOfClass:nsColorClass]) { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 4, - [bridge, nsColorClass](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - const char* selectors[] = { - "colorWithSRGBRed:green:blue:alpha:", - "colorWithCalibratedRed:green:blue:alpha:", - "colorWithDeviceRed:green:blue:alpha:", - }; - for (const char* selectorName : selectors) { - if (class_getClassMethod(nsColorClass, - sel_getUid(selectorName)) != nullptr) { - return callObjCSelector(runtime, bridge, - static_cast(nsColorClass), true, - selectorName, nullptr, args, count); - } - } - throw facebook::jsi::JSError( - runtime, "NSColor RGB initializer is not available."); - }); - } - } -#endif - - if (property == "initWithFireDateIntervalTargetSelectorUserInfoRepeats") { - Class timerClass = NSClassFromString(@"NSTimer"); - if (object_ != nil && timerClass != Nil && - [object_ isKindOfClass:timerClass]) { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 6, - [bridge, timerClass](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - if (count < 6) { - throw facebook::jsi::JSError( - runtime, "NSTimer initializer expects six arguments."); - } - return callObjCSelector( - runtime, bridge, static_cast(timerClass), true, - "timerWithTimeInterval:target:selector:userInfo:repeats:", - nullptr, args + 1, count - 1); - }); - } - } - - Value expando = bridge_->findObjectExpando(runtime, object_, property); - if (!expando.isUndefined()) { - return expando; - } - - if (object_ != nil) { - try { - Value receiver = bridge_->findRoundTripValue(runtime, object_); - Value resolverValue = runtime.global().getProperty( - runtime, "__nativeScriptGetNativeApiPrototypeProperty"); - if (receiver.isObject() && resolverValue.isObject() && - resolverValue.asObject(runtime).isFunction(runtime)) { - Value prototype = - bridge_->findClassPrototype(runtime, object_getClass(object_)); - Value prototypeOrName = prototype.isObject() - ? Value(runtime, prototype) - : Value::undefined(); - if (prototypeOrName.isUndefined()) { - Value classWrapper = bridge_->findObjectExpando( - runtime, object_, "__nativeApiClassWrapper"); - if (classWrapper.isObject()) { - Object wrapperObject = classWrapper.asObject(runtime); - Value wrapperPrototype = - wrapperObject.getProperty(runtime, "prototype"); - if (wrapperPrototype.isObject()) { - prototypeOrName = std::move(wrapperPrototype); - } - } - } - if (prototypeOrName.isUndefined()) { - const char* className = object_getClassName(object_); - prototypeOrName = makeString(runtime, - className != nullptr ? className : ""); - } - Value resolved = resolverValue.asObject(runtime) - .asFunction(runtime) - .call(runtime, std::move(prototypeOrName), - Value(runtime, receiver), - makeString(runtime, property)); - if (resolved.isObject()) { - Object result = resolved.asObject(runtime); - Value found = result.getProperty(runtime, "found"); - if (found.isBool() && found.getBool()) { - return result.getProperty(runtime, "value"); - } - } - } - } catch (const std::exception&) { - } - } - - if (object_ != nil && [object_ isKindOfClass:[NSArray class]]) { - NSArray* array = static_cast(object_); - if (property == "length") { - return static_cast(array.count); - } - if (auto index = parseArrayIndexProperty(property)) { - if (*index >= array.count) { - return Value::undefined(); - } - id element = [array objectAtIndex:*index]; - NativeApiJsiType elementType = nativeObjectReturnType(); - return convertNativeReturnValue(runtime, bridge_, elementType, &element); - } - } - - if (object_ != nil) { - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(object_getClass(object_))) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, false)) { - SEL selector = sel_getUid(propertyMember->selectorName.c_str()); - if ([object_ respondsToSelector:selector]) { - return callObjectSelector(runtime, propertyMember->selectorName, - propertyMember, nullptr, 0); - } - std::string booleanSelectorName = - booleanGetterSelectorForProperty(property); - if (booleanSelectorName != propertyMember->selectorName) { - SEL booleanSelector = sel_getUid(booleanSelectorName.c_str()); - if ([object_ respondsToSelector:booleanSelector]) { - NativeApiMember getterMember = *propertyMember; - getterMember.selectorName = booleanSelectorName; - return callObjectSelector(runtime, getterMember.selectorName, - &getterMember, nullptr, 0); - } - } - } - - if (selectMethodMember(members, property, false, 0) != nullptr) { - auto bridge = bridge_; - id object = object_; - std::weak_ptr weakSelf = - shared_from_this(); - std::string memberName = property; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), - 0, - [bridge, object, weakSelf, memberName](Runtime& runtime, - const Value&, - const Value* args, - size_t count) -> Value { - const NativeApiSymbol* symbol = - bridge->findClassForRuntimeClass(object_getClass(object)); - if (symbol == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C metadata is not available for object."); - } - const NativeApiMember* selected = selectMethodMember( - bridge->membersForClass(*symbol), memberName, false, count); - if (selected == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C selector is not available: " + - memberName); - } - if (auto self = weakSelf.lock()) { - return self->callObjectSelector( - runtime, selected->selectorName, selected, args, count); - } - return callObjCSelector(runtime, bridge, object, false, - selected->selectorName, selected, args, - count); - }); - } - } - - if (auto selectorName = - runtimeSelectorNameForProperty(object_getClass(object_), false, - property)) { - if (selectorArgumentCount(*selectorName) == 0 && - hasRuntimeSetterForProperty(object_getClass(object_), false, - property)) { - return callObjectSelector(runtime, *selectorName, nullptr, nullptr, 0); - } - - auto bridge = bridge_; - id object = object_; - std::weak_ptr weakSelf = shared_from_this(); - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, object, weakSelf, selectorName = *selectorName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - if (auto self = weakSelf.lock()) { - return self->callObjectSelector(runtime, selectorName, nullptr, - args, count); - } - return callObjCSelector(runtime, bridge, object, false, - selectorName, nullptr, args, count); - }); - } - - if ([object_ isKindOfClass:[NSDictionary class]]) { - NSString* key = [NSString stringWithUTF8String:property.c_str()]; - if (key != nil) { - id value = [static_cast(object_) objectForKey:key]; - if (value != nil) { - NativeApiJsiType valueType = nativeObjectReturnType(); - return convertNativeReturnValue(runtime, bridge_, valueType, &value); - } - } - } - } - - return Value::undefined(); - } - - void set(Runtime& runtime, const PropNameID& name, const Value& value) override { - std::string property = name.utf8(runtime); - if (object_ == nil) { - throw facebook::jsi::JSError(runtime, "Cannot set property on nil object."); - } - - if (const NativeApiSymbol* symbol = - bridge_->findClassForRuntimeClass(object_getClass(object_))) { - const auto& members = bridge_->membersForClass(*symbol); - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, property, false)) { - if (propertyMember->readonly) { - throw facebook::jsi::JSError( - runtime, "Attempted to assign to readonly property."); - } - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, object_, false, - setterMember.selectorName, &setterMember, args, 1); - return; - } - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - if ([object_ respondsToSelector:selector]) { - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, object_, false, setterSelectorName, - nullptr, args, 1); - return; - } - - bridge_->setObjectExpando(runtime, object_, property, value); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(6); - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "className"); - addPropertyName(runtime, names, "nativeAddress"); - addPropertyName(runtime, names, "constructor"); - addPropertyName(runtime, names, "superclass"); - addPropertyName(runtime, names, "super"); - addPropertyName(runtime, names, "invoke"); - addPropertyName(runtime, names, "send"); - addPropertyName(runtime, names, "takeRetainedValue"); - addPropertyName(runtime, names, "takeUnretainedValue"); - addPropertyName(runtime, names, "toString"); - return names; - } - - private: - std::shared_ptr bridge_; - id object_ = nil; - bool ownsObject_ = false; - bool consumed_ = false; -}; - -class NativeApiClassHostObject final : public HostObject { - public: - NativeApiClassHostObject(std::shared_ptr bridge, - NativeApiSymbol symbol) - : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} - - Class nativeClass() const { - return objc_lookUpClass(symbol_.runtimeName.c_str()); - } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "class"); - } - if (property == "name") { - return makeString(runtime, symbol_.name); - } - if (property == "runtimeName") { - return makeString(runtime, symbol_.runtimeName); - } - if (property == "available") { - return objc_lookUpClass(symbol_.runtimeName.c_str()) != nil; - } - if (property == "metadataOffset") { - return static_cast(symbol_.offset); - } - if (property == "__superclass") { - if (symbol_.superclassOffset == MD_SECTION_OFFSET_NULL) { - return Value::undefined(); - } - const NativeApiSymbol* superclass = - bridge_->findClassByOffset(symbol_.superclassOffset); - if (superclass == nullptr) { - return Value::undefined(); - } - return makeNativeClassValue(runtime, bridge_, *superclass); - } - if (property == "__staticMembers" || property == "__instanceMembers") { - bool staticMembers = property == "__staticMembers"; - const auto& members = bridge_->surfaceMembersForClass(symbol_); - Array result(runtime, members.size()); - size_t index = 0; - for (const auto& member : members) { - bool memberIsStatic = - (member.flags & metagen::mdMemberStatic) != 0; - if (memberIsStatic != staticMembers) { - continue; - } - Object descriptor(runtime); - descriptor.setProperty(runtime, "name", makeString(runtime, member.name)); - descriptor.setProperty(runtime, "selectorName", - makeString(runtime, member.selectorName)); - descriptor.setProperty( - runtime, "argumentCount", - static_cast(selectorArgumentCount(member.selectorName))); - descriptor.setProperty(runtime, "property", member.property); - descriptor.setProperty(runtime, "readonly", member.readonly); - descriptor.setProperty(runtime, "setterSelectorName", - makeString(runtime, member.setterSelectorName)); - result.setValueAtIndex(runtime, index++, descriptor); - } - Array compact(runtime, index); - for (size_t i = 0; i < index; i++) { - compact.setValueAtIndex(runtime, i, result.getValueAtIndex(runtime, i)); - } - return compact; - } - if (property == "toString") { - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [symbol = symbol_](Runtime& runtime, const Value&, - const Value*, size_t) -> Value { - return makeString(runtime, - "[NativeApiJsiClass " + symbol.name + "]"); - }); - } - if (property == "construct" || property == "alloc" || property == "new") { - auto bridge = bridge_; - auto symbol = symbol_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property), 0, - [bridge, symbol, property](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - - id result = nil; - if (property == "construct" && count == 1) { - void* pointer = nullptr; - if (args[0].isNumber()) { - pointer = reinterpret_cast( - static_cast(args[0].getNumber())); - } else if (args[0].isObject()) { - Object object = args[0].asObject(runtime); - if (object.isHostObject(runtime)) { - pointer = object - .getHostObject( - runtime) - ->pointer(); - } else if (object.isHostObject( - runtime)) { - pointer = object - .getHostObject( - runtime) - ->data(); - } else if (object.isHostObject( - runtime)) { - pointer = object - .getHostObject( - runtime) - ->object(); - } - } - return makeNativeObjectValue(runtime, bridge, - static_cast(pointer), false); - } - - if (property == "new") { - if (count != 0) { - throw facebook::jsi::JSError( - runtime, "new does not take arguments; use invoke for an " - "explicit Objective-C selector."); - } - result = [[cls alloc] init]; - } else { - if (count != 0) { - throw facebook::jsi::JSError( - runtime, "alloc does not take arguments; call invoke on the " - "allocated object for an explicit init selector."); - } - result = [cls alloc]; - } - - return makeNativeObjectValue(runtime, bridge, result, true); - }); - } - if (property == "invoke" || property == "send") { - auto bridge = bridge_; - auto symbol = symbol_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 1, - [bridge, symbol](Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - std::string selectorName = - readStringArg(runtime, args, count, 0, "selector"); - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - return callObjCSelector(runtime, bridge, static_cast(cls), true, - selectorName, nullptr, args + 1, - count - 1); - }); - } - - const auto& members = bridge_->membersForClass(symbol_); - if (const NativeApiMember* propertyMember = - selectWritablePropertyMember(members, property, true)) { - auto bridge = bridge_; - auto symbol = symbol_; - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - SEL selector = sel_getUid(propertyMember->selectorName.c_str()); - if (class_getClassMethod(cls, selector) != nullptr) { - return callObjCSelector(runtime, bridge, static_cast(cls), true, - propertyMember->selectorName, propertyMember, - nullptr, 0); - } - } - - if (selectMethodMember(members, property, true, 0) != nullptr) { - auto bridge = bridge_; - auto symbol = symbol_; - std::string memberName = property; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, symbol, memberName](Runtime& runtime, const Value&, - const Value* args, - size_t count) -> Value { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol.name); - } - const NativeApiMember* selected = selectMethodMember( - bridge->membersForClass(symbol), memberName, true, count); - if (selected == nullptr) { - throw facebook::jsi::JSError( - runtime, "Objective-C selector is not available: " + - memberName); - } - return callObjCSelector(runtime, bridge, static_cast(cls), true, - selected->selectorName, selected, args, - count); - }); - } - - Class cls = objc_lookUpClass(symbol_.runtimeName.c_str()); - if (cls != nil) { - if (auto selectorName = - runtimeSelectorNameForProperty(cls, true, property)) { - if (selectorArgumentCount(*selectorName) == 0 && - hasRuntimeSetterForProperty(cls, true, property)) { - return callObjCSelector(runtime, bridge_, static_cast(cls), true, - *selectorName, nullptr, nullptr, 0); - } - - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, property.c_str()), 0, - [bridge, cls, selectorName = *selectorName]( - Runtime& runtime, const Value&, const Value* args, - size_t count) -> Value { - return callObjCSelector(runtime, bridge, static_cast(cls), - true, selectorName, nullptr, args, - count); - }); - } - } - - return Value::undefined(); - } - - void set(Runtime& runtime, const PropNameID& name, const Value& value) override { - std::string property = name.utf8(runtime); - Class cls = objc_lookUpClass(symbol_.runtimeName.c_str()); - if (cls == nil) { - throw facebook::jsi::JSError( - runtime, "Objective-C class is not available: " + symbol_.name); - } - - const auto& members = bridge_->membersForClass(symbol_); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, true)) { - if (propertyMember->readonly) { - throw facebook::jsi::JSError( - runtime, "Attempted to assign to readonly property."); - } - NativeApiMember setterMember = *propertyMember; - setterMember.selectorName = propertyMember->setterSelectorName; - setterMember.signatureOffset = propertyMember->setterSignatureOffset; - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, static_cast(cls), true, - setterMember.selectorName, &setterMember, args, 1); - return; - } - - std::string setterSelectorName = setterSelectorForProperty(property); - SEL selector = sel_getUid(setterSelectorName.c_str()); - if (class_getClassMethod(cls, selector) != nullptr) { - Value args[] = {Value(runtime, value)}; - callObjCSelector(runtime, bridge_, static_cast(cls), true, - setterSelectorName, nullptr, args, 1); - return; - } - - throw facebook::jsi::JSError(runtime, - "No writable native property: " + property); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - names.reserve(8); - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "name"); - addPropertyName(runtime, names, "runtimeName"); - addPropertyName(runtime, names, "available"); - addPropertyName(runtime, names, "metadataOffset"); - addPropertyName(runtime, names, "toString"); - addPropertyName(runtime, names, "construct"); - addPropertyName(runtime, names, "alloc"); - addPropertyName(runtime, names, "new"); - addPropertyName(runtime, names, "invoke"); - addPropertyName(runtime, names, "send"); - return names; - } - - private: - std::shared_ptr bridge_; - NativeApiSymbol symbol_; -}; - -Value makeNativeObjectValue(Runtime& runtime, - const std::shared_ptr& bridge, - id object, bool ownsObject) { - if (object == nil) { - return Value::null(); - } - - Value cached = bridge->findRoundTripValue(runtime, object); - if (!cached.isUndefined()) { - if (ownsObject) { - [object release]; - } - return cached; - } - - Object result = Object::createFromHostObject( - runtime, - std::make_shared(bridge, object, ownsObject)); - bridge->rememberRoundTripValue(runtime, object, Value(runtime, result)); - return result; -} - -Value globalNativeSymbolValue(Runtime& runtime, const NativeApiSymbol& symbol, - const char* expectedKind) { - Object global = runtime.global(); - Value cacheValue = global.getProperty( - runtime, "__nativeScriptNativeApiGlobalCache"); - if (!cacheValue.isObject()) { - return Value::undefined(); - } - - Object cache = cacheValue.asObject(runtime); - auto readCache = [&](const std::string& name) -> Value { - if (name.empty()) { - return Value::undefined(); - } - - Value value = cache.getProperty(runtime, name.c_str()); - if (!value.isObject()) { - return Value::undefined(); - } - - try { - Object object = value.asObject(runtime); - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == expectedKind) { - return value; - } - } catch (const std::exception&) { - } - - return Value::undefined(); - }; - - Value value = readCache(symbol.name); - if (!value.isUndefined()) { - return value; - } - if (symbol.runtimeName != symbol.name) { - value = readCache(symbol.runtimeName); - if (!value.isUndefined()) { - return value; - } - } - - try { - if (std::strcmp(expectedKind, "class") == 0) { - Value classResolverValue = global.getProperty( - runtime, "__nativeScriptResolveNativeApiClassWrapper"); - if (classResolverValue.isObject() && - classResolverValue.asObject(runtime).isFunction(runtime)) { - Function classResolver = - classResolverValue.asObject(runtime).asFunction(runtime); - auto resolveClassWrapper = [&](const std::string& name) -> Value { - if (name.empty()) { - return Value::undefined(); - } - Value resolved = classResolver.call(runtime, makeString(runtime, name)); - return resolved.isObject() ? std::move(resolved) : Value::undefined(); - }; - - value = resolveClassWrapper(symbol.name); - if (!value.isUndefined()) { - return value; - } - if (symbol.runtimeName != symbol.name) { - value = resolveClassWrapper(symbol.runtimeName); - if (!value.isUndefined()) { - return value; - } - } - } - } - - Value resolverValue = - global.getProperty(runtime, "__nativeScriptResolveNativeApiGlobal"); - if (resolverValue.isObject() && - resolverValue.asObject(runtime).isFunction(runtime)) { - Function resolver = resolverValue.asObject(runtime).asFunction(runtime); - auto resolveGlobal = [&](const std::string& name) -> Value { - if (name.empty()) { - return Value::undefined(); - } - Value resolved = resolver.call(runtime, makeString(runtime, name), - makeString(runtime, expectedKind)); - if (resolved.isObject()) { - return resolved; - } - return Value::undefined(); - }; - - value = resolveGlobal(symbol.name); - if (!value.isUndefined()) { - return value; - } - if (symbol.runtimeName != symbol.name) { - value = resolveGlobal(symbol.runtimeName); - if (!value.isUndefined()) { - return value; - } - } - } - } catch (const std::exception&) { - } - - return Value::undefined(); -} - -Value makeNativeClassValue(Runtime& runtime, - const std::shared_ptr& bridge, - NativeApiSymbol symbol) { - Class cls = objc_lookUpClass(symbol.runtimeName.c_str()); - Value cachedClass = bridge->findClassValue(runtime, cls); - if (!cachedClass.isUndefined()) { - return cachedClass; - } - Value globalValue = globalNativeSymbolValue(runtime, symbol, "class"); - if (!globalValue.isUndefined()) { - return globalValue; - } - return Object::createFromHostObject( - runtime, - std::make_shared(bridge, std::move(symbol))); -} - -Protocol* lookupProtocolByNativeName(const std::string& name) { - Protocol* protocol = objc_getProtocol(name.c_str()); - if (protocol != nullptr) { - return protocol; - } - constexpr const char* suffix = "Protocol"; - size_t suffixLength = std::strlen(suffix); - if (name.size() > suffixLength && - name.compare(name.size() - suffixLength, suffixLength, suffix) == 0) { - protocol = objc_getProtocol( - name.substr(0, name.size() - suffixLength).c_str()); - } - return protocol; -} - -class NativeApiProtocolHostObject final : public HostObject { - public: - NativeApiProtocolHostObject(std::shared_ptr bridge, - NativeApiSymbol symbol) - : bridge_(std::move(bridge)), symbol_(std::move(symbol)) {} - - Protocol* nativeProtocol() const { - Protocol* protocol = lookupProtocolByNativeName(symbol_.runtimeName); - if (protocol == nullptr && symbol_.runtimeName != symbol_.name) { - protocol = lookupProtocolByNativeName(symbol_.name); - } - return protocol; - } - - const NativeApiSymbol& symbol() const { return symbol_; } - - Value get(Runtime& runtime, const PropNameID& name) override { - std::string property = name.utf8(runtime); - if (property == "kind") { - return makeString(runtime, "protocol"); - } - if (property == "name") { - return makeString(runtime, symbol_.name); - } - if (property == "runtimeName") { - return makeString(runtime, symbol_.runtimeName); - } - if (property == "available") { - return nativeProtocol() != nullptr; - } - if (property == "metadataOffset") { - return static_cast(symbol_.offset); - } - if (property == "nativeAddress") { - return static_cast( - reinterpret_cast(nativeProtocol())); - } - if (property == "prototype") { - Object prototype(runtime); - for (const auto& member : bridge_->membersForProtocol(symbol_)) { - if (prototype.hasProperty(runtime, member.name.c_str())) { - continue; - } - if (member.property) { - defineProtocolProperty(runtime, prototype, member, false); - } else { - prototype.setProperty(runtime, member.name.c_str(), - makeProtocolMemberFunction(runtime, member, - false)); - } - } - return prototype; - } - if (property == "toString") { - auto symbol = symbol_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [symbol](Runtime& runtime, const Value&, const Value*, size_t) -> Value { - return makeString(runtime, - "[NativeApiJsiProtocol " + symbol.name + "]"); - }); - } - const auto& members = bridge_->membersForProtocol(symbol_); - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, true)) { - return makeProtocolPropertyGetter(runtime, *propertyMember, true); - } - if (const NativeApiMember* propertyMember = - selectPropertyMember(members, property, false)) { - return makeProtocolPropertyGetter(runtime, *propertyMember, true); - } - if (const NativeApiMember* methodMember = - selectMethodMember(members, property, true, 0)) { - return makeProtocolMemberFunction(runtime, *methodMember, true); - } - if (const NativeApiMember* methodMember = - selectMethodMember(members, property, false, 0)) { - return makeProtocolMemberFunction(runtime, *methodMember, true); - } - return Value::undefined(); - } - - std::vector getPropertyNames(Runtime& runtime) override { - std::vector names; - addPropertyName(runtime, names, "kind"); - addPropertyName(runtime, names, "name"); - addPropertyName(runtime, names, "runtimeName"); - addPropertyName(runtime, names, "available"); - addPropertyName(runtime, names, "metadataOffset"); - addPropertyName(runtime, names, "nativeAddress"); - addPropertyName(runtime, names, "prototype"); - addPropertyName(runtime, names, "toString"); - for (const auto& member : bridge_->membersForProtocol(symbol_)) { - addPropertyName(runtime, names, member.name.c_str()); - } - return names; - } - - private: - static Class classReceiverFromThis(Runtime& runtime, const Value& thisValue) { - if (!thisValue.isObject()) { - return Nil; - } - - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->nativeClass(); - } - - Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); - if (wrappedClass.isObject()) { - Object wrappedObject = wrappedClass.asObject(runtime); - if (wrappedObject.isHostObject(runtime)) { - return wrappedObject.getHostObject(runtime) - ->nativeClass(); - } - } - - Value kindValue = object.getProperty(runtime, "kind"); - if (kindValue.isString() && - kindValue.asString(runtime).utf8(runtime) == "class") { - Value runtimeNameValue = object.getProperty(runtime, "runtimeName"); - if (!runtimeNameValue.isString()) { - runtimeNameValue = object.getProperty(runtime, "name"); - } - if (runtimeNameValue.isString()) { - std::string runtimeName = - runtimeNameValue.asString(runtime).utf8(runtime); - return objc_lookUpClass(runtimeName.c_str()); - } - } - - return Nil; - } - - id objectReceiverFromThis(Runtime& runtime, const Value& thisValue) const { - if (!thisValue.isObject()) { - return nil; - } - - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->object(); - } - - return nil; - } - - Value makeProtocolMemberFunction(Runtime& runtime, NativeApiMember member, - bool receiverIsClass) const { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, - [bridge, member, receiverIsClass](Runtime& runtime, - const Value& thisValue, - const Value* args, - size_t count) -> Value { - id receiver = nil; - if (receiverIsClass) { - receiver = static_cast( - classReceiverFromThis(runtime, thisValue)); - } else if (thisValue.isObject()) { - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - receiver = object.getHostObject(runtime) - ->object(); - } - } - - if (receiver == nil) { - throw facebook::jsi::JSError( - runtime, "Protocol member requires a native receiver."); - } - return callObjCSelector(runtime, bridge, receiver, receiverIsClass, - member.selectorName, &member, args, count); - }); - } - - Value makeProtocolPropertyGetter(Runtime& runtime, NativeApiMember member, - bool receiverIsClass) const { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, member.name.c_str()), 0, - [bridge, member, receiverIsClass](Runtime& runtime, - const Value& thisValue, - const Value*, size_t) -> Value { - id receiver = nil; - if (receiverIsClass) { - receiver = static_cast( - classReceiverFromThis(runtime, thisValue)); - } else if (thisValue.isObject()) { - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - receiver = object.getHostObject(runtime) - ->object(); - } - } - - if (receiver == nil) { - throw facebook::jsi::JSError( - runtime, "Protocol property requires a native receiver."); - } - return callObjCSelector(runtime, bridge, receiver, receiverIsClass, - member.selectorName, &member, nullptr, 0); - }); - } - - Value makeProtocolPropertySetter(Runtime& runtime, NativeApiMember member, - bool receiverIsClass) const { - auto bridge = bridge_; - return Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, member.setterSelectorName.c_str()), - 1, - [bridge, member, receiverIsClass](Runtime& runtime, - const Value& thisValue, - const Value* args, - size_t count) -> Value { - id receiver = nil; - if (receiverIsClass) { - receiver = static_cast( - classReceiverFromThis(runtime, thisValue)); - } else if (thisValue.isObject()) { - Object object = thisValue.asObject(runtime); - if (object.isHostObject(runtime)) { - receiver = object.getHostObject(runtime) - ->object(); - } - } - - if (receiver == nil) { - throw facebook::jsi::JSError( - runtime, "Protocol property requires a native receiver."); - } - if (count < 1) { - throw facebook::jsi::JSError( - runtime, "Protocol property setter expects a value."); - } - - NativeApiMember setterMember = member; - setterMember.selectorName = member.setterSelectorName; - setterMember.signatureOffset = member.setterSignatureOffset; - return callObjCSelector(runtime, bridge, receiver, receiverIsClass, - setterMember.selectorName, &setterMember, - args, 1); - }); - } - - void defineProtocolProperty(Runtime& runtime, Object& target, - const NativeApiMember& member, - bool receiverIsClass) const { - try { - Object objectCtor = runtime.global().getPropertyAsObject(runtime, "Object"); - Function defineProperty = - objectCtor.getPropertyAsFunction(runtime, "defineProperty"); - Object descriptor(runtime); - descriptor.setProperty(runtime, "configurable", true); - descriptor.setProperty(runtime, "enumerable", true); - descriptor.setProperty(runtime, "get", - makeProtocolPropertyGetter(runtime, member, - receiverIsClass)); - if (!member.readonly && !member.setterSelectorName.empty()) { - descriptor.setProperty(runtime, "set", - makeProtocolPropertySetter(runtime, member, - receiverIsClass)); - } - defineProperty.call(runtime, target, makeString(runtime, member.name), - descriptor); - } catch (const std::exception&) { - } - } - - std::shared_ptr bridge_; - NativeApiSymbol symbol_; -}; - -Value makeNativeProtocolValue(Runtime& runtime, - const std::shared_ptr& bridge, - NativeApiSymbol symbol) { - Value globalValue = globalNativeSymbolValue(runtime, symbol, "protocol"); - if (!globalValue.isUndefined()) { - return globalValue; - } - return Object::createFromHostObject( - runtime, - std::make_shared(bridge, std::move(symbol))); -} - -Class nativeClassFromJsiObject(Runtime& runtime, const Object& object) { - if (object.isHostObject(runtime)) { - return object.getHostObject(runtime)->nativeClass(); - } - - Value wrappedClass = object.getProperty(runtime, "__nativeApiClass"); - if (wrappedClass.isObject()) { - Object wrappedObject = wrappedClass.asObject(runtime); - if (wrappedObject.isHostObject(runtime)) { - return wrappedObject.getHostObject(runtime) - ->nativeClass(); - } - } - return Nil; -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h b/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h deleted file mode 100644 index 022047607..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiInstall.h +++ /dev/null @@ -1,1728 +0,0 @@ -Object CreateNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { - auto bridge = std::make_shared(config); - return Object::createFromHostObject(runtime, - std::make_shared(std::move(bridge))); -} - -void NativeApiJsiWriteSmokeStage(const char* stage) { - const char* enabled = getenv("NATIVESCRIPT_RN_TURBO_SMOKE_MARKER"); - if (enabled == nullptr || enabled[0] == '\0') { - return; - } - - NSString* path = - [NSTemporaryDirectory() stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; - NSString* content = [NSString stringWithFormat:@"stage=%s\n", stage != nullptr ? stage : ""]; - [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; -} - -void InstallAggregateGlobals(Runtime& runtime, Object& api, const char* namesFunction) { - Value metadataValue = api.getProperty(runtime, "metadata"); - if (!metadataValue.isObject()) { - return; - } - Object metadata = metadataValue.asObject(runtime); - Value namesValue = metadata.getProperty(runtime, namesFunction); - if (!namesValue.isObject()) { - return; - } - Object namesObject = namesValue.asObject(runtime); - if (!namesObject.isFunction(runtime)) { - return; - } - Value namesResult = namesObject.asFunction(runtime).call(runtime); - if (!namesResult.isObject() || !namesResult.asObject(runtime).isArray(runtime)) { - return; - } - Array names = namesResult.asObject(runtime).getArray(runtime); - Object global = runtime.global(); - for (size_t i = 0; i < names.size(runtime); i++) { - Value nameValue = names.getValueAtIndex(runtime, i); - if (!nameValue.isString()) { - continue; - } - std::string name = nameValue.asString(runtime).utf8(runtime); - if (name.empty() || global.hasProperty(runtime, name.c_str())) { - continue; - } - try { - Value aggregate = api.getProperty(runtime, name.c_str()); - if (!aggregate.isUndefined()) { - global.setProperty(runtime, name.c_str(), aggregate); - } - } catch (const std::exception&) { - // Some React Native globals are read-only even when hasProperty misses - // them. Keep NativeScript initialization resilient and skip collisions. - } - } -} - -std::string jsStringLiteral(const char* value) { - std::string result = "'"; - if (value != nullptr) { - for (const char* current = value; *current != '\0'; current++) { - switch (*current) { - case '\\': - result += "\\\\"; - break; - case '\'': - result += "\\'"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - default: - result += *current; - break; - } - } - } - result += "'"; - return result; -} - -void InstallNativeApiJsiGlobalSymbols(Runtime& runtime, const char* globalName) { - NativeApiJsiWriteSmokeStage("jsi:globals:before-eval"); - static const char* GlobalInstaller = R"JSI_GLOBALS( -(function(nativeApiGlobalName) { - 'use strict'; - var api = globalThis[nativeApiGlobalName]; - var installedFlagName = '__nativeScriptNativeApiGlobalsInstalled'; - if (!api || globalThis[installedFlagName]) { - return; - } - - var cacheName = '__nativeScriptNativeApiGlobalCache'; - var typeCodeKey = '__nativeApiTypeCode'; - var classWrappers = typeof WeakMap === 'function' ? new WeakMap() : null; - var classWrappersByName = Object.create(null); - var resolvingGlobal = Object.create(null); - - function globalCache() { - var existing = globalThis[cacheName]; - if (existing && typeof existing === 'object') { - return existing; - } - var cache = Object.create(null); - Object.defineProperty(globalThis, cacheName, { - configurable: false, - enumerable: false, - writable: false, - value: cache - }); - return cache; - } - - function cacheGlobal(name, value) { - if (name && value !== undefined) { - globalCache()[name] = value; - } - } - - function resolveCachedGlobal(name, expectedKind) { - if (!name) { - return undefined; - } - var cached = globalCache()[name]; - if (cached && (typeof cached === 'object' || typeof cached === 'function') && cached.kind === expectedKind) { - return cached; - } - if (resolvingGlobal[name] || !Object.prototype.hasOwnProperty.call(globalThis, name)) { - return undefined; - } - resolvingGlobal[name] = true; - try { - var value = globalThis[name]; - if (value && (typeof value === 'object' || typeof value === 'function') && value.kind === expectedKind) { - cacheGlobal(name, value); - return value; - } - } finally { - delete resolvingGlobal[name]; - } - return undefined; - } - - function defineLazyGlobal(name, resolve, force, nativeKind) { - if (!name) { - return; - } - if (!force && Object.prototype.hasOwnProperty.call(globalThis, name)) { - try { - var existingDescriptor = Object.getOwnPropertyDescriptor(globalThis, name); - if (existingDescriptor && Object.prototype.hasOwnProperty.call(existingDescriptor, 'value')) { - cacheGlobal(name, existingDescriptor.value); - } - } catch (_) { - } - return; - } - var nativeDefineLazyGlobal = api.__defineLazyGlobal; - if (nativeKind && typeof nativeDefineLazyGlobal === 'function' && - typeof globalThis.__nativeScriptResolveNativeApiLazyGlobal === 'function') { - try { - if (nativeDefineLazyGlobal(name, nativeKind, !!force)) { - return; - } - } catch (_) { - } - } - try { - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - get: function() { - var value = resolve(name); - cacheGlobal(name, value); - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - writable: true, - value: value - }); - return value; - }, - set: function(value) { - // Assignment over a lazy global must behave like a plain global - // assignment (@nativescript/core writes shims such as - // global.System), not throw "no setter for property". - cacheGlobal(name, value); - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - } - }); - } catch (_) { - var value = resolve(name); - if (value !== undefined) { - cacheGlobal(name, value); - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - writable: true, - value: value - }); - } - } - } - - Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiGlobal', { - configurable: false, - enumerable: false, - writable: false, - value: resolveCachedGlobal - }); - - Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiClassWrapper', { - configurable: false, - enumerable: false, - writable: false, - value: function(name) { - return name ? classWrappersByName[name] : undefined; - } - }); - - function findPrototypeDescriptor(className, property) { - var prototype; - if (className && (typeof className === 'object' || typeof className === 'function')) { - prototype = className; - } else { - var wrapper = className ? classWrappersByName[className] : undefined; - prototype = wrapper && wrapper.prototype; - } - while (prototype != null) { - var descriptor = Object.getOwnPropertyDescriptor(prototype, property); - if (descriptor) { - return descriptor; - } - prototype = Object.getPrototypeOf(prototype); - } - return undefined; - } - - Object.defineProperty(globalThis, '__nativeScriptGetNativeApiPrototypeProperty', { - configurable: false, - enumerable: false, - writable: false, - value: function(className, receiver, property) { - var descriptor = findPrototypeDescriptor(className, property); - if (!descriptor) { - return { found: false }; - } - if (typeof descriptor.get === 'function') { - return { found: true, value: descriptor.get.call(receiver) }; - } - if (typeof descriptor.value === 'function') { - return { found: true, value: descriptor.value.bind(receiver) }; - } - if ('value' in descriptor) { - return { found: true, value: descriptor.value }; - } - return { found: true, value: undefined }; - } - }); - - Object.defineProperty(globalThis, '__nativeScriptCreateNativeApiIterator', { - configurable: false, - enumerable: false, - writable: false, - value: function(receiver, prototype) { - if (!receiver || typeof Symbol !== 'function') { - return undefined; - } - var descriptor = findPrototypeDescriptor(prototype || receiver.className, Symbol.iterator); - if (descriptor && typeof descriptor.value === 'function') { - return descriptor.value.call(receiver); - } - if (descriptor && typeof descriptor.get === 'function') { - var getterValue = descriptor.get.call(receiver); - if (typeof getterValue === 'function') { - return getterValue.call(receiver); - } - } - var iteratorMethod = receiver[Symbol.iterator]; - return typeof iteratorMethod === 'function' - ? iteratorMethod.call(receiver) - : undefined; - } - }); - - function wrapAggregateConstructor(nativeConstructor) { - if (typeof nativeConstructor !== 'function') { - return nativeConstructor; - } - var aggregate = function NativeScriptAggregate(initialValue) { - return nativeConstructor(initialValue); - }; - try { - Object.defineProperty(aggregate, Symbol.hasInstance, { - configurable: true, - enumerable: false, - value: function(value) { - return !!value && - typeof value === 'object' && - value.kind === nativeConstructor.kind && - value.name === nativeConstructor.runtimeName; - } - }); - } catch (_) { - } - ['kind', 'runtimeName', 'metadataOffset', 'sizeof', 'fields', 'equals'].forEach(function(key) { - try { - Object.defineProperty(aggregate, key, { - configurable: true, - enumerable: false, - writable: false, - value: nativeConstructor[key] - }); - } catch (_) { - } - }); - return aggregate; - } - - function setDescriptorValue(target, property, receiver, value) { - var descriptor = Object.getOwnPropertyDescriptor(target, property); - if (!descriptor) { - return false; - } - if (typeof descriptor.set === 'function') { - descriptor.set.call(receiver, value); - return true; - } - if (descriptor.writable) { - if (receiver && receiver !== target) { - Object.defineProperty(receiver, property, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - } else { - target[property] = value; - } - return true; - } - return false; - } - - function isConstructorOptions(value) { - if (!value || typeof value !== 'object' || Array.isArray(value)) { - return false; - } - if (value.kind || value.nativeAddress || value instanceof Date) { - return false; - } - return Object.getPrototypeOf(value) === Object.prototype || - Object.getPrototypeOf(value) === null; - } - - function capitalizeToken(value) { - value = String(value || ''); - return value ? value.charAt(0).toUpperCase() + value.slice(1) : value; - } - - function selectorCandidatesFromOptions(options) { - var keys = Object.keys(options || {}); - if (!keys.length) { - return []; - } - var first = capitalizeToken(keys[0]); - var tail = ''; - for (var i = 1; i < keys.length; i++) { - tail += keys[i] + ':'; - } - return [ - 'initWith' + first + ':' + tail, - 'init' + first + ':' + tail - ]; - } - - function valuesFromOptions(options) { - return Object.keys(options || {}).map(function(key) { - return options[key]; - }); - } - - function selectorScoreForArguments(selectorName, args) { - if (!selectorName || selectorName.indexOf('init') !== 0) { - return -1; - } - if (selectorName === 'init') { - return args.length === 0 ? 100 : -1; - } - if (args.length === 0) { - return -1; - } - var lower = selectorName.toLowerCase(); - var first = args[0]; - var score = 1; - if (Array.isArray(first)) { - if (lower.indexOf('array') !== -1) { - score += 40; - } - } else if (typeof first === 'string') { - if (lower.indexOf('string') !== -1) { - score += 40; - } - if (lower.indexOf('url') !== -1) { - score += 10; - } - } else if (typeof first === 'number') { - if (lower.indexOf('primitive') !== -1) { - score += 50; - } - if (lower.indexOf('int') !== -1 || - lower.indexOf('integer') !== -1 || - lower.indexOf('number') !== -1 || - lower.indexOf('float') !== -1 || - lower.indexOf('double') !== -1 || - lower.indexOf('long') !== -1 || - lower.indexOf('short') !== -1) { - score += 30; - } - } else if (isConstructorOptions(first)) { - if (lower.indexOf('struct') !== -1 || - lower.indexOf('structure') !== -1) { - score += 40; - } - if (lower.indexOf('dictionary') !== -1) { - score += 20; - } - } else if (first === null || typeof first === 'undefined') { - score += 5; - } else if (lower.indexOf('object') !== -1 || - lower.indexOf('url') !== -1 || - lower.indexOf('data') !== -1) { - score += 20; - } - - var allStrings = args.length > 1 && args.every(function(value) { - return typeof value === 'string'; - }); - var allNumbers = args.length > 1 && args.every(function(value) { - return typeof value === 'number'; - }); - if (allStrings && lower.indexOf('string') !== -1) { - score += 25; - } - if (allNumbers && - (lower.indexOf('int') !== -1 || lower.indexOf('number') !== -1)) { - score += 25; - } - return score; - } - - function initializerMembers(nativeClass, argumentCount) { - var members = nativeClass.__instanceMembers || []; - var result = []; - for (var i = 0; i < members.length; i++) { - var member = members[i]; - if (!member || member.property || !member.selectorName) { - continue; - } - if (member.selectorName.indexOf('init') !== 0) { - continue; - } - if (typeof argumentCount === 'number' && - member.argumentCount !== argumentCount) { - continue; - } - result.push(member); - } - return result; - } - - function chooseInitializer(nativeClass, args, optionSelectors) { - var members = initializerMembers(nativeClass, args.length); - if (!members.length) { - return null; - } - if (optionSelectors && optionSelectors.length) { - for (var i = 0; i < optionSelectors.length; i++) { - for (var j = 0; j < members.length; j++) { - if (members[j].selectorName === optionSelectors[i]) { - return members[j]; - } - } - } - } - - var best = null; - var bestScore = -1; - for (var k = 0; k < members.length; k++) { - var score = selectorScoreForArguments(members[k].selectorName, args); - if (score > bestScore) { - bestScore = score; - best = members[k]; - } - } - return bestScore >= 0 ? best : null; - } - - function chooseInitializerBySelectors(nativeClass, args, selectors) { - if (!selectors || !selectors.length) { - return null; - } - var members = initializerMembers(nativeClass, args.length); - for (var i = 0; i < selectors.length; i++) { - for (var j = 0; j < members.length; j++) { - if (members[j].selectorName === selectors[i]) { - return members[j]; - } - } - } - return null; - } - - function unavailableInitializerError(error) { - return error && - /Objective-C selector is not available/.test(String(error.message || error)); - } - - function constructNativeInstance(nativeClass, args) { - if (args.length === 1 && - args[0] && - typeof args[0] === 'object' && - (args[0].kind === 'pointer' || args[0].kind === 'reference') && - typeof nativeClass.construct === 'function') { - return nativeClass.construct(args[0]); - } - - var actualArgs = args; - var initializer = null; - if (args.length === 1 && isConstructorOptions(args[0])) { - var optionSelectors = selectorCandidatesFromOptions(args[0]); - if (!optionSelectors.length) { - throw new Error('No initializer found that matches constructor invocation.'); - } - var optionArgs = valuesFromOptions(args[0]); - initializer = chooseInitializerBySelectors( - nativeClass, - optionArgs, - optionSelectors - ); - if (initializer) { - actualArgs = optionArgs; - } - } - if (!initializer) { - initializer = chooseInitializer(nativeClass, actualArgs, null); - } - if (!initializer) { - throw new Error('No initializer found that matches constructor invocation.'); - } - if (typeof nativeClass.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); - } - var instance = nativeClass.alloc(); - if (initializer.selectorName === 'init') { - if (typeof instance.init !== 'function') { - throw new Error('No initializer found that matches constructor invocation.'); - } - return instance.init(); - } - try { - if (initializer.name && typeof instance[initializer.name] === 'function') { - return instance[initializer.name].apply(instance, actualArgs); - } - var invokeArgs = [initializer.selectorName]; - Array.prototype.push.apply(invokeArgs, actualArgs); - return instance.invoke.apply(instance, invokeArgs); - } catch (error) { - if (unavailableInitializerError(error)) { - throw new Error('No initializer found that matches constructor invocation.'); - } - throw error; - } - } - - function wrapNativeClass(nativeClass) { - if (!nativeClass || (typeof nativeClass !== 'object' && typeof nativeClass !== 'function')) { - return nativeClass; - } - var nativeClassName = nativeClass.runtimeName || nativeClass.name || ''; - if (nativeClassName && classWrappersByName[nativeClassName]) { - if (classWrappers) { - try { - classWrappers.set(nativeClass, classWrappersByName[nativeClassName]); - } catch (_) { - } - } - return classWrappersByName[nativeClassName]; - } - if (classWrappers) { - var cached = classWrappers.get(nativeClass); - if (cached) { - return cached; - } - } - var constructable = function NativeScriptNativeClass() { - var args = Array.prototype.slice.call(arguments); - var redirectConstructor = this && this.constructor; - if (redirectConstructor && - redirectConstructor !== constructable && - redirectConstructor !== wrapper && - typeof redirectConstructor.__nativeApiEnsureClass === 'function') { - var redirectedWrapper = redirectConstructor.__nativeApiEnsureClass(); - if (redirectedWrapper && - redirectedWrapper !== constructable && - redirectedWrapper !== wrapper && - typeof redirectedWrapper.apply === 'function') { - return rememberClassOnInstance( - redirectedWrapper.apply(this, args), - redirectConstructor - ); - } - } - if (args.length > 0) { - return rememberInstanceClass(constructNativeInstance(nativeClass, args)); - } - if (typeof nativeClass.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); - } - var instance = nativeClass.alloc(); - if (instance && typeof instance.init === 'function') { - return rememberInstanceClass(instance.init()); - } - return rememberInstanceClass(instance); - }; - function rememberInstanceClass(instance) { - return rememberClassOnInstance(instance, wrapper || constructable); - } - // Static allocators may be invoked with a TypeScript-derived class as - // the receiver (core does `_super.new.call(this)`); those must - // materialize and allocate the derived Objective-C class, not the base. - function derivedClassWrapper(target) { - if (target && target !== constructable && target !== wrapper && - typeof target.__nativeApiEnsureClass === 'function') { - var derived = target.__nativeApiEnsureClass(); - if (derived && derived !== constructable && derived !== wrapper) { - return derived; - } - } - return undefined; - } - try { - Object.defineProperty(constructable, 'name', { - configurable: true, - enumerable: false, - value: nativeClassName || nativeClass.name || 'NativeScriptNativeClass' - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'extend', { - configurable: true, - enumerable: false, - writable: false, - value: function(methods, options) { - if (methods == null || typeof methods !== 'object') { - throw new Error('extend() first parameter must be an object'); - } - var extendOptions = options || {}; - if (typeof Symbol === 'function' && - Object.prototype.hasOwnProperty.call(methods, Symbol.iterator)) { - try { - extendOptions = Object.assign({}, extendOptions, { - __hasIterator: true - }); - } catch (_) { - extendOptions.__hasIterator = true; - } - } - var extendedNativeClass = api.__extendClass(nativeClass, methods, extendOptions); - var extended = wrapNativeClass(extendedNativeClass); - try { - Object.setPrototypeOf(extended, wrapper || constructable); - } catch (_) { - } - var extendedPrototype = Object.create(constructable.prototype || null); - try { - Object.defineProperties(extendedPrototype, Object.getOwnPropertyDescriptors(methods)); - } catch (_) { - Object.keys(methods).forEach(function(key) { - extendedPrototype[key] = methods[key]; - }); - } - try { - Object.defineProperty(extendedPrototype, 'constructor', { - configurable: true, - enumerable: false, - writable: true, - value: extended - }); - } catch (_) { - } - extended.prototype = extendedPrototype; - try { - api.__rememberClassWrapper(extendedNativeClass, extended, extendedPrototype); - } catch (_) { - } - return extended; - } - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'alloc', { - configurable: true, - enumerable: false, - writable: true, - value: function() { - var derived = derivedClassWrapper(this); - if (derived && typeof derived.alloc === 'function') { - return rememberClassOnInstance(derived.alloc.apply(derived, arguments), this); - } - return rememberInstanceClass(nativeClass.alloc.apply(nativeClass, arguments)); - } - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'new', { - configurable: true, - enumerable: false, - writable: false, - value: function() { - if (arguments.length !== 0) { - throw new Error('new does not take arguments; use invoke for an explicit Objective-C selector.'); - } - var derived = derivedClassWrapper(this); - if (derived && typeof derived.new === 'function') { - return rememberClassOnInstance(derived.new(), this); - } - if (typeof nativeClass.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); - } - var instance = nativeClass.alloc(); - if (instance && typeof instance.init === 'function') { - return rememberInstanceClass(instance.init()); - } - return rememberInstanceClass(instance); - } - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'caller', { - configurable: true, - enumerable: false, - writable: false, - value: null - }); - } catch (_) { - } - try { - Object.defineProperty(constructable, 'arguments', { - configurable: true, - enumerable: false, - writable: false, - value: null - }); - } catch (_) { - } - var basePrototypeTarget = {}; - var classMembersInstalled = false; - function installClassMembers(target, members, receiverIsClass) { - if (!target || !members || typeof members.length !== 'number') { - return; - } - for (var i = 0; i < members.length; i++) { - var member = members[i]; - if (!member || !member.name || Object.prototype.hasOwnProperty.call(target, member.name)) { - continue; - } - try { - if (member.property) { - var descriptor = { - configurable: true, - enumerable: false, - get: receiverIsClass - ? (function(name, selectorName) { - return function() { - return selectorName - ? nativeClass.invoke(selectorName) - : nativeClass[name]; - }; - })(member.name, member.selectorName) - : (function(name) { - return function() { - return api.__invokeBase(nativeClass, this, name); - }; - })(member.name) - }; - if (!member.readonly) { - descriptor.set = receiverIsClass - ? (function(name, setterSelectorName) { - return function(value) { - if (setterSelectorName) { - return nativeClass.invoke(setterSelectorName, value); - } - nativeClass[name] = value; - }; - })(member.name, member.setterSelectorName) - : (function(name) { - return function(value) { - return api.__invokeBase(nativeClass, this, name, value); - }; - })(member.name); - } - Object.defineProperty(target, member.name, descriptor); - } else { - Object.defineProperty(target, member.name, { - configurable: true, - enumerable: false, - writable: true, - value: receiverIsClass - ? (function(name) { - return function() { - if (this && typeof this === 'object' && this.kind === 'object') { - var baseArgs = [nativeClass, this, name]; - Array.prototype.push.apply(baseArgs, arguments); - return api.__invokeBase.apply(api, baseArgs); - } - return nativeClass[name].apply(nativeClass, arguments); - }; - })(member.name) - : (function(name) { - return function() { - var args = [nativeClass, this, name]; - Array.prototype.push.apply(args, arguments); - return api.__invokeBase.apply(api, args); - }; - })(member.name) - }); - } - } catch (_) { - } - } - } - function installNativeClassMembersIfNeeded() { - if (classMembersInstalled) { - return; - } - classMembersInstalled = true; - installClassMembers(constructable, nativeClass.__staticMembers, true); - installClassMembers(basePrototypeTarget, nativeClass.__instanceMembers, false); - try { - delete constructable.__nativeApiInstallMembers; - } catch (_) { - } - } - try { - Object.defineProperty(constructable, '__nativeApiInstallMembers', { - configurable: true, - enumerable: false, - writable: false, - value: installNativeClassMembersIfNeeded - }); - } catch (_) { - } - try { - Object.defineProperty(basePrototypeTarget, 'constructor', { - configurable: true, - enumerable: false, - writable: true, - value: constructable - }); - } catch (_) { - } - try { - Object.defineProperty(basePrototypeTarget, 'toString', { - configurable: true, - enumerable: false, - writable: true, - value: function() { - return '[object NativeScriptObject]'; - } - }); - } catch (_) { - } - try { - if (typeof Symbol === 'function' && Symbol.iterator && - typeof api.__fastEnumeration === 'function') { - Object.defineProperty(basePrototypeTarget, Symbol.iterator, { - configurable: true, - enumerable: false, - writable: true, - value: function() { - return api.__fastEnumeration(this); - } - }); - } - } catch (_) { - } - constructable.prototype = typeof Proxy === 'function' - ? new Proxy(basePrototypeTarget, { - get: function(target, property, receiver) { - installNativeClassMembersIfNeeded(); - if (property in target) { - return Reflect.get(target, property, receiver); - } - if (typeof property === 'symbol') { - return undefined; - } - return function() { - var args = [nativeClass, this, String(property)]; - Array.prototype.push.apply(args, arguments); - return api.__invokeBase.apply(api, args); - }; - }, - set: function(target, property, value, receiver) { - if (property === 'prototype') { - target[property] = value; - return true; - } - if (setDescriptorValue(target, property, receiver, value)) { - return true; - } - if (receiver && receiver !== target) { - Object.defineProperty(receiver, property, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - return true; - } - target[property] = value; - return true; - }, - has: function(target, property) { - installNativeClassMembersIfNeeded(); - return property in target; - }, - ownKeys: function(target) { - installNativeClassMembersIfNeeded(); - return Reflect.ownKeys(target); - }, - getOwnPropertyDescriptor: function(target, property) { - installNativeClassMembersIfNeeded(); - return Reflect.getOwnPropertyDescriptor(target, property); - } - }) - : basePrototypeTarget; - try { - Object.defineProperty(constructable, Symbol.hasInstance, { - configurable: true, - enumerable: false, - value: function(value) { - if (!value || typeof value !== 'object') { - return false; - } - // `this` is the constructor instanceof was invoked on. A - // TypeScript-derived native class inherits this method through the - // wrapper prototype chain until it materializes, so membership must - // be answered for the DERIVED Objective-C class — and before - // materialization no instance of it can exist. - try { - if (this && this !== constructable && this !== wrapper && - this.__nativeApiTypeScriptState) { - var derivedWrapper = this.__nativeApiTypeScriptState.wrapper; - if (!derivedWrapper) { - return false; - } - if (derivedWrapper !== constructable && derivedWrapper !== wrapper) { - return derivedWrapper[Symbol.hasInstance](value); - } - } - } catch (_) { - } - var expectedName = nativeClass.runtimeName || nativeClass.name; - try { - // Pass the proxied wrapper: the raw constructable carries no - // __nativeApiClass, so it does not marshal to the Objective-C - // Class and isKindOfClass() misreports. - if (typeof value.isKindOfClass === 'function' && - value.isKindOfClass(wrapper || constructable) === true) { - return true; - } - } catch (_) { - } - try { - var current = typeof value.class === 'function' ? value.class() : null; - while (current) { - if (current === wrapper || current === constructable) { - return true; - } - var currentName = current.runtimeName || current.name; - if (typeof expectedName === 'string' && currentName === expectedName) { - return true; - } - var next = current.superclass || null; - if (typeof next === 'function' && next.kind !== 'class') { - next = next.call(current); - } - current = next || null; - } - } catch (_) { - } - return typeof expectedName === 'string' && value.className === expectedName; - } - }); - } catch (_) { - } - var cachedNativeFunctions = typeof Map === 'function' ? new Map() : null; - var wrapper = typeof Proxy === 'function' - ? new Proxy(constructable, { - get: function(target, property, receiver) { - if (property === '__nativeApiClass') { - return nativeClass; - } - if (property === 'toString') { - return function() { - return String(nativeClass); - }; - } - if (property === 'hasOwnProperty') { - return function(key) { - installNativeClassMembersIfNeeded(); - return Object.prototype.hasOwnProperty.call(target, key); - }; - } - if (Object.prototype.hasOwnProperty.call(target, property) || - property === 'prototype' || - property === 'length' || - property === 'name') { - return Reflect.get(target, property, receiver); - } - installNativeClassMembersIfNeeded(); - if (Object.prototype.hasOwnProperty.call(target, property)) { - return Reflect.get(target, property, receiver); - } - if (cachedNativeFunctions && cachedNativeFunctions.has(property)) { - return cachedNativeFunctions.get(property); - } - var nativeValue = nativeClass[property]; - if (nativeValue !== undefined) { - if (typeof nativeValue === 'function') { - if (cachedNativeFunctions) { - cachedNativeFunctions.set(property, nativeValue); - } - try { - Object.defineProperty(target, property, { - configurable: true, - enumerable: false, - writable: false, - value: nativeValue - }); - } catch (_) { - } - } - return nativeValue; - } - var reflected = Reflect.get(target, property, receiver); - if (reflected !== undefined || property in target) { - return reflected; - } - installNativeClassMembersIfNeeded(); - reflected = Reflect.get(target, property, receiver); - if (reflected !== undefined || property in target) { - return reflected; - } - return reflected; - }, - set: function(target, property, value, receiver) { - if (property === 'prototype') { - target[property] = value; - return true; - } - if (setDescriptorValue(target, property, receiver, value)) { - return true; - } - try { - nativeClass[property] = value; - return true; - } catch (_) { - } - if (receiver && receiver !== target) { - Object.defineProperty(receiver, property, { - configurable: true, - enumerable: true, - writable: true, - value: value - }); - return true; - } - return Reflect.set(target, property, value, receiver); - }, - has: function(target, property) { - installNativeClassMembersIfNeeded(); - return property in target || property in nativeClass; - }, - ownKeys: function(target) { - installNativeClassMembersIfNeeded(); - return Reflect.ownKeys(target).filter(function(key) { - return key !== 'new' && - key !== 'hasOwnProperty' && - key !== '__nativeApiInstallMembers'; - }); - }, - getOwnPropertyDescriptor: function(target, property) { - installNativeClassMembersIfNeeded(); - return Reflect.getOwnPropertyDescriptor(target, property); - } - }) - : constructable; - if (classWrappers) { - classWrappers.set(nativeClass, wrapper); - } - try { - var nativeSuperclass = nativeClass.__superclass; - if (nativeSuperclass && nativeSuperclass !== nativeClass) { - var superclassWrapper = wrapNativeClass(nativeSuperclass); - if (superclassWrapper && superclassWrapper !== wrapper && - typeof Object.setPrototypeOf === 'function') { - Object.setPrototypeOf(wrapper, superclassWrapper); - } - } - } catch (_) { - } - try { - api.__rememberClassWrapper(nativeClass, wrapper, constructable.prototype); - } catch (_) { - } - if (nativeClassName) { - classWrappersByName[nativeClassName] = wrapper; - cacheGlobal(nativeClassName, wrapper); - if (!Object.prototype.hasOwnProperty.call(globalThis, nativeClassName)) { - try { - Object.defineProperty(globalThis, nativeClassName, { - configurable: true, - enumerable: false, - writable: false, - value: wrapper - }); - } catch (_) { - } - } - } - if (nativeClass.name && nativeClass.name !== nativeClassName) { - classWrappersByName[nativeClass.name] = wrapper; - cacheGlobal(nativeClass.name, wrapper); - } - return wrapper; - } - - function rememberClassOnInstance(instance, classWrapper) { - if (instance && typeof instance === 'object' && classWrapper) { - try { - if (typeof api.__rememberObjectClassWrapper === 'function') { - api.__rememberObjectClassWrapper(instance, classWrapper); - } else { - instance.__nativeApiClassWrapper = classWrapper; - } - } catch (_) { - } - } - return instance; - } - - function isNativeClassLike(value) { - if (!value || (typeof value !== 'object' && typeof value !== 'function')) { - return false; - } - if (value.kind === 'class') { - return true; - } - try { - return !!value.__nativeApiClass; - } catch (_) { - return false; - } - } - - function nativeClassLikeHandle(value) { - if (!value || (typeof value !== 'object' && typeof value !== 'function')) { - return value; - } - try { - if (typeof value.__nativeApiEnsureClass === 'function') { - value = value.__nativeApiEnsureClass(); - } - } catch (_) { - } - try { - return value.__nativeApiClass || value; - } catch (_) { - return value; - } - } - - function materializeTypeScriptNativeClass(constructor) { - if (!constructor || typeof constructor !== 'function') { - return undefined; - } - var state = constructor.__nativeApiTypeScriptState; - if (!state) { - return undefined; - } - if (state.wrapper) { - return state.wrapper; - } - if (state.materializing) { - return state.base; - } - - state.materializing = true; - try { - var baseWrapper = state.base; - if (baseWrapper && typeof baseWrapper.__nativeApiEnsureClass === 'function') { - baseWrapper = baseWrapper.__nativeApiEnsureClass(); - } - - var options = {}; - var className = constructor.ObjCClassName || constructor.name; - if (className) { - options.name = className; - } - if (constructor.ObjCProtocols) { - options.protocols = constructor.ObjCProtocols; - } - if (constructor.ObjCExposedMethods) { - options.exposedMethods = constructor.ObjCExposedMethods; - } - - var nativeBase = nativeClassLikeHandle(baseWrapper); - var nativeClass = api.__extendClass(nativeBase, constructor.prototype || {}, options); - var wrapper = wrapNativeClass(nativeClass); - state.wrapper = wrapper; - - try { - Object.setPrototypeOf(constructor, wrapper); - } catch (_) { - } - try { - api.__rememberClassWrapper(nativeClass, constructor, constructor.prototype || {}); - } catch (_) { - } - return wrapper; - } finally { - state.materializing = false; - } - } - - function defineTypeScriptStaticForwarder(constructor, name, isProperty, readonly) { - if (!name || name === 'length' || name === 'name' || name === 'prototype' || - Object.prototype.hasOwnProperty.call(constructor, name)) { - return; - } - - var descriptor = { - configurable: true, - enumerable: false - }; - - if (isProperty) { - descriptor.get = function() { - var wrapper = materializeTypeScriptNativeClass(constructor); - return wrapper ? wrapper[name] : undefined; - }; - if (!readonly) { - descriptor.set = function(value) { - var wrapper = materializeTypeScriptNativeClass(constructor); - if (wrapper) { - wrapper[name] = value; - } - }; - } - } else { - descriptor.writable = true; - descriptor.value = function() { - if (name === 'class') { - materializeTypeScriptNativeClass(constructor); - return constructor; - } - if (name === 'superclass') { - var state = constructor.__nativeApiTypeScriptState; - return state && state.base; - } - var wrapper = materializeTypeScriptNativeClass(constructor); - var member = wrapper && wrapper[name]; - if (typeof member !== 'function') { - throw new TypeError(String(name) + ' is not a function'); - } - var result = member.apply(wrapper, arguments); - if (name === 'alloc' || name === 'new' || name === 'construct') { - return rememberClassOnInstance(result, constructor); - } - return result; - }; - } - - try { - Object.defineProperty(constructor, name, descriptor); - } catch (_) { - } - } - - function installTypeScriptNativeClassSupport(constructor, base) { - if (!constructor || typeof constructor !== 'function' || !isNativeClassLike(base)) { - return false; - } - if (constructor.__nativeApiTypeScriptState) { - return true; - } - - try { - Object.defineProperty(constructor, '__nativeApiTypeScriptState', { - configurable: false, - enumerable: false, - writable: false, - value: { - base: base, - wrapper: null, - materializing: false - } - }); - } catch (_) { - constructor.__nativeApiTypeScriptState = { - base: base, - wrapper: null, - materializing: false - }; - } - - try { - Object.defineProperty(constructor, '__nativeApiEnsureClass', { - configurable: false, - enumerable: false, - writable: false, - value: function() { - return materializeTypeScriptNativeClass(constructor); - } - }); - } catch (_) { - } - - try { - Object.defineProperty(constructor, '__nativeApiClass', { - configurable: true, - enumerable: false, - get: function() { - var wrapper = materializeTypeScriptNativeClass(constructor); - return wrapper && wrapper.__nativeApiClass; - } - }); - } catch (_) { - } - - ['alloc', 'new', 'class', 'superclass', 'extend'].forEach(function(name) { - defineTypeScriptStaticForwarder(constructor, name, false, false); - }); - - try { - var members = base.__staticMembers || []; - for (var i = 0; i < members.length; i++) { - var member = members[i]; - if (member && member.name) { - defineTypeScriptStaticForwarder( - constructor, - member.name, - !!member.property, - !!member.readonly - ); - } - } - } catch (_) { - } - - return true; - } - - function installTypeScriptNativeHelpers() { - var extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function(d, b) { d.__proto__ = b; }) || - function(d, b) { - for (var p in b) { - if (Object.prototype.hasOwnProperty.call(b, p)) { - d[p] = b[p]; - } - } - }; - - globalThis.__extends = function(d, b) { - if (typeof b !== 'function' && b !== null) { - throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); - } - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - if (b !== null) { - installTypeScriptNativeClassSupport(d, b); - } - }; - - globalThis.NativeClass = function NativeClass(constructor) { - if (constructor && typeof constructor.__nativeApiEnsureClass === 'function') { - constructor.__nativeApiEnsureClass(); - } - return constructor; - }; - - globalThis.ObjCClass = function ObjCClass() { - var protocols = Array.prototype.slice.call(arguments); - return function(constructor) { - if (constructor.ObjCProtocols) { - Array.prototype.push.apply(constructor.ObjCProtocols, protocols); - } else { - constructor.ObjCProtocols = protocols; - } - if (typeof constructor.__nativeApiEnsureClass === 'function') { - constructor.__nativeApiEnsureClass(); - } - return constructor; - }; - }; - } - - function wrapInteropFactory(nativeFactory, properties) { - if (typeof nativeFactory !== 'function' || nativeFactory.__nativeScriptConstructable) { - return nativeFactory; - } - var constructable = function NativeScriptInteropValue() { - return nativeFactory.apply(undefined, arguments); - }; - try { - if (nativeFactory.prototype) { - constructable.prototype = nativeFactory.prototype; - } - } catch (_) { - } - try { - Object.defineProperty(constructable, Symbol.hasInstance, { - configurable: true, - enumerable: false, - value: function(value) { - return !!value && typeof value === 'object' && value.kind === properties.kind; - } - }); - } catch (_) { - } - Object.keys(properties).forEach(function(key) { - try { - Object.defineProperty(constructable, key, { - configurable: true, - enumerable: false, - writable: false, - value: properties[key] - }); - } catch (_) { - } - }); - Object.defineProperty(constructable, '__nativeScriptConstructable', { - configurable: false, - enumerable: false, - writable: false, - value: true - }); - return constructable; - } - - function installInteropConstructors() { - var interop = globalThis.interop; - if (!interop || typeof interop !== 'object') { - return; - } - var pointerSize; - try { - if (typeof interop.sizeof === 'function' && interop.types && interop.types.pointer !== undefined) { - pointerSize = interop.sizeof(interop.types.pointer); - } - } catch (_) { - pointerSize = undefined; - } - interop.Pointer = wrapInteropFactory(interop.Pointer, { kind: 'pointer', sizeof: pointerSize }); - interop.Reference = wrapInteropFactory(interop.Reference, { kind: 'reference', sizeof: pointerSize }); - interop.FunctionReference = wrapInteropFactory( - interop.FunctionReference, - { kind: 'functionReference', sizeof: pointerSize } - ); - if (interop.types && typeof interop.types === 'object') { - Object.keys(interop.types).forEach(function(name) { - var value = interop.types[name]; - if (typeof value !== 'number') { - return; - } - var boxed = { - valueOf: function() { return value; }, - toString: function() { return String(value); } - }; - Object.defineProperty(boxed, typeCodeKey, { - configurable: false, - enumerable: false, - writable: false, - value: value - }); - interop.types[name] = boxed; - }); - } - } - - function defineInlineFunction(name, value) { - if (Object.prototype.hasOwnProperty.call(globalThis, name)) { - return; - } - Object.defineProperty(globalThis, name, { - configurable: true, - enumerable: false, - writable: true, - value: value - }); - } - - function installInlineFunctions() { - var makePoint = function(x, y) { return { x: x, y: y }; }; - var makeSize = function(width, height) { return { width: width, height: height }; }; - var makeRect = function(x, y, width, height) { - return { origin: { x: x, y: y }, size: { width: width, height: height } }; - }; - defineInlineFunction('CGPointMake', makePoint); - defineInlineFunction('NSMakePoint', makePoint); - defineInlineFunction('CGSizeMake', makeSize); - defineInlineFunction('NSMakeSize', makeSize); - defineInlineFunction('CGRectMake', makeRect); - defineInlineFunction('NSMakeRect', makeRect); - defineInlineFunction('NSMakeRange', function(location, length) { - return { location: location, length: length }; - }); - defineInlineFunction('UIEdgeInsetsMake', function(top, left, bottom, right) { - return { top: top, left: left, bottom: bottom, right: right }; - }); - } - - function names(kind) { - var metadata = api.metadata; - var fn = metadata && metadata[kind]; - return typeof fn === 'function' ? fn() : []; - } - - function nameSet(values) { - var result = Object.create(null); - (values || []).forEach(function(value) { - result[value] = true; - }); - return result; - } - - var classNameList = names('classNames'); - var functionNameList = names('functionNames'); - var constantNameList = names('constantNames'); - var protocolNameList = names('protocolNames'); - var enumNameList = names('enumNames'); - var functionNameSet = nameSet(functionNameList); - var constantNameSet = nameSet(constantNameList); - var classNameSet = nameSet(classNameList); - var protocolNameSet = nameSet(protocolNameList); - var enumNameSet = nameSet(enumNameList); - - function resolveNativeApiEnum(enumName) { - return (api.getEnum && api.getEnum(enumName)) || api[enumName]; - } - - Object.defineProperty(globalThis, '__nativeScriptResolveNativeApiLazyGlobal', { - configurable: false, - enumerable: false, - writable: false, - value: function(name, kind) { - var value; - if (kind === 'class') { - value = wrapNativeClass(api[name]); - } else if (kind === 'function' || kind === 'constant') { - value = api[name]; - } else if (kind === 'protocol') { - value = (api.getProtocol && api.getProtocol(name)) || api[name]; - } else if (kind === 'enum') { - value = resolveNativeApiEnum(name); - } else if (kind === 'struct') { - value = wrapAggregateConstructor((api.getStruct && api.getStruct(name)) || api[name]); - } else if (kind === 'union') { - value = wrapAggregateConstructor((api.getUnion && api.getUnion(name)) || api[name]); - } else if (kind && kind.indexOf('enumMember:') === 0) { - var enumValue = resolveNativeApiEnum(kind.slice('enumMember:'.length)); - value = enumValue && enumValue[name]; - } else { - value = api[name]; - } - cacheGlobal(name, value); - return value; - } - }); - - classNameList.forEach(function(name) { - defineLazyGlobal(name, function(className) { - return wrapNativeClass(api[className]); - }, false, 'class'); - }); - functionNameList.forEach(function(name) { - defineLazyGlobal(name, function(functionName) { - return api[functionName]; - }, false, 'function'); - }); - constantNameList.forEach(function(name) { - defineLazyGlobal(name, function(constantName) { - return api[constantName]; - }, false, 'constant'); - }); - protocolNameList.forEach(function(name) { - defineLazyGlobal(name, function(protocolName) { - return (api.getProtocol && api.getProtocol(protocolName)) || api[protocolName]; - }, false, 'protocol'); - }); - enumNameList.forEach(function(name) { - defineLazyGlobal(name, resolveNativeApiEnum, false, 'enum'); - var enumValue = resolveNativeApiEnum(name); - if (!enumValue || typeof enumValue !== 'object') { - return; - } - Object.keys(enumValue).forEach(function(memberName) { - if (/^-?\d+$/.test(memberName)) { - return; - } - defineLazyGlobal(memberName, function() { - return enumValue[memberName]; - }, false, 'enumMember:' + name); - }); - }); - names('structNames').forEach(function(name) { - var conflictsWithValue = - !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || - !!protocolNameSet[name] || !!enumNameSet[name]; - defineLazyGlobal(name, function(structName) { - return wrapAggregateConstructor((api.getStruct && api.getStruct(structName)) || api[structName]); - }, !conflictsWithValue, 'struct'); - }); - names('unionNames').forEach(function(name) { - var conflictsWithValue = - !!functionNameSet[name] || !!constantNameSet[name] || !!classNameSet[name] || - !!protocolNameSet[name] || !!enumNameSet[name]; - defineLazyGlobal(name, function(unionName) { - return wrapAggregateConstructor((api.getUnion && api.getUnion(unionName)) || api[unionName]); - }, !conflictsWithValue, 'union'); - }); - - if (typeof globalThis.UIColor === 'undefined' && - typeof globalThis.NSColor !== 'undefined') { - globalThis.UIColor = globalThis.NSColor; - cacheGlobal('UIColor', globalThis.UIColor); - } - var colorCtor = globalThis.UIColor || globalThis.NSColor; - if (colorCtor && colorCtor.prototype && - typeof colorCtor.prototype.initWithRedGreenBlueAlpha !== 'function') { - colorCtor.prototype.initWithRedGreenBlueAlpha = function(red, green, blue, alpha) { - if (typeof this.initWithSRGBRedGreenBlueAlpha === 'function') { - return this.initWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof this.initWithCalibratedRedGreenBlueAlpha === 'function') { - return this.initWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithSRGBRedGreenBlueAlpha === 'function') { - return colorCtor.colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha); - } - if (typeof colorCtor.colorWithCalibratedRedGreenBlueAlpha === 'function') { - return colorCtor.colorWithCalibratedRedGreenBlueAlpha(red, green, blue, alpha); - } - return this; - }; - } - defineLazyGlobal('CC_SHA256', function() { return api.CC_SHA256; }); - - installInteropConstructors(); - installTypeScriptNativeHelpers(); - installInlineFunctions(); - - try { - Object.defineProperty(globalThis, installedFlagName, { - configurable: false, - enumerable: false, - writable: false, - value: true - }); - } catch (_) { - } -}) -)JSI_GLOBALS"; - - std::string script(GlobalInstaller); - script += "("; - script += jsStringLiteral(globalName); - script += ");"; - runtime.evaluateJavaScript(std::make_shared(std::move(script)), - "NativeApiJsiGlobals.js"); - NativeApiJsiWriteSmokeStage("jsi:globals:after-eval"); -} - -void InstallNativeApiJSI(Runtime& runtime, const NativeApiJsiConfig& config) { - const char* globalName = config.globalName != nullptr && config.globalName[0] != '\0' - ? config.globalName - : "__nativeScriptNativeApi"; - NativeApiJsiWriteSmokeStage("jsi:create-api"); - Object api = CreateNativeApiJSI(runtime, config); - Object global = runtime.global(); - NativeApiJsiWriteSmokeStage("jsi:set-global"); - global.setProperty(runtime, globalName, api); - - NativeApiJsiWriteSmokeStage("jsi:set-interop"); - Value existingInterop = global.getProperty(runtime, "interop"); - if (existingInterop.isUndefined() || existingInterop.isNull()) { - global.setProperty(runtime, "interop", api.getProperty(runtime, "interop")); - } - if (config.installGlobalSymbols) { - NativeApiJsiWriteSmokeStage("jsi:install-globals"); - InstallNativeApiJsiGlobalSymbols(runtime, globalName); - } else { - NativeApiJsiWriteSmokeStage("jsi:install-aggregate-globals"); - InstallAggregateGlobals(runtime, api, "protocolNames"); - } - NativeApiJsiWriteSmokeStage("jsi:installed"); -} diff --git a/NativeScript/ffi/shared/jsi/NativeApiJsiInvocation.h b/NativeScript/ffi/shared/jsi/NativeApiJsiInvocation.h deleted file mode 100644 index fdc21b5b1..000000000 --- a/NativeScript/ffi/shared/jsi/NativeApiJsiInvocation.h +++ /dev/null @@ -1,518 +0,0 @@ -bool isValidMetadataStringOffset(MDMetadataReader* metadata, - MDSectionOffset offset) { - if (metadata == nullptr || metadata->constantsOffset < metadata->stringsOffset) { - return false; - } - return offset < metadata->constantsOffset - metadata->stringsOffset; -} - -bool startsWith(const std::string& value, const std::string& prefix) { - return value.size() >= prefix.size() && - value.compare(0, prefix.size(), prefix) == 0; -} - -bool endsWith(const std::string& value, const std::string& suffix) { - return value.size() >= suffix.size() && - value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0; -} - -std::string stripEnumSuffix(const std::string& enumName) { - static const std::vector suffixes = { - "Options", "Option", "Enums", "Enum", "Result", "Direction", - "Orientation", "Style", "Mask", "Type", "Status", "Modes", "Mode", "s"}; - - for (const auto& suffix : suffixes) { - if (enumName.size() > suffix.size() && endsWith(enumName, suffix)) { - return enumName.substr(0, enumName.size() - suffix.size()); - } - } - - return enumName; -} - -bool isNSComparisonResultOrderingName(const std::string& enumName, - const std::string& member) { - if (enumName != "NSComparisonResult") { - return false; - } - return member == "Ascending" || member == "Same" || member == "Descending"; -} - -Value enumToObject(Runtime& runtime, MDMetadataReader* metadata, - const NativeApiSymbol& symbol) { - Object result(runtime); - if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { - return result; - } - - std::string enumName = symbol.name; - std::string strippedPrefix = stripEnumSuffix(enumName); - MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); - bool next = true; - while (next) { - auto nameOffset = metadata->getOffset(offset); - next = (nameOffset & metagen::mdSectionOffsetNext) != 0; - nameOffset &= ~metagen::mdSectionOffsetNext; - offset += sizeof(MDSectionOffset); - - const char* memberName = metadata->resolveString(nameOffset); - int64_t value = metadata->getEnumValue(offset); - offset += sizeof(int64_t); - - std::string canonicalName = memberName != nullptr ? memberName : ""; - std::vector aliases; - aliases.push_back(canonicalName); - - if (!strippedPrefix.empty() && startsWith(canonicalName, strippedPrefix) && - canonicalName.size() > strippedPrefix.size()) { - aliases.push_back(canonicalName.substr(strippedPrefix.size())); - } else if (!strippedPrefix.empty() && - !startsWith(canonicalName, strippedPrefix)) { - aliases.push_back(strippedPrefix + canonicalName); - } - - if (startsWith(enumName, "NS") && !startsWith(canonicalName, "NS")) { - aliases.push_back(std::string("NS") + canonicalName); - } - - if (enumName == "NSStringCompareOptions" && - !endsWith(canonicalName, "Search")) { - aliases.push_back(canonicalName + "Search"); - aliases.push_back(std::string("NS") + canonicalName + "Search"); - } - - if (!startsWith(canonicalName, "k")) { - aliases.push_back(std::string("k") + enumName + canonicalName); - } - - if (isNSComparisonResultOrderingName(enumName, canonicalName)) { - aliases.push_back(std::string("Ordered") + canonicalName); - aliases.push_back(std::string("NSOrdered") + canonicalName); - } - - std::vector uniqueAliases; - std::unordered_set seenAliases; - for (const auto& alias : aliases) { - if (!alias.empty() && seenAliases.insert(alias).second) { - uniqueAliases.push_back(alias); - } - } - - for (const auto& alias : uniqueAliases) { - result.setProperty(runtime, alias.c_str(), static_cast(value)); - } - - char valueKey[32] = {}; - snprintf(valueKey, sizeof(valueKey), "%lld", static_cast(value)); - if (!result.hasProperty(runtime, valueKey)) { - std::string reverseName = - uniqueAliases.size() > 1 ? uniqueAliases[1] : canonicalName; - result.setProperty(runtime, valueKey, makeString(runtime, reverseName)); - } - } - return result; -} - -Value constantToValue(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol) { - MDMetadataReader* metadata = bridge->metadata(); - if (metadata == nullptr || symbol.offset == MD_SECTION_OFFSET_NULL) { - return Value::undefined(); - } - - MDSectionOffset offset = symbol.offset + sizeof(MDSectionOffset); - auto evalKind = metadata->getVariableEvalKind(offset); - offset += sizeof(metagen::MDVariableEvalKind); - - switch (evalKind) { - case metagen::mdEvalInt64: - return static_cast(metadata->getInt64(offset)); - case metagen::mdEvalDouble: - return metadata->getDouble(offset); - case metagen::mdEvalString: { - if (isValidMetadataStringOffset(metadata, offset)) { - auto stringOffset = metadata->getOffset(offset); - return makeString(runtime, metadata->resolveString(stringOffset)); - } - - void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); - if (symbolPtr == nullptr) { - return Value::undefined(); - } - - NativeApiJsiType stringObjectType; - stringObjectType.kind = metagen::mdTypeNSStringObject; - stringObjectType.ffiType = &ffi_type_pointer; - stringObjectType.supported = true; - return convertNativeReturnValue(runtime, bridge, stringObjectType, - symbolPtr); - } - case metagen::mdEvalNone: - break; - } - - MDSectionOffset typeOffset = offset; - NativeApiJsiType type = parseMetadataJsiType(metadata, &typeOffset, bridge.get()); - if (unsupportedJsiType(type)) { - throw facebook::jsi::JSError( - runtime, "Native constant type is not supported by pure JSI: " + - symbol.name); - } - - void* symbolPtr = dlsym(bridge->selfDl(), symbol.name.c_str()); - if (symbolPtr == nullptr) { - return Value::undefined(); - } - return convertNativeReturnValue(runtime, bridge, type, symbolPtr); -} - -void prepareJsiArgument(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, const Value& arg, - size_t index, NativeApiJsiArgumentFrame& frame) { - ffi_type* ffiType = ffiTypeForJsiArgument(type); - size_t size = - ffiType != nullptr && ffiType->size > 0 ? ffiType->size : nativeSizeForType(type); - void* target = frame.storageAt(index, size); - convertJsiFfiArgument(runtime, bridge, type, arg, target, frame); -} - -void prepareJsiArguments(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiSignature& signature, - const Value* args, size_t count, - NativeApiJsiArgumentFrame& frame) { - if (count != signature.argumentTypes.size()) { - throw facebook::jsi::JSError( - runtime, "Actual arguments count: \"" + std::to_string(count) + - "\". Expected: \"" + - std::to_string(signature.argumentTypes.size()) + "\"."); - } - - for (size_t i = 0; i < signature.argumentTypes.size(); i++) { - prepareJsiArgument(runtime, bridge, signature.argumentTypes[i], args[i], i, - frame); - } -} - -Value callNativeFunctionPointer( - Runtime& runtime, const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* pointer, bool block, const Value* args, - size_t count) { - if (pointer == nullptr) { - throw facebook::jsi::JSError(runtime, "Native function pointer is null."); - } - if (bridge == nullptr || bridge->metadata() == nullptr || - type.signatureOffset == MD_SECTION_OFFSET_NULL) { - throw facebook::jsi::JSError( - runtime, "Native function pointer metadata is unavailable."); - } - - auto signature = parseMetadataJsiSignature( - bridge->metadata(), type.signatureOffset, block ? 1 : 0, bridge.get()); - if (!signature || !signature->prepared || signature->variadic || - unsupportedJsiType(signature->returnType)) { - throw facebook::jsi::JSError( - runtime, - "Native function pointer signature is not supported by pure JSI."); - } - - NativeApiJsiArgumentFrame frame(signature->argumentTypes.size()); - prepareJsiArguments(runtime, bridge, *signature, args, count, frame); - - std::vector values; - if (block) { - values.reserve(signature->argumentTypes.size() + 1); - values.push_back(&pointer); - for (size_t i = 0; i < signature->argumentTypes.size(); i++) { - values.push_back(frame.values()[i]); - } - } - - void* callable = pointer; - if (block) { - auto literal = static_cast(pointer); - if (literal == nullptr || literal->invoke == nullptr) { - throw facebook::jsi::JSError(runtime, "Native block invoke pointer is null."); - } - callable = literal->invoke; - } - - std::vector returnStorage( - std::max(nativeSizeForType(signature->returnType), sizeof(void*)), 0); - performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { - ffi_call(&signature->cif, FFI_FN(callable), returnStorage.data(), - block ? values.data() : frame.values()); - }); - - return convertNativeReturnValue(runtime, bridge, signature->returnType, - returnStorage.data()); -} - -Value wrapNativeFunctionPointer(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiJsiType& type, void* pointer, - bool block) { - const char* functionName = block ? "NativeApiJsiBlock" : "NativeApiJsiFunctionPointer"; - auto function = Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, functionName), 0, - [bridge, type, pointer, block](Runtime& runtime, const Value&, - const Value* args, size_t count) -> Value { - return callNativeFunctionPointer(runtime, bridge, type, pointer, block, - args, count); - }); - function.setProperty(runtime, "kind", - makeString(runtime, block ? "block" : "functionPointer")); - function.setProperty( - runtime, "__nativeApiPointerObject", - createPointer(runtime, bridge, pointer)); - function.setProperty( - runtime, "__nativeApiPointer", - static_cast(reinterpret_cast(pointer))); - function.setProperty( - runtime, "nativeAddress", - static_cast(reinterpret_cast(pointer))); - function.setProperty(runtime, "sizeof", - static_cast(sizeof(void*))); - function.setProperty( - runtime, "toString", - Function::createFromHostFunction( - runtime, PropNameID::forAscii(runtime, "toString"), 0, - [pointer, block](Runtime& runtime, const Value&, const Value*, - size_t) -> Value { - char address[32] = {}; - snprintf(address, sizeof(address), "%p", pointer); - return makeString(runtime, - std::string("[NativeApiJsi ") + - (block ? "Block " : "FunctionPointer ") + - address + "]"); - })); - return function; -} - -Value callCFunction(Runtime& runtime, - const std::shared_ptr& bridge, - const NativeApiSymbol& symbol, const Value* args, - size_t count) { - MDMetadataReader* metadata = bridge->metadata(); - if (metadata == nullptr) { - throw facebook::jsi::JSError(runtime, "Native metadata is not loaded."); - } - - void* fnptr = dlsym(bridge->selfDl(), symbol.name.c_str()); - if (fnptr == nullptr) { - throw facebook::jsi::JSError(runtime, - "Native function is not available: " + - symbol.name); - } - - MDSectionOffset signatureOffset = - metadata->signaturesOffset + - metadata->getOffset(symbol.offset + sizeof(MDSectionOffset)); - auto signature = parseMetadataJsiSignature( - metadata, signatureOffset, 0, bridge.get(), - (metadata->getFunctionFlag(symbol.offset + sizeof(MDSectionOffset) * 2) & - metagen::mdFunctionReturnOwned) != 0); - if (!signature || !signature->prepared || signature->variadic || - unsupportedJsiType(signature->returnType)) { - throw facebook::jsi::JSError( - runtime, "Native function signature is not supported by pure JSI: " + - symbol.name); - } - - NativeApiJsiArgumentFrame frame(signature->argumentTypes.size()); - prepareJsiArguments(runtime, bridge, *signature, args, count, frame); - - if (symbol.name == "NSApplicationMain" || - symbol.name == "UIApplicationMain") { - runtime.drainMicrotasks(); - } - - std::vector returnStorage( - std::max(nativeSizeForType(signature->returnType), sizeof(void*)), 0); - bool dispatchingNativeCallToUI = shouldDispatchNativeCallToUI(); - bool retainedReturn = false; - performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { - ffi_call(&signature->cif, FFI_FN(fnptr), returnStorage.data(), - frame.values()); - if (dispatchingNativeCallToUI && - !signature->returnType.returnOwned && - isObjectiveCObjectType(signature->returnType)) { - id object = *reinterpret_cast(returnStorage.data()); - if (object != nil) { - [object retain]; - retainedReturn = true; - } - } - }); - - NativeApiJsiType returnType = signature->returnType; - if (retainedReturn) { - returnType.returnOwned = true; - } - if (symbol.name == "CFBagContainsValue" && - (returnType.kind == metagen::mdTypeChar || - returnType.kind == metagen::mdTypeUChar || - returnType.kind == metagen::mdTypeUInt8)) { - return *returnStorage.data() != 0; - } - return convertNativeReturnValue(runtime, bridge, returnType, - returnStorage.data()); -} - -bool signatureSupportedForJsiInvocation( - const std::optional& signature) { - if (!signature || !signature->prepared || signature->variadic || - unsupportedJsiType(signature->returnType)) { - return false; - } - for (const auto& argType : signature->argumentTypes) { - if (unsupportedJsiType(argType)) { - return false; - } - } - return true; -} - -Value callObjCSelector(Runtime& runtime, - const std::shared_ptr& bridge, - id receiver, bool receiverIsClass, - const std::string& selectorName, - const NativeApiMember* member, - const Value* args, size_t count, - Class dispatchSuperClass) { - if (receiver == nil) { - throw facebook::jsi::JSError(runtime, - "Cannot send Objective-C selector to nil."); - } - - SEL selector = sel_registerName(selectorName.c_str()); - Class receiverClass = - receiverIsClass ? static_cast(receiver) : object_getClass(receiver); - Class lookupClass = dispatchSuperClass != Nil ? dispatchSuperClass : receiverClass; - Method method = receiverIsClass ? class_getClassMethod(lookupClass, selector) - : class_getInstanceMethod(lookupClass, selector); - if (method == nullptr && - (dispatchSuperClass != Nil || ![receiver respondsToSelector:selector])) { - throw facebook::jsi::JSError(runtime, - "Objective-C selector is not available: " + - selectorName); - } - - std::optional signature; - std::optional runtimeSignature; - if (member != nullptr && - member->signatureOffset != MD_SECTION_OFFSET_NULL && - member->signatureOffset != 0) { - signature = parseMetadataJsiSignature( - bridge->metadata(), member->signatureOffset, 2, bridge.get(), - (member->flags & metagen::mdMemberReturnOwned) != 0); - } - if (method != nullptr) { - runtimeSignature = parseObjCMethodJsiSignature(method, bridge.get()); - } - if (signatureSupportedForJsiInvocation(signature) && - signatureSupportedForJsiInvocation(runtimeSignature)) { - reconcileObjCMethodRuntimeSignature(&*signature, *runtimeSignature); - } - if (!signatureSupportedForJsiInvocation(signature) && runtimeSignature) { - signature = std::move(runtimeSignature); - } - - if (!signatureSupportedForJsiInvocation(signature)) { - throw facebook::jsi::JSError( - runtime, "Objective-C signature is not supported by pure JSI: " + - selectorName); - } - signature->selectorName = selectorName; - - NativeApiJsiArgumentFrame frame(signature->argumentTypes.size()); - const bool isNSErrorOutMethod = isNSErrorOutJsiMethodSignature(*signature); - if (isNSErrorOutMethod) { - size_t expected = signature->argumentTypes.size(); - if (count > expected || count + 1 < expected) { - throw facebook::jsi::JSError( - runtime, "Actual arguments count: \"" + std::to_string(count) + - "\". Expected: \"" + std::to_string(expected) + "\"."); - } - } - - const bool hasImplicitNSErrorOutArg = - isNSErrorOutMethod && count + 1 == signature->argumentTypes.size(); - NSError* implicitNSError = nil; - if (hasImplicitNSErrorOutArg) { - for (size_t i = 0; i < count; i++) { - prepareJsiArgument(runtime, bridge, signature->argumentTypes[i], args[i], i, - frame); - } - - size_t outArgIndex = signature->argumentTypes.size() - 1; - void* target = frame.storageAt(outArgIndex, sizeof(NSError**)); - NSError** implicitNSErrorOutArg = &implicitNSError; - *static_cast(target) = implicitNSErrorOutArg; - } else { - prepareJsiArguments(runtime, bridge, *signature, args, count, frame); - } - - std::vector values; - values.reserve(signature->argumentTypes.size() + 2); - struct objc_super superReceiver = {receiver, dispatchSuperClass}; - struct objc_super* superReceiverPtr = &superReceiver; - if (dispatchSuperClass != Nil) { - values.push_back(&superReceiverPtr); - } else { - values.push_back(&receiver); - } - values.push_back(&selector); - for (size_t i = 0; i < signature->argumentTypes.size(); i++) { - values.push_back(frame.values()[i]); - } - - std::vector returnStorage( - std::max(nativeSizeForType(signature->returnType), sizeof(void*)), 0); - bool dispatchingNativeCallToUI = shouldDispatchNativeCallToUI(); - bool retainedReturn = false; - performNativeInvocation(runtime, bridge->nativeInvocationInvoker(), [&]() { -#if defined(__x86_64__) - bool isStret = signature->returnType.ffiType->size > 16 && - signature->returnType.ffiType->type == FFI_TYPE_STRUCT; - void (*target)(void) = dispatchSuperClass != Nil - ? (isStret ? FFI_FN(objc_msgSendSuper_stret) - : FFI_FN(objc_msgSendSuper)) - : (isStret ? FFI_FN(objc_msgSend_stret) - : FFI_FN(objc_msgSend)); - ffi_call(&signature->cif, target, returnStorage.data(), values.data()); -#else - ffi_call(&signature->cif, - dispatchSuperClass != Nil ? FFI_FN(objc_msgSendSuper) - : FFI_FN(objc_msgSend), - returnStorage.data(), values.data()); -#endif - if (dispatchingNativeCallToUI && - !signature->returnType.returnOwned && - isObjectiveCObjectType(signature->returnType)) { - id object = *reinterpret_cast(returnStorage.data()); - if (object != nil) { - [object retain]; - retainedReturn = true; - } - } - }); - - NativeApiJsiType returnType = signature->returnType; - if ((selectorName == "valueForKey:" || selectorName == "valueForKeyPath:") && - isObjectiveCObjectType(returnType)) { - returnType.kind = metagen::mdTypeAnyObject; - } - if (retainedReturn) { - returnType.returnOwned = true; - } - if (hasImplicitNSErrorOutArg && implicitNSError != nil) { - const char* errorMessage = [[implicitNSError description] UTF8String]; - throw facebook::jsi::JSError( - runtime, errorMessage != nullptr ? errorMessage : "Unknown NSError"); - } - return convertNativeReturnValue(runtime, bridge, returnType, - returnStorage.data()); -} diff --git a/NativeScript/ffi/v8/NativeApiV8.h b/NativeScript/ffi/v8/NativeApiV8.h deleted file mode 100644 index cf8c4054a..000000000 --- a/NativeScript/ffi/v8/NativeApiV8.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H -#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H - -#include "ffi/shared/direct/NativeApiDirect.h" -#include "v8.h" - -namespace nativescript { - -using NativeApiV8Config = NativeApiDirectConfig; - -void InstallNativeApiV8(v8::Isolate* isolate, - v8::Local context, - const NativeApiV8Config& config = NativeApiV8Config{}); - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiV8(v8::Isolate* isolate, - v8::Local context, - const char* metadataPath); - -#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_H diff --git a/NativeScript/ffi/v8/NativeApiV8.mm b/NativeScript/ffi/v8/NativeApiV8.mm deleted file mode 100644 index 154a11947..000000000 --- a/NativeScript/ffi/v8/NativeApiV8.mm +++ /dev/null @@ -1,181 +0,0 @@ -#include "NativeApiV8.h" - -#ifdef TARGET_ENGINE_V8 - -#include "NativeApiV8Runtime.h" - -namespace nativescript { - -using NativeApiJsiConfig = NativeApiDirectConfig; -using NativeApiJsiScheduler = NativeApiDirectScheduler; - -namespace { - -using facebook::jsi::Array; -using facebook::jsi::ArrayBuffer; -using facebook::jsi::BigInt; -using facebook::jsi::Function; -using facebook::jsi::HostObject; -using facebook::jsi::MutableBuffer; -using facebook::jsi::Object; -using facebook::jsi::PropNameID; -using facebook::jsi::Runtime; -using facebook::jsi::String; -using facebook::jsi::StringBuffer; -using facebook::jsi::Value; -using metagen::MDMemberFlag; -using metagen::MDMetadataReader; -using metagen::MDSectionOffset; -using metagen::MDTypeKind; - -// clang-format off -#include "jsi/NativeApiJsiBridge.h" -// clang-format on - -#define NATIVESCRIPT_NATIVE_API_HAS_ENGINE_LAZY_GLOBALS 1 -#define NATIVESCRIPT_NATIVE_API_RETAIN_RUNTIME 1 -#define NATIVESCRIPT_NATIVE_API_RUNTIME_SCOPE 1 - -struct NativeApiV8LazyGlobalData { - NativeApiV8LazyGlobalData(v8::Isolate* isolate, const std::string& name, - const std::string& kind) { - nameValue.Reset(isolate, facebook::jsi::v8direct::makeV8String(isolate, name)); - kindValue.Reset(isolate, facebook::jsi::v8direct::makeV8String(isolate, kind)); - } - - ~NativeApiV8LazyGlobalData() { - nameValue.Reset(); - kindValue.Reset(); - } - - v8::Global nameValue; - v8::Global kindValue; -}; - -std::shared_ptr retainNativeApiJsiRuntime(Runtime& runtime) { - return std::make_shared(runtime.state()); -} - -class NativeApiJsiRuntimeScope final { - public: - explicit NativeApiJsiRuntimeScope(Runtime& runtime) - : locker_(runtime.isolate()), - isolateScope_(runtime.isolate()), - handleScope_(runtime.isolate()), - context_(runtime.context()), - contextScope_(context_) {} - - private: - v8::Locker locker_; - v8::Isolate::Scope isolateScope_; - v8::HandleScope handleScope_; - v8::Local context_; - v8::Context::Scope contextScope_; -}; - -void NativeApiV8LazyGlobalGetter(v8::Local, - const v8::PropertyCallbackInfo& info) { - v8::Isolate* isolate = info.GetIsolate(); - v8::HandleScope handleScope(isolate); - v8::Local context = isolate->GetCurrentContext(); - if (!info.Data()->IsExternal()) { - return; - } - - auto* data = static_cast(info.Data().As()->Value()); - if (data == nullptr) { - return; - } - v8::Local nameValue = data->nameValue.Get(isolate); - v8::Local kindValue = data->kindValue.Get(isolate); - - v8::Local global = context->Global(); - v8::Local resolverValue; - if (!global - ->Get(context, facebook::jsi::v8direct::makeV8String( - isolate, "__nativeScriptResolveNativeApiLazyGlobal")) - .ToLocal(&resolverValue) || - !resolverValue->IsFunction()) { - return; - } - - v8::TryCatch tryCatch(isolate); - v8::Local args[] = {nameValue, kindValue}; - v8::Local result; - if (!resolverValue.As()->Call(context, global, 2, args).ToLocal(&result)) { - if (tryCatch.HasCaught()) { - isolate->ThrowException(tryCatch.Exception()); - } - return; - } - if (global->Delete(context, nameValue).FromMaybe(false)) { - global->DefineOwnProperty(context, nameValue, result, v8::DontEnum).FromMaybe(false); - } - info.GetReturnValue().Set(result); -} - -bool InstallNativeApiEngineLazyGlobal(Runtime& runtime, std::shared_ptr, - const std::string& name, const std::string& kind, - bool force) { - if (name.empty() || kind.empty()) { - return false; - } - - v8::Isolate* isolate = runtime.isolate(); - v8::EscapableHandleScope handleScope(isolate); - v8::Local context = runtime.context(); - v8::Local global = context->Global(); - v8::Local property = facebook::jsi::v8direct::makeV8String(isolate, name); - if (!force && global->HasOwnProperty(context, property).FromMaybe(false)) { - return false; - } - - auto data = std::make_shared(isolate, name, kind); - v8::Local external = v8::External::New(isolate, data.get()); - - bool installed = global - ->SetNativeDataProperty(context, property, NativeApiV8LazyGlobalGetter, - nullptr, external, v8::DontEnum) - .FromMaybe(false); - if (installed) { - runtime.state()->retainedNativeData.push_back(std::move(data)); - } - return installed; -} - -// clang-format off -#include "jsi/NativeApiJsiHostObjects.h" -#include "jsi/NativeApiJsiCallbacks.h" -#include "jsi/NativeApiJsiConversion.h" -#include "jsi/NativeApiJsiInvocation.h" -#include "jsi/NativeApiJsiClassBuilder.h" -#include "jsi/NativeApiJsiHostObject.h" -// clang-format on - -} // namespace - -#include "jsi/NativeApiJsiInstall.h" - -void InstallNativeApiV8(v8::Isolate* isolate, v8::Local context, - const NativeApiV8Config& config) { - if (isolate == nullptr || context.IsEmpty()) { - return; - } - v8::Locker locker(isolate); - v8::Isolate::Scope isolateScope(isolate); - v8::HandleScope handleScope(isolate); - v8::Context::Scope contextScope(context); - Runtime runtime(isolate, context); - InstallNativeApiJSI(runtime, config); -} - -} // namespace nativescript - -extern "C" void NativeScriptInstallNativeApiV8(v8::Isolate* isolate, v8::Local context, - const char* metadataPath) { - nativescript::NativeApiV8Config config; - config.metadataPath = metadataPath; - nativescript::InstallNativeApiV8(isolate, context, config); -} - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/v8/NativeApiV8HostObjects.mm b/NativeScript/ffi/v8/NativeApiV8HostObjects.mm deleted file mode 100644 index 8a11b15c4..000000000 --- a/NativeScript/ffi/v8/NativeApiV8HostObjects.mm +++ /dev/null @@ -1,187 +0,0 @@ -#include "NativeApiV8Runtime.h" - -#ifdef TARGET_ENGINE_V8 - -namespace facebook { -namespace jsi { - -namespace v8direct { - -Value valueFromLocal(Runtime& runtime, v8::Local value) { return Value(runtime, value); } - -v8::Local hostObjectTemplate(Runtime& runtime) { - auto state = runtime.state(); - if (state->hostObjectTemplate.IsEmpty()) { - v8::Local objectTemplate = v8::ObjectTemplate::New(runtime.isolate()); - objectTemplate->SetInternalFieldCount(1); - objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( - [](v8::Local property, - const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - Value result = holder->hostObject->get( - runtime, PropNameID(propertyNameToUtf8(info.GetIsolate(), property))); - if (!result.isUndefined()) { - info.GetReturnValue().Set(result.local(runtime)); - return v8::Intercepted::kYes; - } - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - return v8::Intercepted::kNo; - }, - [](v8::Local property, v8::Local value, - const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - holder->hostObject->set(runtime, - PropNameID(propertyNameToUtf8(info.GetIsolate(), property)), - Value(runtime, value)); - return v8::Intercepted::kYes; - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - }, - nullptr, nullptr, - [](const v8::PropertyCallbackInfo& info) { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return; - } - Runtime runtime(holder->state); - try { - auto propertyNames = holder->hostObject->getPropertyNames(runtime); - v8::Local result = - v8::Array::New(info.GetIsolate(), static_cast(propertyNames.size())); - for (size_t i = 0; i < propertyNames.size(); i++) { - std::string name = propertyNames[i].utf8(runtime); - result - ->Set(runtime.context(), static_cast(i), - makeV8String(info.GetIsolate(), name)) - .FromMaybe(false); - } - info.GetReturnValue().Set(result); - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - } - }, - v8::Local(), v8::PropertyHandlerFlags::kNone)); - objectTemplate->SetHandler(v8::IndexedPropertyHandlerConfiguration( - [](uint32_t index, const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - Value result = holder->hostObject->get(runtime, PropNameID(std::to_string(index))); - if (!result.isUndefined()) { - info.GetReturnValue().Set(result.local(runtime)); - return v8::Intercepted::kYes; - } - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - return v8::Intercepted::kNo; - }, - [](uint32_t index, v8::Local value, - const v8::PropertyCallbackInfo& info) -> v8::Intercepted { - auto* holder = - static_cast(info.Holder()->GetAlignedPointerFromInternalField(0)); - if (holder == nullptr || holder->hostObject == nullptr) { - return v8::Intercepted::kNo; - } - Runtime runtime(holder->state); - try { - holder->hostObject->set(runtime, PropNameID(std::to_string(index)), - Value(runtime, value)); - return v8::Intercepted::kYes; - } catch (const std::exception& exception) { - throwV8Exception(info.GetIsolate(), exception); - return v8::Intercepted::kYes; - } - }, - nullptr, nullptr, nullptr, v8::Local(), v8::PropertyHandlerFlags::kNone)); - state->hostObjectTemplate.Reset(runtime.isolate(), objectTemplate); - } - return state->hostObjectTemplate.Get(runtime.isolate()); -} - -void hostObjectWeakCallback(const v8::WeakCallbackInfo& info) { - delete info.GetParameter(); -} - -void functionWeakCallback(const v8::WeakCallbackInfo& info) { - delete info.GetParameter(); -} - -} // namespace v8direct - -Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken) { - v8::Local object = - v8direct::hostObjectTemplate(runtime)->NewInstance(runtime.context()).ToLocalChecked(); - auto* holder = new v8direct::HostObjectHolder(runtime.state(), std::move(host), typeToken); - object->SetAlignedPointerInInternalField(0, holder); - holder->object.Reset(runtime.isolate(), object); - holder->object.SetWeak(holder, v8direct::hostObjectWeakCallback, - v8::WeakCallbackType::kParameter); - return Object::fromValueStorage(Value(runtime, object).storage_); -} - -Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback) { - auto* holder = new v8direct::FunctionHolder(runtime.state(), std::move(callback)); - v8::Local data = v8::External::New(runtime.isolate(), holder); - v8::Local functionTemplate = v8::FunctionTemplate::New( - runtime.isolate(), - [](const v8::FunctionCallbackInfo& info) { - auto* holder = - static_cast(info.Data().As()->Value()); - Runtime runtime(holder->state); - std::vector args; - args.reserve(info.Length()); - for (int i = 0; i < info.Length(); i++) { - args.push_back(Value(runtime, info[i])); - } - try { - Value thisValue(runtime, info.This()); - Value result = holder->callback(runtime, thisValue, args.empty() ? nullptr : args.data(), - args.size()); - info.GetReturnValue().Set(result.local(runtime)); - } catch (const std::exception& exception) { - v8direct::throwV8Exception(info.GetIsolate(), exception); - } - }, - data); - v8::Local function = - functionTemplate->GetFunction(runtime.context()).ToLocalChecked(); - std::string functionName = name.utf8(runtime); - if (!functionName.empty()) { - function->SetName(v8direct::makeV8String(runtime.isolate(), functionName)); - } - holder->function.Reset(runtime.isolate(), function); - holder->function.SetWeak(holder, v8direct::functionWeakCallback, - v8::WeakCallbackType::kParameter); - return Function(Object::fromValueStorage(Value(runtime, function).storage_)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/v8/NativeApiV8Runtime.h b/NativeScript/ffi/v8/NativeApiV8Runtime.h deleted file mode 100644 index 81c95a085..000000000 --- a/NativeScript/ffi/v8/NativeApiV8Runtime.h +++ /dev/null @@ -1,736 +0,0 @@ -#ifndef NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H -#define NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H - -#ifdef TARGET_ENGINE_V8 - -#import -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Metadata.h" -#include "MetadataReader.h" -#include "ffi.h" -#include "v8.h" - -@protocol NativeApiJsiClassBuilderProtocol -@end - -#ifdef EMBED_METADATA_SIZE -extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; -#endif - -namespace facebook { -namespace jsi { - -class Runtime; -class Value; -class Object; -class Function; -class Array; -class String; -class BigInt; -class ArrayBuffer; - -class JSError : public std::runtime_error { - public: - JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} - explicit JSError(const std::string& message) : std::runtime_error(message) {} -}; - -class StringBuffer { - public: - explicit StringBuffer(std::string value) : value_(std::move(value)) {} - const char* data() const { return value_.data(); } - size_t size() const { return value_.size(); } - - private: - std::string value_; -}; - -class MutableBuffer { - public: - virtual ~MutableBuffer() = default; - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -class PropNameID { - public: - PropNameID() = default; - explicit PropNameID(std::string value) : value_(std::move(value)) {} - - static PropNameID forAscii(Runtime&, const char* value) { - return PropNameID(value != nullptr ? value : ""); - } - - static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } - - std::string utf8(Runtime&) const { return value_; } - - private: - std::string value_; -}; - -class HostObject { - public: - virtual ~HostObject() = default; - virtual Value get(Runtime& runtime, const PropNameID& name); - virtual void set(Runtime& runtime, const PropNameID& name, const Value& value); - virtual std::vector getPropertyNames(Runtime& runtime); -}; - -using HostFunctionType = std::function; - -namespace v8direct { - -struct RuntimeState { - explicit RuntimeState(v8::Isolate* isolate, v8::Local context) : isolate(isolate) { - this->context.Reset(isolate, context); - } - - ~RuntimeState() { context.Reset(); } - - v8::Local localContext() const { return context.Get(isolate); } - - v8::Isolate* isolate = nullptr; - v8::Global context; - v8::Global hostObjectTemplate; - std::vector> retainedNativeData; -}; - -struct ValueStorage { - enum class Kind { - Undefined, - Null, - Bool, - Number, - V8, - }; - - explicit ValueStorage(Kind kind) : kind(kind) {} - - ~ValueStorage() { value.Reset(); } - - Kind kind = Kind::Undefined; - bool boolValue = false; - double numberValue = 0; - v8::Global value; -}; - -template -const void* hostObjectTypeToken() { - static int token = 0; - return &token; -} - -struct HostObjectHolder { - HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, - const void* typeToken) - : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} - - ~HostObjectHolder() { object.Reset(); } - - std::shared_ptr state; - std::shared_ptr hostObject; - const void* typeToken = nullptr; - v8::Global object; -}; - -struct FunctionHolder { - FunctionHolder(std::shared_ptr state, HostFunctionType callback) - : state(std::move(state)), callback(std::move(callback)) {} - - ~FunctionHolder() { function.Reset(); } - - std::shared_ptr state; - HostFunctionType callback; - v8::Global function; -}; - -struct ArrayBufferHolder { - explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} - - std::shared_ptr buffer; - v8::Global object; -}; - -inline v8::Local makeV8String(v8::Isolate* isolate, const std::string& value) { - return v8::String::NewFromUtf8(isolate, value.c_str(), v8::NewStringType::kNormal, - static_cast(value.size())) - .ToLocalChecked(); -} - -inline std::string toUtf8(v8::Isolate* isolate, v8::Local value) { - if (value.IsEmpty()) { - return {}; - } - v8::String::Utf8Value utf8(isolate, value); - return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); -} - -inline std::string propertyNameToUtf8(v8::Isolate* isolate, v8::Local property) { - if (property->IsSymbol() && - property.As()->StrictEquals(v8::Symbol::GetIterator(isolate))) { - return "Symbol.iterator"; - } - return toUtf8(isolate, property); -} - -inline std::string currentExceptionMessage(v8::Isolate* isolate, v8::TryCatch& tryCatch) { - if (tryCatch.HasCaught()) { - return toUtf8(isolate, tryCatch.Exception()); - } - return "NativeScript direct V8 operation failed."; -} - -inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { - isolate->ThrowException(v8::Exception::Error(makeV8String(isolate, exception.what()))); -} - -} // namespace v8direct - -class Runtime { - public: - Runtime(v8::Isolate* isolate, v8::Local context) - : state_(std::make_shared(isolate, context)) {} - - explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} - - v8::Isolate* isolate() const { return state_->isolate; } - v8::Local context() const { return state_->localContext(); } - std::shared_ptr state() const { return state_; } - - Object global(); - - Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); - - void drainMicrotasks() { isolate()->PerformMicrotaskCheckpoint(); } - - private: - std::shared_ptr state_; -}; - -class String { - public: - String() = default; - String(Runtime& runtime, v8::Local value); - - static String createFromUtf8(Runtime& runtime, const char* value) { - return String(runtime, - v8direct::makeV8String(runtime.isolate(), value != nullptr ? value : "")); - } - - static String createFromUtf8(Runtime& runtime, const std::string& value) { - return String(runtime, v8direct::makeV8String(runtime.isolate(), value)); - } - - static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { - return String(runtime, v8::String::NewFromUtf8( - runtime.isolate(), - reinterpret_cast( - value != nullptr ? value : reinterpret_cast("")), - v8::NewStringType::kNormal, static_cast(length)) - .ToLocalChecked()); - } - - std::string utf8(Runtime& runtime) const { - return v8direct::toUtf8(runtime.isolate(), local(runtime)); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const; - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class Value { - public: - Value() - : storage_( - std::make_shared(v8direct::ValueStorage::Kind::Undefined)) {} - - Value(bool value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::Bool)) { - storage_->boolValue = value; - } - - Value(double value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::Number)) { - storage_->numberValue = value; - } - - Value(int value) : Value(static_cast(value)) {} - Value(uint32_t value) : Value(static_cast(value)) {} - - Value(Runtime& runtime, const Value& value) : storage_(value.storage_) {} - Value(Runtime& runtime, Value&& value) : storage_(std::move(value.storage_)) {} - Value(Runtime& runtime, const String& value) : storage_(value.storage_) {} - Value(Runtime& runtime, const Object& object); - Value(Runtime& runtime, const Function& function); - Value(Runtime& runtime, const Array& array); - Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); - Value(Runtime& runtime, const BigInt& bigint); - - static Value undefined() { return Value(); } - - static Value null() { - Value value; - value.storage_ = std::make_shared(v8direct::ValueStorage::Kind::Null); - return value; - } - - bool isUndefined() const; - bool isNull() const; - bool isBool() const; - bool getBool() const; - bool isNumber() const; - double getNumber() const; - - bool isObject() const; - bool isString() const; - bool isBigInt() const; - bool isSymbol() const; - - Object asObject(Runtime& runtime) const; - String asString(Runtime& runtime) const; - BigInt getBigInt(Runtime& runtime) const; - - v8::Local local(Runtime& runtime) const { - v8::Isolate* isolate = runtime.isolate(); - switch (storage_->kind) { - case v8direct::ValueStorage::Kind::Undefined: - return v8::Undefined(isolate); - case v8direct::ValueStorage::Kind::Null: - return v8::Null(isolate); - case v8direct::ValueStorage::Kind::Bool: - return v8::Boolean::New(isolate, storage_->boolValue); - case v8direct::ValueStorage::Kind::Number: - return v8::Number::New(isolate, storage_->numberValue); - case v8direct::ValueStorage::Kind::V8: - return storage_->value.Get(isolate); - } - } - - Value(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); - } - - private: - friend class Runtime; - friend class Object; - friend class String; - friend class BigInt; - friend class ArrayBuffer; - friend class Function; - friend class Array; - - std::shared_ptr storage_; -}; - -class Object { - public: - Object() = default; - explicit Object(Runtime& runtime) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), v8::Object::New(runtime.isolate())); - } - - static Object fromValueStorage(std::shared_ptr storage) { - Object object; - object.storage_ = std::move(storage); - return object; - } - - template - static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { - auto baseHost = std::static_pointer_cast(std::move(host)); - return createFromHostObjectWithToken(runtime, std::move(baseHost), - v8direct::hostObjectTypeToken()); - } - - Value getProperty(Runtime& runtime, const char* name) const { - return getProperty(runtime, - v8direct::makeV8String(runtime.isolate(), name != nullptr ? name : "")); - } - - Value getProperty(Runtime& runtime, const std::string& name) const { - return getProperty(runtime, name.c_str()); - } - - Value getProperty(Runtime& runtime, const Value& key) const { - return getProperty(runtime, key.local(runtime)); - } - - Value getProperty(Runtime& runtime, v8::Local key) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Object getPropertyAsObject(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime); - } - - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - void setProperty(Runtime& runtime, const char* name, const Value& value) { - setProperty(runtime, v8direct::makeV8String(runtime.isolate(), name != nullptr ? name : ""), - value); - } - - void setProperty(Runtime& runtime, const char* name, const String& value) { - setProperty(runtime, name, Value(runtime, value)); - } - - void setProperty(Runtime& runtime, const char* name, const Object& value) { - setProperty(runtime, name, Value(runtime, value)); - } - - void setProperty(Runtime& runtime, const char* name, const Function& value); - void setProperty(Runtime& runtime, const char* name, const Array& value); - void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); - void setProperty(Runtime& runtime, const char* name, bool value) { - setProperty(runtime, name, Value(value)); - } - void setProperty(Runtime& runtime, const char* name, double value) { - setProperty(runtime, name, Value(value)); - } - - void setProperty(Runtime& runtime, const std::string& name, const Value& value) { - setProperty(runtime, name.c_str(), value); - } - - void setProperty(Runtime& runtime, const Value& key, const Value& value) { - setProperty(runtime, key.local(runtime), value); - } - - void setProperty(Runtime& runtime, v8::Local key, const Value& value) { - v8::TryCatch tryCatch(runtime.isolate()); - if (!local(runtime)->Set(runtime.context(), key, value.local(runtime)).FromMaybe(false)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - } - - bool hasProperty(Runtime& runtime, const char* name) const { - v8::TryCatch tryCatch(runtime.isolate()); - return local(runtime) - ->Has(runtime.context(), - v8direct::makeV8String(runtime.isolate(), name != nullptr ? name : "")) - .FromMaybe(false); - } - - bool isFunction(Runtime& runtime) const { return local(runtime)->IsFunction(); } - bool isArray(Runtime& runtime) const { return local(runtime)->IsArray(); } - bool isArrayBuffer(Runtime& runtime) const { return local(runtime)->IsArrayBuffer(); } - - Function asFunction(Runtime& runtime) const; - Array getArray(Runtime& runtime) const; - ArrayBuffer getArrayBuffer(Runtime& runtime) const; - Array getPropertyNames(Runtime& runtime) const; - - template - bool isHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - return holder != nullptr && holder->typeToken == v8direct::hostObjectTypeToken(); - } - - template - std::shared_ptr getHostObject(Runtime& runtime) const { - auto holder = hostObjectHolder(runtime); - if (holder == nullptr || holder->typeToken != v8direct::hostObjectTypeToken()) { - return nullptr; - } - return std::static_pointer_cast(holder->hostObject); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - protected: - friend class Value; - friend class Runtime; - friend class Function; - friend class Array; - friend class ArrayBuffer; - - explicit Object(std::shared_ptr storage) : storage_(std::move(storage)) {} - - static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, - const void* typeToken); - - v8direct::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { - v8::Local object = local(runtime); - if (object->InternalFieldCount() < 1) { - return nullptr; - } - return static_cast(object->GetAlignedPointerFromInternalField(0)); - } - - std::shared_ptr storage_; -}; - -class Function : public Object { - public: - Function() = default; - explicit Function(Object object) : Object(std::move(object.storage_)) {} - - static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, - HostFunctionType callback); - - Value call(Runtime& runtime, const Value* args, size_t count) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->Call(runtime.context(), runtime.context()->Global(), static_cast(argv.size()), - argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value call(Runtime& runtime) const { - return call(runtime, static_cast(nullptr), 0); - } - - Value call(Runtime& runtime, std::nullptr_t, size_t) const { - return call(runtime, static_cast(nullptr), 0); - } - - template - Value call(Runtime& runtime, const Value (&args)[N], size_t count) const { - return call(runtime, static_cast(args), count); - } - - template - Value call(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return call(runtime, static_cast(argv), sizeof...(Args)); - } - - Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, - size_t count = 0) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->Call(runtime.context(), thisObject.local(runtime), static_cast(argv.size()), - argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { - v8::TryCatch tryCatch(runtime.isolate()); - std::vector> argv; - argv.reserve(count); - for (size_t i = 0; i < count; i++) { - argv.push_back(args[i].local(runtime)); - } - v8::Local result; - if (!local(runtime) - .As() - ->NewInstance(runtime.context(), static_cast(argv.size()), argv.data()) - .ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { - return callAsConstructor(runtime, static_cast(nullptr), 0); - } - - template - Value callAsConstructor(Runtime& runtime, const Value (&args)[N], size_t count) const { - return callAsConstructor(runtime, static_cast(args), count); - } - - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const { - Value argv[] = {Value(runtime, std::forward(args))...}; - return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } -}; - -class Array : public Object { - public: - explicit Array(Runtime& runtime, size_t size) - : Object(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), - v8::Array::New(runtime.isolate(), static_cast(size))); - } - - explicit Array(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { return local(runtime).As()->Length(); } - - Value getValueAtIndex(Runtime& runtime, size_t index) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Value(runtime, result); - } - - void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { - v8::TryCatch tryCatch(runtime.isolate()); - if (!local(runtime) - ->Set(runtime.context(), static_cast(index), value.local(runtime)) - .FromMaybe(false)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - } - - void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { - setValueAtIndex(runtime, index, Value(runtime, value)); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } -}; - -class BigInt { - public: - BigInt() = default; - BigInt(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); - } - - static BigInt fromInt64(Runtime& runtime, int64_t value) { - return BigInt(runtime, v8::BigInt::New(runtime.isolate(), value)); - } - - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - return BigInt(runtime, v8::BigInt::NewFromUnsigned(runtime.isolate(), value)); - } - - String toString(Runtime& runtime, int radix) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - (void)radix; - if (!local(runtime)->ToString(runtime.context()).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return String(runtime, result); - } - - v8::Local local(Runtime& runtime) const { - return storage_->value.Get(runtime.isolate()).As(); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } - - private: - friend class Value; - std::shared_ptr storage_; -}; - -class ArrayBuffer : public Object { - public: - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : Object(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - auto holder = new v8direct::ArrayBufferHolder(std::move(buffer)); - auto backingStore = v8::ArrayBuffer::NewBackingStore( - holder->buffer->data(), holder->buffer->size(), - [](void*, size_t, void* deleterData) { - auto* holder = static_cast(deleterData); - holder->object.Reset(); - delete holder; - }, - holder); - v8::Local arrayBuffer = - v8::ArrayBuffer::New(runtime.isolate(), std::move(backingStore)); - storage_->value.Reset(runtime.isolate(), arrayBuffer); - holder->object.Reset(runtime.isolate(), arrayBuffer); - } - - explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} - - size_t size(Runtime& runtime) const { return local(runtime).As()->ByteLength(); } - - uint8_t* data(Runtime& runtime) const { - auto backingStore = local(runtime).As()->GetBackingStore(); - return static_cast(backingStore->Data()); - } - - operator Value() const { - Value value; - value.storage_ = storage_; - return value; - } -}; -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 - -#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H diff --git a/NativeScript/ffi/v8/NativeApiV8Runtime.mm b/NativeScript/ffi/v8/NativeApiV8Runtime.mm deleted file mode 100644 index 681a211b0..000000000 --- a/NativeScript/ffi/v8/NativeApiV8Runtime.mm +++ /dev/null @@ -1,36 +0,0 @@ -#include "NativeApiV8Runtime.h" - -#ifdef TARGET_ENGINE_V8 - -namespace facebook { -namespace jsi { - -Object Runtime::global() { - return Object::fromValueStorage(Value(*this, context()->Global()).storage_); -} - -Value Runtime::evaluateJavaScript(std::shared_ptr buffer, - const std::string& sourceURL) { - v8::TryCatch tryCatch(isolate()); - v8::Local source = - v8::String::NewFromUtf8(isolate(), buffer != nullptr ? buffer->data() : "", - v8::NewStringType::kNormal, - buffer != nullptr ? static_cast(buffer->size()) : 0) - .ToLocalChecked(); - v8::Local resourceName = v8direct::makeV8String(isolate(), sourceURL); - v8::ScriptOrigin origin(resourceName); - v8::Local script; - if (!v8::Script::Compile(context(), source, &origin).ToLocal(&script)) { - throw JSError(*this, v8direct::currentExceptionMessage(isolate(), tryCatch)); - } - v8::Local result; - if (!script->Run(context()).ToLocal(&result)) { - throw JSError(*this, v8direct::currentExceptionMessage(isolate(), tryCatch)); - } - return Value(*this, result); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/v8/NativeApiV8Value.mm b/NativeScript/ffi/v8/NativeApiV8Value.mm deleted file mode 100644 index d8b34428d..000000000 --- a/NativeScript/ffi/v8/NativeApiV8Value.mm +++ /dev/null @@ -1,177 +0,0 @@ -#include "NativeApiV8Runtime.h" - -#ifdef TARGET_ENGINE_V8 - -namespace facebook { -namespace jsi { - -Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } - -void HostObject::set(Runtime&, const PropNameID&, const Value&) {} - -std::vector HostObject::getPropertyNames(Runtime&) { return {}; } - -String::String(Runtime& runtime, v8::Local value) - : storage_(std::make_shared(v8direct::ValueStorage::Kind::V8)) { - storage_->value.Reset(runtime.isolate(), value); -} - -String::operator Value() const { - Value value; - value.storage_ = storage_; - return value; -} - -Value::Value(Runtime&, const Object& object) : storage_(object.storage_) {} -Value::Value(Runtime&, const Function& function) : storage_(function.storage_) {} -Value::Value(Runtime&, const Array& array) : storage_(array.storage_) {} -Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) : storage_(arrayBuffer.storage_) {} -Value::Value(Runtime&, const BigInt& bigint) : storage_(bigint.storage_) {} - -bool Value::isObject() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsObject(); -} - -bool Value::isUndefined() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Undefined) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsUndefined(); -} - -bool Value::isNull() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Null) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsNull(); -} - -bool Value::isBool() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Bool) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsBoolean(); -} - -bool Value::getBool() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Bool) { - return storage_->boolValue; - } - if (storage_->kind == v8direct::ValueStorage::Kind::V8 && !storage_->value.IsEmpty()) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - if (isolate != nullptr) { - return storage_->value.Get(isolate)->BooleanValue(isolate); - } - } - return false; -} - -bool Value::isNumber() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Number) { - return true; - } - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return isolate != nullptr && storage_->value.Get(isolate)->IsNumber(); -} - -double Value::getNumber() const { - if (storage_->kind == v8direct::ValueStorage::Kind::Number) { - return storage_->numberValue; - } - if (storage_->kind == v8direct::ValueStorage::Kind::V8 && !storage_->value.IsEmpty()) { - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - if (isolate != nullptr) { - return storage_->value.Get(isolate)->NumberValue(isolate->GetCurrentContext()).FromMaybe(0); - } - } - return 0; -} - -bool Value::isString() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return storage_->value.Get(isolate)->IsString(); -} - -bool Value::isBigInt() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return storage_->value.Get(isolate)->IsBigInt(); -} - -bool Value::isSymbol() const { - if (storage_->kind != v8direct::ValueStorage::Kind::V8 || storage_->value.IsEmpty()) { - return false; - } - v8::Isolate* isolate = v8::Isolate::GetCurrent(); - return storage_->value.Get(isolate)->IsSymbol(); -} - -Object Value::asObject(Runtime& runtime) const { return Object::fromValueStorage(storage_); } - -String Value::asString(Runtime& runtime) const { - return String(runtime, local(runtime).As()); -} - -BigInt Value::getBigInt(Runtime& runtime) const { - return BigInt(runtime, local(runtime).As()); -} - -Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { - return getProperty(runtime, name).asObject(runtime).asFunction(runtime); -} - -Function Object::asFunction(Runtime& runtime) const { return Function(*this); } - -Array Object::getArray(Runtime& runtime) const { return Array(*this); } - -ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const { return ArrayBuffer(*this); } - -Array Object::getPropertyNames(Runtime& runtime) const { - v8::TryCatch tryCatch(runtime.isolate()); - v8::Local result; - if (!local(runtime)->GetPropertyNames(runtime.context()).ToLocal(&result)) { - throw JSError(runtime, v8direct::currentExceptionMessage(runtime.isolate(), tryCatch)); - } - return Array(Object::fromValueStorage(Value(runtime, result).storage_)); -} - -void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { - setProperty(runtime, name, Value(runtime, value)); -} - -} // namespace jsi -} // namespace facebook - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/jsi/hermes/HermesRuntime.h b/NativeScript/jsi/hermes/HermesRuntime.h new file mode 100644 index 000000000..a01a29ea2 --- /dev/null +++ b/NativeScript/jsi/hermes/HermesRuntime.h @@ -0,0 +1,1522 @@ +#ifndef NS_JSI_HERMES_HERMES_RUNTIME_H +#define NS_JSI_HERMES_HERMES_RUNTIME_H + +// nativescript::engine for Hermes. +// +// nativescript::engine was modelled on facebook::jsi, and Hermes speaks the +// real thing, so this file is deliberately much smaller than +// jsi/v8/V8Runtime.h. It is not, however, a set of `using` declarations, and +// the reason is one property jsi does not have: +// +// jsi::Value and jsi::Object are MOVE-ONLY. Copying one needs the Runtime +// (Runtime::cloneObject and friends), because a jsi handle is an owned +// PointerValue rather than a refcounted cell. +// +// nativescript::engine's Value is copyable -- V8's layer implements it as a +// shared_ptr -- and the shim leans on that everywhere: it stores +// Values in std::vector, assigns them into napi_ref__ and CallbackInfo, and +// converts Object -> Value implicitly. Aliasing the jsi types directly would +// require rewriting those ~40 call sites in shared code, which is exactly the +// code the V8 path also compiles. +// +// So the shape here is the same one V8Runtime.h uses: a refcounted storage cell +// holding the engine's own handle, with the engine::* types as thin copyable +// front ends over it. Everything that touches JS forwards straight to jsi. +// +// Two other differences are worth naming, because they are the only real logic +// in this file: +// +// 1. Exceptions. jsi throws facebook::jsi::JSError; the shim catches +// nativescript::engine::JSError. Neither can catch the other, so every +// operation that can throw is wrapped and translated (see `guard`), and +// the host-function/host-object adapters translate back on the way out. +// +// 2. HostObject::receiver(). jsi's HostObject::get/set are handed no +// receiver, so the adapter keeps a jsi::WeakObject to the wrapper it was +// installed on and locks it for the duration of each dispatch. It must be +// weak: an owned handle from a host object to its own JS wrapper is a +// strong self-cycle, the weak finaliser never runs, and ObjectManager +// never calls makeInstanceWeak. +// +// Borrowed values: jsi hands host functions `const jsi::Value*` that is valid +// for the duration of the call. A borrowed engine::Value points straight at it +// and allocates nothing, mirroring V8's Kind::V8Borrowed. Value(Runtime&, +// const Value&) promotes a borrowed value to an owned one, which is what makes +// napi_create_reference and friends safe on a callback argument. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class ArrayBuffer; +class PropNameID; +class HostObject; + +using StringBuffer = ::facebook::jsi::StringBuffer; +using MutableBuffer = ::facebook::jsi::MutableBuffer; + +// --------------------------------------------------------------------------- +// Errors +// --------------------------------------------------------------------------- + +// Mirrors jsi/v8/V8Runtime.h's JSError exactly, including the optional carried +// value -- ShimTypes.h's exceptionFrom() reads value() and expects a pointer +// that may be null. +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} + + // Defined after Value. + JSError(Runtime& runtime, const std::string& message, const Value& value, + std::string stack); + + const Value* value() const { return value_.get(); } + const std::string& stack() const { return stack_; } + + private: + std::shared_ptr value_; + std::string stack_; +}; + +namespace hermesengine { + +namespace jsi = ::facebook::jsi; + +// One owned jsi handle, shared by every engine::Value/Object/String that refers +// to it. The typed views are materialised at most once: jsi's getObject() and +// getString() clone, and the shim asks for them repeatedly on the same value +// (asObject(env, x).getProperty(...) runs on every property read). +struct ValueStorage { + jsi::Value value; + + explicit ValueStorage(jsi::Value v) : value(std::move(v)) {} + + jsi::Object& object(jsi::Runtime& rt) { + if (!object_.has_value()) { + object_.emplace(value.getObject(rt)); + } + return *object_; + } + + jsi::String& string(jsi::Runtime& rt) { + if (!string_.has_value()) { + string_.emplace(value.getString(rt)); + } + return *string_; + } + + private: + std::optional object_; + std::optional string_; +}; + +using StoragePtr = std::shared_ptr; + +inline StoragePtr makeStorage(jsi::Value value) { + return std::make_shared(std::move(value)); +} + +// A distinct address per host-object type, so isHostObject can answer +// without RTTI on the engine::HostObject hierarchy (the adapter below is the +// only jsi::HostObject there is, so dynamic_cast cannot distinguish them). +template +inline const void* hostObjectTypeToken() { + static const char token = 0; + return &token; +} + +} // namespace hermesengine + +// --------------------------------------------------------------------------- +// Runtime +// --------------------------------------------------------------------------- + +class Runtime { + public: + explicit Runtime(std::unique_ptr<::facebook::jsi::Runtime> runtime) + : owned_(std::move(runtime)), runtime_(owned_.get()) {} + + // Non-owning. Hermes is entered from Java threads (JNI callbacks, the worker + // pool, the concurrency specs' five background threads) and its VM captures + // per-thread stack bounds, so the runtime must be wrapped in a + // jsi::ThreadSafeRuntime whose lock()/unlock() register the calling thread. + // The wrapper lives in the JSR holder and does the locking at the js_lock_env + // boundary; the work itself runs against getUnsafeRuntime(), which is exactly + // what napi/hermes/jsr.cpp does. Decorating every call instead would lock + // per jsi operation and make the benchmark measure the decorator. + explicit Runtime(::facebook::jsi::Runtime& runtime) : runtime_(&runtime) {} + + ::facebook::jsi::Runtime& jsi() const { return *runtime_; } + + // A stable, per-runtime identity. + // + // engine::Runtime is a value wrapper around shared engine state, and the + // host-function trampolines construct a fresh one on the stack for every + // callback. So `&runtime` is NOT stable and must never be used as a map key; + // this is. The pointer is opaque and only ever compared or hashed. + const void* identity() const { return runtime_; } + + Object global(); + + Value evaluateJavaScript(std::shared_ptr buffer, + const std::string& sourceURL); + + void drainMicrotasks() { runtime_->drainMicrotasks(); } + + private: + std::unique_ptr<::facebook::jsi::Runtime> owned_; + ::facebook::jsi::Runtime* runtime_ = nullptr; +}; + +namespace hermesengine { + +// Translates a jsi exception into the engine's. Every entry point that can +// reach the VM goes through this; the shim's NS_LEAVE only knows +// engine::JSError, and a jsi::JSError escaping it would be reported as +// napi_generic_failure with the JS exception discarded. +// +// Defined after Value/Runtime are complete; see the bottom of this file. +template +auto guard(Runtime& runtime, F&& body) -> decltype(body()); + +} // namespace hermesengine + +// --------------------------------------------------------------------------- +// PropNameID +// --------------------------------------------------------------------------- + +// Borrows the engine's own name handle when the engine supplied one (a host +// object interception), and converts to UTF-8 only if someone asks. Names built +// by getPropertyNames carry a string instead and have no handle. +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) + : value_(std::move(value)), hasUtf8_(true) {} + + PropNameID(::facebook::jsi::Runtime& runtime, + const ::facebook::jsi::PropNameID& name) + : runtime_(&runtime), name_(&name) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? std::string(value) : std::string()); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { + return PropNameID(value); + } + + std::string utf8(Runtime&) const { return utf8(); } + + std::string utf8() const { + if (!hasUtf8_) { + if (runtime_ != nullptr && name_ != nullptr) { + value_ = name_->utf8(*runtime_); + } + hasUtf8_ = true; + } + return value_; + } + + private: + ::facebook::jsi::Runtime* runtime_ = nullptr; + const ::facebook::jsi::PropNameID* name_ = nullptr; + mutable std::string value_; + mutable bool hasUtf8_ = false; +}; + +// --------------------------------------------------------------------------- +// HostObject +// --------------------------------------------------------------------------- + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); + + // Indexed access, taking the index as an integer rather than as the decimal + // string the named form would hand over. See V8Runtime.h for the full note. + // jsi has no indexed hook and no way to read a PropNameID without building a + // std::string, so nothing here calls these -- an index keeps arriving through + // get/set as a name. They exist so a HostObject that overrides them still + // compiles and behaves identically against Hermes. + virtual Value getValueAtIndex(Runtime& runtime, uint32_t index); + virtual bool setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value); + + bool hasIndexedAccess() const { return indexedAccess_; } + void setIndexedAccess(bool value) { indexedAccess_ = value; } + + // The JS object standing for this host object, valid ONLY for the duration of + // the call currently being dispatched. Non-owning on purpose: an owned handle + // to its own wrapper is a strong self-cycle that stops the finalizer running. + const Value* receiver() const { return receiver_; } + + class ReceiverScope { + public: + ReceiverScope(HostObject& host, const Value& receiver) : host_(host) { + host_.receiver_ = &receiver; + } + ~ReceiverScope() { host_.receiver_ = nullptr; } + ReceiverScope(const ReceiverScope&) = delete; + ReceiverScope& operator=(const ReceiverScope&) = delete; + + private: + HostObject& host_; + }; + + private: + const Value* receiver_ = nullptr; + bool indexedAccess_ = false; +}; + +using HostFunctionType = + std::function; + +// --------------------------------------------------------------------------- +// Value +// --------------------------------------------------------------------------- + +class Value { + public: + enum class Kind : uint8_t { Undefined, Null, Bool, Number, Owned, Borrowed }; + + Value() = default; + Value(bool value) : kind_(Kind::Bool), bool_(value) {} + Value(double value) : kind_(Kind::Number), number_(value) {} + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + // Value("foo") must not silently become a bool. + template + Value(const char*) { + static_assert(!std::is_same::value, + "Value cannot be constructed from const char*"); + } + + static Value undefined() { return Value(); } + static Value null() { + Value v; + v.kind_ = Kind::Null; + return v; + } + + // Explicit copy. Promotes a borrowed value to an owned one: a borrowed handle + // is only valid for the dispatch that produced it, and this is the + // constructor every call site that outlives the call already uses + // (napi_create_reference, napi_throw, CallbackInfo storage). + Value(Runtime& runtime, const Value& other) { + if (other.kind_ == Kind::Borrowed) { + kind_ = Kind::Owned; + storage_ = hermesengine::makeStorage( + ::facebook::jsi::Value(runtime.jsi(), *other.borrowed_)); + return; + } + kind_ = other.kind_; + bool_ = other.bool_; + number_ = other.number_; + storage_ = other.storage_; + borrowed_ = other.borrowed_; + } + + Value(Runtime&, const Object& object); + Value(Runtime&, const String& string); + + // Implicit copies share storage; they do NOT promote. Same contract as the + // V8 layer. + Value(const Value&) = default; + Value& operator=(const Value&) = default; + Value(Value&&) noexcept = default; + Value& operator=(Value&&) noexcept = default; + + // Points at a jsi::Value the caller owns and outlives this. Allocates + // nothing. Used for host-function arguments and host-object receivers. + static Value borrowed(const ::facebook::jsi::Value& value) { + Value result; + result.kind_ = Kind::Borrowed; + result.borrowed_ = &value; + return result; + } + + static Value fromStorage(hermesengine::StoragePtr storage) { + Value result; + if (storage == nullptr) return result; // undefined + result.kind_ = Kind::Owned; + result.storage_ = std::move(storage); + return result; + } + + bool isUndefined() const { + return kind_ == Kind::Undefined || + (isPointer() && jsiRef().isUndefined()); + } + bool isNull() const { + return kind_ == Kind::Null || (isPointer() && jsiRef().isNull()); + } + bool isBool() const { + return kind_ == Kind::Bool || (isPointer() && jsiRef().isBool()); + } + bool isNumber() const { + return kind_ == Kind::Number || (isPointer() && jsiRef().isNumber()); + } + bool isString() const { return isPointer() && jsiRef().isString(); } + bool isSymbol() const { return isPointer() && jsiRef().isSymbol(); } + bool isBigInt() const { return isPointer() && jsiRef().isBigInt(); } + bool isObject() const { return isPointer() && jsiRef().isObject(); } + + bool getBool() const { + return kind_ == Kind::Bool ? bool_ : jsiRef().getBool(); + } + double getNumber() const { + return kind_ == Kind::Number ? number_ : jsiRef().getNumber(); + } + + Object asObject(Runtime& runtime) const; + // Borrowing is a V8-only capability; see jsi/v8/V8Runtime.h. Everywhere else + // this is the owning conversion, and callers get the stronger guarantee. + // Declared, not defined: Object is still incomplete here, so a body calling + // asObject would not compile. Defined next to asObject further down. + Object asObjectBorrowed(Runtime& runtime) const; + String asString(Runtime& runtime) const; + + // Portable spellings of "give me the UTF-8" and "make me a string value". + // V8 and QuickJS implement these without materialising an owning String; + // Hermes values are jsi handles all the way down and String is a view onto + // the same storage, so here they are the old two-step. Declared on every + // engine so the shared bridge can call one name. + std::string utf8(Runtime& runtime) const; + static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length); + + // The jsi handle behind this value, materialising one for the inline scalar + // kinds. Returned by value because the scalar kinds have no handle to refer + // to. + ::facebook::jsi::Value toJsi(Runtime& runtime) const { + switch (kind_) { + case Kind::Undefined: + return ::facebook::jsi::Value::undefined(); + case Kind::Null: + return ::facebook::jsi::Value::null(); + case Kind::Bool: + return ::facebook::jsi::Value(bool_); + case Kind::Number: + return ::facebook::jsi::Value(number_); + case Kind::Borrowed: + return ::facebook::jsi::Value(runtime.jsi(), *borrowed_); + case Kind::Owned: + return storage_ != nullptr + ? ::facebook::jsi::Value(runtime.jsi(), storage_->value) + : ::facebook::jsi::Value::undefined(); + } + return ::facebook::jsi::Value::undefined(); + } + + bool isPointer() const { + return kind_ == Kind::Owned || kind_ == Kind::Borrowed; + } + + // Only valid when isPointer(). + const ::facebook::jsi::Value& jsiRef() const { + return kind_ == Kind::Borrowed ? *borrowed_ : storage_->value; + } + + Kind kind() const { return kind_; } + + private: + friend class Object; + friend class String; + + // Storage for a pointer-kind value, materialising one for a borrowed value. + // Sharing rather than cloning is what makes asObject cheap on repeat. + hermesengine::StoragePtr sharedStorage(Runtime& runtime) const; + + Kind kind_ = Kind::Undefined; + bool bool_ = false; + double number_ = 0; + const ::facebook::jsi::Value* borrowed_ = nullptr; + // Mutable because sharedStorage() memoises the promotion of a borrowed value + // (see its definition). Never read except through sharedStorage(): jsiRef() + // answers a borrowed value from borrowed_ whether or not the cache is warm, + // so the memo changes no observable behaviour. + mutable hermesengine::StoragePtr storage_; +}; + +// --------------------------------------------------------------------------- +// String +// --------------------------------------------------------------------------- + +class String { + public: + String() = default; + + static String createFromUtf8(Runtime& runtime, const char* value); + static String createFromUtf8(Runtime& runtime, const std::string& value); + static String createFromUtf8(Runtime& runtime, const uint8_t* value, + size_t length); + + std::string utf8(Runtime& runtime) const; + + operator Value() const { return Value::fromStorage(storage_); } + + static String fromStorage(hermesengine::StoragePtr storage) { + String result; + result.storage_ = std::move(storage); + return result; + } + + private: + friend class Value; + hermesengine::StoragePtr storage_; +}; + +// --------------------------------------------------------------------------- +// Object +// --------------------------------------------------------------------------- + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime); + + static Object fromStorage(hermesengine::StoragePtr storage) { + Object result; + result.storage_ = std::move(storage); + return result; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto base = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken( + runtime, std::move(base), hermesengine::hostObjectTypeToken()); + } + + // A native (Java/ObjC-backed) instance, as opposed to an opaque host object. + // + // Only V8 distinguishes a masking from a non-masking named interceptor, and + // there the difference is large: a native instance carries the class + // prototype where the field accessors live, so a masking interceptor would + // divert every named read into the trap instead of letting V8 resolve it (and + // form a load IC) on the prototype. This backend has no such distinction, so + // a native instance is built exactly like any other host object; the separate + // name exists so callers can express the intent once, for every engine. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + return createFromHostObject(runtime, std::move(host)); + } + + Value getProperty(Runtime& runtime, const char* name) const; + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + Value getProperty(Runtime& runtime, const Value& key) const; + + // Borrowing is a V8-only capability; see jsi/v8/V8Runtime.h. + // + // The name exists on every engine so the Node-API shim's read paths stay + // engine-neutral, but this engine cannot honour it: a read here hands back a + // handle the caller must keep alive (QuickJS returns a +1 refcount, JSC needs + // JSValueProtect, Hermes owns its jsi::Value), and a bare handle rooted only + // by an ambient scope has no equivalent. So these are the owning reads, and + // callers get the stronger guarantee. + Value getPropertyBorrowed(Runtime& runtime, const char* name) const { + return getProperty(runtime, name); + } + Value getPropertyBorrowed(Runtime& runtime, const Value& key) const { + return getProperty(runtime, key); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const; + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value); + void setProperty(Runtime& runtime, const char* name, const Object& value); + void setProperty(Runtime& runtime, const char* name, const String& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, + const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value); + + bool hasProperty(Runtime& runtime, const char* name) const; + + bool isFunction(Runtime& runtime) const; + bool isArray(Runtime& runtime) const; + bool isArrayBuffer(Runtime& runtime) const; + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + return hostObjectOf(runtime, hermesengine::hostObjectTypeToken()) != + nullptr; + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + std::shared_ptr host = + hostObjectOf(runtime, hermesengine::hostObjectTypeToken()); + if (host == nullptr) return nullptr; + return std::static_pointer_cast(std::move(host)); + } + + // ---- Native state ------------------------------------------------------- + // + // This is the one engine where the facility is native rather than emulated: + // jsi::Object::setNativeState is an internal slot on the object, not a + // property, so reading it back does no name lookup at all. See + // jsi/v8/V8Runtime.h for why the Node-API shim needs it -- napi_unwrap runs + // on every marshalled field access and used to cost two prototype-chain + // walks for `__nsWrap`, which on Hermes could not be avoided the way V8's + // non-masking host-object template avoids it (jsi's HostObject intercepts + // every property with no fallback). + template + void setNativeState(Runtime& runtime, std::shared_ptr state) { + setNativeStateWithToken(runtime, + std::static_pointer_cast(std::move(state)), + hermesengine::hostObjectTypeToken()); + } + + template + std::shared_ptr getNativeState(Runtime& runtime) const { + std::shared_ptr host = + nativeStateOf(runtime, hermesengine::hostObjectTypeToken()); + if (host == nullptr) return nullptr; + return std::static_pointer_cast(std::move(host)); + } + + operator Value() const { return Value::fromStorage(storage_); } + + ::facebook::jsi::Object& jsiObject(Runtime& runtime) const; + + const hermesengine::StoragePtr& storage() const { return storage_; } + + protected: + friend class Value; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + static Object createFromHostObjectWithToken(Runtime& runtime, + std::shared_ptr host, + const void* typeToken); + + std::shared_ptr hostObjectOf(Runtime& runtime, + const void* typeToken) const; + + void setNativeStateWithToken(Runtime& runtime, + std::shared_ptr host, + const void* typeToken); + std::shared_ptr nativeStateOf(Runtime& runtime, + const void* typeToken) const; + + hermesengine::StoragePtr storage_; +}; + +// --------------------------------------------------------------------------- +// Function +// --------------------------------------------------------------------------- + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object)) {} + + static Function createFromHostFunction(Runtime& runtime, + const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback); + + // Like createFromHostFunction, but the result is usable with `new` and its + // `prototype` is a writable own property. See the definition for what Hermes + // requires here and why. + static Function createFromHostConstructor(Runtime& runtime, + const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args = nullptr, + size_t count = 0) const; + Value callWithThis(Runtime& runtime, const Object& thisObject, + const Value* args = nullptr, size_t count = 0) const; + Value callAsConstructor(Runtime& runtime, const Value* args = nullptr, + size_t count = 0) const; + + operator Value() const { return Value::fromStorage(storage_); } +}; + +// --------------------------------------------------------------------------- +// Array / ArrayBuffer +// --------------------------------------------------------------------------- + +class Array : public Object { + public: + Array() = default; + Array(Runtime& runtime, size_t size); + explicit Array(Object object) : Object(std::move(object)) {} + + size_t size(Runtime& runtime) const; + Value getValueAtIndex(Runtime& runtime, size_t index) const; + + // See Object::getPropertyBorrowed. + Value getValueAtIndexBorrowed(Runtime& runtime, size_t index) const { + return getValueAtIndex(runtime, index); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value); + + operator Value() const { return Value::fromStorage(storage_); } +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer() = default; + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer); + explicit ArrayBuffer(Object object) : Object(std::move(object)) {} + + uint8_t* data(Runtime& runtime) const; + size_t size(Runtime& runtime) const; + + operator Value() const { return Value::fromStorage(storage_); } +}; + +// --------------------------------------------------------------------------- +// WeakObject +// --------------------------------------------------------------------------- + +class WeakObject { + public: + WeakObject() = default; + WeakObject(Runtime& runtime, const Value& value); + + Value lock(Runtime& runtime) const; + bool empty() const { return weak_ == nullptr && strong_ == nullptr; } + void reset() { + weak_.reset(); + strong_.reset(); + } + + private: + std::shared_ptr<::facebook::jsi::WeakObject> weak_; + // Non-object values have no weak form; keeping them strong matches what the + // shim asks for (a reference that still reads back) and never pins a wrapper, + // because only objects have finalizers. + hermesengine::StoragePtr strong_; +}; + +// =========================================================================== +// Definitions +// =========================================================================== + +namespace hermesengine { + +// The jsi::HostObject actually installed on the JS object. Owns the +// engine::HostObject, carries its type token, and keeps a weak handle to the +// wrapper so receiver() can be answered per dispatch. +class HostObjectAdapter final : public jsi::HostObject { + public: + HostObjectAdapter(Runtime* runtime, std::shared_ptr host, + const void* typeToken) + : runtime_(runtime), host_(std::move(host)), typeToken_(typeToken) {} + + const void* typeToken() const { return typeToken_; } + const std::shared_ptr& host() const { return host_; } + + void attach(jsi::Runtime& rt, const jsi::Object& self) { + weakSelf_.emplace(rt, self); + } + + jsi::Value get(jsi::Runtime& rt, const jsi::PropNameID& name) override; + void set(jsi::Runtime& rt, const jsi::PropNameID& name, + const jsi::Value& value) override; + std::vector getPropertyNames(jsi::Runtime& rt) override; + + private: + // Locks the wrapper for one dispatch. Empty (undefined) if it has already + // been collected, which cannot normally happen while a property access on it + // is in flight. + jsi::Value self(jsi::Runtime& rt) const { + if (!weakSelf_.has_value()) return jsi::Value::undefined(); + return weakSelf_->lock(rt); + } + + Runtime* runtime_; + std::shared_ptr host_; + const void* typeToken_; + std::optional weakSelf_; +}; + +// Translates an engine::JSError back into a jsi::JSError, preserving the thrown +// value when there is one. Without this, a napi_callback that failed would +// surface in JS as a plain std::exception -> "Error: ", losing the +// error's type and everything the runtime attached to it. +[[noreturn]] inline void rethrowAsJsi(Runtime& runtime, + const engine::JSError& error) { + if (const engine::Value* thrown = error.value()) { + throw jsi::JSError(runtime.jsi(), thrown->toJsi(runtime)); + } + throw jsi::JSError(runtime.jsi(), std::string(error.what())); +} + +template +auto guard(Runtime& runtime, F&& body) -> decltype(body()) { + try { + return body(); + } catch (const jsi::JSError& error) { + // The thrown value is carried through, exactly as the V8 layer does, so + // ShimTypes.h's exceptionFrom() can hand JS the original object rather than + // a rebuilt one. + engine::Value value = engine::Value::fromStorage( + makeStorage(jsi::Value(runtime.jsi(), error.value()))); + throw engine::JSError(runtime, error.getMessage(), value, error.getStack()); + } catch (const jsi::JSIException& error) { + throw engine::JSError(runtime, std::string(error.what())); + } +} + +} // namespace hermesengine + +// --- JSError --------------------------------------------------------------- + +inline JSError::JSError(Runtime&, const std::string& message, + const Value& value, std::string stack) + : std::runtime_error(message), + value_(std::make_shared(value)), + stack_(std::move(stack)) {} + +// --- HostObject defaults --------------------------------------------------- + +inline Value HostObject::get(Runtime&, const PropNameID&) { + return Value::undefined(); +} +inline bool HostObject::set(Runtime&, const PropNameID&, const Value&) { + return false; +} +inline std::vector HostObject::getPropertyNames(Runtime&) { + return {}; +} + +// The defaults reproduce what an index does today on every engine: stringify +// it and take the named path. Nothing in the Hermes layer calls them. +inline Value HostObject::getValueAtIndex(Runtime& runtime, uint32_t index) { + return get(runtime, PropNameID(std::to_string(index))); +} +inline bool HostObject::setValueAtIndex(Runtime& runtime, uint32_t index, + const Value& value) { + return set(runtime, PropNameID(std::to_string(index)), value); +} + +// --- Value ----------------------------------------------------------------- + +inline Value::Value(Runtime&, const Object& object) + : kind_(Kind::Owned), storage_(object.storage_) {} + +inline Value::Value(Runtime&, const String& string) + : kind_(Kind::Owned), storage_(string.storage_) {} + +inline hermesengine::StoragePtr Value::sharedStorage(Runtime& runtime) const { + if (kind_ == Kind::Owned) return storage_; + if (kind_ == Kind::Borrowed) { + // Memoised, because jsi has no way to borrow an Object out of a Value: + // jsi::Value::getObject clones the PointerValue, so every promotion is a + // make_shared plus a Hermes handle clone, and its destruction a matching + // release. The Node-API shim asks the *same* arena slot who it is three + // times per marshalled field read -- napi_get_host_object_data, the + // napi_get_value_external it falls through to, and napi_unwrap -- and a + // simpleperf profile of `javaObject.intField` in a loop attributes ~85% of + // napi_get_host_object_data to that materialise/destroy churn and only + // ~9% to the host-object token check it exists to perform. + // + // Caching is safe rather than merely faster: a borrowed value aliases a + // jsi::Value the caller owns for the duration of the dispatch, so the + // promoted handle can never outlive what it was cloned from, and every + // other reader (jsiRef, toJsi, the scalar predicates) answers from + // borrowed_ regardless. It also warms ValueStorage's cached jsi::Object, + // so the three lookups now share one materialised object as well. + if (storage_ == nullptr) { + storage_ = hermesengine::makeStorage( + ::facebook::jsi::Value(runtime.jsi(), *borrowed_)); + } + return storage_; + } + return hermesengine::makeStorage(toJsi(runtime)); +} + +inline Object Value::asObject(Runtime& runtime) const { + return Object::fromStorage(sharedStorage(runtime)); +} + +inline Object Value::asObjectBorrowed(Runtime& runtime) const { + return asObject(runtime); +} + +inline String Value::asString(Runtime& runtime) const { + return String::fromStorage(sharedStorage(runtime)); +} + +// --- Runtime --------------------------------------------------------------- + +inline Object Runtime::global() { + return Object::fromStorage( + hermesengine::makeStorage(::facebook::jsi::Value(runtime_->global()))); +} + +inline Value Runtime::evaluateJavaScript(std::shared_ptr buffer, + const std::string& sourceURL) { + try { + return Value::fromStorage(hermesengine::makeStorage( + runtime_->evaluateJavaScript(std::move(buffer), sourceURL))); + } catch (const ::facebook::jsi::JSError& error) { + // A real JS throw from the evaluated script: keep the thrown value. + ::nativescript::engine::Value value = Value::fromStorage( + hermesengine::makeStorage(::facebook::jsi::Value(*runtime_, + error.value()))); + throw JSError(*this, error.getMessage(), value, error.getStack()); + } catch (const ::facebook::jsi::JSIException& error) { + // Not a JSError: Hermes reports a *compile* failure as a JSINativeException + // reading "Compiling JS failed: 3:1:invalid expression". Every other engine + // raises a real SyntaxError, and the module specs assert the type + // ("main started SyntaxError main ended"), so name it -- ShimTypes.h's + // errorFromMessage then rebuilds the right constructor from the prefix. + // Only evaluate can fail this way; a runtime error thrown by the script is + // a JSError and keeps its value above. + throw JSError(*this, "SyntaxError: " + std::string(error.what())); + } +} + +// --- String ---------------------------------------------------------------- + +inline String String::createFromUtf8(Runtime& runtime, const char* value) { + const char* text = value != nullptr ? value : ""; + return createFromUtf8(runtime, reinterpret_cast(text), + std::strlen(text)); +} + +inline String String::createFromUtf8(Runtime& runtime, + const std::string& value) { + return createFromUtf8(runtime, + reinterpret_cast(value.data()), + value.size()); +} + +inline String String::createFromUtf8(Runtime& runtime, const uint8_t* value, + size_t length) { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + return String::fromStorage(hermesengine::makeStorage( + ::facebook::jsi::Value(::facebook::jsi::String::createFromUtf8( + rt, + value != nullptr ? value : reinterpret_cast(""), + length)))); + }); +} + +inline std::string Value::utf8(Runtime& runtime) const { + return asString(runtime).utf8(runtime); +} + +inline Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t length) { + return Value(runtime, + String::createFromUtf8(runtime, reinterpret_cast(data), length)); +} + +inline std::string String::utf8(Runtime& runtime) const { + if (storage_ == nullptr) return {}; + return hermesengine::guard(runtime, [&] { + return storage_->string(runtime.jsi()).utf8(runtime.jsi()); + }); +} + +// --- Object ---------------------------------------------------------------- + +inline Object::Object(Runtime& runtime) + : storage_(hermesengine::makeStorage( + ::facebook::jsi::Value(::facebook::jsi::Object(runtime.jsi())))) {} + +inline ::facebook::jsi::Object& Object::jsiObject(Runtime& runtime) const { + return storage_->object(runtime.jsi()); +} + +inline Value Object::getProperty(Runtime& runtime, const char* name) const { + return hermesengine::guard(runtime, [&] { + return Value::fromStorage(hermesengine::makeStorage( + jsiObject(runtime).getProperty(runtime.jsi(), name))); + }); +} + +inline Value Object::getProperty(Runtime& runtime, const Value& key) const { + return hermesengine::guard(runtime, [&] { + if (key.isPointer()) { + return Value::fromStorage(hermesengine::makeStorage( + jsiObject(runtime).getProperty(runtime.jsi(), key.jsiRef()))); + } + const ::facebook::jsi::Value materialised = key.toJsi(runtime); + return Value::fromStorage(hermesengine::makeStorage( + jsiObject(runtime).getProperty(runtime.jsi(), materialised))); + }); +} + +inline Object Object::getPropertyAsObject(Runtime& runtime, + const char* name) const { + return getProperty(runtime, name).asObject(runtime); +} + +inline Function Object::getPropertyAsFunction(Runtime& runtime, + const char* name) const { + return Function(getPropertyAsObject(runtime, name)); +} + +inline void Object::setProperty(Runtime& runtime, const char* name, + const Value& value) { + hermesengine::guard(runtime, [&]() -> int { + jsiObject(runtime).setProperty(runtime.jsi(), name, value.toJsi(runtime)); + return 0; + }); +} + +inline void Object::setProperty(Runtime& runtime, const char* name, + const Object& value) { + setProperty(runtime, name, Value::fromStorage(value.storage_)); +} + +inline void Object::setProperty(Runtime& runtime, const char* name, + const String& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +inline void Object::setProperty(Runtime& runtime, const Value& key, + const Value& value) { + hermesengine::guard(runtime, [&]() -> int { + const ::facebook::jsi::Value materialisedKey = + key.isPointer() ? ::facebook::jsi::Value(runtime.jsi(), key.jsiRef()) + : key.toJsi(runtime); + jsiObject(runtime).setProperty(runtime.jsi(), materialisedKey, + value.toJsi(runtime)); + return 0; + }); +} + +inline bool Object::hasProperty(Runtime& runtime, const char* name) const { + return hermesengine::guard(runtime, [&] { + return jsiObject(runtime).hasProperty(runtime.jsi(), name); + }); +} + +inline bool Object::isFunction(Runtime& runtime) const { + return jsiObject(runtime).isFunction(runtime.jsi()); +} + +inline bool Object::isArray(Runtime& runtime) const { + return jsiObject(runtime).isArray(runtime.jsi()); +} + +inline bool Object::isArrayBuffer(Runtime& runtime) const { + return jsiObject(runtime).isArrayBuffer(runtime.jsi()); +} + +inline Function Object::asFunction(Runtime& runtime) const { + return Function(*this); +} + +inline Array Object::getArray(Runtime& runtime) const { return Array(*this); } + +inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const { + return ArrayBuffer(*this); +} + +inline Array Object::getPropertyNames(Runtime& runtime) const { + return hermesengine::guard(runtime, [&] { + return Array(Object::fromStorage(hermesengine::makeStorage( + ::facebook::jsi::Value( + jsiObject(runtime).getPropertyNames(runtime.jsi()))))); + }); +} + +inline Object Object::createFromHostObjectWithToken( + Runtime& runtime, std::shared_ptr host, const void* typeToken) { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + auto adapter = std::make_shared( + &runtime, std::move(host), typeToken); + ::facebook::jsi::Object created = + ::facebook::jsi::Object::createFromHostObject(rt, adapter); + // Weak, and set after creation: the adapter needs a handle on the wrapper + // to answer receiver(), and a strong one would be a self-cycle. + adapter->attach(rt, created); + return Object::fromStorage( + hermesengine::makeStorage(::facebook::jsi::Value(std::move(created)))); + }); +} + +inline std::shared_ptr Object::hostObjectOf( + Runtime& runtime, const void* typeToken) const { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + ::facebook::jsi::Object& object = jsiObject(runtime); + if (!object.isHostObject(rt)) { + return nullptr; + } + std::shared_ptr adapter = + object.getHostObject(rt); + if (adapter == nullptr || adapter->typeToken() != typeToken) return nullptr; + return adapter->host(); +} + +namespace hermesengine { + +// The payload jsi native state actually holds. jsi has no type tag of its own, +// so the token rides along and isHostObject-style identification stays a +// pointer compare rather than a dynamic_cast on a per-field-read path. +struct NativeStateBox : ::facebook::jsi::NativeState { + NativeStateBox(std::shared_ptr host, const void* typeToken) + : host(std::move(host)), typeToken(typeToken) {} + std::shared_ptr host; + const void* typeToken = nullptr; +}; + +} // namespace hermesengine + +inline void Object::setNativeStateWithToken(Runtime& runtime, + std::shared_ptr host, + const void* typeToken) { + hermesengine::guard(runtime, [&]() -> int { + jsiObject(runtime).setNativeState( + runtime.jsi(), std::make_shared( + std::move(host), typeToken)); + return 0; + }); +} + +inline std::shared_ptr Object::nativeStateOf( + Runtime& runtime, const void* typeToken) const { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + ::facebook::jsi::Object& object = jsiObject(runtime); + if (!object.hasNativeState(rt)) return nullptr; + // static, not dynamic: this shim is the only thing in the build that sets + // jsi native state, and the token below is the identity check. + auto* box = static_cast( + object.getNativeState(rt).get()); + if (box == nullptr || box->typeToken != typeToken) return nullptr; + return box->host; +} + +// --- Function -------------------------------------------------------------- + +namespace hermesengine { + +// Host-function arguments are borrowed, never cloned: jsi guarantees the array +// is valid for the duration of the call, which is exactly Node-API's rule for +// callback arguments. This is the marshalling hot path -- one clone per +// argument per call would be the single largest cost in the shim. +struct BorrowedArgs { + static constexpr size_t kInline = 8; + + BorrowedArgs(const jsi::Value* args, size_t count) : count_(count) { + engine::Value* target = inline_; + if (count > kInline) { + heap_.resize(count); + target = heap_.data(); + } + for (size_t i = 0; i < count; i++) { + target[i] = engine::Value::borrowed(args[i]); + } + data_ = target; + } + + const engine::Value* data() const { return data_; } + size_t count() const { return count_; } + + private: + engine::Value inline_[kInline]; + std::vector heap_; + const engine::Value* data_ = nullptr; + size_t count_; +}; + +inline jsi::Function makeHostFunction(Runtime& runtime, const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback) { + jsi::Runtime& rt = runtime.jsi(); + Runtime* enginePtr = &runtime; + const std::string text = name.utf8(); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forUtf8(rt, text), paramCount, + [enginePtr, callback = std::move(callback)]( + jsi::Runtime& jsRuntime, const jsi::Value& thisVal, + const jsi::Value* args, size_t count) -> jsi::Value { + (void)jsRuntime; + Runtime& engineRuntime = *enginePtr; + BorrowedArgs borrowed(args, count); + const engine::Value self = engine::Value::borrowed(thisVal); + try { + engine::Value outcome = + callback(engineRuntime, self, borrowed.data(), borrowed.count()); + return outcome.toJsi(engineRuntime); + } catch (const engine::JSError& error) { + rethrowAsJsi(engineRuntime, error); + } + }); +} + +// Builds the object a construct call should run against: Object.create( +// Ctor.prototype), which is what the spec's OrdinaryCreateFromConstructor does +// and what every other engine hands a Node-API class constructor. +// +// Hermes does neither half of that for a native function. `new f()` passes the +// host function `undefined` as `this` and then *requires* it to return an +// object -- "FinalizableNativeFunction constructor must return an object" was +// 74 of the first run's 106 failures, and another 24 were `Worker should be +// called as a constructor!`, which is the Worker constructor finding no `this`. +// +// So the constructor trampoline synthesises the receiver and returns it. The +// function is held weakly: a strong handle from a function's own host data to +// its own prototype (which links back through `constructor`) is a cycle the GC +// cannot break. +inline jsi::Value makeConstructReceiver(jsi::Runtime& rt, + std::optional& weakSelf) { + if (!weakSelf.has_value()) return jsi::Value(jsi::Object(rt)); + jsi::Value self = weakSelf->lock(rt); + if (!self.isObject()) return jsi::Value(jsi::Object(rt)); + jsi::Value prototype = self.getObject(rt).getProperty(rt, "prototype"); + if (!prototype.isObject()) return jsi::Value(jsi::Object(rt)); + return jsi::Value(jsi::Object::create(rt, prototype)); +} + +// A JS function that wraps a host function so the result is constructible. +// +// Whether Hermes lets you `new` a native function is a property of the Hermes +// build, not of jsi: the vendored static-hermes allows it, the Hermes React +// Native ships throws "This function cannot be used as a constructor". Since +// @nativescript/react-native runs on whatever Hermes the app has, the +// constructor cannot be a native function at all. An ordinary JS function is +// constructible everywhere, gets a correct `this` from the language itself, and +// comes with a `prototype` whose `constructor` back-pointer is already right. +// +// Cached on the global because the factory is per-runtime and there is nowhere +// else to hang per-runtime state; the property is non-enumerable so it does not +// show up in application code. +inline jsi::Function hostConstructorFactory(jsi::Runtime& rt) { + static constexpr const char* kFactoryKey = "__nsHostConstructorFactory"; + + jsi::Value cached = rt.global().getProperty(rt, kFactoryKey); + if (cached.isObject() && cached.getObject(rt).isFunction(rt)) { + return cached.getObject(rt).getFunction(rt); + } + + jsi::Value made = rt.evaluateJavaScript( + std::make_shared( + "(function (hostFn, name, length) {" + " function NativeScriptClass() {" + " return hostFn.apply(this, arguments);" + " }" + " Object.defineProperty(NativeScriptClass, 'name'," + " { value: name, configurable: true });" + " Object.defineProperty(NativeScriptClass, 'length'," + " { value: length, configurable: true });" + " return NativeScriptClass;" + "})"), + "nativescript:host-constructor"); + + jsi::Function factory = made.getObject(rt).getFunction(rt); + + jsi::Object global = rt.global(); + jsi::Function defineProperty = global.getPropertyAsObject(rt, "Object") + .getPropertyAsFunction(rt, "defineProperty"); + jsi::Object descriptor(rt); + descriptor.setProperty(rt, "value", jsi::Value(rt, factory)); + descriptor.setProperty(rt, "enumerable", false); + descriptor.setProperty(rt, "configurable", true); + defineProperty.call(rt, jsi::Value(rt, global), + jsi::String::createFromAscii(rt, kFactoryKey), + jsi::Value(rt, descriptor)); + + return factory; +} + +inline jsi::Function makeHostConstructor(Runtime& runtime, + const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback) { + jsi::Runtime& rt = runtime.jsi(); + Runtime* enginePtr = &runtime; + const std::string text = name.utf8(); + auto weakSelf = std::make_shared>(); + + jsi::Function fn = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forUtf8(rt, text), paramCount, + [enginePtr, weakSelf, callback = std::move(callback)]( + jsi::Runtime& jsRuntime, const jsi::Value& thisVal, + const jsi::Value* args, size_t count) -> jsi::Value { + Runtime& engineRuntime = *enginePtr; + const jsi::Value receiver = + thisVal.isObject() ? jsi::Value(jsRuntime, thisVal) + : makeConstructReceiver(jsRuntime, *weakSelf); + BorrowedArgs borrowed(args, count); + const engine::Value self = engine::Value::borrowed(receiver); + try { + engine::Value outcome = + callback(engineRuntime, self, borrowed.data(), borrowed.count()); + jsi::Value result = outcome.toJsi(engineRuntime); + if (result.isObject()) return result; + return jsi::Value(jsRuntime, receiver); + } catch (const engine::JSError& error) { + rethrowAsJsi(engineRuntime, error); + } + }); + + // Wrap it in a real JS function; see hostConstructorFactory above. The + // wrapper brings its own writable `prototype` carrying the `constructor` + // back-pointer the spec requires, which is what stops `instance.constructor` + // walking past the class prototype and reporting every native class as + // "Object". + jsi::Function factory = hostConstructorFactory(rt); + jsi::Value made = factory.call(rt, jsi::Value(rt, fn), + jsi::String::createFromUtf8(rt, text), + jsi::Value(static_cast(paramCount))); + jsi::Function ctor = made.getObject(rt).getFunction(rt); + + // Weak, and pointing at the wrapper rather than the host function: it is the + // wrapper that owns `prototype`, and it is what a direct (non-`new`) call has + // to synthesise a receiver from. Strong would be a cycle through + // prototype.constructor that the GC cannot break. + weakSelf->emplace(rt, ctor); + return ctor; +} + +} // namespace hermesengine + +inline Function Function::createFromHostFunction(Runtime& runtime, + const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback) { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + ::facebook::jsi::Function fn = hermesengine::makeHostFunction( + runtime, name, paramCount, std::move(callback)); + return Function(Object::fromStorage(hermesengine::makeStorage( + ::facebook::jsi::Value(std::move(fn))))); + }); +} + +inline Function Function::createFromHostConstructor(Runtime& runtime, + const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback) { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + ::facebook::jsi::Function fn = hermesengine::makeHostConstructor( + runtime, name, paramCount, std::move(callback)); + return Function(Object::fromStorage(hermesengine::makeStorage( + ::facebook::jsi::Value(std::move(fn))))); + }); +} + +namespace hermesengine { + +// Materialises engine Values into the contiguous jsi::Value array the jsi call +// API wants. jsi::Value is move-only, so this cannot alias the caller's array. +struct JsiArgs { + JsiArgs(Runtime& runtime, const engine::Value* args, size_t count) { + values_.reserve(count); + for (size_t i = 0; i < count; i++) { + values_.push_back(args[i].toJsi(runtime)); + } + } + + const jsi::Value* data() const { return values_.data(); } + size_t count() const { return values_.size(); } + + private: + std::vector values_; +}; + +} // namespace hermesengine + +inline Value Function::call(Runtime& runtime, const Value* args, + size_t count) const { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + hermesengine::JsiArgs converted(runtime, args, count); + ::facebook::jsi::Function fn = jsiObject(runtime).getFunction(rt); + return Value::fromStorage(hermesengine::makeStorage( + fn.call(rt, converted.data(), converted.count()))); + }); +} + +inline Value Function::callWithThis(Runtime& runtime, const Object& thisObject, + const Value* args, size_t count) const { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + hermesengine::JsiArgs converted(runtime, args, count); + ::facebook::jsi::Function fn = jsiObject(runtime).getFunction(rt); + return Value::fromStorage(hermesengine::makeStorage(fn.callWithThis( + rt, thisObject.jsiObject(runtime), converted.data(), + converted.count()))); + }); +} + +inline Value Function::callAsConstructor(Runtime& runtime, const Value* args, + size_t count) const { + return hermesengine::guard(runtime, [&] { + ::facebook::jsi::Runtime& rt = runtime.jsi(); + hermesengine::JsiArgs converted(runtime, args, count); + ::facebook::jsi::Function fn = jsiObject(runtime).getFunction(rt); + return Value::fromStorage(hermesengine::makeStorage( + fn.callAsConstructor(rt, converted.data(), converted.count()))); + }); +} + +// --- Array / ArrayBuffer --------------------------------------------------- + +inline Array::Array(Runtime& runtime, size_t size) + : Object(Object::fromStorage(hermesengine::makeStorage( + ::facebook::jsi::Value(::facebook::jsi::Array(runtime.jsi(), size))))) {} + +inline size_t Array::size(Runtime& runtime) const { + return hermesengine::guard(runtime, [&] { + return jsiObject(runtime).getArray(runtime.jsi()).size(runtime.jsi()); + }); +} + +inline Value Array::getValueAtIndex(Runtime& runtime, size_t index) const { + return hermesengine::guard(runtime, [&] { + return Value::fromStorage(hermesengine::makeStorage( + jsiObject(runtime) + .getArray(runtime.jsi()) + .getValueAtIndex(runtime.jsi(), index))); + }); +} + +inline void Array::setValueAtIndex(Runtime& runtime, size_t index, + const Value& value) { + hermesengine::guard(runtime, [&]() -> int { + jsiObject(runtime) + .getArray(runtime.jsi()) + .setValueAtIndex(runtime.jsi(), index, value.toJsi(runtime)); + return 0; + }); +} + +inline ArrayBuffer::ArrayBuffer(Runtime& runtime, + std::shared_ptr buffer) + : Object(Object::fromStorage(hermesengine::makeStorage( + ::facebook::jsi::Value(::facebook::jsi::ArrayBuffer( + runtime.jsi(), std::move(buffer)))))) {} + +inline uint8_t* ArrayBuffer::data(Runtime& runtime) const { + return hermesengine::guard(runtime, [&] { + return jsiObject(runtime).getArrayBuffer(runtime.jsi()).data(runtime.jsi()); + }); +} + +inline size_t ArrayBuffer::size(Runtime& runtime) const { + return hermesengine::guard(runtime, [&] { + return jsiObject(runtime).getArrayBuffer(runtime.jsi()).size(runtime.jsi()); + }); +} + +// --- WeakObject ------------------------------------------------------------ + +inline WeakObject::WeakObject(Runtime& runtime, const Value& value) { + if (!value.isObject()) { + // Nothing weak to make; keep it alive so the reference still reads back. + strong_ = hermesengine::makeStorage(value.toJsi(runtime)); + return; + } + Object object = value.asObject(runtime); + weak_ = std::make_shared<::facebook::jsi::WeakObject>( + runtime.jsi(), object.jsiObject(runtime)); +} + +inline Value WeakObject::lock(Runtime& runtime) const { + if (strong_ != nullptr) return Value::fromStorage(strong_); + if (weak_ == nullptr) return Value::undefined(); + return Value::fromStorage( + hermesengine::makeStorage(weak_->lock(runtime.jsi()))); +} + +// --- HostObjectAdapter ----------------------------------------------------- + +namespace hermesengine { + +inline jsi::Value HostObjectAdapter::get(jsi::Runtime& rt, + const jsi::PropNameID& name) { + engine::Runtime& runtime = *runtime_; + const jsi::Value receiver = self(rt); + const engine::Value engineReceiver = engine::Value::borrowed(receiver); + engine::PropNameID engineName(rt, name); + engine::HostObject::ReceiverScope scope(*host_, engineReceiver); + try { + return host_->get(runtime, engineName).toJsi(runtime); + } catch (const engine::JSError& error) { + rethrowAsJsi(runtime, error); + } +} + +inline void HostObjectAdapter::set(jsi::Runtime& rt, + const jsi::PropNameID& name, + const jsi::Value& value) { + engine::Runtime& runtime = *runtime_; + const jsi::Value receiver = self(rt); + const engine::Value engineReceiver = engine::Value::borrowed(receiver); + const engine::Value engineValue = engine::Value::borrowed(value); + engine::PropNameID engineName(rt, name); + engine::HostObject::ReceiverScope scope(*host_, engineReceiver); + try { + host_->set(runtime, engineName, engineValue); + } catch (const engine::JSError& error) { + rethrowAsJsi(runtime, error); + } +} + +inline std::vector HostObjectAdapter::getPropertyNames( + jsi::Runtime& rt) { + engine::Runtime& runtime = *runtime_; + const jsi::Value receiver = self(rt); + const engine::Value engineReceiver = engine::Value::borrowed(receiver); + engine::HostObject::ReceiverScope scope(*host_, engineReceiver); + std::vector out; + try { + std::vector names = host_->getPropertyNames(runtime); + out.reserve(names.size()); + for (const engine::PropNameID& name : names) { + out.push_back(jsi::PropNameID::forUtf8(rt, name.utf8())); + } + } catch (const engine::JSError& error) { + rethrowAsJsi(runtime, error); + } + return out; +} + +} // namespace hermesengine + +} // namespace engine +} // namespace nativescript + +#endif // NS_JSI_HERMES_HERMES_RUNTIME_H diff --git a/NativeScript/jsi/hermes/NativeApiJsi.h b/NativeScript/jsi/hermes/NativeApiJsi.h new file mode 100644 index 000000000..b22773e79 --- /dev/null +++ b/NativeScript/jsi/hermes/NativeApiJsi.h @@ -0,0 +1,26 @@ +#ifndef NATIVE_API_JSI_H +#define NATIVE_API_JSI_H + +#include + +#include "jsi/shared/NativeApiBackendConfig.h" + +namespace nativescript { + +using NativeApiJsiScheduler = NativeApiBackendScheduler; +using NativeApiJsiConfig = NativeApiBackendConfig; + +facebook::jsi::Object CreateNativeApiJSI( + facebook::jsi::Runtime& runtime, + const NativeApiJsiConfig& config = NativeApiJsiConfig{}); + +void InstallNativeApiJSI( + facebook::jsi::Runtime& runtime, + const NativeApiJsiConfig& config = NativeApiJsiConfig{}); + +} // namespace nativescript + +extern "C" void NativeScriptInstallNativeApiJSI( + facebook::jsi::Runtime* runtime, const char* metadataPath); + +#endif // NATIVE_API_JSI_H diff --git a/NativeScript/jsi/jsc/JSCHostObjects.cpp b/NativeScript/jsi/jsc/JSCHostObjects.cpp new file mode 100644 index 000000000..6fcc61f09 --- /dev/null +++ b/NativeScript/jsi/jsc/JSCHostObjects.cpp @@ -0,0 +1,502 @@ +#include "jsi/jsc/JSCRuntime.h" +#include "jsi/shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_JSC + +namespace nativescript { +namespace engine { + +namespace jscengine { + +JSClassRef hostClass(Runtime& runtime); +JSClassRef functionClass(Runtime& runtime); +JSClassRef constructorClass(Runtime& runtime); +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); + +// Hand a caught JSError back to JSC as the thrown value it actually was. +// +// setException() below rebuilds an Error from what(), which is right for a +// native failure but wrong for an exception that started life in JS: it drops +// the identity of the object -- NativeScriptException's `nativeException`, a +// SyntaxError's name -- and the stack. JSError now carries the value +// (jscengine::toJSError), so prefer it and keep the rebuild as the fallback for +// errors this layer raised itself. +void setEngineException(Runtime& runtime, JSContextRef context, JSValueRef* exception, + const JSError& error) { + if (exception == nullptr) { + return; + } + if (const Value* thrown = error.value()) { + if (!thrown->isUndefined() && !thrown->isNull()) { + *exception = thrown->local(runtime); + return; + } + } + *exception = makeError(context, error.what()); +} + + +// JSC has no indexed callback: `a[0]` reaches getProperty/setProperty as the +// property name "0". Reading the index straight off the UTF-16 buffer the +// JSStringRef already holds costs a handful of instructions and allocates +// nothing, where stringToUtf8 builds a std::string (sized by +// JSStringGetMaximumUTF8CStringSize, so it over-allocates) that the host object +// then has to parse back into an integer. +// +// Canonicalisation matches what a host object doing this in terms of the name +// would apply: no leading zeros, at most 2^32 - 2. +bool stringAsArrayIndex(JSStringRef name, uint32_t* index) { + size_t length = JSStringGetLength(name); + if (length == 0 || length > 10) { // a uint32 has at most 10 digits + return false; + } + const JSChar* chars = JSStringGetCharactersPtr(name); + if (chars == nullptr) { + return false; + } + if (chars[0] == '0') { // canonical form has no leading zeros; only "0" itself + if (length != 1) { + return false; + } + *index = 0; + return true; + } + uint64_t value = 0; + for (size_t i = 0; i < length; i++) { + if (chars[i] < '0' || chars[i] > '9') { + return false; + } + value = value * 10 + static_cast(chars[i] - '0'); + } + if (value > 4294967294ULL) { // the largest array index is 2^32 - 2 + return false; + } + *index = static_cast(value); + return true; +} + +bool isNativeInstancePrototypeBypassExcluded(JSStringRef propertyName) { + return JSStringIsEqualToUTF8CString(propertyName, "kind") || + JSStringIsEqualToUTF8CString(propertyName, "className") || + JSStringIsEqualToUTF8CString(propertyName, "nativeAddress") || + JSStringIsEqualToUTF8CString(propertyName, "class") || + JSStringIsEqualToUTF8CString(propertyName, "constructor") || + JSStringIsEqualToUTF8CString(propertyName, "super") || + JSStringIsEqualToUTF8CString(propertyName, "invoke") || + JSStringIsEqualToUTF8CString(propertyName, "send") || + JSStringIsEqualToUTF8CString(propertyName, "takeRetainedValue") || + JSStringIsEqualToUTF8CString(propertyName, "takeUnretainedValue") || + JSStringIsEqualToUTF8CString(propertyName, "toString"); +} + +bool shouldDeferToNativeInstancePrototype(JSContextRef context, + JSObjectRef object, + JSStringRef propertyName, + HostObjectHolder* holder) { + if (context == nullptr || object == nullptr || propertyName == nullptr || + holder == nullptr || + !holder->nativeInstance || + isNativeInstancePrototypeBypassExcluded(propertyName)) { + return false; + } + + JSValueRef prototypeValue = JSObjectGetPrototype(context, object); + if (prototypeValue == nullptr || !JSValueIsObject(context, prototypeValue)) { + return false; + } + + JSValueRef exception = nullptr; + JSObjectRef prototypeObject = + JSValueToObject(context, prototypeValue, &exception); + if (exception != nullptr || prototypeObject == nullptr) { + return false; + } + + exception = nullptr; + bool found = JSObjectHasProperty(context, prototypeObject, propertyName); + return exception == nullptr && found; +} + +JSValueRef hostGetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, + JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder == nullptr || holder->hostObject == nullptr) { + return nullptr; + } + Runtime runtime(holder->state); + // Ahead of the prototype deferral: an index is never a named property of a + // native instance, so that lookup can only fail for one. + uint32_t index = 0; + if (holder->hostObject->hasIndexedAccess() && + stringAsArrayIndex(propertyName, &index)) { + try { + const Value receiver = Value::borrowed(runtime, object); + HostObject::ReceiverScope receiverScope(*holder->hostObject, receiver); + Value result = holder->hostObject->getValueAtIndex(runtime, index); + return result.isUndefined() ? nullptr : result.local(runtime); + } catch (const JSError& error) { + setEngineException(runtime, context, exception, error); + return JSValueMakeUndefined(context); + } catch (const std::exception& error) { + setException(context, exception, error); + return JSValueMakeUndefined(context); + } + } + if (shouldDeferToNativeInstancePrototype(context, object, propertyName, + holder)) { + return nullptr; + } + try { + // Non-owning, for this dispatch only; see HostObject::receiver(). + const Value receiver = Value::borrowed(runtime, object); + HostObject::ReceiverScope receiverScope(*holder->hostObject, receiver); + Value result = holder->hostObject->get(runtime, PropNameID(stringToUtf8(propertyName))); + return result.isUndefined() ? nullptr : result.local(runtime); + } catch (const JSError& error) { + setEngineException(runtime, context, exception, error); + return JSValueMakeUndefined(context); + } catch (const std::exception& error) { + setException(context, exception, error); + return JSValueMakeUndefined(context); + } +} + +bool hostSetProperty(JSContextRef context, JSObjectRef object, JSStringRef propertyName, + JSValueRef value, JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder == nullptr || holder->hostObject == nullptr) { + return false; + } + Runtime runtime(holder->state); + try { + const Value receiver = Value::borrowed(runtime, object); + HostObject::ReceiverScope receiverScope(*holder->hostObject, receiver); + uint32_t index = 0; + if (holder->hostObject->hasIndexedAccess() && + stringAsArrayIndex(propertyName, &index)) { + return holder->hostObject->setValueAtIndex(runtime, index, + Value::borrowed(runtime, value)); + } + return holder->hostObject->set(runtime, PropNameID(stringToUtf8(propertyName)), + Value::borrowed(runtime, value)); + } catch (const JSError& error) { + setEngineException(runtime, context, exception, error); + return true; + } catch (const std::exception& error) { + setException(context, exception, error); + return true; + } +} + +void hostGetPropertyNames(JSContextRef, JSObjectRef object, + JSPropertyNameAccumulatorRef propertyNames) { + auto* holder = static_cast(JSObjectGetPrivate(object)); + if (holder == nullptr || holder->hostObject == nullptr) { + return; + } + Runtime runtime(holder->state); + try { + const Value receiver = Value::borrowed(runtime, object); + HostObject::ReceiverScope receiverScope(*holder->hostObject, receiver); + for (const auto& property : holder->hostObject->getPropertyNames(runtime)) { + JSStringRef name = makeJSString(property.utf8(runtime)); + JSPropertyNameAccumulatorAddName(propertyNames, name); + JSStringRelease(name); + } + } catch (const std::exception&) { + } +} + +void hostFinalize(JSObjectRef object) { + delete static_cast(JSObjectGetPrivate(object)); +} + +JSValueRef functionCall(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, + size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(function)); + if (holder == nullptr || !holder->callback) { + return JSValueMakeUndefined(context); + } + Runtime runtime(holder->state); + StackValueArray args(argumentCount); + for (size_t i = 0; i < argumentCount; i++) { + args.emplace(i, Value::borrowed(runtime, arguments[i])); + } + try { + Value thisValue = Value::borrowed(runtime, thisObject); + Value result = + holder->callback(runtime, thisValue, args.size() == 0 ? nullptr : args.data(), + args.size()); + return result.local(runtime); + } catch (const JSError& error) { + setEngineException(runtime, context, exception, error); + return JSValueMakeUndefined(context); + } catch (const std::exception& error) { + setException(context, exception, error); + return JSValueMakeUndefined(context); + } +} + +// `new Ctor(...)` on a function built by createFromHostConstructor. +// +// JSObjectCallAsConstructorCallback is handed no receiver and must return an +// object, so this synthesises one the way OrdinaryCreateFromConstructor does: +// a plain object whose [[Prototype]] is the constructor's `prototype`. The +// host callback then sees it as `this`, which is what napi_define_class's +// constructor expects (it calls napi_wrap on it and usually returns nothing). +JSObjectRef functionConstruct(JSContextRef context, JSObjectRef constructor, + size_t argumentCount, const JSValueRef arguments[], + JSValueRef* exception) { + auto* holder = static_cast(JSObjectGetPrivate(constructor)); + if (holder == nullptr || !holder->callback) { + return JSObjectMake(context, nullptr, nullptr); + } + Runtime runtime(holder->state); + + JSObjectRef self = JSObjectMake(context, nullptr, nullptr); + JSStringRef prototypeName = makeJSString("prototype"); + JSValueRef prototype = JSObjectGetProperty(context, constructor, prototypeName, nullptr); + JSStringRelease(prototypeName); + if (prototype != nullptr && JSValueIsObject(context, prototype)) { + JSObjectSetPrototype(context, self, prototype); + } + + StackValueArray args(argumentCount); + for (size_t i = 0; i < argumentCount; i++) { + args.emplace(i, Value::borrowed(runtime, arguments[i])); + } + try { + Value thisValue = Value::borrowed(runtime, self); + Value result = holder->callback(runtime, thisValue, args.size() == 0 ? nullptr : args.data(), + args.size()); + // A constructor may legitimately return a different object (the runtime's + // ObjectManager does for already-known Java instances); anything else means + // "keep the receiver", matching [[Construct]]. + if (result.isObject()) { + JSValueRef returned = result.local(runtime); + if (returned != nullptr && JSValueIsObject(context, returned)) { + return JSValueToObject(context, returned, nullptr); + } + } + return self; + } catch (const JSError& error) { + setEngineException(runtime, context, exception, error); + return self; + } catch (const std::exception& error) { + setException(context, exception, error); + return self; + } +} + +// `value instanceof Ctor`, restoring the ordinary meaning. +// +// JSC does not fall back to the prototype-chain walk for a JSObjectMake'd +// object: JSCallbackObject overrides customHasInstance, and that override +// consults only the JSClass chain's hasInstance callbacks and returns false +// when there is none. So without this every `instanceof` against a +// napi_define_class constructor answered false -- the whole +// When_calling_instanceof_* family, "should return true for instances that +// inherit from a base type", and the generated-proxy specs. +// +// Symbol.hasInstance still wins over this, which matters: MetadataNode installs +// one on every Java interface so `obj instanceof SomeInterface` reports +// implementation rather than inheritance. JSC checks that first. +bool functionHasInstance(JSContextRef context, JSObjectRef constructor, + JSValueRef possibleInstance, JSValueRef* exception) { + if (possibleInstance == nullptr || !JSValueIsObject(context, possibleInstance)) { + return false; + } + JSStringRef name = makeJSString("prototype"); + JSValueRef prototype = JSObjectGetProperty(context, constructor, name, exception); + JSStringRelease(name); + if (prototype == nullptr || !JSValueIsObject(context, prototype)) { + return false; + } + JSObjectRef object = JSValueToObject(context, possibleInstance, nullptr); + if (object == nullptr) { + return false; + } + for (JSValueRef walk = JSObjectGetPrototype(context, object); + walk != nullptr && JSValueIsObject(context, walk);) { + if (JSValueIsStrictEqual(context, walk, prototype)) { + return true; + } + JSObjectRef step = JSValueToObject(context, walk, nullptr); + if (step == nullptr) { + break; + } + walk = JSObjectGetPrototype(context, step); + } + return false; +} + +void functionFinalize(JSObjectRef object) { + delete static_cast(JSObjectGetPrivate(object)); +} + +JSClassRef hostClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->hostClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineHostObject"; + definition.getProperty = hostGetProperty; + definition.setProperty = hostSetProperty; + definition.getPropertyNames = hostGetPropertyNames; + definition.finalize = hostFinalize; + state->hostClass = JSClassCreate(&definition); + } + return state->hostClass; +} + +JSClassRef functionClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->functionClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineFunction"; + definition.callAsFunction = functionCall; + definition.finalize = functionFinalize; + state->functionClass = JSClassCreate(&definition); + } + return state->functionClass; +} + +JSClassRef constructorClass(Runtime& runtime) { + auto state = runtime.state(); + if (state->constructorClass == nullptr) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.className = "NativeScriptEngineConstructor"; + // Both callbacks: a class constructor is still callable (napi_is_function + // must answer true, and JSC's ordinary instanceof requires IsCallable). + definition.callAsFunction = functionCall; + definition.callAsConstructor = functionConstruct; + definition.hasInstance = functionHasInstance; + definition.finalize = functionFinalize; + state->constructorClass = JSClassCreate(&definition); + } + return state->constructorClass; +} + +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function) { + if (context == nullptr || function == nullptr) { + return; + } + + JSValueRef exception = nullptr; + JSStringRef functionName = makeJSString("Function"); + JSValueRef functionValue = + JSObjectGetProperty(context, JSContextGetGlobalObject(context), functionName, &exception); + JSStringRelease(functionName); + if (exception != nullptr || functionValue == nullptr || + !JSValueIsObject(context, functionValue)) { + return; + } + + exception = nullptr; + JSObjectRef functionConstructor = JSValueToObject(context, functionValue, &exception); + if (exception != nullptr || functionConstructor == nullptr) { + return; + } + + JSStringRef prototypeName = makeJSString("prototype"); + JSValueRef prototypeValue = + JSObjectGetProperty(context, functionConstructor, prototypeName, &exception); + JSStringRelease(prototypeName); + if (exception != nullptr || prototypeValue == nullptr || + !JSValueIsObject(context, prototypeValue)) { + return; + } + + JSObjectSetPrototype(context, function, prototypeValue); +} + +} // namespace jscengine + +Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken, bool nativeInstance) { + auto* holder = + new jscengine::HostObjectHolder(runtime.state(), std::move(host), typeToken, nativeInstance); + JSObjectRef object = JSObjectMake(runtime.context(), jscengine::hostClass(runtime), holder); + return Object::fromValueStorage(Value(runtime, object).storage_); +} + +Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback) { + auto* holder = new jscengine::FunctionHolder(runtime.state(), std::move(callback)); + JSObjectRef function = JSObjectMake(runtime.context(), jscengine::functionClass(runtime), holder); + jscengine::setFunctionPrototype(runtime.context(), function); + std::string functionName = name.utf8(runtime); + if (!functionName.empty()) { + JSStringRef property = jscengine::makeJSString("name"); + JSStringRef valueString = jscengine::makeJSString(functionName); + JSValueRef value = JSValueMakeString(runtime.context(), valueString); + JSObjectSetProperty(runtime.context(), function, property, value, kJSPropertyAttributeReadOnly, + nullptr); + JSStringRelease(valueString); + JSStringRelease(property); + } + return Function(Object::fromValueStorage(Value(runtime, function).storage_)); +} + +Function Function::createFromHostConstructor(Runtime& runtime, const PropNameID& name, + unsigned int, HostFunctionType callback) { + // Same holder/finalizer lifetime as createFromHostFunction; only the class + // differs (it carries callAsConstructor) and a `prototype` is installed. + auto* holder = new jscengine::FunctionHolder(runtime.state(), std::move(callback)); + JSObjectRef function = + JSObjectMake(runtime.context(), jscengine::constructorClass(runtime), holder); + + // `name` is installed BEFORE the function prototype, and the order is + // load-bearing. + // + // JSObjectSetProperty only defines an *own* property when the name is absent + // from the whole prototype chain; otherwise it does an ordinary put. Once + // Function.prototype is in the chain, `name` is found there -- non-writable, + // per spec -- so the put failed silently in sloppy mode and the constructor + // inherited Function.prototype.name, which is the empty string. That is what + // `java.lang.Object.name` and `obj.constructor.name` read back as. + std::string functionName = name.utf8(runtime); + if (!functionName.empty()) { + JSStringRef property = jscengine::makeJSString("name"); + JSStringRef valueString = jscengine::makeJSString(functionName); + JSValueRef value = JSValueMakeString(runtime.context(), valueString); + JSObjectSetProperty(runtime.context(), function, property, value, kJSPropertyAttributeReadOnly, + nullptr); + JSStringRelease(valueString); + JSStringRelease(property); + } + + jscengine::setFunctionPrototype(runtime.context(), function); + + // A writable, non-enumerable own `prototype`. MetadataNode chains class + // prototypes with a plain `ctor.prototype = ...` assignment + // (napi_util::set_prototype); a JSObjectMake'd object has no `prototype` at + // all, so without this the assignment creates one on first write but + // `ctor.prototype` reads undefined until then -- and napi_define_class reads + // it immediately to hang the methods off. + { + JSStringRef property = jscengine::makeJSString("prototype"); + JSObjectRef prototype = JSObjectMake(runtime.context(), nullptr, nullptr); + // The prototype's `constructor` back-pointer. Per spec a function's + // prototype carries one (non-enumerable, writable, configurable); V8's + // Function::New installs it, but a JSObjectMake'd object has none, so + // `instance.constructor` walked past the class prototype to + // Object.prototype.constructor and every native class reported its name as + // "Object". + { + JSStringRef ctorName = jscengine::makeJSString("constructor"); + JSObjectSetProperty(runtime.context(), prototype, ctorName, function, + kJSPropertyAttributeDontEnum, nullptr); + JSStringRelease(ctorName); + } + JSObjectSetProperty(runtime.context(), function, property, prototype, + kJSPropertyAttributeDontEnum, nullptr); + JSStringRelease(property); + } + + return Function(Object::fromValueStorage(Value(runtime, function).storage_)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/jsi/jsc/JSCRuntime.cpp b/NativeScript/jsi/jsc/JSCRuntime.cpp new file mode 100644 index 000000000..a6d64baa7 --- /dev/null +++ b/NativeScript/jsi/jsc/JSCRuntime.cpp @@ -0,0 +1,30 @@ +#include "jsi/jsc/JSCRuntime.h" + +#ifdef TARGET_ENGINE_JSC + +namespace nativescript { +namespace engine { + +Object Runtime::global() { + return Object::fromValueStorage(Value(*this, JSContextGetGlobalObject(context())).storage_); +} + +Value Runtime::evaluateJavaScript(std::shared_ptr buffer, + const std::string& sourceURL) { + JSStringRef source = JSStringCreateWithUTF8CString( + buffer != nullptr ? std::string(buffer->data(), buffer->size()).c_str() : ""); + JSStringRef url = jscengine::makeJSString(sourceURL); + JSValueRef exception = nullptr; + JSValueRef result = JSEvaluateScript(context(), source, nullptr, url, 1, &exception); + JSStringRelease(source); + JSStringRelease(url); + if (exception != nullptr) { + throw jscengine::toJSError(*this, exception); + } + return Value(*this, result); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/jsi/jsc/JSCRuntime.h b/NativeScript/jsi/jsc/JSCRuntime.h new file mode 100644 index 000000000..c5518a408 --- /dev/null +++ b/NativeScript/jsi/jsc/JSCRuntime.h @@ -0,0 +1,1263 @@ +#ifndef NATIVESCRIPT_JSI_JSC_JSC_RUNTIME_H +#define NATIVESCRIPT_JSI_JSC_JSC_RUNTIME_H + +// JavaScriptCore behind the JSI-shaped `nativescript::engine` API. +// +// This header is platform-neutral: it depends on JavaScriptCore and the C++ +// standard library, and on nothing from Foundation, the Objective-C runtime, +// or the Apple metadata format. Android compiles it as-is. +// +// The Apple bridge's extra baggage lives in +// ffi/objc/jsc/NativeApiJSCRuntime.h, which includes this file. Apple sources +// keep including that path unchanged. + +#ifdef TARGET_ENGINE_JSC + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +// Mirrors jsi::JSError. See the V8 engine layer for why the thrown value +// matters: rebuilding an error from its message drops whatever the runtime +// attached to it (NativeScriptException's `nativeException`) and its stack. +// +// Throw sites in this layer go through jscengine::toJSError, which populates +// the payload. A JSError raised by any other constructor carries no value and +// value() reports null; callers fall back to the message. +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} + + JSError(Runtime& runtime, const std::string& message, const Value& value, + std::string stack); + + const Value* value() const { return value_.get(); } + const std::string& stack() const { return stack_; } + + private: + std::shared_ptr value_; + std::string stack_; +}; + +namespace jscengine { +struct ValueStorage; +} // namespace jscengine + +// A handle that does not keep its referent alive. +// +// Mirrors the V8 and Hermes engine layers. The Node-API shim needs it for +// napi_ref: a ref is strong above refcount 0 and weak at 0, and the Android +// runtime's ObjectManager depends on the weak half -- it holds every +// host-object proxy through a refcount-0 ref precisely so that collection runs +// the finalizer that calls makeInstanceWeak. +// +// Backed by the JavaScript `WeakRef` built-in rather than JSWeakCreate. JSC's +// native weak APIs (JSWeakPrivate.h, JSWeakObjectMapRefPrivate.h) live in +// private headers that the Apple JavaScriptCore.framework does not ship, and +// this header is shared with the Apple runtime; `WeakRef` is public API on +// both. The cost is a JS call in lock(), against a cached +// WeakRef.prototype.deref. +class WeakObject { + public: + WeakObject() = default; + WeakObject(Runtime& runtime, const Value& value); + + // The referent, or undefined if it has been collected (or was never an + // object). + Value lock(Runtime& runtime) const; + + bool empty() const { return storage_ == nullptr; } + void reset() { storage_.reset(); } + + private: + std::shared_ptr storage_; +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); + + // Indexed access, taking the index as an integer rather than as the decimal + // string the named form would hand over. See V8Runtime.h for the full note. + // JSC has no indexed hook, so this layer recognises an index-shaped property + // name and routes it here -- but only for a host object that opted in, since + // the named path also does the prototype handling. + virtual Value getValueAtIndex(Runtime& runtime, uint32_t index); + virtual bool setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value); + + bool hasIndexedAccess() const { return indexedAccess_; } + void setIndexedAccess(bool value) { indexedAccess_ = value; } + + // The JS object standing for this host object, valid ONLY for the duration of + // the call the engine is currently dispatching. + // + // Handed in per call rather than stored, and deliberately non-owning. A + // HostObject that holds an owned handle to its own wrapper is a strong + // self-cycle: JSC only finalizes a JSObjectRef once nothing references it, so + // the cycle would keep every host object permanently alive -- the class + // finalizer would never run, ObjectManager would never call makeInstanceWeak + // and every Java instance would stay strongly held. + // + // Mirrors the V8 and Hermes engine layers; the Apple bridge does not use it, + // so this is purely additive there. + const Value* receiver() const { return receiver_; } + + // Sets the receiver for one dispatch and clears it on scope exit. + class ReceiverScope { + public: + ReceiverScope(HostObject& host, const Value& receiver) : host_(host) { + host_.receiver_ = &receiver; + } + ~ReceiverScope() { host_.receiver_ = nullptr; } + ReceiverScope(const ReceiverScope&) = delete; + ReceiverScope& operator=(const ReceiverScope&) = delete; + + private: + HostObject& host_; + }; + + private: + const Value* receiver_ = nullptr; + bool indexedAccess_ = false; +}; + +using HostFunctionType = std::function; + +namespace jscengine { + +inline std::string stringToUtf8(JSStringRef string) { + if (string == nullptr) { + return {}; + } + size_t capacity = JSStringGetMaximumUTF8CStringSize(string); + std::string result(capacity, '\0'); + size_t written = JSStringGetUTF8CString(string, result.data(), capacity); + if (written == 0) { + return {}; + } + result.resize(written - 1); + return result; +} + +// Decode strict UTF-8 into UTF-16, returning false on any malformed sequence. +// +// This used to go through NSString, which is why it was the last thing keeping +// the JSC engine layer tied to Foundation. JSStringCreateWithUTF8CString is +// not a substitute on its own: it takes a NUL-terminated C string, so it +// truncates at an embedded U+0000, which is a legal JavaScript character. +inline bool decodeUtf8ToUtf16(const std::string& value, + std::vector& out) { + out.clear(); + out.reserve(value.size()); + + const auto* bytes = reinterpret_cast(value.data()); + const size_t size = value.size(); + + for (size_t i = 0; i < size;) { + const unsigned char lead = bytes[i]; + uint32_t codePoint = 0; + size_t extra = 0; + + if (lead < 0x80) { + codePoint = lead; + } else if ((lead & 0xE0) == 0xC0) { + codePoint = lead & 0x1Fu; + extra = 1; + } else if ((lead & 0xF0) == 0xE0) { + codePoint = lead & 0x0Fu; + extra = 2; + } else if ((lead & 0xF8) == 0xF0) { + codePoint = lead & 0x07u; + extra = 3; + } else { + return false; // continuation byte or 5/6-byte form + } + + if (i + extra >= size) { + return false; // truncated sequence (holds trivially when extra == 0) + } + + for (size_t j = 1; j <= extra; j++) { + const unsigned char continuation = bytes[i + j]; + if ((continuation & 0xC0) != 0x80) { + return false; + } + codePoint = (codePoint << 6) | (continuation & 0x3Fu); + } + + // Reject overlong forms, surrogates encoded directly, and out-of-range. + static constexpr uint32_t kMinForLength[4] = {0x0, 0x80, 0x800, 0x10000}; + if (codePoint < kMinForLength[extra] || codePoint > 0x10FFFF || + (codePoint >= 0xD800 && codePoint <= 0xDFFF)) { + return false; + } + + if (codePoint <= 0xFFFF) { + out.push_back(static_cast(codePoint)); + } else { + codePoint -= 0x10000; + out.push_back(static_cast(0xD800 + (codePoint >> 10))); + out.push_back(static_cast(0xDC00 + (codePoint & 0x3FF))); + } + + i += extra + 1; + } + + return true; +} + +inline JSStringRef makeJSString(const std::string& value) { + std::vector characters; + if (!decodeUtf8ToUtf16(value, characters)) { + // Malformed UTF-8; fall back to whatever JSC makes of it, matching the + // behaviour of the previous NSString-returns-nil path. + return JSStringCreateWithUTF8CString(value.c_str()); + } + return JSStringCreateWithCharacters(characters.data(), characters.size()); +} + +inline JSStringRef makeJSString(const char* value) { + return JSStringCreateWithUTF8CString(value != nullptr ? value : ""); +} + +inline std::string valueToUtf8(JSContextRef context, JSValueRef value) { + if (value == nullptr) { + return {}; + } + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(context, value, &exception); + if (string == nullptr || exception != nullptr) { + if (string != nullptr) { + JSStringRelease(string); + } + return {}; + } + std::string result = stringToUtf8(string); + JSStringRelease(string); + return result; +} + +inline JSValueRef makeError(JSContextRef context, const std::string& message) { + JSStringRef string = makeJSString(message); + JSValueRef argument = JSValueMakeString(context, string); + JSStringRelease(string); + JSValueRef exception = nullptr; + JSObjectRef error = JSObjectMakeError(context, 1, &argument, &exception); + if (error != nullptr && exception == nullptr) { + return error; + } + return argument; +} + +inline void setException(JSContextRef context, JSValueRef* exception, const std::exception& error) { + if (exception != nullptr) { + *exception = makeError(context, error.what()); + } +} + +struct RuntimeState { + explicit RuntimeState(JSGlobalContextRef context) : context(context) {} + + ~RuntimeState() { + if (hostClass != nullptr) { + JSClassRelease(hostClass); + } + if (functionClass != nullptr) { + JSClassRelease(functionClass); + } + if (selectorGroupFunctionClass != nullptr) { + JSClassRelease(selectorGroupFunctionClass); + } + if (constructorClass != nullptr) { + JSClassRelease(constructorClass); + } + if (nativeStateKey != nullptr) { + JSStringRelease(nativeStateKey); + } + if (context != nullptr) { + if (weakRefConstructor != nullptr) { + JSValueUnprotect(context, weakRefConstructor); + } + if (weakRefDeref != nullptr) { + JSValueUnprotect(context, weakRefDeref); + } + } + } + + JSGlobalContextRef context = nullptr; + JSClassRef hostClass = nullptr; + JSClassRef functionClass = nullptr; + JSClassRef selectorGroupFunctionClass = nullptr; + // Backs Function::createFromHostConstructor. Separate from functionClass + // because it additionally carries a callAsConstructor callback. + JSClassRef constructorClass = nullptr; + // `WeakRef` and `WeakRef.prototype.deref`, resolved once and protected. + // WeakObject would otherwise do two global lookups and a prototype walk per + // napi_get_reference_value on a weak ref, which ObjectManager runs often. + JSObjectRef weakRefConstructor = nullptr; + JSObjectRef weakRefDeref = nullptr; + // The key every native-state slot hangs off, created once per runtime. + // JSC's C API has no private-symbol or own-only property accessor, so this + // is still a named property -- but a non-enumerable one under a cached + // JSStringRef, and read with a single lookup instead of a has+get pair. + JSStringRef nativeStateKey = nullptr; +}; + +struct ValueStorage { + enum class Kind { + Undefined, + Null, + Bool, + Number, + JSC, + JSCBorrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + + ~ValueStorage() { + if (kind == Kind::JSC && context != nullptr && value != nullptr) { + JSValueUnprotect(context, value); + } + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + JSGlobalContextRef context = nullptr; + JSValueRef value = nullptr; +}; + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken, bool nativeInstance = false) + : state(std::move(state)), + hostObject(std::move(hostObject)), + typeToken(typeToken), + nativeInstance(nativeInstance) {} + + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; + // Set for wrappers around an ObjC instance, whose JS prototype is allowed to + // shadow a native property. Recorded as a flag rather than derived from + // typeToken: the wrapper class is defined inside an anonymous namespace in + // the engine translation unit, so a type token taken anywhere else names a + // different type and never compares equal. + bool nativeInstance = false; + + // Set only when this holder backs Object::setNativeState. See there: it + // records which object the state belongs to, so a read can tell an own + // payload from an inherited one. Never dereferenced -- compared only -- and + // the holder is reachable solely through that object's own property, so it + // cannot outlive it. + JSObjectRef stateOwner = nullptr; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + + std::shared_ptr state; + HostFunctionType callback; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + std::shared_ptr buffer; +}; + +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); + +// Build a JSError that carries the thrown JS value, not just its text. +// +// Rebuilding an error from its message drops whatever was attached to it -- +// NativeScriptException hangs the originating Java throwable off +// `nativeException` -- and drops its stack. Every throw site in this layer goes +// through here so a JS exception crosses the engine boundary intact. +// +// Additive: JSError already had the carrying constructor and value() simply +// reported null before. Callers that only read what() are unaffected. +JSError toJSError(Runtime& runtime, JSValueRef exception); + +// Lazily resolved and cached on RuntimeState; null if the engine has no +// `WeakRef` (in which case WeakObject degrades to always-empty, which the shim +// reads as "already collected"). +JSObjectRef weakRefConstructor(Runtime& runtime); +JSObjectRef weakRefDeref(Runtime& runtime); + +} // namespace jscengine + +class Runtime { + public: + explicit Runtime(JSGlobalContextRef context) + : state_(std::make_shared(context)) {} + + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + + JSGlobalContextRef context() const { return state_->context; } + std::shared_ptr state() const { return state_; } + + // A stable, per-runtime identity. + // + // engine::Runtime is a value wrapper around shared engine state, and the + // host-function trampolines construct a fresh one on the stack for every + // callback. So `&runtime` is NOT stable and must never be used as a map key; + // this is. The pointer is opaque and only ever compared or hashed. + const void* identity() const { return state_.get(); } + + // See RuntimeState::nativeStateKey. Created on first use. + JSStringRef nativeStateKey() const { + if (state_->nativeStateKey == nullptr) { + state_->nativeStateKey = JSStringCreateWithUTF8CString("__nsNativeState"); + } + return state_->nativeStateKey; + } + + Object global(); + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + void drainMicrotasks() {} + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, JSStringRef string); + + static String createFromUtf8(Runtime& runtime, const char* value) { + JSStringRef string = jscengine::makeJSString(value); + String result(runtime, string); + JSStringRelease(string); + return result; + } + + static String createFromUtf8(Runtime& runtime, const std::string& value) { + JSStringRef string = jscengine::makeJSString(value); + String result(runtime, string); + JSStringRelease(string); + return result; + } + + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + std::string text(reinterpret_cast(value), length); + return createFromUtf8(runtime, text); + } + + std::string utf8(Runtime& runtime) const; + JSValueRef local(Runtime& runtime) const { return storage_->value; } + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(jscengine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(jscengine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(jscengine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == jscengine::ValueStorage::Kind::JSCBorrowed) { + // Promote borrowed to owned + storage_ = std::make_shared(jscengine::ValueStorage::Kind::JSC); + storage_->context = runtime.context(); + storage_->value = value.borrowedValue_ != nullptr ? value.borrowedValue_ + : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), storage_->value); + kind_ = jscengine::ValueStorage::Kind::JSC; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedContext_ = value.borrowedContext_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedContext_(value.borrowedContext_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value); + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + Value(Runtime& runtime, JSValueRef value) + : kind_(jscengine::ValueStorage::Kind::JSC), + storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), storage_->value); + } + + static Value borrowed(Runtime& runtime, JSValueRef value) { + Value result; + result.kind_ = jscengine::ValueStorage::Kind::JSCBorrowed; + result.borrowedContext_ = runtime.context(); + result.borrowedValue_ = value != nullptr ? value : JSValueMakeUndefined(runtime.context()); + return result; + } + + static Value undefined() { return Value(); } + static Value null() { + Value value; + value.kind_ = jscengine::ValueStorage::Kind::Null; + return value; + } + + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + return JSValueIsStrictEqual(runtime.context(), lhs.local(runtime), + rhs.local(runtime)); + } + + bool isUndefined() const { + return kind_ == jscengine::ValueStorage::Kind::Undefined || + (isJSC() && JSValueIsUndefined(jscContext(), jscValue())); + } + bool isNull() const { + return kind_ == jscengine::ValueStorage::Kind::Null || + (isJSC() && JSValueIsNull(jscContext(), jscValue())); + } + bool isBool() const { + return kind_ == jscengine::ValueStorage::Kind::Bool || + (isJSC() && JSValueIsBoolean(jscContext(), jscValue())); + } + bool getBool() const { + if (kind_ == jscengine::ValueStorage::Kind::Bool) { + return boolValue_; + } + return isJSC() && JSValueToBoolean(jscContext(), jscValue()); + } + bool isNumber() const { + return kind_ == jscengine::ValueStorage::Kind::Number || + (isJSC() && JSValueIsNumber(jscContext(), jscValue())); + } + double getNumber() const { + if (kind_ == jscengine::ValueStorage::Kind::Number) { + return numberValue_; + } + return isJSC() ? JSValueToNumber(jscContext(), jscValue(), nullptr) : 0; + } + + bool isObject() const { return isJSC() && JSValueIsObject(jscContext(), jscValue()); } + bool isString() const { return isJSC() && JSValueIsString(jscContext(), jscValue()); } + bool isBigInt() const { + if (!isJSC()) { + return false; + } + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + return JSValueIsBigInt(jscContext(), jscValue()); + } + return false; + } + bool isSymbol() const { return isJSC() && JSValueIsSymbol(jscContext(), jscValue()); } + + Object asObject(Runtime& runtime) const; + // Borrowing is a V8-only capability; see jsi/v8/V8Runtime.h. Everywhere else + // this is the owning conversion, and callers get the stronger guarantee. + // Declared, not defined: Object is still incomplete here, so a body calling + // asObject would not compile. Defined next to asObject in JSCValue.cpp. + Object asObjectBorrowed(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + // Portable spellings of "give me the UTF-8" and "make me a string value". + // V8 and QuickJS implement these without materialising an owning String; JSC + // values are protected rather than scope-rooted and its String is where the + // protect lives, so here they are exactly the old two-step. Declared on every + // engine so the shared bridge can call one name. + std::string utf8(Runtime& runtime) const; + static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length); + + JSValueRef local(Runtime& runtime) const { + switch (kind_) { + case jscengine::ValueStorage::Kind::Undefined: + return JSValueMakeUndefined(runtime.context()); + case jscengine::ValueStorage::Kind::Null: + return JSValueMakeNull(runtime.context()); + case jscengine::ValueStorage::Kind::Bool: + return JSValueMakeBoolean(runtime.context(), boolValue_); + case jscengine::ValueStorage::Kind::Number: + return JSValueMakeNumber(runtime.context(), numberValue_); + case jscengine::ValueStorage::Kind::JSC: + return storage_->value; + case jscengine::ValueStorage::Kind::JSCBorrowed: + return borrowedValue_; + } + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + bool isJSC() const { + return kind_ == jscengine::ValueStorage::Kind::JSC || + kind_ == jscengine::ValueStorage::Kind::JSCBorrowed; + } + JSContextRef jscContext() const { + return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedContext_ + : storage_->context; + } + JSValueRef jscValue() const { + return kind_ == jscengine::ValueStorage::Kind::JSCBorrowed ? borrowedValue_ : storage_->value; + } + + jscengine::ValueStorage::Kind kind_ = jscengine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + JSGlobalContextRef borrowedContext_ = nullptr; + JSValueRef borrowedValue_ = nullptr; + std::shared_ptr storage_; +}; + +// Defined here rather than with the class: constructing the shared_ptr needs +// Value to be complete. +inline JSError::JSError(Runtime& runtime, const std::string& message, + const Value& value, std::string stack) + : std::runtime_error(message), + value_(std::make_shared(runtime, value)), + stack_(std::move(stack)) {} + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = JSObjectMake(runtime.context(), nullptr, nullptr); + JSValueProtect(runtime.context(), storage_->value); + } + + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + jscengine::hostObjectTypeToken()); + } + + // A native (Java/ObjC-backed) instance, as opposed to an opaque host object. + // + // On V8 the distinction is about speed: a masking named interceptor would + // divert every named read into the trap instead of letting V8 resolve it on + // the class prototype and form a load IC. + // + // On JSC it is about correctness. The class's getProperty callback runs + // before the prototype chain is consulted, so unless the holder is marked, a + // JS property on the prototype can never shadow a native one of the same + // name -- the native value always wins. The mark is what lets hostGetProperty + // defer; see shouldDeferToNativeInstancePrototype. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + jscengine::hostObjectTypeToken(), + /* nativeInstance */ true); + } + + Value getProperty(Runtime& runtime, const char* name) const { + JSStringRef property = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSValueRef result = + JSObjectGetProperty(runtime.context(), local(runtime), property, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + return Value(runtime, result); + } + + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + + Value getProperty(Runtime& runtime, const Value& key) const { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyForKey(runtime.context(), local(runtime), + key.local(runtime), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + return Value(runtime, result); + } + + // Borrowing is a V8-only capability; see jsi/v8/V8Runtime.h. + // + // The name exists on every engine so the Node-API shim's read paths stay + // engine-neutral, but this engine cannot honour it: a read here hands back a + // handle the caller must keep alive (QuickJS returns a +1 refcount, JSC needs + // JSValueProtect, Hermes owns its jsi::Value), and a bare handle rooted only + // by an ambient scope has no equivalent. So these are the owning reads, and + // callers get the stronger guarantee. + Value getPropertyBorrowed(Runtime& runtime, const char* name) const { + return getProperty(runtime, name); + } + Value getPropertyBorrowed(Runtime& runtime, const Value& key) const { + return getProperty(runtime, key); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + JSStringRef property = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), + kJSPropertyAttributeNone, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + } + + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyForKey(runtime.context(), local(runtime), key.local(runtime), + value.local(runtime), kJSPropertyAttributeNone, &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + JSStringRef property = jscengine::makeJSString(name); + bool result = JSObjectHasProperty(runtime.context(), local(runtime), property); + JSStringRelease(property); + return result; + } + + bool isFunction(Runtime& runtime) const { + return JSObjectIsFunction(runtime.context(), local(runtime)); + } + + bool isArray(Runtime& runtime) const { + JSStringRef name = jscengine::makeJSString("Array"); + JSValueRef constructorValue = JSObjectGetProperty( + runtime.context(), JSContextGetGlobalObject(runtime.context()), name, nullptr); + JSStringRelease(name); + if (constructorValue == nullptr || !JSValueIsObject(runtime.context(), constructorValue)) { + return false; + } + JSObjectRef constructor = JSValueToObject(runtime.context(), constructorValue, nullptr); + JSValueRef exception = nullptr; + bool result = + JSValueIsInstanceOfConstructor(runtime.context(), local(runtime), constructor, &exception); + return exception == nullptr && result; + } + + bool isArrayBuffer(Runtime& runtime) const { + JSValueRef exception = nullptr; + JSTypedArrayType type = + JSValueGetTypedArrayType(runtime.context(), storage_->value, &exception); + return exception == nullptr && type == kJSTypedArrayTypeArrayBuffer; + } + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == jscengine::hostObjectTypeToken(); + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != jscengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + + // ---- Native state ------------------------------------------------------- + // + // See jsi/v8/V8Runtime.h for what this is for. JSC's C API exposes neither + // private symbols nor an own-property accessor, so the slot is a + // non-enumerable named property under a cached key -- one lookup where the + // old __nsWrap path did two, and invisible to Object.keys/for-in/JSON. + // + // The payload is still a HostObject, so finaliser timing is unchanged. + template + void setNativeState(Runtime& runtime, std::shared_ptr state) { + Object holder = Object::createFromHostObject(runtime, std::move(state)); + // Stamp the holder with the object it belongs to; getNativeState uses it to + // reject an inherited payload. See the read below for why that is needed + // here and on no other backend. + if (auto* record = static_cast( + JSObjectGetPrivate(holder.local(runtime)))) { + record->stateOwner = local(runtime); + } + JSValueRef exception = nullptr; + JSObjectSetProperty(runtime.context(), local(runtime), runtime.nativeStateKey(), + holder.local(runtime), kJSPropertyAttributeDontEnum, &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + } + + template + std::shared_ptr getNativeState(Runtime& runtime) const { + JSValueRef exception = nullptr; + JSValueRef holder = JSObjectGetProperty(runtime.context(), local(runtime), + runtime.nativeStateKey(), &exception); + if (exception != nullptr || holder == nullptr || + !JSValueIsObject(runtime.context(), holder)) { + return nullptr; + } + auto* record = static_cast(JSObjectGetPrivate( + reinterpret_cast(const_cast(holder)))); + // Own-property semantics, which this backend has to enforce by hand. + // + // V8 keeps native state in a private symbol and QuickJS in a class-backed + // opaque slot, so on both a read can only ever see the object's own + // payload. The JSC C API has neither, so the state is a named property -- + // and JSObjectGetProperty walks the prototype chain. Without this check an + // object that merely *inherits* from something carrying state reads that + // state back as its own: every Java wrapper chains to java.lang.Object's + // prototype, so MethodCache::GetType named every argument "java/lang/Object" + // and Java's overload resolution collapsed onto the Object overload. + if (record != nullptr && record->stateOwner != nullptr && + record->stateOwner != local(runtime)) { + return nullptr; + } + if (record == nullptr || record->typeToken != jscengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(record->hostObject); + } + + JSObjectRef local(Runtime& runtime) const { + return reinterpret_cast(const_cast(storage_->value)); + } + + operator Value() const { return Value::fromStorage(storage_); } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + explicit Object(std::shared_ptr storage) + : storage_(std::move(storage)) {} + + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken, + bool nativeInstance = false); + + jscengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { + return static_cast(JSObjectGetPrivate(local(runtime))); + } + + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + // Like createFromHostFunction, but the result is usable with `new` and has a + // *writable* own `prototype`. + // + // A JSObjectMake'd object of a JSClass with only a callAsFunction callback is + // callable but not constructible, and has no `prototype` property at all -- + // so `new Ctor()` throws and MetadataNode's plain `ctor.prototype = ...` + // assignment (napi_util::set_prototype) has nothing to overwrite. Both are + // what napi_define_class needs. + // + // JSC's JSObjectCallAsConstructorCallback is handed no `this` and must return + // an object, so the trampoline synthesises the receiver the way + // OrdinaryCreateFromConstructor does. Like Hermes, and unlike V8, that means + // new.target cannot be observed. No JS wrapper function is involved, so stack + // frames are unchanged -- the Worker constructor reads frames[2] to resolve + // its module directory. + static Function createFromHostConstructor(Runtime& runtime, const PropNameID& name, + unsigned int paramCount, HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args, size_t count) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsFunction( + runtime.context(), local(runtime), JSContextGetGlobalObject(runtime.context()), argv.size(), + argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + return Value(runtime, result); + } + + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + // `count` is deduced rather than fixed to size_t on purpose. + // + // With a `size_t` parameter, `fn.call(rt, args, 2)` needed an int -> size_t + // conversion here while the variadic overload below matched exactly -- so the + // variadic won, and silently reinterpreted (array, count) as a two-argument + // JS call passing the array and the number. The array then decayed to a + // pointer and converted to `bool`, so console.log(str) came out as "true". + // Deducing the count makes this overload exact too, and partial ordering then + // prefers it over the pack. V8's backend has carried this fix since the + // Node-API shim work; it was never propagated here. + template >>> + Value call(Runtime& runtime, const Value (&args)[N], Count count) const { + return call(runtime, static_cast(args), static_cast(count)); + } + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = + JSObjectCallAsFunction(runtime.context(), local(runtime), thisObject.local(runtime), + argv.size(), argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsConstructor(runtime.context(), local(runtime), argv.size(), + argv.empty() ? nullptr : argv.data(), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + return Value(runtime, result); + } + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + // `count` is deduced rather than fixed to size_t on purpose. + // + // With a `size_t` parameter, `fn.call(rt, args, 2)` needed an int -> size_t + // conversion here while the variadic overload below matched exactly -- so the + // variadic won, and silently reinterpreted (array, count) as a two-argument + // JS call passing the array and the number. The array then decayed to a + // pointer and converted to `bool`, so console.log(str) came out as "true". + // Deducing the count makes this overload exact too, and partial ordering then + // prefers it over the pack. V8's backend has carried this fix since the + // Node-API shim work; it was never propagated here. + template >>> + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], Count count) const { + return callAsConstructor(runtime, static_cast(args), static_cast(count)); + } + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + std::vector initial(size, JSValueMakeUndefined(runtime.context())); + JSValueRef exception = nullptr; + storage_->context = runtime.context(); + storage_->value = + JSObjectMakeArray(runtime.context(), initial.size(), initial.data(), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + JSValueProtect(runtime.context(), storage_->value); + } + + explicit Array(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { + Value length = getProperty(runtime, "length"); + return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; + } + + // See Object::getPropertyBorrowed. + Value getValueAtIndexBorrowed(Runtime& runtime, size_t index) const { + return getValueAtIndex(runtime, index); + } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + JSValueRef exception = nullptr; + JSValueRef result = JSObjectGetPropertyAtIndex(runtime.context(), local(runtime), + static_cast(index), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + return Value(runtime, result); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + JSValueRef exception = nullptr; + JSObjectSetPropertyAtIndex(runtime.context(), local(runtime), static_cast(index), + value.local(runtime), &exception); + if (exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + } + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, JSValueRef value) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = value; + JSValueProtect(runtime.context(), storage_->value); + } + + static BigInt fromInt64(Runtime& runtime, int64_t value) { + JSValueRef exception = nullptr; + JSValueRef result = nullptr; + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + result = JSBigIntCreateWithInt64(runtime.context(), value, &exception); + } + if (result == nullptr || exception != nullptr) { + result = JSValueMakeNumber(runtime.context(), static_cast(value)); + } + return BigInt(runtime, result); + } + + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + JSValueRef exception = nullptr; + JSValueRef result = nullptr; + if (__builtin_available(macOS 15.0, iOS 18.0, *)) { + result = JSBigIntCreateWithUInt64(runtime.context(), value, &exception); + } + if (result == nullptr || exception != nullptr) { + result = JSValueMakeNumber(runtime.context(), static_cast(value)); + } + return BigInt(runtime, result); + } + + String toString(Runtime& runtime, int) const { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); + if (string == nullptr || exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + String result(runtime, string); + JSStringRelease(string); + return result; + } + + JSValueRef local(Runtime& runtime) const { return storage_->value; } + + operator Value() const { return Value::fromStorage(storage_); } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + auto* holder = new jscengine::ArrayBufferHolder(std::move(buffer)); + JSValueRef exception = nullptr; + storage_->context = runtime.context(); + storage_->value = JSObjectMakeArrayBufferWithBytesNoCopy( + runtime.context(), holder->buffer->data(), holder->buffer->size(), + [](void*, void* deallocatorContext) { + delete static_cast(deallocatorContext); + }, + holder, &exception); + if (exception != nullptr) { + delete holder; + throw jscengine::toJSError(runtime, exception); + } + JSValueProtect(runtime.context(), storage_->value); + } + + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { + JSValueRef exception = nullptr; + return JSObjectGetArrayBufferByteLength(runtime.context(), local(runtime), &exception); + } + + uint8_t* data(Runtime& runtime) const { + JSValueRef exception = nullptr; + return static_cast( + JSObjectGetArrayBufferBytesPtr(runtime.context(), local(runtime), &exception)); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC + +#endif // NATIVESCRIPT_JSI_JSC_JSC_RUNTIME_H diff --git a/NativeScript/jsi/jsc/JSCValue.cpp b/NativeScript/jsi/jsc/JSCValue.cpp new file mode 100644 index 000000000..e68f07fc6 --- /dev/null +++ b/NativeScript/jsi/jsc/JSCValue.cpp @@ -0,0 +1,257 @@ +#include "jsi/jsc/JSCRuntime.h" + +#ifdef TARGET_ENGINE_JSC + +namespace nativescript { +namespace engine { + +namespace jscengine { + +JSError toJSError(Runtime& runtime, JSValueRef exception) { + JSGlobalContextRef context = runtime.context(); + std::string message = valueToUtf8(context, exception); + if (exception == nullptr || context == nullptr) { + return JSError(runtime, message); + } + + std::string stack; + if (JSValueIsObject(context, exception)) { + JSObjectRef object = JSValueToObject(context, exception, nullptr); + if (object != nullptr) { + // A thrown Error stringifies as "Name: message"; the shim splits on that + // prefix when it has to rebuild one, so keep what() in that shape and + // carry the object itself for everything else. + JSStringRef stackName = makeJSString("stack"); + JSValueRef stackValue = JSObjectGetProperty(context, object, stackName, nullptr); + JSStringRelease(stackName); + if (stackValue != nullptr && JSValueIsString(context, stackValue)) { + stack = valueToUtf8(context, stackValue); + } + } + } + + return JSError(runtime, message, Value(runtime, exception), std::move(stack)); +} + +JSObjectRef weakRefConstructor(Runtime& runtime) { + auto state = runtime.state(); + if (state->weakRefConstructor != nullptr) { + return state->weakRefConstructor; + } + JSGlobalContextRef context = state->context; + JSStringRef name = makeJSString("WeakRef"); + JSValueRef value = + JSObjectGetProperty(context, JSContextGetGlobalObject(context), name, nullptr); + JSStringRelease(name); + if (value == nullptr || !JSValueIsObject(context, value)) { + return nullptr; + } + JSObjectRef constructor = JSValueToObject(context, value, nullptr); + if (constructor == nullptr || !JSObjectIsConstructor(context, constructor)) { + return nullptr; + } + JSValueProtect(context, constructor); + state->weakRefConstructor = constructor; + return constructor; +} + +JSObjectRef weakRefDeref(Runtime& runtime) { + auto state = runtime.state(); + if (state->weakRefDeref != nullptr) { + return state->weakRefDeref; + } + JSObjectRef constructor = weakRefConstructor(runtime); + if (constructor == nullptr) { + return nullptr; + } + JSGlobalContextRef context = state->context; + JSStringRef prototypeName = makeJSString("prototype"); + JSValueRef prototype = JSObjectGetProperty(context, constructor, prototypeName, nullptr); + JSStringRelease(prototypeName); + if (prototype == nullptr || !JSValueIsObject(context, prototype)) { + return nullptr; + } + JSObjectRef prototypeObject = JSValueToObject(context, prototype, nullptr); + JSStringRef derefName = makeJSString("deref"); + JSValueRef deref = JSObjectGetProperty(context, prototypeObject, derefName, nullptr); + JSStringRelease(derefName); + if (deref == nullptr || !JSValueIsObject(context, deref)) { + return nullptr; + } + JSObjectRef function = JSValueToObject(context, deref, nullptr); + if (function == nullptr || !JSObjectIsFunction(context, function)) { + return nullptr; + } + JSValueProtect(context, function); + state->weakRefDeref = function; + return function; +} + +} // namespace jscengine + +WeakObject::WeakObject(Runtime& runtime, const Value& value) { + if (!value.isObject()) { + // Primitives cannot be weakly held, and the shim only ever weakens objects. + // Staying empty makes lock() report undefined, i.e. "already gone". + return; + } + JSObjectRef constructor = jscengine::weakRefConstructor(runtime); + if (constructor == nullptr) { + return; + } + JSGlobalContextRef context = runtime.context(); + JSValueRef target = value.local(runtime); + JSValueRef exception = nullptr; + JSObjectRef reference = + JSObjectCallAsConstructor(context, constructor, 1, &target, &exception); + if (reference == nullptr || exception != nullptr) { + return; + } + storage_ = std::make_shared(jscengine::ValueStorage::Kind::JSC); + storage_->context = context; + storage_->value = reference; + JSValueProtect(context, reference); +} + +Value WeakObject::lock(Runtime& runtime) const { + if (storage_ == nullptr || storage_->value == nullptr) { + return Value::undefined(); + } + JSObjectRef deref = jscengine::weakRefDeref(runtime); + if (deref == nullptr) { + return Value::undefined(); + } + JSGlobalContextRef context = runtime.context(); + JSValueRef exception = nullptr; + JSValueRef result = JSObjectCallAsFunction( + context, deref, reinterpret_cast(const_cast(storage_->value)), + 0, nullptr, &exception); + if (exception != nullptr || result == nullptr || JSValueIsUndefined(context, result)) { + return Value::undefined(); + } + return Value(runtime, result); +} + +Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } +bool HostObject::set(Runtime&, const PropNameID&, const Value&) { return true; } +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } + +// The defaults reproduce what the engine used to do for an index: stringify it +// and take the named path. A host object that does not override these is +// therefore unaffected by the indexed routing. +Value HostObject::getValueAtIndex(Runtime& runtime, uint32_t index) { + return get(runtime, PropNameID(std::to_string(index))); +} + +bool HostObject::setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value) { + return set(runtime, PropNameID(std::to_string(index)), value); +} + +String::String(Runtime& runtime, JSStringRef string) + : storage_(std::make_shared(jscengine::ValueStorage::Kind::JSC)) { + storage_->context = runtime.context(); + storage_->value = JSValueMakeString(runtime.context(), string); + JSValueProtect(runtime.context(), storage_->value); +} + +std::string String::utf8(Runtime& runtime) const { + return jscengine::valueToUtf8(runtime.context(), storage_->value); +} + +String::operator Value() const { return Value::fromStorage(storage_); } + +Value::Value(Runtime&, const String& value) { + storage_ = value.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Object& object) { + storage_ = object.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Function& function) { + storage_ = function.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Array& array) { + storage_ = array.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) { + storage_ = arrayBuffer.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const BigInt& bigint) { + storage_ = bigint.storage_; + kind_ = storage_ ? storage_->kind : jscengine::ValueStorage::Kind::Undefined; +} + +Object Value::asObject(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Promote borrowed to owned storage for Object. + auto s = std::make_shared(jscengine::ValueStorage::Kind::JSC); + s->context = runtime.context(); + s->value = borrowedValue_ != nullptr ? borrowedValue_ : JSValueMakeUndefined(runtime.context()); + JSValueProtect(runtime.context(), s->value); + return Object::fromValueStorage(std::move(s)); +} + +Object Value::asObjectBorrowed(Runtime& runtime) const { + return asObject(runtime); +} + +String Value::asString(Runtime& runtime) const { + JSValueRef exception = nullptr; + JSStringRef string = JSValueToStringCopy(runtime.context(), local(runtime), &exception); + if (string == nullptr || exception != nullptr) { + throw jscengine::toJSError(runtime, exception); + } + String result(runtime, string); + JSStringRelease(string); + return result; +} + +std::string Value::utf8(Runtime& runtime) const { return asString(runtime).utf8(runtime); } + +Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t length) { + return Value(runtime, String::createFromUtf8(runtime, + reinterpret_cast(data), length)); +} + +BigInt Value::getBigInt(Runtime& runtime) const { return BigInt(runtime, local(runtime)); } + +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime).asFunction(runtime); +} +Function Object::asFunction(Runtime&) const { return Function(*this); } +Array Object::getArray(Runtime&) const { return Array(*this); } +ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } + +Array Object::getPropertyNames(Runtime& runtime) const { + JSPropertyNameArrayRef propertyNames = + JSObjectCopyPropertyNames(runtime.context(), local(runtime)); + size_t count = JSPropertyNameArrayGetCount(propertyNames); + Array result(runtime, count); + for (size_t i = 0; i < count; i++) { + JSStringRef name = JSPropertyNameArrayGetNameAtIndex(propertyNames, i); + result.setValueAtIndex(runtime, i, String(runtime, name)); + } + JSPropertyNameArrayRelease(propertyNames); + return result; +} + +void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC diff --git a/NativeScript/jsi/quickjs/QuickJSHostObjects.cpp b/NativeScript/jsi/quickjs/QuickJSHostObjects.cpp new file mode 100644 index 000000000..7e0d82b46 --- /dev/null +++ b/NativeScript/jsi/quickjs/QuickJSHostObjects.cpp @@ -0,0 +1,692 @@ +#include "jsi/quickjs/QuickJSRuntime.h" +#include "../shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_QUICKJS + +namespace nativescript { +class NativeApiObjectHostObject; +} + +namespace nativescript { +namespace engine { + +namespace quickjsengine { + +JSClassID gHostClassId = 0; +JSClassID gFunctionClassId = 0; +JSClassID gNativeStateClassId = 0; + +namespace { +std::mutex& runtimeStatesMutex() { + static auto* mutex = new std::mutex(); + return *mutex; +} + +std::unordered_map>& runtimeStates() { + static auto* states = new std::unordered_map>(); + return *states; +} +} // namespace + +std::shared_ptr stateForContext(JSContext* context) { + std::lock_guard lock(runtimeStatesMutex()); + auto& states = runtimeStates(); + auto it = states.find(context); + if (it != states.end()) { + return it->second; + } + auto state = std::make_shared(context); + states[context] = state; + return state; +} + +void releaseStateForContext(JSContext* context) { + std::shared_ptr state; + { + std::lock_guard lock(runtimeStatesMutex()); + auto it = runtimeStates().find(context); + if (it != runtimeStates().end()) { + state = it->second; + runtimeStates().erase(it); + } + } + // The interned native-state atom must be released here, while the context is + // still alive: a HostObjectHolder holds a shared_ptr to this state and is + // freed by a GC finaliser during JS_FreeContext, so ~RuntimeState can run + // after the context is gone. Leaving the atom would also trip QuickJS' + // atom-leak check at JS_FreeRuntime. + if (state != nullptr && state->nativeStateAtom != JS_ATOM_NULL) { + JS_FreeAtom(context, state->nativeStateAtom); + state->nativeStateAtom = JS_ATOM_NULL; + } +} + +// QuickJS interns a canonical array index as a *tagged integer* atom rather +// than as a string: JS_ValueToAtom turns an int JSValue straight into +// `index | JS_ATOM_TAG_INT`, and that is the atom the exotic get/set handlers +// receive for `a[0]`. So the index can be recovered with a mask, where +// JS_AtomToCString would allocate a C string that the host object then has to +// parse back into an integer on every element access. +// +// JS_ATOM_TAG_INT is engine-internal -- it appears in quickjs.c, not quickjs.h +// -- and is the same value in bellard QuickJS and quickjs-ng. Rather than trust +// that, each runtime verifies it once (verifyIndexAtomTagging) by asking the +// engine for the atom of a known integer through the same public API the +// property paths use. If the check fails, indexAtomsAreTagged stays false and +// every access takes the string path it took before, which is still correct. +static constexpr JSAtom kAtomTagInt = 1U << 31; + +static inline bool atomAsArrayIndex(const RuntimeState& state, JSAtom atom, uint32_t* index) { + if (!state.indexAtomsAreTagged || (atom & kAtomTagInt) == 0) { + return false; + } + // A tagged-int atom holds at most JS_ATOM_MAX_INT (2^31 - 1), which is inside + // the valid array-index range, so no bound check is needed here. + *index = atom & ~kAtomTagInt; + return true; +} + +static void verifyIndexAtomTagging(JSContext* ctx, RuntimeState* state) { + const uint32_t probe = 1234; + JSAtom atom = JS_ValueToAtom(ctx, JS_NewInt32(ctx, static_cast(probe))); + state->indexAtomsAreTagged = (atom & kAtomTagInt) != 0 && (atom & ~kAtomTagInt) == probe; + JS_FreeAtom(ctx, atom); +} + +static bool isNativeInstancePrototypeBypassExcluded(JSContext* ctx, + JSAtom atom) { + const char* name = JS_AtomToCString(ctx, atom); + if (name == nullptr) { + return true; + } + bool excluded = + std::strcmp(name, "kind") == 0 || + std::strcmp(name, "className") == 0 || + std::strcmp(name, "nativeAddress") == 0 || + std::strcmp(name, "class") == 0 || + std::strcmp(name, "constructor") == 0 || + std::strcmp(name, "super") == 0 || + std::strcmp(name, "invoke") == 0 || + std::strcmp(name, "send") == 0 || + std::strcmp(name, "takeRetainedValue") == 0 || + std::strcmp(name, "takeUnretainedValue") == 0 || + std::strcmp(name, "toString") == 0; + JS_FreeCString(ctx, name); + return excluded; +} + +static void freePropertyDescriptor(JSContext* ctx, + JSPropertyDescriptor& desc) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, desc.value); +} + +static JSValue nativePrototypeProperty(JSContext* ctx, JSValueConst obj, + JSAtom atom, JSValueConst receiver, + HostObjectHolder* holder, + bool* handled) { + *handled = false; + if (holder == nullptr || + holder->typeToken != hostObjectTypeToken()) { + return JS_UNDEFINED; + } + + JSValue prototype = JS_GetPrototype(ctx, obj); + if (JS_IsException(prototype)) { + *handled = true; + return prototype; + } + + for (size_t depth = 0; depth < 64 && JS_IsObject(prototype); depth++) { + JSPropertyDescriptor desc = {}; + int found = JS_GetOwnProperty(ctx, &desc, prototype, atom); + if (found < 0) { + JS_FreeValue(ctx, prototype); + *handled = true; + return JS_EXCEPTION; + } + if (found > 0) { + if (isNativeInstancePrototypeBypassExcluded(ctx, atom)) { + freePropertyDescriptor(ctx, desc); + JS_FreeValue(ctx, prototype); + return JS_UNDEFINED; + } + + *handled = true; + JS_FreeValue(ctx, prototype); + if ((desc.flags & JS_PROP_GETSET) != 0) { + JSValue getter = desc.getter; + JS_FreeValue(ctx, desc.setter); + JS_FreeValue(ctx, desc.value); + if (JS_IsUndefined(getter)) { + JS_FreeValue(ctx, getter); + return JS_UNDEFINED; + } + JSValue result = JS_Call(ctx, getter, receiver, 0, nullptr); + JS_FreeValue(ctx, getter); + return result; + } + + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return desc.value; + } + + JSValue nextPrototype = JS_GetPrototype(ctx, prototype); + JS_FreeValue(ctx, prototype); + if (JS_IsException(nextPrototype)) { + *handled = true; + return nextPrototype; + } + prototype = nextPrototype; + } + + JS_FreeValue(ctx, prototype); + return JS_UNDEFINED; +} + +static JSValue nativeHostGet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return JS_UNDEFINED; + } + // The holder already owns the RuntimeState, so take it from there rather than + // from stateForContext(), which takes a process-wide mutex and hashes the + // JSContext* on every property access. On an 8-entry marshalling profile that + // lookup was 600 ms of self time plus 360 ms inside pthread_mutex. + Runtime runtime(holder->state); + try { + // Ahead of the prototype walk: an index is never a named property of a + // native instance, so that walk can only fail for one. + uint32_t index = 0; + if (holder->hostObject->hasIndexedAccess() && + atomAsArrayIndex(*holder->state, atom, &index)) { + Value self = Value::borrowed(runtime, obj); + HostObject::ReceiverScope receiverScope(*holder->hostObject, self); + return holder->hostObject->getValueAtIndex(runtime, index).local(runtime); + } + + bool handledByPrototype = false; + JSValue prototypeResult = + nativePrototypeProperty(ctx, obj, atom, receiver, holder, + &handledByPrototype); + if (handledByPrototype) { + return prototypeResult; + } + + // The receiver for this dispatch is the host object itself, matching what + // the Node-API binding passes as `host_object` (quickjs-api.c's + // host_object_get dups `obj`, not `receiver`). Non-owning: see + // HostObject::receiver. + Value self = Value::borrowed(runtime, obj); + HostObject::ReceiverScope receiverScope(*holder->hostObject, self); + Value result = holder->hostObject->get(runtime, PropNameID(atomToUtf8(ctx, atom))); + if (!result.isUndefined()) { + return result.local(runtime); + } + return JS_UNDEFINED; + } catch (const JSError& error) { + // Re-throw the original value, not a TypeError built from its text: the + // Node-API shim carries the thrown object (and NativeScriptException's + // `nativeException` with it) on JSError. + return throwJSError(runtime, error); + } catch (const std::exception& error) { + return throwError(ctx, error); + } +} + +static int nativeHostSet(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, + JSValueConst, int) { + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return 0; + } + // The holder already owns the RuntimeState, so take it from there rather than + // from stateForContext(), which takes a process-wide mutex and hashes the + // JSContext* on every property access. On an 8-entry marshalling profile that + // lookup was 600 ms of self time plus 360 ms inside pthread_mutex. + Runtime runtime(holder->state); + try { + Value self = Value::borrowed(runtime, obj); + HostObject::ReceiverScope receiverScope(*holder->hostObject, self); + uint32_t index = 0; + if (holder->hostObject->hasIndexedAccess() && + atomAsArrayIndex(*holder->state, atom, &index)) { + return holder->hostObject->setValueAtIndex(runtime, index, + Value::borrowed(runtime, value)) + ? 1 + : 0; + } + bool handled = holder->hostObject->set( + runtime, PropNameID(atomToUtf8(ctx, atom)), + Value::borrowed(runtime, value)); + return handled ? 1 : 0; + } catch (const JSError& error) { + throwJSError(runtime, error); + return -1; + } catch (const std::exception& error) { + throwError(ctx, error); + return -1; + } +} + +static int nativeHostHas(JSContext* ctx, JSValueConst obj, JSAtom atom) { + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return 0; + } + // The holder already owns the RuntimeState, so take it from there rather than + // from stateForContext(), which takes a process-wide mutex and hashes the + // JSContext* on every property access. On an 8-entry marshalling profile that + // lookup was 600 ms of self time plus 360 ms inside pthread_mutex. + Runtime runtime(holder->state); + try { + Value self = Value::borrowed(runtime, obj); + HostObject::ReceiverScope receiverScope(*holder->hostObject, self); + auto names = holder->hostObject->getPropertyNames(runtime); + std::string requested = atomToUtf8(ctx, atom); + for (const auto& name : names) { + if (name.utf8(runtime) == requested) { + return 1; + } + } + } catch (const std::exception&) { + } + return 0; +} + +static int nativeHostOwnNames(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, + JSValueConst obj) { + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + *ptab = nullptr; + *plen = 0; + return 0; + } + // See nativeHostGet: the RuntimeState comes off the holder, not out of the + // mutex-guarded context map. + Runtime runtime(holder->state); + Value self = Value::borrowed(runtime, obj); + HostObject::ReceiverScope receiverScope(*holder->hostObject, self); + auto names = holder->hostObject->getPropertyNames(runtime); + *plen = static_cast(names.size()); + // A host object that reports no names must not reach the allocator: + // quickjs-ng asserts count != 0 && size != 0 in js_calloc_rt, where bellard + // QuickJS returns a valid empty block. JSON.stringify on such an object goes + // Object.keys -> JS_GetOwnPropertyNamesInternal -> here, and aborted the + // whole runtime on QUICKJS_NG. + if (names.empty()) { + *ptab = nullptr; + return 0; + } + *ptab = static_cast(js_mallocz(ctx, sizeof(JSPropertyEnum) * names.size())); + for (uint32_t i = 0; i < *plen; i++) { + (*ptab)[i].is_enumerable = true; + (*ptab)[i].atom = JS_NewAtom(ctx, names[i].utf8(runtime).c_str()); + } + return 0; +} + +static void nativeHostFinalize(JSRuntime*, JSValue value) { + auto* holder = static_cast(JS_GetOpaque(value, gHostClassId)); + delete holder; +} + +static JSValue invokeFunctionHolder(JSContext* ctx, FunctionHolder* holder, JSValueConst thisValue, + int argc, JSValueConst* argv) { + if (holder == nullptr || !holder->callback) { + return JS_UNDEFINED; + } + // See nativeHostGet: the RuntimeState comes off the holder, not out of the + // mutex-guarded context map. + Runtime runtime(holder->state); + StackValueArray args(static_cast(argc)); + for (int i = 0; i < argc; i++) { + args.emplace(static_cast(i), Value::borrowed(runtime, argv[i])); + } + try { + Value self = Value::borrowed(runtime, thisValue); + Value result = + holder->callback(runtime, self, args.size() == 0 ? nullptr : args.data(), args.size()); + return result.local(runtime); + } catch (const JSError& error) { + return throwJSError(runtime, error); + } catch (const std::exception& error) { + return throwError(ctx, error); + } +} + +static JSValue nativeFunctionCall(JSContext* ctx, JSValue function, JSValue thisValue, int argc, + JSValue* argv, int flags) { + auto* holder = static_cast(JS_GetOpaque(function, gFunctionClassId)); + if ((flags & JS_CALL_FLAG_CONSTRUCTOR) == 0) { + return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); + } + + // Called through `new`. QuickJS hands a JSClassCall the *new target* as + // `thisValue` and takes whatever comes back as the construction result -- it + // does not create the receiver for a non-bytecode callee. So synthesise it + // here the way OrdinaryCreateFromConstructor does, and fall back to it when + // the callback returns a non-object, which is what a JS constructor body + // does. + JSValue prototype = JS_GetPropertyStr(ctx, thisValue, "prototype"); + if (JS_IsException(prototype)) { + return prototype; + } + // gNativeStateClassId, not a plain object: the receiver a host constructor + // builds is exactly the object the Node-API shim napi_wraps + // (ObjectManager::Link runs on `this`), and a class-backed object carries an + // opaque slot that napi_unwrap can read in a field load instead of two + // prototype-chain property lookups. The class has no exotic table, so this + // object behaves as an ordinary object in every other respect. + JSValue self = JS_IsObject(prototype) ? JS_NewObjectProtoClass(ctx, prototype, gNativeStateClassId) + : JS_NewObjectClass(ctx, gNativeStateClassId); + JS_FreeValue(ctx, prototype); + if (JS_IsException(self)) { + return self; + } + JSValue result = invokeFunctionHolder(ctx, holder, self, argc, argv); + if (JS_IsException(result) || JS_IsObject(result)) { + JS_FreeValue(ctx, self); + return result; + } + JS_FreeValue(ctx, result); + return self; +} + +static JSValue nativeFunctionCallData(JSContext* ctx, JSValue thisValue, int argc, JSValue* argv, + int, JSValue* data) { + auto* holder = static_cast(JS_GetOpaque(data[0], gFunctionClassId)); + return invokeFunctionHolder(ctx, holder, thisValue, argc, argv); +} + +static void nativeStateFinalize(JSRuntime*, JSValue value) { + auto* holder = static_cast(JS_GetOpaque(value, gNativeStateClassId)); + delete holder; +} + +static void nativeFunctionFinalize(JSRuntime*, JSValue value) { + auto* holder = static_cast(JS_GetOpaque(value, gFunctionClassId)); + delete holder; +} + +// The exotic table for host objects. +// +// On Android the vendored QuickJS is patched (see +// platforms/android/tools/patches/quickjs*/): JS_GetPropertyInternal compares +// the class's exotic table against `NapiHostObjectExoticMethods` *by address* +// and, when they match, treats get_property as a NON-masking fallback -- own +// properties and the whole prototype chain are consulted first, and the host is +// asked only if nothing was found. Every other exotic stays authoritative. +// +// The Node-API binding (vendor/quickjs/quickjs-api.c) owns that symbol on +// its build. On the jsi build that file is not linked, so this layer must both +// define it -- otherwise quickjs.c has an undefined reference -- and register +// its class with it, or host objects would mask their own prototypes and every +// native method on a Java instance would read back undefined. +// +// Off Android the symbol does not exist and the table stays file-local, so the +// Apple runtime is unaffected. +#if defined(USE_HOST_OBJECT) && defined(__ANDROID__) +extern "C" JSClassExoticMethods NapiHostObjectExoticMethods = { +#else +static JSClassExoticMethods NapiHostObjectExoticMethods = { +#endif + .get_own_property = nullptr, + .get_own_property_names = nativeHostOwnNames, + .delete_property = nullptr, + .define_own_property = nullptr, + .has_property = nativeHostHas, + .get_property = nativeHostGet, + .set_property = nativeHostSet, +}; + +void ensureClasses(Runtime& runtime) { + auto state = runtime.state(); + JSRuntime* rt = JS_GetRuntime(runtime.context()); + if (!state->indexAtomTaggingChecked) { + verifyIndexAtomTagging(runtime.context(), state.get()); + state->indexAtomTaggingChecked = true; + } + if (gHostClassId == 0) { + newClassId(rt, &gHostClassId); + } + if (!state->hostClassRegistered) { + JSClassDef def = {}; + def.class_name = "NativeScriptEngineHostObject"; + def.exotic = &NapiHostObjectExoticMethods; + def.finalizer = nativeHostFinalize; + JS_NewClass(rt, gHostClassId, &def); + JS_SetClassProto(runtime.context(), gHostClassId, JS_NewObject(runtime.context())); + state->hostClassRegistered = true; + } + if (gFunctionClassId == 0) { + newClassId(rt, &gFunctionClassId); + } + if (!state->functionClassRegistered) { + JSClassDef def = {}; + def.class_name = "NativeScriptEngineFunction"; + def.call = nativeFunctionCall; + def.finalizer = nativeFunctionFinalize; + JS_NewClass(rt, gFunctionClassId, &def); + JS_SetClassProto(runtime.context(), gFunctionClassId, JS_NewObject(runtime.context())); + state->functionClassRegistered = true; + } + if (gNativeStateClassId == 0) { + newClassId(rt, &gNativeStateClassId); + } + if (!state->nativeStateClassRegistered) { + JSClassDef def = {}; + def.class_name = "Object"; + // No exotic table on purpose. This class exists only to give an ordinary + // object an opaque slot; an exotic table here would put an interception + // hook on every property access of every instance, which is the cost this + // whole change exists to remove. + def.finalizer = nativeStateFinalize; + JS_NewClass(rt, gNativeStateClassId, &def); + // Object.prototype, so an instance built by JS_NewObjectClass (the branch + // taken when a constructor has no object `prototype`) is a normal object + // rather than a null-prototype one. + JSContext* ctx = runtime.context(); + JSValue global = JS_GetGlobalObject(ctx); + JSValue objectCtor = JS_GetPropertyStr(ctx, global, "Object"); + JSValue objectProto = JS_GetPropertyStr(ctx, objectCtor, "prototype"); + JS_FreeValue(ctx, objectCtor); + JS_FreeValue(ctx, global); + if (JS_IsObject(objectProto)) { + JS_SetClassProto(ctx, gNativeStateClassId, objectProto); + } else { + JS_FreeValue(ctx, objectProto); + JS_SetClassProto(ctx, gNativeStateClassId, JS_NewObject(ctx)); + } + state->nativeStateClassRegistered = true; + } +} + +} // namespace quickjsengine + +quickjsengine::HostObjectHolder* Object::hostObjectHolder(Runtime& runtime) const { + quickjsengine::ensureClasses(runtime); + JSValue object = local(runtime); + auto* holder = static_cast( + JS_GetOpaque(object, quickjsengine::gHostClassId)); + JS_FreeValue(runtime.context(), object); + return holder; +} + +void Object::setNativeStateWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + quickjsengine::ensureClasses(runtime); + JSContext* ctx = runtime.context(); + JSValue self = local(runtime); + + // Fast path: the object carries an opaque slot of its own. + if (JS_GetClassID(self) == quickjsengine::gNativeStateClassId) { + // Replacing an existing payload must free the old one; nothing else will, + // and QuickJS reports leaks at JS_FreeRuntime. + delete static_cast( + JS_GetOpaque(self, quickjsengine::gNativeStateClassId)); + JS_SetOpaque(self, new quickjsengine::HostObjectHolder(runtime.state(), std::move(host), + typeToken)); + JS_FreeValue(ctx, self); + return; + } + + // Fallback for an object JS created itself, which Node-API still allows + // napi_wrap on. A non-enumerable property under an interned atom, holding + // the payload in a host object exactly as the old __nsWrap slot did. + Object holder = Object::createFromHostObjectWithToken(runtime, std::move(host), typeToken); + // JS_DefinePropertyValue takes ownership of the value it is handed. Writable + // and configurable so a second wrap replaces the first, as assigning did. + int rc = JS_DefinePropertyValue(ctx, self, runtime.nativeStateAtom(), holder.local(runtime), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, self); + if (rc < 0) { + throw quickjsengine::caughtError(runtime, "QuickJS native state set failed."); + } +} + +std::shared_ptr Object::nativeStateOf(Runtime& runtime, const void* typeToken) const { + JSContext* ctx = runtime.context(); + JSValue self = local(runtime); + + // JS_GetAnyOpaque returns the object's opaque word without a class check, so + // the class id it hands back must be validated before the pointer is used -- + // on an object of another class that word is a different union member. + JSClassID classId = 0; + void* opaque = JS_GetAnyOpaque(self, &classId); + if (quickjsengine::gNativeStateClassId != 0 && classId == quickjsengine::gNativeStateClassId) { + JS_FreeValue(ctx, self); + auto* holder = static_cast(opaque); + if (holder == nullptr || holder->typeToken != typeToken) return nullptr; + return holder->hostObject; + } + + // Property fallback. Own-only: the miss is the common case (every receiver + // the runtime probes), and walking a prototype chain to answer it was the + // cost this change removes. + if (runtime.state()->nativeStateAtom == JS_ATOM_NULL) { + JS_FreeValue(ctx, self); + return nullptr; + } + JSPropertyDescriptor descriptor; + int rc = JS_GetOwnProperty(ctx, &descriptor, self, runtime.nativeStateAtom()); + JS_FreeValue(ctx, self); + if (rc <= 0) return nullptr; + JS_FreeValue(ctx, descriptor.getter); + JS_FreeValue(ctx, descriptor.setter); + std::shared_ptr result; + auto* holder = static_cast( + JS_GetOpaque(descriptor.value, quickjsengine::gHostClassId)); + if (holder != nullptr && holder->typeToken == typeToken) { + result = holder->hostObject; + } + JS_FreeValue(ctx, descriptor.value); + return result; +} + +Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + quickjsengine::ensureClasses(runtime); + auto* holder = new quickjsengine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + JSValue object = JS_NewObjectClass(runtime.context(), quickjsengine::gHostClassId); + JS_SetOpaque(object, holder); + Object result = Object::fromValueStorage(Value(runtime, object).storage_); + JS_FreeValue(runtime.context(), object); + return result; +} + +Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, + unsigned int parameterCount, HostFunctionType callback) { + quickjsengine::ensureClasses(runtime); + auto* holder = new quickjsengine::FunctionHolder(runtime.state(), std::move(callback)); + JSValue data = JS_NewObjectClass(runtime.context(), quickjsengine::gFunctionClassId); + if (JS_IsException(data)) { + delete holder; + throw JSError(runtime, "QuickJS host function data allocation failed."); + } + JS_SetOpaque(data, holder); + + JSValue function = JS_NewCFunctionData(runtime.context(), quickjsengine::nativeFunctionCallData, + static_cast(parameterCount), 0, 1, &data); + JS_FreeValue(runtime.context(), data); + if (JS_IsException(function)) { + throw JSError(runtime, "QuickJS host function allocation failed."); + } + + std::string functionName = name.utf8(runtime); + JSValue nameValue = JS_NewStringLen(runtime.context(), functionName.data(), functionName.size()); + JS_DefinePropertyValueStr(runtime.context(), function, "name", nameValue, JS_PROP_CONFIGURABLE); + Function result = Function(Object::fromValueStorage(Value(runtime, function).storage_)); + JS_FreeValue(runtime.context(), function); + return result; +} + +Function Function::createFromHostConstructor(Runtime& runtime, const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback) { + quickjsengine::ensureClasses(runtime); + JSContext* ctx = runtime.context(); + + // An object of the engine's function class rather than JS_NewCFunctionData: + // its JSClassCall receives QuickJS' call flags, which is the only way to tell + // `new` from a plain call, and its constructor bit can be set. See the header. + auto* holder = new quickjsengine::FunctionHolder(runtime.state(), std::move(callback)); + JSValue function = JS_NewObjectClass(ctx, quickjsengine::gFunctionClassId); + if (JS_IsException(function)) { + delete holder; + throw JSError(runtime, "QuickJS host constructor allocation failed."); + } + JS_SetOpaque(function, holder); + JS_SetConstructorBit(ctx, function, 1); + + // The class prototype registered in ensureClasses is a plain object, so + // without this the constructor would not inherit call/apply/bind/toString and + // `ctor instanceof Function` would be false. + JSValue global = JS_GetGlobalObject(ctx); + JSValue functionCtor = JS_GetPropertyStr(ctx, global, "Function"); + JSValue functionProto = JS_GetPropertyStr(ctx, functionCtor, "prototype"); + if (JS_IsObject(functionProto)) { + JS_SetPrototype(ctx, function, functionProto); + } + JS_FreeValue(ctx, functionProto); + JS_FreeValue(ctx, functionCtor); + JS_FreeValue(ctx, global); + + // JS_DefinePropertyValueStr, not a plain set: `name` is defined as an own + // property, so the non-writable Function.prototype.name installed on the + // prototype chain just above cannot swallow it the way it swallows a [[Set]] + // in sloppy mode. (That is what made ctor.name read back as "" on JSC.) + const std::string functionName = name.utf8(runtime); + JS_DefinePropertyValueStr( + ctx, function, "name", + JS_NewStringLen(ctx, functionName.data(), functionName.size()), JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, function, "length", + JS_NewUint32(ctx, static_cast(paramCount)), + JS_PROP_CONFIGURABLE); + + // WRITABLE is the whole point: napi_define_class reads this object back and + // the runtime later reassigns it outright to chain class prototypes. + // + // The prototype also needs its `constructor` back-pointer. Per spec a + // function's prototype carries a non-enumerable, writable, configurable + // `constructor` naming the function; V8 (Function::New) and JSC install it + // for us, but a bare JS_NewObject has none, so `instance.constructor` walked + // straight past it to Object.prototype.constructor and every native class + // reported its name as "Object". + JSValue prototype = JS_NewObject(ctx); + JS_DefinePropertyValueStr(ctx, prototype, "constructor", JS_DupValue(ctx, function), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValueStr(ctx, function, "prototype", prototype, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + Function result = Function(Object::fromValueStorage(Value(runtime, function).storage_)); + JS_FreeValue(ctx, function); + return result; +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/jsi/quickjs/QuickJSRuntime.cpp b/NativeScript/jsi/quickjs/QuickJSRuntime.cpp new file mode 100644 index 000000000..6384516c4 --- /dev/null +++ b/NativeScript/jsi/quickjs/QuickJSRuntime.cpp @@ -0,0 +1,40 @@ +#include "jsi/quickjs/QuickJSRuntime.h" + +#ifdef TARGET_ENGINE_QUICKJS + +namespace nativescript { +namespace engine { + +String BigInt::toString(Runtime& runtime, int) const { + JSValue value = local(runtime); + JSValue stringValue = JS_ToString(runtime.context(), value); + JS_FreeValue(runtime.context(), value); + String result(runtime, stringValue); + JS_FreeValue(runtime.context(), stringValue); + return result; +} + +Object Runtime::global() { + JSValue global = JS_GetGlobalObject(context()); + Object result = Object::fromValueStorage(Value(*this, global).storage_); + JS_FreeValue(context(), global); + return result; +} + +Value Runtime::evaluateJavaScript(std::shared_ptr buffer, + const std::string& sourceURL) { + JSValue result = + JS_Eval(context(), buffer != nullptr ? buffer->data() : "", + buffer != nullptr ? buffer->size() : 0, sourceURL.c_str(), JS_EVAL_TYPE_GLOBAL); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(*this, "QuickJS script evaluation failed."); + } + Value value(*this, result); + JS_FreeValue(context(), result); + return value; +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/jsi/quickjs/QuickJSRuntime.h b/NativeScript/jsi/quickjs/QuickJSRuntime.h new file mode 100644 index 000000000..c2d0f63c0 --- /dev/null +++ b/NativeScript/jsi/quickjs/QuickJSRuntime.h @@ -0,0 +1,1239 @@ +#ifndef NATIVESCRIPT_JSI_QUICKJS_QUICKJS_RUNTIME_H +#define NATIVESCRIPT_JSI_QUICKJS_QUICKJS_RUNTIME_H + +// QuickJS behind the JSI-shaped `nativescript::engine` API. +// +// This header is platform-neutral: it depends on QuickJS and the C++ standard +// library, and on nothing from Foundation, the Objective-C runtime, or the +// Apple metadata format. Android compiles it as-is. +// +// The Apple bridge's extra baggage -- Foundation, , the Mach-O +// metadata section lookup, and the NativeApiClassBuilderProtocol forward +// declaration -- lives in ffi/objc/quickjs/NativeApiQuickJSRuntime.h, which +// includes this file. Apple sources keep including that path unchanged. + +#ifdef TARGET_ENGINE_QUICKJS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "quickjs.h" + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +// Mirrors jsi::JSError. See the V8 engine layer for why the thrown value +// matters: rebuilding an error from its message drops whatever the runtime +// attached to it (NativeScriptException's `nativeException`) and its stack. +// +// This engine is not yet wired to the Node-API shim, so nothing populates the +// payload here and value() always reports null -- callers fall back to the +// message. The API exists so the shim stays engine-neutral. +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} + + JSError(Runtime& runtime, const std::string& message, const Value& value, + std::string stack); + + const Value* value() const { return value_.get(); } + const std::string& stack() const { return stack_; } + + private: + std::shared_ptr value_; + std::string stack_; +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) : value_(std::move(value)) {} + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? value : ""); + } + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + std::string utf8(Runtime&) const { return value_; } + + private: + std::string value_; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); + + // Indexed access, taking the index as an integer rather than as the decimal + // string the named form would hand over. See V8Runtime.h for the full note. + // QuickJS has no indexed hook, so this layer recognises an index-carrying + // atom and routes it here -- but only for a host object that opted in, since + // the named path also does the prototype handling. + virtual Value getValueAtIndex(Runtime& runtime, uint32_t index); + virtual bool setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value); + + bool hasIndexedAccess() const { return indexedAccess_; } + void setIndexedAccess(bool value) { indexedAccess_ = value; } + + // The JS object standing for this host object, valid ONLY for the duration of + // the call the engine is currently dispatching. Mirrors the V8 and Hermes + // layers; see V8Runtime.h for the full note. + // + // Handed in per call rather than stored, and deliberately non-owning. A + // HostObject holding an owned handle to its own wrapper is a strong + // self-cycle: the wrapper is then permanently reachable, its finalizer never + // runs, and on Android ObjectManager never calls makeInstanceWeak -- every + // Java instance stays pinned. + const Value* receiver() const { return receiver_; } + + // Sets the receiver for one dispatch and clears it on scope exit. + class ReceiverScope { + public: + ReceiverScope(HostObject& host, const Value& receiver) : host_(host) { + host_.receiver_ = &receiver; + } + ~ReceiverScope() { host_.receiver_ = nullptr; } + ReceiverScope(const ReceiverScope&) = delete; + ReceiverScope& operator=(const ReceiverScope&) = delete; + + private: + HostObject& host_; + }; + + private: + const Value* receiver_ = nullptr; + bool indexedAccess_ = false; +}; + +using HostFunctionType = std::function; + +namespace quickjsengine { + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct RuntimeState { + explicit RuntimeState(JSContext* context) : context(context) {} + JSContext* context = nullptr; + bool hostClassRegistered = false; + bool functionClassRegistered = false; + bool selectorGroupDataClassRegistered = false; + bool nativeStateClassRegistered = false; + // The atom every native-state slot hangs off, interned once per runtime. + // Classic QuickJS has no JS_NewSymbol, so this is a string atom -- but the + // property is defined non-enumerable and read with JS_GetOwnProperty, which + // skips the prototype chain entirely. + // + // Freed by releaseStateForContext, not by ~RuntimeState: a HostObjectHolder + // keeps a shared_ptr to this state and is itself released by a GC finaliser + // during JS_FreeContext, so the state can outlive the context it names. + JSAtom nativeStateAtom = JS_ATOM_NULL; + // Whether an array index can be read straight out of the atom the exotic + // handlers are handed. Established once per runtime in ensureClasses; see + // atomAsArrayIndex in QuickJSHostObjects.cpp. + bool indexAtomsAreTagged = false; + bool indexAtomTaggingChecked = false; +}; + +extern JSClassID gHostClassId; +extern JSClassID gFunctionClassId; +// An ordinary object that happens to carry an opaque slot: no exotic table, so +// property access on it is exactly what it is on a plain object. Used for the +// receiver a host constructor builds, which is the object the Node-API shim +// wraps. See Object::setNativeState. +extern JSClassID gNativeStateClassId; + +// JS_NewClassID gained a JSRuntime* parameter in quickjs-ng. Apple and Android's +// QUICKJS_NG build take the two-argument form, Android's classic QuickJS build +// the one-argument form; both are the same call. Resolved by overload rather +// than by #if, because there is no version macro that separates the two +// consistently across all three vendored copies. +// Both template parameters are deduced so that each overload's return type is +// dependent -- a non-dependent decltype would be a hard error, not a +// substitution failure, and the wrong arm would break the build outright. +template +inline auto newClassIdImpl(R* runtime, C* classId, int) + -> decltype(JS_NewClassID(runtime, classId)) { + return JS_NewClassID(runtime, classId); +} +template +inline auto newClassIdImpl(R*, C* classId, long) -> decltype(JS_NewClassID(classId)) { + return JS_NewClassID(classId); +} +inline JSClassID newClassId(JSRuntime* runtime, JSClassID* classId) { + return newClassIdImpl(runtime, classId, 0); +} + +// JS_IsBigInt and JS_IsArray dropped their JSContext* in quickjs-ng. Android's +// QUICKJS_NG build takes the one-argument form; Android's classic QuickJS and +// the Apple copy take the two-argument one. Same overload trick as above. +template +inline auto isBigIntImpl(C* context, const V& value, int) + -> decltype(JS_IsBigInt(context, value)) { + return JS_IsBigInt(context, value); +} +template +inline auto isBigIntImpl(C*, const V& value, long) -> decltype(JS_IsBigInt(value)) { + return JS_IsBigInt(value); +} +inline bool isBigInt(JSContext* context, JSValueConst value) { + return isBigIntImpl(context, value, 0) != 0; +} + +template +inline auto isArrayImpl(C* context, const V& value, int) + -> decltype(JS_IsArray(context, value)) { + return JS_IsArray(context, value); +} +template +inline auto isArrayImpl(C*, const V& value, long) -> decltype(JS_IsArray(value)) { + return JS_IsArray(value); +} +inline bool isArray(JSContext* context, JSValueConst value) { + return isArrayImpl(context, value, 0) != 0; +} + +std::shared_ptr stateForContext(JSContext* context); + +// Drops the RuntimeState cached for a context that is about to be freed. +// +// The cache is keyed by JSContext*, and the allocator hands the same address +// back for the next runtime -- workers create and destroy one each. A surviving +// entry would give the new context a state that already says +// hostClassRegistered, so JS_NewClass would never run for its JSRuntime and +// every host object created on it would carry an unregistered class id. +void releaseStateForContext(JSContext* context); + +struct ValueStorage { + enum class Kind { + Undefined, + Null, + Bool, + Number, + QuickJS, + QuickJSBorrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + ~ValueStorage() { + if (kind == Kind::QuickJS && context != nullptr && !JS_IsUninitialized(value)) { + JS_FreeValue(context, value); + } + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + JSContext* context = nullptr; + JSValue value = JS_UNINITIALIZED; +}; + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + std::shared_ptr state; + HostFunctionType callback; +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + std::shared_ptr buffer; +}; + +inline std::string valueToUtf8(JSContext* context, JSValueConst value) { + size_t length = 0; + const char* cString = JS_ToCStringLen(context, &length, value); + if (cString == nullptr) { + return {}; + } + std::string result(cString, length); + JS_FreeCString(context, cString); + return result; +} + +inline std::string currentExceptionMessage(JSContext* context) { + JSValue exception = JS_GetException(context); + std::string message = valueToUtf8(context, exception); + JS_FreeValue(context, exception); + return message.empty() ? std::string("QuickJS function call failed.") + : message; +} + +inline std::string atomToUtf8(JSContext* context, JSAtom atom) { + const char* cString = JS_AtomToCString(context, atom); + if (cString == nullptr) { + return {}; + } + std::string result(cString); + JS_FreeCString(context, cString); + return result; +} + +inline JSValue throwError(JSContext* context, const std::exception& error) { + return JS_ThrowTypeError(context, "%s", error.what()); +} + +void ensureClasses(Runtime& runtime); + +} // namespace quickjsengine + +// Mirrors jsi::WeakObject. lock() returns undefined once the referent has been +// collected. +// +// Backed by a JS `WeakRef`, which is what QuickJS exposes -- it has no +// C-level weak handle. Same mechanism the Node-API binding uses +// (vendor/quickjs/quickjs-api.c's napi_create_reference), so the two +// binding layers have the same collection behaviour. +// +// A value WeakRef cannot hold -- a primitive, or a string -- is kept strongly +// instead. Those are not garbage in the sense a weak reference cares about, and +// reporting them as collected would be wrong. +class WeakObject { + public: + WeakObject() = default; + WeakObject(Runtime& runtime, const Value& value); + Value lock(Runtime& runtime) const; + bool empty() const { return storage_ == nullptr; } + void reset() { + storage_.reset(); + isWeakRef_ = false; + } + + private: + std::shared_ptr storage_; + bool isWeakRef_ = false; +}; + +class Runtime { + public: + explicit Runtime(JSContext* context) : state_(quickjsengine::stateForContext(context)) {} + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + JSContext* context() const { return state_->context; } + std::shared_ptr state() const { return state_; } + + // A stable, per-runtime identity. + // + // engine::Runtime is a value wrapper around shared engine state, and the + // host-function trampolines construct a fresh one on the stack for every + // callback. So `&runtime` is NOT stable and must never be used as a map key; + // this is. The pointer is opaque and only ever compared or hashed. + const void* identity() const { return state_.get(); } + // See RuntimeState::nativeStateAtom. Interned on first use. + JSAtom nativeStateAtom() const { + if (state_->nativeStateAtom == JS_ATOM_NULL) { + state_->nativeStateAtom = JS_NewAtom(state_->context, "__nsNativeState"); + } + return state_->nativeStateAtom; + } + Object global(); + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + void drainMicrotasks() { + JSContext* ctx = context(); + JSRuntime* rt = JS_GetRuntime(ctx); + JSContext* jobCtx = nullptr; + while (JS_ExecutePendingJob(rt, &jobCtx) > 0) { + } + } + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, JSValue value); + + // The three factories below adopt the reference JS_New*String returns instead + // of going through String(Runtime&, JSValue), which *duplicates* it. That + // constructor's contract is "the caller keeps its own reference and frees it" + // -- correct for Value::asString, which does free -- but a freshly created + // string has no other owner, so duplicating it left the refcount permanently + // one too high. Every JS string built through this layer on QuickJS leaked. + static String adopt(Runtime& runtime, JSValue value) { + String result; + result.storage_ = std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS); + result.storage_->context = runtime.context(); + result.storage_->value = value; + return result; + } + static String createFromUtf8(Runtime& runtime, const char* value) { + return adopt(runtime, JS_NewString(runtime.context(), value != nullptr ? value : "")); + } + static String createFromUtf8(Runtime& runtime, const std::string& value) { + return adopt(runtime, JS_NewStringLen(runtime.context(), value.data(), value.size())); + } + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + return adopt(runtime, + JS_NewStringLen(runtime.context(), reinterpret_cast(value), length)); + } + std::string utf8(Runtime& runtime) const; + JSValue local(Runtime& runtime) const; + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(quickjsengine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(quickjsengine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(quickjsengine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { + // Promote borrowed to owned + storage_ = std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS); + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value.borrowedValue_); + kind_ = quickjsengine::ValueStorage::Kind::QuickJS; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedContext_ = value.borrowedContext_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + borrowedContext_(value.borrowedContext_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) {} + Value(Runtime& runtime, const String& value) : storage_(value.storage_) { + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; + } + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + Value(Runtime& runtime, JSValue value) + : kind_(quickjsengine::ValueStorage::Kind::QuickJS), + storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); + } + + static Value borrowed(Runtime& runtime, JSValueConst value) { + Value result; + result.kind_ = quickjsengine::ValueStorage::Kind::QuickJSBorrowed; + result.borrowedContext_ = runtime.context(); + result.borrowedValue_ = value; + return result; + } + + static Value undefined() { return Value(); } + static Value null() { + Value value; + value.kind_ = quickjsengine::ValueStorage::Kind::Null; + return value; + } + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + JSValue lhsValue = lhs.local(runtime); + JSValue rhsValue = rhs.local(runtime); + bool equal = JS_IsStrictEqual(runtime.context(), lhsValue, rhsValue); + JS_FreeValue(runtime.context(), lhsValue); + JS_FreeValue(runtime.context(), rhsValue); + return equal; + } + bool isUndefined() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Undefined) { + return true; + } + return isQuickJS() && JS_IsUndefined(jsValue()); + } + bool isNull() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Null) { + return true; + } + return isQuickJS() && JS_IsNull(jsValue()); + } + bool isBool() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { + return true; + } + return isQuickJS() && JS_IsBool(jsValue()); + } + bool getBool() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Bool) { + return boolValue_; + } + if (isQuickJS()) { + return JS_ToBool(jsContext(), jsValue()) != 0; + } + return false; + } + bool isNumber() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Number) { + return true; + } + return isQuickJS() && JS_IsNumber(jsValue()); + } + double getNumber() const { + if (kind_ == quickjsengine::ValueStorage::Kind::Number) { + return numberValue_; + } + if (isQuickJS()) { + double value = 0; + JS_ToFloat64(jsContext(), &value, jsValue()); + return value; + } + return 0; + } + bool isObject() const { return isQuickJS() && JS_IsObject(jsValue()); } + bool isString() const { return isQuickJS() && JS_IsString(jsValue()); } + bool isBigInt() const { + return isQuickJS() && quickjsengine::isBigInt(jsContext(), jsValue()); + } + bool isSymbol() const { return isQuickJS() && JS_IsSymbol(jsValue()); } + + Object asObject(Runtime& runtime) const; + // Borrowing is a V8-only capability; see jsi/v8/V8Runtime.h. Everywhere else + // this is the owning conversion, and callers get the stronger guarantee. + // Declared, not defined: Object is still incomplete here, so a body calling + // asObject would not compile. Defined next to asObject in QuickJSValue.cpp. + Object asObjectBorrowed(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + // Read the UTF-8 of a string value without materialising a String. See the + // comment on the V8 declaration: String is an owning type, and building one + // costs a make_shared plus a JS_DupValue/JS_FreeValue pair for a handle that + // dies two statements later. + std::string utf8(Runtime& runtime) const; + + // Create a string value, adopting the reference JS_NewStringLen returns + // instead of duplicating it. QuickJS strings are refcounted rather than + // scope-rooted, so unlike V8 this still needs owning storage -- but it does + // not need a second reference. + static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length); + + JSValue local(Runtime& runtime) const { + switch (kind_) { + case quickjsengine::ValueStorage::Kind::Undefined: + return JS_UNDEFINED; + case quickjsengine::ValueStorage::Kind::Null: + return JS_NULL; + case quickjsengine::ValueStorage::Kind::Bool: + return JS_NewBool(runtime.context(), boolValue_); + case quickjsengine::ValueStorage::Kind::Number: + return JS_NewFloat64(runtime.context(), numberValue_); + case quickjsengine::ValueStorage::Kind::QuickJS: + case quickjsengine::ValueStorage::Kind::QuickJSBorrowed: + return JS_DupValue(runtime.context(), jsValue()); + } + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + bool isQuickJS() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJS || + kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed; + } + JSContext* jsContext() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedContext_ + : storage_->context; + } + JSValue jsValue() const { + return kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed ? borrowedValue_ + : storage_->value; + } + + quickjsengine::ValueStorage::Kind kind_ = quickjsengine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + JSContext* borrowedContext_ = nullptr; + JSValue borrowedValue_ = JS_UNINITIALIZED; + std::shared_ptr storage_; +}; + +// Defined here rather than with the class: constructing the shared_ptr needs +// Value to be complete. +inline WeakObject::WeakObject(Runtime& runtime, const Value& value) { + JSContext* ctx = runtime.context(); + JSValue target = value.local(runtime); + storage_ = + std::make_shared(quickjsengine::ValueStorage::Kind::QuickJS); + storage_->context = ctx; + + if (!JS_IsObject(target) && !JS_IsSymbol(target)) { + storage_->value = target; + return; + } + + JSValue global = JS_GetGlobalObject(ctx); + JSValue weakRefCtor = JS_GetPropertyStr(ctx, global, "WeakRef"); + JS_FreeValue(ctx, global); + if (!JS_IsFunction(ctx, weakRefCtor)) { + JS_FreeValue(ctx, weakRefCtor); + storage_->value = target; + return; + } + JSValue args[1] = {target}; + JSValue weakRef = JS_CallConstructor(ctx, weakRefCtor, 1, args); + JS_FreeValue(ctx, weakRefCtor); + if (JS_IsException(weakRef)) { + // Nothing may throw out of here: this runs from napi_create_reference, + // which has no way to report it. Falling back to a strong handle keeps the + // reference usable; it merely stops being collectable. + JS_FreeValue(ctx, JS_GetException(ctx)); + JS_FreeValue(ctx, weakRef); + storage_->value = target; + return; + } + JS_FreeValue(ctx, target); + storage_->value = weakRef; + isWeakRef_ = true; +} + +inline Value WeakObject::lock(Runtime& runtime) const { + if (storage_ == nullptr) { + return Value::undefined(); + } + if (!isWeakRef_) { + return Value::fromStorage(storage_); + } + JSValue target = JS_WeakRef_Deref(runtime.context(), storage_->value); + Value result(runtime, target); + JS_FreeValue(runtime.context(), target); + return result; +} + +inline JSError::JSError(Runtime& runtime, const std::string& message, + const Value& value, std::string stack) + : std::runtime_error(message), + value_(std::make_shared(runtime, value)), + stack_(std::move(stack)) {} + +namespace quickjsengine { + +// Take the context's pending exception and wrap it in a JSError that CARRIES +// the thrown value, not just its text. +// +// Rebuilding an error from its message loses two things the Node-API shim +// needs: the error's type (a SyntaxError arrives as the string +// "SyntaxError: ..." and reaches JS as a plain Error) and anything the runtime +// attached to it -- NativeScriptException hangs the originating Java throwable +// off `nativeException`, and dropping it makes `e.nativeException.getStackTrace()` +// undefined. This mirrors what the V8 and Hermes layers already do. +// +// Additive: JSError's payload was previously never populated by this engine and +// every existing caller reads only what(), which is unchanged. +inline JSError caughtError(Runtime& runtime, const char* fallback) { + JSContext* context = runtime.context(); + JSValue exception = JS_GetException(context); + // JS_GetException yields null when nothing is pending, which some failure + // paths (a bad atom, an out-of-range index) can reach. Reporting the string + // "null" as the message would be worse than the caller's fallback. + if (JS_IsNull(exception) || JS_IsUninitialized(exception)) { + JS_FreeValue(context, exception); + return JSError(runtime, fallback != nullptr ? fallback : "QuickJS call failed."); + } + std::string message = valueToUtf8(context, exception); + std::string stack; + if (JS_IsObject(exception)) { + JSValue stackValue = JS_GetPropertyStr(context, exception, "stack"); + if (JS_IsString(stackValue)) { + stack = valueToUtf8(context, stackValue); + } + JS_FreeValue(context, stackValue); + } + Value value(runtime, exception); + JS_FreeValue(context, exception); + if (message.empty()) { + message = fallback != nullptr ? fallback : "QuickJS call failed."; + } + return JSError(runtime, message, value, std::move(stack)); +} + +// Re-throw a JSError into JS, preserving the original thrown value when the +// error carries one. Without this every error crossing the host boundary was +// flattened into a TypeError built from its message. +inline JSValue throwJSError(Runtime& runtime, const JSError& error) { + JSContext* context = runtime.context(); + if (const Value* thrown = error.value()) { + if (!thrown->isUndefined() && !thrown->isNull()) { + return JS_Throw(context, thrown->local(runtime)); + } + } + return throwError(context, error); +} + +} // namespace quickjsengine + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_NewObject(runtime.context()); + } + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + quickjsengine::hostObjectTypeToken()); + } + + // A native (Java/ObjC-backed) instance, as opposed to an opaque host object. + // + // Only V8 distinguishes a masking from a non-masking named interceptor, and + // there the difference is large: a native instance carries the class + // prototype where the field accessors live, so a masking interceptor would + // divert every named read into the trap instead of letting V8 resolve it (and + // form a load IC) on the prototype. This backend has no such distinction, so + // a native instance is built exactly like any other host object; the separate + // name exists so callers can express the intent once, for every engine. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + return createFromHostObject(runtime, std::move(host)); + } + + Value getProperty(Runtime& runtime, const char* name) const { + JSValue object = local(runtime); + JSValue result = JS_GetPropertyStr(runtime.context(), object, name != nullptr ? name : ""); + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(runtime, "QuickJS property get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + Value getProperty(Runtime& runtime, const Value& key) const { + JSValue object = local(runtime); + JSValue keyValue = key.local(runtime); + JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); + JS_FreeValue(runtime.context(), keyValue); + JSValue result = + atom == JS_ATOM_NULL ? JS_UNDEFINED : JS_GetProperty(runtime.context(), object, atom); + if (atom != JS_ATOM_NULL) { + JS_FreeAtom(runtime.context(), atom); + } + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(runtime, "QuickJS property get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + // Borrowing is a V8-only capability; see jsi/v8/V8Runtime.h. + // + // The name exists on every engine so the Node-API shim's read paths stay + // engine-neutral, but this engine cannot honour it: a read here hands back a + // handle the caller must keep alive (QuickJS returns a +1 refcount, JSC needs + // JSValueProtect, Hermes owns its jsi::Value), and a bare handle rooted only + // by an ambient scope has no equivalent. So these are the owning reads, and + // callers get the stronger guarantee. + Value getPropertyBorrowed(Runtime& runtime, const char* name) const { + return getProperty(runtime, name); + } + Value getPropertyBorrowed(Runtime& runtime, const Value& key) const { + return getProperty(runtime, key); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + JSValue object = local(runtime); + JSValue localValue = value.local(runtime); + int status = + JS_SetPropertyStr(runtime.context(), object, name != nullptr ? name : "", localValue); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw quickjsengine::caughtError(runtime, "QuickJS property set failed."); + } + } + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + JSValue object = local(runtime); + JSValue keyValue = key.local(runtime); + JSAtom atom = JS_ValueToAtom(runtime.context(), keyValue); + JS_FreeValue(runtime.context(), keyValue); + JSValue localValue = value.local(runtime); + int status = + atom == JS_ATOM_NULL ? -1 : JS_SetProperty(runtime.context(), object, atom, localValue); + if (atom != JS_ATOM_NULL) { + JS_FreeAtom(runtime.context(), atom); + } + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw quickjsengine::caughtError(runtime, "QuickJS property set failed."); + } + } + bool hasProperty(Runtime& runtime, const char* name) const { + JSValue object = local(runtime); + JSAtom atom = JS_NewAtom(runtime.context(), name != nullptr ? name : ""); + int result = JS_HasProperty(runtime.context(), object, atom); + JS_FreeAtom(runtime.context(), atom); + JS_FreeValue(runtime.context(), object); + return result > 0; + } + bool isFunction(Runtime& runtime) const { + JSValue object = local(runtime); + bool result = JS_IsFunction(runtime.context(), object); + JS_FreeValue(runtime.context(), object); + return result; + } + bool isArray(Runtime& runtime) const { + JSValue object = local(runtime); + bool result = quickjsengine::isArray(runtime.context(), object); + JS_FreeValue(runtime.context(), object); + return result; + } + bool isArrayBuffer(Runtime& runtime) const { + JSValue object = local(runtime); + // JS_IsArrayBuffer2 rather than JS_IsArrayBuffer: the same predicate, but + // it is the spelling present in every vendored QuickJS here. Classic + // QuickJS (Android's non-NG build) has no JS_IsArrayBuffer at all. + bool result = JS_IsArrayBuffer2(runtime.context(), object) != 0; + JS_FreeValue(runtime.context(), object); + return result; + } + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == quickjsengine::hostObjectTypeToken(); + } + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != quickjsengine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + // ---- Native state ------------------------------------------------------- + // + // See jsi/v8/V8Runtime.h for what this is for. QuickJS gets it from its own + // per-object opaque slot: objects created for a host constructor's `this` + // are built with JS_NewObjectProtoClass and a dedicated class that carries + // no exotic table (so ordinary property access on them is unchanged) but + // does carry an opaque pointer and a finalizer. Reading the payload back is + // then a field load plus a class-id compare, where the old __nsWrap path did + // two full prototype-chain walks. + // + // Node-API allows napi_wrap on any object, including a plain `{}` JS made + // itself, which is not class-backed. Those fall back to a non-enumerable + // property under an interned atom, read own-only with JS_GetOwnProperty. + // + // In both cases the payload is a HostObject released when the object is + // collected, so finalizer timing is what it was. + template + void setNativeState(Runtime& runtime, std::shared_ptr state) { + setNativeStateWithToken(runtime, std::static_pointer_cast(std::move(state)), + quickjsengine::hostObjectTypeToken()); + } + + template + std::shared_ptr getNativeState(Runtime& runtime) const { + std::shared_ptr host = + nativeStateOf(runtime, quickjsengine::hostObjectTypeToken()); + if (host == nullptr) return nullptr; + return std::static_pointer_cast(std::move(host)); + } + + JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } + operator Value() const { return Value::fromStorage(storage_); } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + explicit Object(std::shared_ptr storage) + : storage_(std::move(storage)) {} + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + void setNativeStateWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + std::shared_ptr nativeStateOf(Runtime& runtime, const void* typeToken) const; + quickjsengine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const; + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + // Like createFromHostFunction, but the result may be used with `new`, and its + // `prototype` property is a writable object. + // + // createFromHostFunction builds the function with JS_NewCFunctionData, which + // produces an object whose constructor bit is clear and which has no + // `prototype` property at all -- so `new Ctor()` throws "not a constructor" + // and napi_define_class cannot reach, let alone reassign, `Ctor.prototype`. + // The Android runtime's MetadataNode chains class prototypes with a plain + // `ctor.prototype = ...` assignment, so a read-only (or absent) prototype + // silently drops the whole inheritance chain. + // + // The function is built from the engine's own JSClass instead: its JSClassCall + // receives QuickJS' call flags, so the trampoline can tell `new` from a plain + // call and synthesise the receiver the way OrdinaryCreateFromConstructor does. + // No JS wrapper is involved, so no extra frame appears in stack traces -- + // which matters, because the runtime resolves a Worker's module directory by + // reading `frames[2]`. + static Function createFromHostConstructor(Runtime& runtime, const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback); + Value call(Runtime& runtime, const Value* args, size_t count) const { + JSValue function = local(runtime); + JSValue global = JS_GetGlobalObject(runtime.context()); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_Call(runtime.context(), function, global, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), global); + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(runtime, "QuickJS function call failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + // `count` is deduced rather than fixed to size_t on purpose. + // + // With a `size_t` parameter, `fn.call(rt, args, 2)` needed an int -> size_t + // conversion here while the variadic overload below matched exactly -- so the + // variadic won, and silently reinterpreted (array, count) as a two-argument + // JS call passing the array and the number. The array then decayed to a + // pointer and converted to `bool`, so console.log(str) came out as "true". + // Deducing the count makes this overload exact too, and partial ordering then + // prefers it over the pack. V8's backend has carried this fix since the + // Node-API shim work; it was never propagated here. + template >>> + Value call(Runtime& runtime, const Value (&args)[N], Count count) const { + return call(runtime, static_cast(args), static_cast(count)); + } + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + JSValue function = local(runtime); + JSValue thisValue = thisObject.local(runtime); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_Call(runtime.context(), function, thisValue, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), thisValue); + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(runtime, "QuickJS function call failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + JSValue function = local(runtime); + std::vector argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + JSValue result = JS_CallConstructor(runtime.context(), function, static_cast(argv.size()), + argv.empty() ? nullptr : argv.data()); + for (auto& arg : argv) { + JS_FreeValue(runtime.context(), arg); + } + JS_FreeValue(runtime.context(), function); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(runtime, "QuickJS constructor call failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + // `count` is deduced rather than fixed to size_t on purpose. + // + // With a `size_t` parameter, `fn.call(rt, args, 2)` needed an int -> size_t + // conversion here while the variadic overload below matched exactly -- so the + // variadic won, and silently reinterpreted (array, count) as a two-argument + // JS call passing the array and the number. The array then decayed to a + // pointer and converted to `bool`, so console.log(str) came out as "true". + // Deducing the count makes this overload exact too, and partial ordering then + // prefers it over the pack. V8's backend has carried this fix since the + // Node-API shim work; it was never propagated here. + template >>> + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], Count count) const { + return callAsConstructor(runtime, static_cast(args), static_cast(count)); + } + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_NewArray(runtime.context()); + JS_SetPropertyStr(runtime.context(), storage_->value, "length", + JS_NewUint32(runtime.context(), static_cast(size))); + } + explicit Array(Object object) : Object(std::move(object.storage_)) {} + size_t size(Runtime& runtime) const { + Value length = getProperty(runtime, "length"); + return length.isNumber() ? static_cast(std::max(0, length.getNumber())) : 0; + } + // See Object::getPropertyBorrowed. + Value getValueAtIndexBorrowed(Runtime& runtime, size_t index) const { + return getValueAtIndex(runtime, index); + } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + JSValue object = local(runtime); + JSValue result = JS_GetPropertyUint32(runtime.context(), object, static_cast(index)); + JS_FreeValue(runtime.context(), object); + if (JS_IsException(result)) { + throw quickjsengine::caughtError(runtime, "QuickJS array get failed."); + } + Value value(runtime, result); + JS_FreeValue(runtime.context(), result); + return value; + } + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + JSValue object = local(runtime); + JSValue localValue = value.local(runtime); + int status = + JS_SetPropertyUint32(runtime.context(), object, static_cast(index), localValue); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw quickjsengine::caughtError(runtime, "QuickJS array set failed."); + } + } + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, JSValue value) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); + } + static BigInt fromInt64(Runtime& runtime, int64_t value) { + JSValue result = JS_NewBigInt64(runtime.context(), value); + BigInt bigint(runtime, result); + JS_FreeValue(runtime.context(), result); + return bigint; + } + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + JSValue result = JS_NewBigUint64(runtime.context(), value); + BigInt bigint(runtime, result); + JS_FreeValue(runtime.context(), result); + return bigint; + } + String toString(Runtime& runtime, int) const; + JSValue local(Runtime& runtime) const { return JS_DupValue(runtime.context(), storage_->value); } + operator Value() const { return Value::fromStorage(storage_); } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + auto* holder = new quickjsengine::ArrayBufferHolder(std::move(buffer)); + storage_->context = runtime.context(); + storage_->value = JS_NewArrayBuffer( + runtime.context(), holder->buffer->data(), holder->buffer->size(), + [](JSRuntime*, void* opaque, void*) { + delete static_cast(opaque); + }, + holder, false); + } + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + size_t size(Runtime& runtime) const { + JSValue object = local(runtime); + size_t size = 0; + JS_GetArrayBuffer(runtime.context(), &size, object); + JS_FreeValue(runtime.context(), object); + return size; + } + uint8_t* data(Runtime& runtime) const { + JSValue object = local(runtime); + size_t size = 0; + uint8_t* data = JS_GetArrayBuffer(runtime.context(), &size, object); + JS_FreeValue(runtime.context(), object); + return data; + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS + +#endif // NATIVESCRIPT_JSI_QUICKJS_QUICKJS_RUNTIME_H diff --git a/NativeScript/jsi/quickjs/QuickJSValue.cpp b/NativeScript/jsi/quickjs/QuickJSValue.cpp new file mode 100644 index 000000000..89dc0276c --- /dev/null +++ b/NativeScript/jsi/quickjs/QuickJSValue.cpp @@ -0,0 +1,149 @@ +#include "jsi/quickjs/QuickJSRuntime.h" + +#ifdef TARGET_ENGINE_QUICKJS + +namespace nativescript { +namespace engine { + +Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } +bool HostObject::set(Runtime&, const PropNameID&, const Value&) { return true; } +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } + +// The defaults reproduce what the engine used to do for an index: stringify it +// and take the named path. A host object that does not override these is +// therefore unaffected by the indexed routing. +Value HostObject::getValueAtIndex(Runtime& runtime, uint32_t index) { + return get(runtime, PropNameID(std::to_string(index))); +} + +bool HostObject::setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value) { + return set(runtime, PropNameID(std::to_string(index)), value); +} +String::String(Runtime& runtime, JSValue value) + : storage_(std::make_shared( + quickjsengine::ValueStorage::Kind::QuickJS)) { + storage_->context = runtime.context(); + storage_->value = JS_DupValue(runtime.context(), value); +} +std::string String::utf8(Runtime& runtime) const { + JSValue value = local(runtime); + std::string result = quickjsengine::valueToUtf8(runtime.context(), value); + JS_FreeValue(runtime.context(), value); + return result; +} +JSValue String::local(Runtime& runtime) const { + return JS_DupValue(runtime.context(), storage_->value); +} +String::operator Value() const { return Value::fromStorage(storage_); } +Value::Value(Runtime&, const Object& object) { + storage_ = object.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Function& function) { + storage_ = function.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Array& array) { + storage_ = array.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) { + storage_ = arrayBuffer.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const BigInt& bigint) { + storage_ = bigint.storage_; + kind_ = storage_ ? storage_->kind : quickjsengine::ValueStorage::Kind::Undefined; +} +Object Value::asObject(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Promote to owned storage for Object. + auto s = std::make_shared(kind_); + if (kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { + s->kind = quickjsengine::ValueStorage::Kind::QuickJS; + s->context = borrowedContext_; + s->value = JS_DupValue(borrowedContext_, borrowedValue_); + } + return Object::fromValueStorage(std::move(s)); +} +Object Value::asObjectBorrowed(Runtime& runtime) const { + return asObject(runtime); +} +String Value::asString(Runtime& runtime) const { + JSValue value = local(runtime); + String result(runtime, value); + JS_FreeValue(runtime.context(), value); + return result; +} + +std::string Value::utf8(Runtime& runtime) const { + // JS_ToCStringLen does not consume its argument, so a refcounted value can be + // read in place -- no local(), which dups, and no matching free. + if (kind_ == quickjsengine::ValueStorage::Kind::QuickJS || + kind_ == quickjsengine::ValueStorage::Kind::QuickJSBorrowed) { + return quickjsengine::valueToUtf8(runtime.context(), jsValue()); + } + JSValue value = local(runtime); + std::string result = quickjsengine::valueToUtf8(runtime.context(), value); + JS_FreeValue(runtime.context(), value); + return result; +} + +Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t length) { + Value result; + result.kind_ = quickjsengine::ValueStorage::Kind::QuickJS; + result.storage_ = + std::make_shared(quickjsengine::ValueStorage::Kind::QuickJS); + result.storage_->context = runtime.context(); + result.storage_->value = + JS_NewStringLen(runtime.context(), data != nullptr ? data : "", length); + return result; +} +BigInt Value::getBigInt(Runtime& runtime) const { + JSValue value = local(runtime); + BigInt result(runtime, value); + JS_FreeValue(runtime.context(), value); + return result; +} +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime).asFunction(runtime); +} +Function Object::asFunction(Runtime&) const { return Function(*this); } +Array Object::getArray(Runtime&) const { return Array(*this); } +ArrayBuffer Object::getArrayBuffer(Runtime&) const { return ArrayBuffer(*this); } +Array Object::getPropertyNames(Runtime& runtime) const { + JSValue object = local(runtime); + JSPropertyEnum* properties = nullptr; + uint32_t count = 0; + int status = JS_GetOwnPropertyNames(runtime.context(), &properties, &count, object, + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY); + JS_FreeValue(runtime.context(), object); + if (status < 0) { + throw quickjsengine::caughtError(runtime, "QuickJS property names failed."); + } + Array result(runtime, count); + for (uint32_t i = 0; i < count; i++) { + JSValue nameValue = JS_AtomToValue(runtime.context(), properties[i].atom); + result.setValueAtIndex(runtime, i, Value(runtime, nameValue)); + JS_FreeValue(runtime.context(), nameValue); + JS_FreeAtom(runtime.context(), properties[i].atom); + } + js_free(runtime.context(), properties); + return result; +} +void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { + setProperty(runtime, name, Value(runtime, value)); +} +void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS diff --git a/NativeScript/jsi/shared/NativeApiBackendConfig.h b/NativeScript/jsi/shared/NativeApiBackendConfig.h new file mode 100644 index 000000000..a6c2044e8 --- /dev/null +++ b/NativeScript/jsi/shared/NativeApiBackendConfig.h @@ -0,0 +1,32 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H +#define NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H + +#include +#include + +namespace nativescript { + +class NativeApiBackendScheduler { + public: + virtual ~NativeApiBackendScheduler() = default; + virtual void invokeOnJS(std::function task) = 0; + virtual void invokeOnUI(std::function task) = 0; +}; + +struct NativeApiBackendConfig { + const char* metadataPath = nullptr; + const void* metadataPtr = nullptr; + const char* globalName = "__nativeScriptNativeApi"; + std::shared_ptr scheduler = nullptr; + std::function)> nativeInvocationInvoker = nullptr; + std::function)> nativeCallbackInvoker = nullptr; + std::function)> runtimeCallbackInvoker = nullptr; + std::function)> jsThreadCallbackInvoker = nullptr; + std::function)> jsThreadAsyncCallbackInvoker = nullptr; + bool invokeCallbacksOnNativeCallerThread = false; + bool installGlobalSymbols = false; +}; + +} // namespace nativescript + +#endif // NATIVESCRIPT_FFI_SHARED_NATIVE_API_BACKEND_CONFIG_H diff --git a/NativeScript/jsi/shared/NativeApiStackValueArray.h b/NativeScript/jsi/shared/NativeApiStackValueArray.h new file mode 100644 index 000000000..b91091ddd --- /dev/null +++ b/NativeScript/jsi/shared/NativeApiStackValueArray.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace nativescript::engine { + +template +class StackValueArray { + public: + explicit StackValueArray(size_t count) : count_(count) { + values_ = + count_ > InlineCount + ? static_cast( + ::operator new(sizeof(ValueType) * count_)) + : reinterpret_cast(inlineStorage_); + } + + ~StackValueArray() { + for (size_t i = 0; i < constructed_; i++) { + values_[i].~ValueType(); + } + if (count_ > InlineCount) { + ::operator delete(values_); + } + } + + StackValueArray(const StackValueArray&) = delete; + StackValueArray& operator=(const StackValueArray&) = delete; + + void emplace(size_t index, ValueType&& value) { + new (&values_[index]) ValueType(std::move(value)); + constructed_++; + } + + ValueType* data() { return count_ == 0 ? nullptr : values_; } + size_t size() const { return count_; } + + private: + size_t count_ = 0; + size_t constructed_ = 0; + ValueType* values_ = nullptr; + alignas(ValueType) unsigned char inlineStorage_[sizeof(ValueType) * InlineCount]; +}; + +} // namespace nativescript::engine diff --git a/NativeScript/jsi/shared/PreparedSignatureDispatch.h b/NativeScript/jsi/shared/PreparedSignatureDispatch.h new file mode 100644 index 000000000..17cfc40a4 --- /dev/null +++ b/NativeScript/jsi/shared/PreparedSignatureDispatch.h @@ -0,0 +1,87 @@ +#ifndef NATIVESCRIPT_JSI_SHARED_PREPARED_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_JSI_SHARED_PREPARED_SIGNATURE_DISPATCH_H + +// Lookup side of generated signature dispatch: given a dispatch id, find the +// ahead-of-time generated trampoline for it. +// +// Platform-neutral. It needs only the hashing/lookup core, not the +// metadata-format-specific half that computes the ids -- which is why this can +// live here while ffi/objc/shared/SignatureDispatchCore.h cannot. + +#include "jsi/shared/SignatureHashing.h" + +#ifndef NS_GSD_BACKEND_PREPARED +#define NS_GSD_BACKEND_PREPARED 0 +#endif + +#ifndef NS_GSD_BACKEND_HERMES +#define NS_GSD_BACKEND_HERMES 0 +#endif + +#ifndef NS_GSD_BACKEND_NAPI +#define NS_GSD_BACKEND_NAPI 0 +#endif + +#ifndef NS_HAS_GENERATED_SIGNATURE_DISPATCH +#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 0 +#endif + +#define NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH \ + (NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_NAPI || NS_GSD_BACKEND_PREPARED) + +#if NS_REQUIRES_GENERATED_SIGNATURE_DISPATCH && \ + !NS_HAS_GENERATED_SIGNATURE_DISPATCH +#error GeneratedSignatureDispatch.inc did not enable this generated signature dispatch backend. +#endif + +#if !NS_HAS_GENERATED_SIGNATURE_DISPATCH +namespace nativescript { +inline constexpr ObjCDispatchEntry kGeneratedObjCDispatchEntries[] = { + {0, nullptr}}; +inline constexpr CFunctionDispatchEntry kGeneratedCFunctionDispatchEntries[] = { + {0, nullptr}}; +inline constexpr BlockDispatchEntry kGeneratedBlockDispatchEntries[] = { + {0, nullptr}}; +} // namespace nativescript +#endif + +namespace nativescript { + +inline ObjCPreparedInvoker lookupObjCPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedObjCDispatchEntries, dispatchId); +} + +inline CFunctionPreparedInvoker lookupCFunctionPreparedInvoker( + uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedCFunctionDispatchEntries, dispatchId); +} + +inline BlockPreparedInvoker lookupBlockPreparedInvoker(uint64_t dispatchId) { + if (!isGeneratedDispatchEnabled()) { + return nullptr; + } + return lookupDispatchInvoker( + kGeneratedBlockDispatchEntries, dispatchId); +} + +inline bool isPreparedGeneratedDispatchRequired() { +#if NS_HAS_GENERATED_SIGNATURE_DISPATCH && \ + (NS_GSD_BACKEND_PREPARED || NS_GSD_BACKEND_HERMES) + return isGeneratedDispatchEnabled(); +#else + return false; +#endif +} + +} // namespace nativescript + +#endif // NATIVESCRIPT_JSI_SHARED_PREPARED_SIGNATURE_DISPATCH_H diff --git a/NativeScript/jsi/shared/SignatureHashing.h b/NativeScript/jsi/shared/SignatureHashing.h new file mode 100644 index 000000000..0589c0fe3 --- /dev/null +++ b/NativeScript/jsi/shared/SignatureHashing.h @@ -0,0 +1,110 @@ +#ifndef NS_JSI_SHARED_SIGNATURE_HASHING_H +#define NS_JSI_SHARED_SIGNATURE_HASHING_H + +// Platform-neutral half of generated signature dispatch (GSD). +// +// This header must stay free of Objective-C and of any platform metadata +// format: it is compiled by the Apple runtime and, in future, by the Android +// runtime. The metadata-driven signature hashing (which walks the Objective-C +// metadata via metagen::MDMetadataReader) deliberately stays behind in +// ffi/objc/shared/SignatureDispatchCore.h, because the Android metadata is a +// different binary format entirely. +// +// What is genuinely common is the mechanism: hash a signature with FNV-1a, +// compose a dispatch id from (hash, call kind, flags), and binary-search a +// sorted, ahead-of-time generated table for a typed trampoline. + +#include +#include +#include + +namespace nativescript { + +enum class SignatureCallKind : uint8_t { + ObjCMethod = 1, + CFunction = 2, + BlockInvoke = 3, +}; + +using ObjCPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); +using CFunctionPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); +using BlockPreparedInvoker = void (*)(void* fnptr, void** avalues, + void* rvalue); + +struct ObjCDispatchEntry { + uint64_t dispatchId; + ObjCPreparedInvoker invoker; +}; + +struct CFunctionDispatchEntry { + uint64_t dispatchId; + CFunctionPreparedInvoker invoker; +}; + +struct BlockDispatchEntry { + uint64_t dispatchId; + BlockPreparedInvoker invoker; +}; + +inline constexpr uint64_t kSignatureHashOffsetBasis = 14695981039346656037ull; +inline constexpr uint64_t kSignatureHashPrime = 1099511628211ull; + +inline uint64_t hashBytesFnv1a(const void* data, size_t size, + uint64_t seed = kSignatureHashOffsetBasis) { + const auto* bytes = static_cast(data); + uint64_t hash = seed; + for (size_t i = 0; i < size; i++) { + hash ^= static_cast(bytes[i]); + hash *= kSignatureHashPrime; + } + return hash; +} + +inline uint64_t composeSignatureDispatchId(uint64_t signatureHash, + SignatureCallKind kind, + uint8_t flags) { + const uint8_t kindByte = static_cast(kind); + uint64_t hash = hashBytesFnv1a(&kindByte, sizeof(kindByte)); + hash = hashBytesFnv1a(&flags, sizeof(flags), hash); + return hashBytesFnv1a(&signatureHash, sizeof(signatureHash), hash); +} + +template +inline Invoker lookupDispatchInvoker(const Entry (&entries)[N], + uint64_t dispatchId) { + if (dispatchId == 0 || N <= 1) { + return nullptr; + } + + size_t low = 1; + size_t high = N; + while (low < high) { + const size_t mid = low + ((high - low) >> 1); + const uint64_t midId = entries[mid].dispatchId; + if (midId < dispatchId) { + low = mid + 1; + } else { + high = mid; + } + } + + if (low < N && entries[low].dispatchId == dispatchId) { + return entries[low].invoker; + } + return nullptr; +} + +inline bool isGeneratedDispatchEnabled() { + static const bool enabled = []() { + const char* disableFlag = std::getenv("NS_DISABLE_GSD"); + return disableFlag == nullptr || disableFlag[0] == '\0' || + (disableFlag[0] == '0' && disableFlag[1] == '\0'); + }(); + return enabled; +} + +} // namespace nativescript + +#endif // NS_JSI_SHARED_SIGNATURE_HASHING_H diff --git a/NativeScript/ffi/shared/Tasks.cpp b/NativeScript/jsi/shared/Tasks.cpp similarity index 100% rename from NativeScript/ffi/shared/Tasks.cpp rename to NativeScript/jsi/shared/Tasks.cpp diff --git a/NativeScript/ffi/shared/Tasks.h b/NativeScript/jsi/shared/Tasks.h similarity index 100% rename from NativeScript/ffi/shared/Tasks.h rename to NativeScript/jsi/shared/Tasks.h diff --git a/NativeScript/jsi/v8/V8HostObjects.cpp b/NativeScript/jsi/v8/V8HostObjects.cpp new file mode 100644 index 000000000..b682a01ef --- /dev/null +++ b/NativeScript/jsi/v8/V8HostObjects.cpp @@ -0,0 +1,501 @@ +#include "jsi/v8/V8Runtime.h" +#include "jsi/shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_V8 + +namespace nativescript { +namespace engine { + +namespace v8engine { + +Value valueFromLocal(Runtime& runtime, v8::Local value) { return Value(runtime, value); } + +v8::Local hostObjectTemplate(Runtime& runtime) { + auto state = runtime.state(); + if (state->hostObjectTemplate.IsEmpty()) { + v8::Local objectTemplate = v8::ObjectTemplate::New(runtime.isolate()); + objectTemplate->SetInternalFieldCount(1); + // toString must be own property to override Object.prototype.toString + // when using kNonMasking interceptor. + objectTemplate->Set( + makeV8String(runtime.isolate(), "toString"), + v8::FunctionTemplate::New(runtime.isolate(), + [](const v8::FunctionCallbackInfo& info) { + v8::Local self = info.This(); + if (self.IsEmpty() || self->InternalFieldCount() < 1) return; + auto* holder = static_cast( + self->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) return; + Runtime rt(holder->state); + try { + Value toStr = holder->hostObject->get(rt, PropNameID("toString")); + if (!toStr.isUndefined()) { + v8::Local v8Val = toStr.local(rt); + if (v8Val->IsFunction()) { + v8::Local result; + if (v8Val.As()->Call(rt.context(), self, 0, nullptr) + .ToLocal(&result)) { + info.GetReturnValue().Set(result); + return; + } + } + } + } catch (...) {} + }), + v8::DontEnum); + objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( + [](v8::Local property, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + // Fast path: skip symbols entirely (they never match our properties). + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + v8::Isolate* isolate = info.GetIsolate(); + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + Value result = holder->hostObject->get( + runtime, PropNameID(isolate, property)); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + // Unary + forces the lambda to decay to a function pointer. V8 14.9 + // constrains the setter parameter with requires(is_same_v), + // which a closure type fails even though it converts implicitly. + +[](v8::Local property, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + // Skip symbols, exactly as the getter above does. + // + // This setter used to convert the name through propertyNameToUtf8, + // which spelled Symbol.iterator as the string "Symbol.iterator". + // PropNameID now defers the conversion, and v8::String::Utf8Value on + // a symbol swallows its own TypeError and yields "" -- so every + // symbol-keyed write reached the host object under the empty name. + // Silently writing the wrong property is worse than not intercepting. + // + // kNo is also what makes the pair coherent: the getter never + // intercepts symbols, so a symbol stored here could never be read + // back through it. Letting V8 store it as an ordinary property means + // the write and the read agree. The other two interceptors already + // guard this way. + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + bool handled = holder->hostObject->set( + runtime, PropNameID(info.GetIsolate(), property), + Value(runtime, value)); + return handled ? v8::Intercepted::kYes : v8::Intercepted::kNo; + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, + [](const v8::PropertyCallbackInfo& info) { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return; + } + Runtime runtime(holder->state); + try { + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + auto propertyNames = holder->hostObject->getPropertyNames(runtime); + v8::Local result = + v8::Array::New(info.GetIsolate(), static_cast(propertyNames.size())); + for (size_t i = 0; i < propertyNames.size(); i++) { + std::string name = propertyNames[i].utf8(runtime); + result + ->Set(runtime.context(), static_cast(i), + makeV8String(info.GetIsolate(), name)) + .FromMaybe(false); + } + info.GetReturnValue().Set(result); + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + } + }, + v8::Local(), v8::PropertyHandlerFlags::kNone)); + objectTemplate->SetHandler(v8::IndexedPropertyHandlerConfiguration( + [](uint32_t index, const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + Value result = holder->hostObject->getValueAtIndex(runtime, index); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + // Unary + forces the lambda to decay to a function pointer. V8 14.9 + // constrains the setter parameter with requires(is_same_v), + // which a closure type fails even though it converts implicitly. + +[](uint32_t index, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + // Borrowed, not owned: an owned Value allocates a shared ValueStorage + // and a v8::Global, which on `a[0] = x` is a heap allocation and a + // global-handle create/destroy per element write. The value does not + // outlive this call, and HostObject::setValueAtIndex's default + // promotes it before handing it to the named setter, so a host object + // that does not override the indexed form is unaffected. + holder->hostObject->setValueAtIndex(runtime, index, Value::borrowed(runtime, value)); + return v8::Intercepted::kYes; + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, nullptr, v8::Local(), + v8::PropertyHandlerFlags::kNone)); + state->hostObjectTemplate.Reset(runtime.isolate(), objectTemplate); + } + return state->hostObjectTemplate.Get(runtime.isolate()); +} + +// Template for native object instances — uses kNonMasking so V8 checks +// prototype chain first (methods/properties installed there are found +// without calling the interceptor). +v8::Local nativeObjectTemplate(Runtime& runtime) { + auto state = runtime.state(); + if (state->nativeObjectTemplate.IsEmpty()) { + v8::Local objectTemplate = v8::ObjectTemplate::New(runtime.isolate()); + objectTemplate->SetInternalFieldCount(1); + // toString must be own property to override Object.prototype.toString + objectTemplate->Set( + makeV8String(runtime.isolate(), "toString"), + v8::FunctionTemplate::New(runtime.isolate(), + [](const v8::FunctionCallbackInfo& info) { + v8::Local self = info.This(); + if (self.IsEmpty() || self->InternalFieldCount() < 1) return; + auto* holder = static_cast( + self->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) return; + Runtime rt(holder->state); + try { + Value toStr = holder->hostObject->get(rt, PropNameID("toString")); + if (!toStr.isUndefined()) { + v8::Local v8Val = toStr.local(rt); + if (v8Val->IsFunction()) { + v8::Local result; + if (v8Val.As()->Call(rt.context(), self, 0, nullptr) + .ToLocal(&result)) { + info.GetReturnValue().Set(result); + return; + } + } + } + } catch (...) {} + }), + v8::DontEnum); + objectTemplate->SetHandler(v8::NamedPropertyHandlerConfiguration( + [](v8::Local property, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + v8::Isolate* isolate = info.GetIsolate(); + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + Value result = holder->hostObject->get( + runtime, PropNameID(isolate, property)); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + // Unary + forces the lambda to decay to a function pointer. V8 14.9 + // constrains the setter parameter with requires(is_same_v), + // which a closure type fails even though it converts implicitly. + +[](v8::Local property, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + if (!property->IsString()) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + v8::Isolate* isolate = info.GetIsolate(); + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + bool handled = holder->hostObject->set( + runtime, PropNameID(isolate, property), + Value(runtime, value)); + return handled ? v8::Intercepted::kYes : v8::Intercepted::kNo; + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + nullptr, nullptr, nullptr, v8::Local(), + v8::PropertyHandlerFlags::kNonMasking)); + objectTemplate->SetHandler(v8::IndexedPropertyHandlerConfiguration( + [](uint32_t index, const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + Value result = holder->hostObject->getValueAtIndex(runtime, index); + if (!result.isUndefined()) { + info.GetReturnValue().Set(result.local(runtime)); + return v8::Intercepted::kYes; + } + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + return v8::Intercepted::kNo; + }, + // Unary + forces the lambda to decay to a function pointer. V8 14.9 + // constrains the setter parameter with requires(is_same_v), + // which a closure type fails even though it converts implicitly. + +[](uint32_t index, v8::Local value, + const v8::PropertyCallbackInfo& info) -> v8::Intercepted { + auto* holder = + static_cast(info.Holder()->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (holder == nullptr || holder->hostObject == nullptr) { + return v8::Intercepted::kNo; + } + Runtime runtime(holder->state); + try { + Value __receiver = Value::borrowed(runtime, info.Holder()); + HostObject::ReceiverScope __rs(*holder->hostObject, __receiver); + // Borrowed; see the host-object template above. + holder->hostObject->setValueAtIndex(runtime, index, Value::borrowed(runtime, value)); + return v8::Intercepted::kYes; + } catch (const JSError& error) { + throwV8Exception(info.GetIsolate(), error); + return v8::Intercepted::kYes; + } catch (const std::exception& exception) { + throwV8Exception(info.GetIsolate(), exception); + return v8::Intercepted::kYes; + } + }, + // Masking, unlike the named handler above -- and this is deliberate, + // not an oversight. kNonMasking makes V8 complete a full property + // lookup *before* consulting the interceptor, which is what makes it + // correct for named properties: a Java field or method really does live + // on the prototype and must win. No native instance ever has a real + // indexed own or prototype property (the indexed setter always claims + // the write, so V8 never stores one), so for indices that lookup can + // only ever fail, and kNonMasking buys nothing but its cost -- measured + // at 20-30% on every `javaArray[0]` read and write. + // + // This is also what the reference implementation does: the V8 Node-API + // backend in vendor/v8/v8-api.cpp passes + // kNonMasking for its named handler and default (masking) flags for its + // indexed one. + nullptr, nullptr, nullptr, v8::Local(), + v8::PropertyHandlerFlags::kNone)); + state->nativeObjectTemplate.Reset(runtime.isolate(), objectTemplate); + } + return state->nativeObjectTemplate.Get(runtime.isolate()); +} + +void hostObjectWeakCallback(const v8::WeakCallbackInfo& info) { + delete info.GetParameter(); +} + +void functionWeakCallback(const v8::WeakCallbackInfo& info) { + delete info.GetParameter(); +} + +} // namespace v8engine + +Object Object::createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + v8::Local object = + v8engine::hostObjectTemplate(runtime)->NewInstance(runtime.context()).ToLocalChecked(); + auto* holder = new v8engine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + object->SetAlignedPointerInInternalField(0, holder, v8::kEmbedderDataTypeTagDefault); + holder->object.Reset(runtime.isolate(), object); + holder->object.SetWeak(holder, v8engine::hostObjectWeakCallback, + v8::WeakCallbackType::kParameter); + return Object::fromValueStorage(Value(runtime, object).storage_); +} + +Object Object::createNativeInstanceWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken) { + v8::Local object = + v8engine::nativeObjectTemplate(runtime)->NewInstance(runtime.context()).ToLocalChecked(); + auto* holder = new v8engine::HostObjectHolder(runtime.state(), std::move(host), typeToken); + object->SetAlignedPointerInInternalField(0, holder, v8::kEmbedderDataTypeTagDefault); + holder->object.Reset(runtime.isolate(), object); + holder->object.SetWeak(holder, v8engine::hostObjectWeakCallback, + v8::WeakCallbackType::kParameter); + return Object::fromValueStorage(Value(runtime, object).storage_); +} + +Function Function::createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback) { + auto* holder = new v8engine::FunctionHolder(runtime.state(), std::move(callback)); + v8::Local data = v8::External::New(runtime.isolate(), holder, v8::kExternalPointerTypeTagDefault); + v8::Local functionTemplate = v8::FunctionTemplate::New( + runtime.isolate(), + [](const v8::FunctionCallbackInfo& info) { + auto* holder = + static_cast(info.Data().As()->Value(v8::kExternalPointerTypeTagDefault)); + Runtime runtime(holder->state); + StackValueArray args(static_cast(info.Length())); + for (int i = 0; i < info.Length(); i++) { + args.emplace(static_cast(i), Value::borrowed(runtime, info[i])); + } + try { + Value thisValue = Value::borrowed(runtime, info.This()); + Value result = holder->callback(runtime, thisValue, args.size() == 0 ? nullptr : args.data(), + args.size()); + info.GetReturnValue().Set(result.local(runtime)); + } catch (const JSError& error) { + v8engine::throwV8Exception(info.GetIsolate(), error); + } catch (const std::exception& exception) { + v8engine::throwV8Exception(info.GetIsolate(), exception); + } + }, + data); + v8::Local function = + functionTemplate->GetFunction(runtime.context()).ToLocalChecked(); + std::string functionName = name.utf8(runtime); + if (!functionName.empty()) { + function->SetName(v8engine::makeV8String(runtime.isolate(), functionName)); + } + holder->function.Reset(runtime.isolate(), function); + holder->function.SetWeak(holder, v8engine::functionWeakCallback, + v8::WeakCallbackType::kParameter); + return Function(Object::fromValueStorage(Value(runtime, function).storage_)); +} + +Function Function::createFromHostConstructor(Runtime& runtime, const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback) { + // v8::Function::New rather than a FunctionTemplate: see the header for why. + // The holder/External/weak-callback lifetime handling is identical to + // createFromHostFunction -- only the construction API differs. + auto* holder = new v8engine::FunctionHolder(runtime.state(), std::move(callback)); + v8::Local data = v8::External::New(runtime.isolate(), holder, v8::kExternalPointerTypeTagDefault); + v8::Local function = + v8::Function::New( + runtime.context(), + [](const v8::FunctionCallbackInfo& info) { + auto* holder = + static_cast(info.Data().As()->Value(v8::kExternalPointerTypeTagDefault)); + Runtime runtime(holder->state); + StackValueArray args(static_cast(info.Length())); + for (int i = 0; i < info.Length(); i++) { + args.emplace(static_cast(i), Value::borrowed(runtime, info[i])); + } + try { + Value thisValue = Value::borrowed(runtime, info.This()); + Value result = holder->callback(runtime, thisValue, + args.size() == 0 ? nullptr : args.data(), + args.size()); + info.GetReturnValue().Set(result.local(runtime)); + } catch (const JSError& error) { + v8engine::throwV8Exception(info.GetIsolate(), error); + } catch (const JSError& error) { + v8engine::throwV8Exception(info.GetIsolate(), error); + } catch (const std::exception& exception) { + v8engine::throwV8Exception(info.GetIsolate(), exception); + } + }, + data, static_cast(paramCount), v8::ConstructorBehavior::kAllow) + .ToLocalChecked(); + std::string functionName = name.utf8(runtime); + if (!functionName.empty()) { + function->SetName(v8engine::makeV8String(runtime.isolate(), functionName)); + } + holder->function.Reset(runtime.isolate(), function); + holder->function.SetWeak(holder, v8engine::functionWeakCallback, + v8::WeakCallbackType::kParameter); + return Function(Object::fromValueStorage(Value(runtime, function).storage_)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/jsi/v8/V8Runtime.cpp b/NativeScript/jsi/v8/V8Runtime.cpp new file mode 100644 index 000000000..c38cc345b --- /dev/null +++ b/NativeScript/jsi/v8/V8Runtime.cpp @@ -0,0 +1,36 @@ +#include "jsi/v8/V8Runtime.h" + +#ifdef TARGET_ENGINE_V8 + +namespace nativescript { +namespace engine { + +Object Runtime::global() { + return Object::fromValueStorage(Value(*this, context()->Global()).storage_); +} + +Value Runtime::evaluateJavaScript(std::shared_ptr buffer, + const std::string& sourceURL) { + v8::TryCatch tryCatch(isolate()); + v8::Local source = + v8::String::NewFromUtf8(isolate(), buffer != nullptr ? buffer->data() : "", + v8::NewStringType::kNormal, + buffer != nullptr ? static_cast(buffer->size()) : 0) + .ToLocalChecked(); + v8::Local resourceName = v8engine::makeV8String(isolate(), sourceURL); + v8::ScriptOrigin origin(resourceName); + v8::Local script; + if (!v8::Script::Compile(context(), source, &origin).ToLocal(&script)) { + throw v8engine::caughtError(*this, tryCatch); + } + v8::Local result; + if (!script->Run(context()).ToLocal(&result)) { + throw v8engine::caughtError(*this, tryCatch); + } + return Value(*this, result); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/jsi/v8/V8Runtime.h b/NativeScript/jsi/v8/V8Runtime.h new file mode 100644 index 000000000..6815558f3 --- /dev/null +++ b/NativeScript/jsi/v8/V8Runtime.h @@ -0,0 +1,1357 @@ +#ifndef NATIVESCRIPT_JSI_V8_V8_RUNTIME_H +#define NATIVESCRIPT_JSI_V8_V8_RUNTIME_H + +// V8 behind the JSI-shaped `nativescript::engine` API. +// +// This header is platform-neutral: it depends on V8 and the C++ standard +// library, and on nothing from Foundation, the Objective-C runtime, or the +// Apple metadata format. Android compiles it as-is. +// +// The Apple bridge's extra baggage lives in ffi/objc/v8/NativeApiV8Runtime.h, +// which includes this file. Apple sources keep including that path unchanged. + +#ifdef TARGET_ENGINE_V8 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v8.h" + +namespace nativescript { +namespace engine { + +class Runtime; +class Value; +class Object; +class Function; +class Array; +class String; +class BigInt; +class ArrayBuffer; + +// Mirrors jsi::JSError: it carries the thrown *value*, not just its text. +// +// The message alone is lossy in a way that matters. The Android runtime +// attaches properties to the errors it throws -- NativeScriptException hangs +// the originating Java throwable off `nativeException` -- and rebuilding an +// error from its stringified form silently drops them, along with the original +// stack. Anything that needs `e.nativeException.getStackTrace()` then sees +// undefined. +// +// The payload is optional: engine operations that fail without a JS exception +// (and the engines not yet wired to carry one) still construct message-only +// JSErrors, and callers check value() before using it. +// +// Value is incomplete here, which is fine: constructing the shared_ptr is the +// only operation that needs the complete type, so that constructor is defined +// further down. Copy and destruction type-erase through the deleter captured +// at construction, which is what makes this legal to throw. +class JSError : public std::runtime_error { + public: + JSError(Runtime&, const std::string& message) : std::runtime_error(message) {} + explicit JSError(const std::string& message) : std::runtime_error(message) {} + + // Defined after Value; see above. + JSError(Runtime& runtime, const std::string& message, const Value& value, + std::string stack); + + // Null when this error was built from a message alone. + const Value* value() const { return value_.get(); } + + // The engine's stack for the original throw, empty when unavailable. + const std::string& stack() const { return stack_; } + + private: + std::shared_ptr value_; + std::string stack_; +}; + +namespace v8engine { +struct WeakStorage; +} // namespace v8engine + +// Mirrors jsi::WeakObject. lock() returns undefined once the referent has been +// collected. +class WeakObject { + public: + WeakObject() = default; + WeakObject(Runtime& runtime, const Value& value); + Value lock(Runtime& runtime) const; + bool empty() const; // defined after WeakStorage + void reset() { storage_.reset(); } + + private: + std::shared_ptr storage_; +}; + +class StringBuffer { + public: + explicit StringBuffer(std::string value) : value_(std::move(value)) {} + const char* data() const { return value_.data(); } + size_t size() const { return value_.size(); } + + private: + std::string value_; +}; + +class MutableBuffer { + public: + virtual ~MutableBuffer() = default; + virtual size_t size() const = 0; + virtual uint8_t* data() = 0; +}; + +// A property name, which may be backed by the engine's own handle rather than +// by a string. +// +// This used to hold a std::string unconditionally, so a property interceptor -- +// which receives a v8::Local -- had to run v8::String::Utf8Value and +// build a std::string on the way in, on every intercepted access. Most +// consumers then either hand the name straight back to the engine or compare +// it, so that conversion was frequently pure waste: an instance field read +// converted the same name six times between V8 and the runtime. +// +// Holding the handle defers the conversion to whoever actually wants text, and +// lets a consumer that only forwards the name skip it entirely. +// +// Lifetime: a handle-backed PropNameID borrows a v8::Local and is therefore +// only valid inside the HandleScope that produced it. That suits the dispatch +// it exists for -- the engine builds one per interception and it dies with the +// call. Anything that must outlive the scope (getPropertyNames' return vector) +// is built from a string and has no handle at all. +class PropNameID { + public: + PropNameID() = default; + explicit PropNameID(std::string value) + : value_(std::move(value)), hasUtf8_(true) {} + + PropNameID(v8::Isolate* isolate, v8::Local name) + : isolate_(isolate), name_(name) {} + + static PropNameID forAscii(Runtime&, const char* value) { + return PropNameID(value != nullptr ? std::string(value) : std::string()); + } + + static PropNameID forAscii(Runtime&, const std::string& value) { return PropNameID(value); } + + // Converted at most once, and only if asked. Still returns by value: callers + // on the Apple side assign it straight into a std::string, and handing out a + // reference into a temporary PropNameID would be a footgun for no gain. + std::string utf8(Runtime&) const { return utf8(); } + + std::string utf8() const { + if (!hasUtf8_) { + if (isolate_ != nullptr && !name_.IsEmpty()) { + v8::String::Utf8Value text(isolate_, name_); + if (*text != nullptr) value_.assign(*text, text.length()); + } + hasUtf8_ = true; + } + return value_; + } + + // Empty unless this name came from the engine; see the lifetime note above. + v8::Local local() const { return name_; } + bool hasLocal() const { return !name_.IsEmpty(); } + + private: + v8::Isolate* isolate_ = nullptr; + v8::Local name_; + mutable std::string value_; + mutable bool hasUtf8_ = false; +}; + +class HostObject { + public: + virtual ~HostObject() = default; + virtual Value get(Runtime& runtime, const PropNameID& name); + virtual bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); + + // Indexed access, for a host object that is really an indexable collection. + // + // Reached through the named form, an index arrives as a decimal string that + // the host object has to parse back into an integer -- a heap allocation and + // a parse on every `obj[i]`. These take the integer. The defaults reproduce + // the named form exactly, so overriding them is optional. + // + // An engine with a dedicated indexed hook (V8) always routes an index here. + // An engine that delivers an index as a property name consults + // hasIndexedAccess() first, because for it the named path also carries the + // prototype handling that a non-indexable host object still needs. + virtual Value getValueAtIndex(Runtime& runtime, uint32_t index); + virtual bool setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value); + + bool hasIndexedAccess() const { return indexedAccess_; } + void setIndexedAccess(bool value) { indexedAccess_ = value; } + + // The JS object standing for this host object, valid ONLY for the duration of + // the call the engine is currently dispatching. + // + // Handed in per call rather than stored, and deliberately non-owning. A + // HostObject that holds an owned handle to its own wrapper is a strong + // self-cycle: the engine keeps the wrapper weak so collection can fire the + // finalizer, but the cycle makes the object permanently reachable and the + // weak callback never runs. On Android that meant no host object was ever + // collected, so ObjectManager never called makeInstanceWeak and every Java + // instance stayed strongly held. + const Value* receiver() const { return receiver_; } + + // Sets the receiver for one dispatch and clears it on scope exit. + class ReceiverScope { + public: + ReceiverScope(HostObject& host, const Value& receiver) : host_(host) { + host_.receiver_ = &receiver; + } + ~ReceiverScope() { host_.receiver_ = nullptr; } + ReceiverScope(const ReceiverScope&) = delete; + ReceiverScope& operator=(const ReceiverScope&) = delete; + + private: + HostObject& host_; + }; + + private: + const Value* receiver_ = nullptr; + bool indexedAccess_ = false; +}; + +using HostFunctionType = std::function; + +namespace v8engine { + +struct RuntimeState { + explicit RuntimeState(v8::Isolate* isolate, v8::Local context) : isolate(isolate) { + this->context.Reset(isolate, context); + } + + ~RuntimeState() { + nativeClassArgumentLast.value.Reset(); + nativeClassArgumentLast.nativeClass = nullptr; + for (auto& entry : nativeClassArgumentCache) { + entry.value.Reset(); + entry.nativeClass = nullptr; + } + nativeSelectorArgumentLast.value.Reset(); + nativeSelectorArgumentLast.selector = nullptr; + for (auto& entry : nativeSelectorArgumentCache) { + entry.value.Reset(); + entry.selector = nullptr; + } + nativeStateKey.Reset(); + context.Reset(); + } + + v8::Local localContext() const { + v8::Local ctx = context.Get(isolate); + return ctx.IsEmpty() ? isolate->GetCurrentContext() : ctx; + } + + v8::Isolate* isolate = nullptr; + v8::Global context; + // The private-symbol key every native-state slot hangs off. Private symbols + // are looked up own-only (V8 configures the LookupIterator with + // OWN_SKIP_INTERCEPTOR for them) and are invisible to JS, which is exactly + // what a native payload slot wants: no prototype-chain walk on a miss, no + // interceptor dispatch, and nothing observable from script. + v8::Global nativeStateKey; + v8::Global hostObjectTemplate; + v8::Global nativeObjectTemplate; // kNonMasking for instances + std::vector> retainedNativeData; + // Argument-marshalling caches: they memoise "this JS value denotes that + // native type / method" so a repeated call does not re-resolve it. + // + // Both native handles are held opaquely so this struct stays platform + // neutral. They are Class and SEL on Apple, and the JNI equivalents + // (jclass, jmethodID) on Android -- all four are pointers. The bridge that + // populates and reads these casts back to its own types; see + // ffi/objc/v8/NativeApiV8Marshalling.mm for the Apple side. + struct NativeClassArgumentCacheEntry { + v8::Global value; + const void* nativeClass = nullptr; + }; + NativeClassArgumentCacheEntry nativeClassArgumentLast; + NativeClassArgumentCacheEntry nativeClassArgumentCache[4]; + size_t nativeClassArgumentCacheNext = 0; + struct NativeSelectorArgumentCacheEntry { + v8::Global value; + const void* selector = nullptr; + }; + NativeSelectorArgumentCacheEntry nativeSelectorArgumentLast; + NativeSelectorArgumentCacheEntry nativeSelectorArgumentCache[4]; + size_t nativeSelectorArgumentCacheNext = 0; +}; + +struct ValueStorage { + enum class Kind : uint8_t { + Undefined, + Null, + Bool, + Number, + V8, + V8Borrowed, + }; + + explicit ValueStorage(Kind kind) : kind(kind) {} + + ~ValueStorage() { value.Reset(); } + + // Reset the owned handle and remember which isolate it belongs to. + // + // The isolate is recorded because reading the handle back needs one, and the + // only other way to obtain it -- v8::Isolate::GetCurrent() -- is a + // thread-local read, which is not cheap here: V8 is linked as a prebuilt + // archive compiled with *emulated* TLS, so GetCurrent() is a PLT call into + // __emutls_get_address followed by pthread_getspecific. In the V8-13 + // marshalling profile that pair cost 4.07% + half of 2.13% of the benchmark + // thread, almost all of it reached through Value's type predicates + // (isString, isUndefined, ...), which called GetCurrent() once per question + // asked. No build flag fixes it -- the emutls calls are inside the prebuilt + // V8 -- so the fix is to stop asking. + // + // Storing it is also strictly more correct than GetCurrent(): a handle + // belongs to the isolate that created it, not to whichever isolate happens + // to be entered on this thread. Workers have their own. + void reset(v8::Isolate* isolateIn, v8::Local local) { + isolate = isolateIn; + value.Reset(isolateIn, local); + } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + // Null until an owned handle is stored; readers fall back to GetCurrent(), + // which keeps any caller that Resets `value` directly working unchanged. + v8::Isolate* isolate = nullptr; + v8::Global value; + v8::Local borrowedValue; + + // The isolate this storage's handle belongs to, or the current one if this + // storage was filled without going through reset(). + v8::Isolate* isolateOrCurrent() const { + return isolate != nullptr ? isolate : v8::Isolate::GetCurrent(); + } +}; + +template +const void* hostObjectTypeToken() { + static int token = 0; + return &token; +} + +struct HostObjectHolder { + HostObjectHolder(std::shared_ptr state, std::shared_ptr hostObject, + const void* typeToken) + : state(std::move(state)), hostObject(std::move(hostObject)), typeToken(typeToken) {} + + ~HostObjectHolder() { object.Reset(); } + + std::shared_ptr state; + std::shared_ptr hostObject; + const void* typeToken = nullptr; + v8::Global object; +}; + +struct FunctionHolder { + FunctionHolder(std::shared_ptr state, HostFunctionType callback) + : state(std::move(state)), callback(std::move(callback)) {} + + ~FunctionHolder() { function.Reset(); } + + std::shared_ptr state; + HostFunctionType callback; + v8::Global function; +}; + +// A weak handle: does not keep its referent alive. +// +// Node-API's napi_ref with refcount 0 is weak, and the Android runtime depends +// on it -- ObjectManager holds each host-object proxy through one so that +// dropping it from JS triggers the finalizer that calls makeInstanceWeak. +// Approximating it as strong pins every Java instance for the process +// lifetime. +struct WeakStorage { + v8::Global value; + ~WeakStorage() { value.Reset(); } +}; + +struct ArrayBufferHolder { + explicit ArrayBufferHolder(std::shared_ptr buffer) : buffer(std::move(buffer)) {} + + std::shared_ptr buffer; + v8::Global object; +}; + +inline v8::Local makeV8String(v8::Isolate* isolate, const std::string& value) { + return v8::String::NewFromUtf8(isolate, value.c_str(), v8::NewStringType::kNormal, + static_cast(value.size())) + .ToLocalChecked(); +} + +// Property *names* want kInternalized, not kNormal. +// +// A non-internalized key forces V8 to hash and internalize the string inside +// every Get/Set/Has, and defeats the descriptor-array pointer-compare fast +// path. The Node-API implementation this replaces internalizes every property +// name; we did not, on ~250 named-property call sites in the runtime. +inline v8::Local makeV8Name(v8::Isolate* isolate, const char* value) { + return v8::String::NewFromUtf8(isolate, value != nullptr ? value : "", + v8::NewStringType::kInternalized) + .ToLocalChecked(); +} + +inline std::string toUtf8(v8::Isolate* isolate, v8::Local value) { + if (value.IsEmpty()) { + return {}; + } + v8::String::Utf8Value utf8(isolate, value); + return *utf8 != nullptr ? std::string(*utf8, utf8.length()) : std::string(); +} + +inline std::string propertyNameToUtf8(v8::Isolate* isolate, v8::Local property) { + if (property->IsSymbol() && + property.As()->StrictEquals(v8::Symbol::GetIterator(isolate))) { + return "Symbol.iterator"; + } + return toUtf8(isolate, property); +} + +inline std::string currentExceptionMessage(v8::Isolate* isolate, v8::TryCatch& tryCatch) { + if (tryCatch.HasCaught()) { + return toUtf8(isolate, tryCatch.Exception()); + } + return "NativeScript V8 engine operation failed."; +} + +// Rebuild a JS error from its stringified form, preserving its type. +// +// currentExceptionMessage stringifies the caught exception, which for a JS +// error yields "TypeError: msg" -- the type is in the text. Everything that +// crosses back into JS used to go through v8::Exception::Error, so a +// SyntaxError thrown by a module became a plain Error by the time JS caught it. +inline v8::Local makeV8Error(v8::Isolate* isolate, const std::string& text) { + // Direct calls rather than a table of function pointers: the v8::Exception + // factories gained an optional second parameter in newer V8, so their + // signatures differ across the versions this header serves. + const auto rest = [&](size_t prefix) { + return makeV8String(isolate, text.substr(prefix)); + }; + if (text.compare(0, 12, "RangeError: ") == 0) return v8::Exception::RangeError(rest(12)); + if (text.compare(0, 16, "ReferenceError: ") == 0) return v8::Exception::ReferenceError(rest(16)); + if (text.compare(0, 13, "SyntaxError: ") == 0) return v8::Exception::SyntaxError(rest(13)); + if (text.compare(0, 11, "TypeError: ") == 0) return v8::Exception::TypeError(rest(11)); + return v8::Exception::Error(makeV8String(isolate, text)); +} + +// Declared here, defined after Value: a JSError may carry the original thrown +// value, and rethrowing that verbatim is what preserves its identity and +// properties. Anything else (including every Apple-side JSError, which is +// message-only) falls back to rebuilding from the text. +inline void throwV8Exception(v8::Isolate* isolate, const JSError& error); +inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception); + +} // namespace v8engine + +class Runtime { + public: + Runtime(v8::Isolate* isolate, v8::Local context) + : state_(std::make_shared(isolate, context)) {} + + explicit Runtime(std::shared_ptr state) : state_(std::move(state)) {} + + v8::Isolate* isolate() const { return state_->isolate; } + v8::Local context() const { return state_->localContext(); } + v8engine::RuntimeState* rawState() const { return state_.get(); } + + // A stable, per-runtime identity. + // + // engine::Runtime is a value wrapper around shared engine state, and the + // host-function trampolines construct a fresh one on the stack for every + // callback. So `&runtime` is NOT stable and must never be used as a map key; + // this is. The pointer is opaque and only ever compared or hashed. + const void* identity() const { return state_.get(); } + + // See RuntimeState::nativeStateKey. Created on first use so a runtime that + // never wraps anything never allocates it. + v8::Local nativeStateKey() const { + if (state_->nativeStateKey.IsEmpty()) { + state_->nativeStateKey.Reset( + state_->isolate, + v8::Private::New(state_->isolate, + v8engine::makeV8Name(state_->isolate, "__nsNativeState"))); + } + return state_->nativeStateKey.Get(state_->isolate); + } + std::shared_ptr state() const { return state_; } + + Object global(); + + Value evaluateJavaScript(std::shared_ptr buffer, const std::string& sourceURL); + + void drainMicrotasks() { isolate()->PerformMicrotaskCheckpoint(); } + + private: + std::shared_ptr state_; +}; + +class String { + public: + String() = default; + String(Runtime& runtime, v8::Local value); + + static String createFromUtf8(Runtime& runtime, const char* value) { + return String(runtime, + v8engine::makeV8String(runtime.isolate(), value != nullptr ? value : "")); + } + + static String createFromUtf8(Runtime& runtime, const std::string& value) { + return String(runtime, v8engine::makeV8String(runtime.isolate(), value)); + } + + static String createFromUtf8(Runtime& runtime, const uint8_t* value, size_t length) { + return String(runtime, v8::String::NewFromUtf8( + runtime.isolate(), + reinterpret_cast( + value != nullptr ? value : reinterpret_cast("")), + v8::NewStringType::kNormal, static_cast(length)) + .ToLocalChecked()); + } + + std::string utf8(Runtime& runtime) const { + return v8engine::toUtf8(runtime.isolate(), local(runtime)); + } + + v8::Local local(Runtime& runtime) const { + if (storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) { + return storage_->borrowedValue.As(); + } + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const; + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class Value { + public: + Value() : kind_(v8engine::ValueStorage::Kind::Undefined) {} + + Value(bool value) : kind_(v8engine::ValueStorage::Kind::Bool), boolValue_(value) {} + + Value(double value) : kind_(v8engine::ValueStorage::Kind::Number), numberValue_(value) {} + + Value(int value) : Value(static_cast(value)) {} + Value(uint32_t value) : Value(static_cast(value)) {} + + Value(Runtime& runtime, const Value& value) { + if (value.kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + // Promote borrowed to owned + storage_ = + std::make_shared(v8engine::ValueStorage::Kind::V8); + storage_->reset(runtime.isolate(), value.borrowedValue_); + kind_ = v8engine::ValueStorage::Kind::V8; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + isolate_ = value.isolate_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + // Must promote exactly as the copy constructor above does. Both spell + // "make a Value owned by this Runtime", and that has to hold whatever the + // argument's value category is: a borrowed Value is a bare v8::Local, alive + // only until its HandleScope closes, so carrying the tag through unchanged + // produces a handle that dangles the moment the scope unwinds. + // + // This was invisible while every caller passed an lvalue -- overload + // resolution picked the copy constructor and promoted. The first caller to + // pass a temporary (the napi shim's refValue, once napi_value became a v8 + // handle slot) got this one instead, stored the borrowed handle in a + // napi_ref, and crashed in GlobalizeReference on the next read. + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + isolate_(value.isolate_), + borrowedValue_(value.borrowedValue_), + storage_(std::move(value.storage_)) { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + storage_ = std::make_shared( + v8engine::ValueStorage::Kind::V8); + storage_->reset(runtime.isolate(), borrowedValue_); + kind_ = v8engine::ValueStorage::Kind::V8; + } + } + Value(Runtime& runtime, const String& value); + Value(Runtime& runtime, const Object& object); + Value(Runtime& runtime, const Function& function); + Value(Runtime& runtime, const Array& array); + Value(Runtime& runtime, const ArrayBuffer& arrayBuffer); + Value(Runtime& runtime, const BigInt& bigint); + + static Value undefined() { return Value(); } + + static Value null() { + Value value; + value.kind_ = v8engine::ValueStorage::Kind::Null; + return value; + } + + static bool strictEquals(Runtime& runtime, const Value& lhs, + const Value& rhs) { + return lhs.local(runtime)->StrictEquals(rhs.local(runtime)); + } + + bool isUndefined() const; + bool isNull() const; + bool isBool() const; + bool getBool() const; + bool isNumber() const; + double getNumber() const; + + bool isObject() const; + bool isString() const; + bool isBigInt() const; + bool isSymbol() const; + + Object asObject(Runtime& runtime) const; + // Like asObject, but a borrowed Value yields a borrowed Object; see + // Object::getPropertyBorrowed for the lifetime contract. + Object asObjectBorrowed(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + // Read the UTF-8 of a string value without materialising a String. + // + // asString(rt).utf8(rt) is the obvious spelling and it is what every caller + // used, but String is an *owning* type: constructing one costs a + // make_shared plus a GlobalHandles::Create, and destroying it + // costs a NodeSpace::Release plus the free -- for a handle that is dead two + // statements later. Reading straight off the Local skips all four. The Local + // is already rooted in the enclosing HandleScope, which is what makes this + // safe rather than merely cheaper. + std::string utf8(Runtime& runtime) const; + + // Create a string value without materialising an owning handle. + // + // The mirror image of utf8(): String::createFromUtf8 globalizes the fresh + // v8::Local so the String can outlive the scope, but a value that is handed + // straight back to the engine (a marshalled Java string, a property name) + // never leaves the HandleScope it was made in. Borrowing the Local instead + // removes the make_shared and the global handle entirely. On V8-13 that pair + // was 81% of all operator new traffic on the marshalling thread. + // + // Contract: the result must not outlive the current HandleScope. Callers that + // need an owning value keep using String::createFromUtf8. + static Value createStringFromUtf8(Runtime& runtime, const char* data, size_t length); + + v8::Local local(Runtime& runtime) const; + + // Only the isolate is ever needed, and some callers (rethrowing a caught + // JSError from a host-function trampoline) have one without a Runtime. + v8::Local local(v8::Isolate* isolate) const { + switch (kind_) { + case v8engine::ValueStorage::Kind::Undefined: + return v8::Undefined(isolate); + case v8engine::ValueStorage::Kind::Null: + return v8::Null(isolate); + case v8engine::ValueStorage::Kind::Bool: + return v8::Boolean::New(isolate, boolValue_); + case v8engine::ValueStorage::Kind::Number: + return v8::Number::New(isolate, numberValue_); + case v8engine::ValueStorage::Kind::V8: + return storage_->value.Get(isolate); + case v8engine::ValueStorage::Kind::V8Borrowed: + return borrowedValue_; + } + } + + Value(Runtime& runtime, v8::Local value) + : kind_(v8engine::ValueStorage::Kind::V8), + storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->reset(runtime.isolate(), value); + } + + static Value borrowed(Runtime& runtime, v8::Local value) { + return borrowed(runtime.isolate(), value); + } + + // Records the isolate the handle belongs to, for the same reason + // ValueStorage::reset does: getNumber()/getBool() on a borrowed value need + // one, and the only other source is v8::Isolate::GetCurrent(), which lands in + // __emutls_get_address inside the prebuilt V8. Callers that have an isolate + // should hand it over rather than make the read pay for it. + static Value borrowed(v8::Isolate* isolate, v8::Local value) { + Value result; + result.kind_ = v8engine::ValueStorage::Kind::V8Borrowed; + result.isolate_ = isolate; + result.borrowedValue_ = value; + return result; + } + + // The Runtime is not used -- borrowing is just tagging a Local -- and callers + // that have only a handle should not have to invent one. The napi shim's + // napi_value -> Value conversion is on the hottest path there is and has no + // Runtime in scope. + static Value borrowed(v8::Local value) { + return borrowed(static_cast(nullptr), value); + } + + // Access the shared storage (for Object/Function/Array interop) + std::shared_ptr storage() const { return storage_; } + + static Value fromStorage(std::shared_ptr s) { + Value v; + v.kind_ = s->kind; + v.boolValue_ = s->boolValue; + v.numberValue_ = s->numberValue; + v.isolate_ = s->isolate; + v.borrowedValue_ = s->borrowedValue; + v.storage_ = std::move(s); + return v; + } + + private: + friend class Runtime; + friend class Object; + friend class String; + friend class BigInt; + friend class ArrayBuffer; + friend class Function; + friend class Array; + + v8engine::ValueStorage::Kind kind_ = v8engine::ValueStorage::Kind::Undefined; + bool boolValue_ = false; + double numberValue_ = 0; + // Only meaningful for the borrowed kind; the owned kind keeps its isolate in + // ValueStorage. Null means "ask the thread", which is the old behaviour. + v8::Isolate* isolate_ = nullptr; + v8::Local borrowedValue_; + std::shared_ptr storage_; + + v8::Isolate* borrowedIsolate() const { + return isolate_ != nullptr ? isolate_ : v8::Isolate::GetCurrent(); + } +}; + +inline v8::Local Value::local(Runtime& runtime) const { + return local(runtime.isolate()); +} + +// Defined after Value for the same reason as JSError's payload ctor. +inline WeakObject::WeakObject(Runtime& runtime, const Value& value) + : storage_(std::make_shared()) { + storage_->value.Reset(runtime.isolate(), value.local(runtime)); + // Parameterless SetWeak: no finalizer callback, the Global simply empties + // when the referent is collected. That is exactly napi_ref's weak contract -- + // napi_get_reference_value then reports undefined. + storage_->value.SetWeak(); +} + +inline bool WeakObject::empty() const { + return storage_ == nullptr || storage_->value.IsEmpty(); +} + +inline Value WeakObject::lock(Runtime& runtime) const { + if (storage_ == nullptr || storage_->value.IsEmpty()) { + return Value::undefined(); + } + return Value(runtime, storage_->value.Get(runtime.isolate())); +} + +// Defined here rather than with the class: constructing the shared_ptr needs +// Value to be complete. +inline JSError::JSError(Runtime& runtime, const std::string& message, + const Value& value, std::string stack) + : std::runtime_error(message), + value_(std::make_shared(runtime, value)), + stack_(std::move(stack)) {} + +namespace v8engine { + +// Turn a caught V8 exception into a JSError that carries the thrown value. +// +// Everything the engine layer throws on a failed operation should come through +// here, so the value survives to whoever catches it. The Node-API shim stores +// it as the pending exception verbatim, which is what keeps runtime-attached +// properties (NativeScriptException's `nativeException`) and the original stack +// intact -- rebuilding an error from its message drops both. +inline JSError caughtError(Runtime& runtime, v8::TryCatch& tryCatch) { + const std::string message = currentExceptionMessage(runtime.isolate(), tryCatch); + if (!tryCatch.HasCaught()) { + return JSError(runtime, message); + } + v8::Local caught = tryCatch.Exception(); + if (caught.IsEmpty()) { + return JSError(runtime, message); + } + // The stack is read eagerly: `error.stack` is a lazy accessor, and the + // TryCatch that makes it resolvable is gone by the time anyone catches this. + std::string stack; + if (caught->IsObject()) { + v8::Local stackValue; + if (caught.As() + ->Get(runtime.context(), makeV8String(runtime.isolate(), "stack")) + .ToLocal(&stackValue) && + stackValue->IsString()) { + stack = toUtf8(runtime.isolate(), stackValue); + } + } + // Owned, not borrowed: this outlives the handle scope it was caught in. + return JSError(runtime, message, Value(runtime, caught), std::move(stack)); +} + +} // namespace v8engine + +namespace v8engine { + +// Overloaded rather than dispatched with dynamic_cast: this tree builds with +// -fno-rtti. Callers catch JSError explicitly before std::exception, so the +// value-carrying overload is chosen at the catch site. +inline void throwV8Exception(v8::Isolate* isolate, const JSError& error) { + if (const Value* thrown = error.value()) { + // Only if it is a real value; see exceptionFrom in the Node-API shim for + // why an undefined payload must not displace the message. + if (!thrown->isUndefined() && !thrown->isNull()) { + // Rethrow the original object, so its identity and any properties the + // runtime attached to it survive. + isolate->ThrowException(thrown->local(isolate)); + return; + } + } + isolate->ThrowException(makeV8Error(isolate, error.what())); +} + +inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { + isolate->ThrowException(makeV8Error(isolate, exception.what())); +} + +} // namespace v8engine + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->reset(runtime.isolate(), v8::Object::New(runtime.isolate())); + } + + static Object fromValueStorage(std::shared_ptr storage) { + Object object; + object.storage_ = std::move(storage); + return object; + } + + template + static Object createFromHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createFromHostObjectWithToken(runtime, std::move(baseHost), + v8engine::hostObjectTypeToken()); + } + + // Create a native object instance using kNonMasking template for fast + // prototype-based property access. + template + static Object createNativeInstanceHostObject(Runtime& runtime, std::shared_ptr host) { + auto baseHost = std::static_pointer_cast(std::move(host)); + return createNativeInstanceWithToken(runtime, std::move(baseHost), + v8engine::hostObjectTypeToken()); + } + + static Object createNativeInstanceWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + Value getProperty(Runtime& runtime, const char* name) const { + return getProperty(runtime, + v8engine::makeV8Name(runtime.isolate(), name)); + } + + Value getProperty(Runtime& runtime, const std::string& name) const { + return getProperty(runtime, name.c_str()); + } + + Value getProperty(Runtime& runtime, const Value& key) const { + return getProperty(runtime, key.local(runtime)); + } + + Value getProperty(Runtime& runtime, v8::Local key) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value(runtime, result); + } + + // Reads that hand back a borrowed handle rather than an owned one. + // + // getProperty returns an *owned* Value: a shared_ptr plus a + // v8::Global, created and then released again as soon as the caller is done. + // For a caller whose result is already rooted by the enclosing HandleScope + // that is pure overhead -- and it is measurable overhead, because a v8::Global + // is a GC root that every scavenge has to scan. In the V8-13 marshalling + // profile GlobalHandles::Create, GlobalizeReference and NodeSpace::Release + // together were 11.3% of the benchmark thread. + // + // The Node-API shim is exactly such a caller: a napi_value is defined to stay + // valid only until its handle scope closes, and every napi handle scope is + // strictly nested inside the v8::HandleScope that NapiScope opens on entry + // from the host. So the shim's read paths use these. + // + // The result is valid only inside the HandleScope that produced it. Anything + // that must outlive it goes through Value(Runtime&, const Value&), which + // promotes -- napi_create_reference and napi_throw already do. + Value getPropertyBorrowed(Runtime& runtime, v8::Local key) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), key).ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value::borrowed(runtime.isolate(), result); + } + + Value getPropertyBorrowed(Runtime& runtime, const char* name) const { + return getPropertyBorrowed(runtime, v8engine::makeV8Name(runtime.isolate(), name)); + } + + Value getPropertyBorrowed(Runtime& runtime, const Value& key) const { + return getPropertyBorrowed(runtime, key.local(runtime)); + } + + Object getPropertyAsObject(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime); + } + + Function getPropertyAsFunction(Runtime& runtime, const char* name) const; + + void setProperty(Runtime& runtime, const char* name, const Value& value) { + setProperty(runtime, v8engine::makeV8Name(runtime.isolate(), name), + value); + } + + void setProperty(Runtime& runtime, const char* name, const String& value) { + setProperty(runtime, name, Value(runtime, value)); + } + + void setProperty(Runtime& runtime, const char* name, const Object& value) { + setProperty(runtime, name, Value(runtime, value)); + } + + void setProperty(Runtime& runtime, const char* name, const Function& value); + void setProperty(Runtime& runtime, const char* name, const Array& value); + void setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value); + void setProperty(Runtime& runtime, const char* name, bool value) { + setProperty(runtime, name, Value(value)); + } + void setProperty(Runtime& runtime, const char* name, double value) { + setProperty(runtime, name, Value(value)); + } + + void setProperty(Runtime& runtime, const std::string& name, const Value& value) { + setProperty(runtime, name.c_str(), value); + } + + void setProperty(Runtime& runtime, const Value& key, const Value& value) { + setProperty(runtime, key.local(runtime), value); + } + + void setProperty(Runtime& runtime, v8::Local key, const Value& value) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime)->Set(runtime.context(), key, value.local(runtime)).FromMaybe(false)) { + throw v8engine::caughtError(runtime, tryCatch); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + v8::TryCatch tryCatch(runtime.isolate()); + return local(runtime) + ->Has(runtime.context(), + v8engine::makeV8Name(runtime.isolate(), name)) + .FromMaybe(false); + } + + bool isFunction(Runtime& runtime) const { return local(runtime)->IsFunction(); } + bool isArray(Runtime& runtime) const { return local(runtime)->IsArray(); } + bool isArrayBuffer(Runtime& runtime) const { return local(runtime)->IsArrayBuffer(); } + + Function asFunction(Runtime& runtime) const; + Array getArray(Runtime& runtime) const; + ArrayBuffer getArrayBuffer(Runtime& runtime) const; + Array getPropertyNames(Runtime& runtime) const; + + template + bool isHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + return holder != nullptr && holder->typeToken == v8engine::hostObjectTypeToken(); + } + + template + std::shared_ptr getHostObject(Runtime& runtime) const { + auto holder = hostObjectHolder(runtime); + if (holder == nullptr || holder->typeToken != v8engine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(holder->hostObject); + } + + // ---- Native state ------------------------------------------------------- + // + // An opaque native payload attached to a JS object, mirroring + // jsi::Object::setNativeState/getNativeState. The point is that reading it + // back is *not* a JS property lookup: napi_unwrap runs on every marshalled + // field access, and doing it as `hasProperty("__nsWrap") + + // getProperty("__nsWrap")` cost two full prototype-chain walks per call. + // + // The payload is still held by a HostObject, so its lifetime and finaliser + // timing are byte-for-byte what they were when the slot was a named + // property: the engine's weak callback releases the holder, the shared_ptr + // drops, and ~T runs the napi_finalize. Only the *key* changed. + template + void setNativeState(Runtime& runtime, std::shared_ptr state) { + Object holder = Object::createFromHostObject(runtime, std::move(state)); + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime) + ->SetPrivate(runtime.context(), runtime.nativeStateKey(), holder.local(runtime)) + .FromMaybe(false)) { + throw v8engine::caughtError(runtime, tryCatch); + } + } + + template + std::shared_ptr getNativeState(Runtime& runtime) const { + v8::Local holder; + if (!local(runtime) + ->GetPrivate(runtime.context(), runtime.nativeStateKey()) + .ToLocal(&holder) || + !holder->IsObject()) { + return nullptr; + } + v8::Local holderObject = holder.As(); + if (holderObject->InternalFieldCount() < 1) return nullptr; + auto* record = static_cast( + holderObject->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + if (record == nullptr || record->typeToken != v8engine::hostObjectTypeToken()) { + return nullptr; + } + return std::static_pointer_cast(record->hostObject); + } + + v8::Local local(Runtime& runtime) const { + if (storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) { + return storage_->borrowedValue.As(); + } + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const { + return Value::fromStorage(storage_); + } + + protected: + friend class Value; + friend class Runtime; + friend class Function; + friend class Array; + friend class ArrayBuffer; + + explicit Object(std::shared_ptr storage) : storage_(std::move(storage)) {} + + static Object createFromHostObjectWithToken(Runtime& runtime, std::shared_ptr host, + const void* typeToken); + + v8engine::HostObjectHolder* hostObjectHolder(Runtime& runtime) const { + v8::Local object = local(runtime); + if (object->InternalFieldCount() < 1) { + return nullptr; + } + return static_cast(object->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault)); + } + + std::shared_ptr storage_; +}; + +class Function : public Object { + public: + Function() = default; + explicit Function(Object object) : Object(std::move(object.storage_)) {} + + static Function createFromHostFunction(Runtime& runtime, const PropNameID& name, unsigned int, + HostFunctionType callback); + + // Like createFromHostFunction, but the result is a constructor whose + // `prototype` property is writable. + // + // createFromHostFunction builds the function from a v8::FunctionTemplate, and + // V8 makes such a function's `prototype` non-writable and non-configurable. + // Node-API callers expect otherwise -- V8's own Node-API implementation uses + // v8::Function::New, whose `prototype` behaves like a normal function's -- + // and the Android runtime relies on it: MetadataNode chains class prototypes + // with a plain `ctor.prototype = ...` assignment, which against a template + // function fails *silently* in sloppy mode and drops the whole inheritance + // chain. + static Function createFromHostConstructor(Runtime& runtime, const PropNameID& name, + unsigned int paramCount, + HostFunctionType callback); + + Value call(Runtime& runtime, const Value* args, size_t count) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->Call(runtime.context(), runtime.context()->Global(), static_cast(argv.size()), + argv.data()) + .ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value(runtime, result); + } + + Value call(Runtime& runtime) const { + return call(runtime, static_cast(nullptr), 0); + } + + Value call(Runtime& runtime, std::nullptr_t, size_t) const { + return call(runtime, static_cast(nullptr), 0); + } + + // `count` is deduced rather than fixed to size_t on purpose. + // + // With a `size_t` parameter, `fn.call(rt, args, 2)` needed an int -> size_t + // conversion here while the variadic overload below matched exactly -- so the + // variadic won, and silently reinterpreted (array, count) as a two-argument + // JS call passing the array and the number. Every such call site in the + // Node-API shim was quietly broken, which surfaced as "Object.defineProperty + // called on non-object" and poisoned the env with a latched pending + // exception. Deducing the count makes this overload exact too, and partial + // ordering then prefers it over the pack. + template >>> + Value call(Runtime& runtime, const Value (&args)[N], Count count) const { + return call(runtime, static_cast(args), + static_cast(count)); + } + + template + Value call(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return call(runtime, static_cast(argv), sizeof...(Args)); + } + + Value callWithThis(Runtime& runtime, const Object& thisObject, const Value* args = nullptr, + size_t count = 0) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->Call(runtime.context(), thisObject.local(runtime), static_cast(argv.size()), + argv.data()) + .ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) const { + v8::TryCatch tryCatch(runtime.isolate()); + std::vector> argv; + argv.reserve(count); + for (size_t i = 0; i < count; i++) { + argv.push_back(args[i].local(runtime)); + } + v8::Local result; + if (!local(runtime) + .As() + ->NewInstance(runtime.context(), static_cast(argv.size()), argv.data()) + .ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value(runtime, result); + } + + Value callAsConstructor(Runtime& runtime, std::nullptr_t, size_t) const { + return callAsConstructor(runtime, static_cast(nullptr), 0); + } + + // Count deduced, matching call() above: with a fixed size_t an int literal + // made the variadic pack win overload resolution and silently constructed + // with (array, count) as two JS arguments. + template >>> + Value callAsConstructor(Runtime& runtime, const Value (&args)[N], Count count) const { + return callAsConstructor(runtime, static_cast(args), count); + } + + template + Value callAsConstructor(Runtime& runtime, Args&&... args) const { + Value argv[] = {Value(runtime, std::forward(args))...}; + return callAsConstructor(runtime, static_cast(argv), sizeof...(Args)); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; + +class Array : public Object { + public: + explicit Array(Runtime& runtime, size_t size) + : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->reset(runtime.isolate(), + v8::Array::New(runtime.isolate(), static_cast(size))); + } + + explicit Array(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { return local(runtime).As()->Length(); } + + Value getValueAtIndex(Runtime& runtime, size_t index) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value(runtime, result); + } + + // See Object::getPropertyBorrowed for the lifetime contract. + Value getValueAtIndexBorrowed(Runtime& runtime, size_t index) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->Get(runtime.context(), static_cast(index)).ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Value::borrowed(runtime.isolate(), result); + } + + void setValueAtIndex(Runtime& runtime, size_t index, const Value& value) { + v8::TryCatch tryCatch(runtime.isolate()); + if (!local(runtime) + ->Set(runtime.context(), static_cast(index), value.local(runtime)) + .FromMaybe(false)) { + throw v8engine::caughtError(runtime, tryCatch); + } + } + + void setValueAtIndex(Runtime& runtime, size_t index, const String& value) { + setValueAtIndex(runtime, index, Value(runtime, value)); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; + +class BigInt { + public: + BigInt() = default; + BigInt(Runtime& runtime, v8::Local value) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->reset(runtime.isolate(), value); + } + + static BigInt fromInt64(Runtime& runtime, int64_t value) { + return BigInt(runtime, v8::BigInt::New(runtime.isolate(), value)); + } + + static BigInt fromUint64(Runtime& runtime, uint64_t value) { + return BigInt(runtime, v8::BigInt::NewFromUnsigned(runtime.isolate(), value)); + } + + String toString(Runtime& runtime, int radix) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + (void)radix; + if (!local(runtime)->ToString(runtime.context()).ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return String(runtime, result); + } + + v8::Local local(Runtime& runtime) const { + if (storage_->kind == v8engine::ValueStorage::Kind::V8Borrowed) { + return storage_->borrowedValue.As(); + } + return storage_->value.Get(runtime.isolate()).As(); + } + + operator Value() const { + return Value::fromStorage(storage_); + } + + private: + friend class Value; + std::shared_ptr storage_; +}; + +class ArrayBuffer : public Object { + public: + ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) + : Object(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + auto holder = new v8engine::ArrayBufferHolder(std::move(buffer)); + auto backingStore = v8::ArrayBuffer::NewBackingStore( + holder->buffer->data(), holder->buffer->size(), + [](void*, size_t, void* deleterData) { + auto* holder = static_cast(deleterData); + holder->object.Reset(); + delete holder; + }, + holder); + v8::Local arrayBuffer = + v8::ArrayBuffer::New(runtime.isolate(), std::move(backingStore)); + storage_->reset(runtime.isolate(), arrayBuffer); + holder->object.Reset(runtime.isolate(), arrayBuffer); + } + + explicit ArrayBuffer(Object object) : Object(std::move(object.storage_)) {} + + size_t size(Runtime& runtime) const { return local(runtime).As()->ByteLength(); } + + uint8_t* data(Runtime& runtime) const { + auto backingStore = local(runtime).As()->GetBackingStore(); + return static_cast(backingStore->Data()); + } + + operator Value() const { + return Value::fromStorage(storage_); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 + +#endif // NATIVESCRIPT_JSI_V8_V8_RUNTIME_H diff --git a/NativeScript/jsi/v8/V8Value.cpp b/NativeScript/jsi/v8/V8Value.cpp new file mode 100644 index 000000000..4c9b129b0 --- /dev/null +++ b/NativeScript/jsi/v8/V8Value.cpp @@ -0,0 +1,304 @@ +#include "jsi/v8/V8Runtime.h" + +#ifdef TARGET_ENGINE_V8 + +namespace nativescript { +namespace engine { + +Value HostObject::get(Runtime&, const PropNameID&) { return Value::undefined(); } + +bool HostObject::set(Runtime&, const PropNameID&, const Value&) { return true; } + +std::vector HostObject::getPropertyNames(Runtime&) { return {}; } + +// The defaults reproduce what the engine used to do for an index: stringify it +// and take the named path. A host object that does not override these is +// therefore unaffected by the indexed routing. +Value HostObject::getValueAtIndex(Runtime& runtime, uint32_t index) { + return get(runtime, PropNameID(std::to_string(index))); +} + +bool HostObject::setValueAtIndex(Runtime& runtime, uint32_t index, const Value& value) { + // Value(runtime, value) promotes: the indexed setter hands over a borrowed + // value, and a host object reached through the named setter has always been + // given an owned one. + return set(runtime, PropNameID(std::to_string(index)), Value(runtime, value)); +} + +String::String(Runtime& runtime, v8::Local value) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->reset(runtime.isolate(), value); +} + +String::operator Value() const { + return Value::fromStorage(storage_); +} + +// Every one of these takes a Runtime, which is this layer's spelling of "give +// me a value I own". Since asObject stopped globalizing a borrowed handle, the +// storage they adopt may be borrowed -- so they route through +// Value(Runtime&, const Value&), whose whole job is to promote exactly that +// case. Copying the tag through unchanged would produce a Value that dangles +// when the enclosing HandleScope unwinds, and whose inline borrowedValue_ was +// never filled at all (that member lives on Value, not on the storage). +namespace { +Value adopt(Runtime& runtime, const std::shared_ptr& storage) { + if (!storage) { + return Value::undefined(); + } + return Value(runtime, Value::fromStorage(storage)); +} +} // namespace + +Value::Value(Runtime& runtime, const String& value) + : Value(adopt(runtime, value.storage_)) {} +Value::Value(Runtime& runtime, const Object& object) + : Value(adopt(runtime, object.storage_)) {} +Value::Value(Runtime& runtime, const Function& function) + : Value(adopt(runtime, function.storage_)) {} +Value::Value(Runtime& runtime, const Array& array) + : Value(adopt(runtime, array.storage_)) {} +Value::Value(Runtime& runtime, const ArrayBuffer& arrayBuffer) + : Value(adopt(runtime, arrayBuffer.storage_)) {} +Value::Value(Runtime& runtime, const BigInt& bigint) + : Value(adopt(runtime, bigint.storage_)) {} + +bool Value::isObject() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsObject(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsObject(); +} + +bool Value::isUndefined() const { + if (kind_ == v8engine::ValueStorage::Kind::Undefined) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return borrowedValue_.IsEmpty() || borrowedValue_->IsUndefined(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsUndefined(); +} + +bool Value::isNull() const { + if (kind_ == v8engine::ValueStorage::Kind::Null) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsNull(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsNull(); +} + +bool Value::isBool() const { + if (kind_ == v8engine::ValueStorage::Kind::Bool) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsBoolean(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsBoolean(); +} + +bool Value::getBool() const { + if (kind_ == v8engine::ValueStorage::Kind::Bool) { + return boolValue_; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + // A borrowed Value has no storage_ -- it is a bare Local -- so the isolate + // comes from the one recorded when it was tagged, falling back to the + // thread only for the handful of callers that had none. + v8::Isolate* isolate = borrowedIsolate(); + return isolate != nullptr && !borrowedValue_.IsEmpty() + ? borrowedValue_->BooleanValue(isolate) + : false; + } + if (kind_ == v8engine::ValueStorage::Kind::V8 && storage_ && !storage_->value.IsEmpty()) { + v8::Isolate* isolate = storage_->isolateOrCurrent(); + if (isolate != nullptr) { + return storage_->value.Get(isolate)->BooleanValue(isolate); + } + } + return false; +} + +bool Value::isNumber() const { + if (kind_ == v8engine::ValueStorage::Kind::Number) { + return true; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsNumber(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return isolate != nullptr && storage_->value.Get(isolate)->IsNumber(); +} + +double Value::getNumber() const { + if (kind_ == v8engine::ValueStorage::Kind::Number) { + return numberValue_; + } + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + // No storage_ on a borrowed Value; see getBool. + v8::Isolate* isolate = borrowedIsolate(); + if (isolate != nullptr && !borrowedValue_.IsEmpty()) { + return borrowedValue_->NumberValue(isolate->GetCurrentContext()).FromMaybe(0); + } + return 0; + } + if (kind_ == v8engine::ValueStorage::Kind::V8 && storage_ && !storage_->value.IsEmpty()) { + v8::Isolate* isolate = storage_->isolateOrCurrent(); + if (isolate != nullptr) { + return storage_->value.Get(isolate)->NumberValue(isolate->GetCurrentContext()).FromMaybe(0); + } + } + return 0; +} + +bool Value::isString() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsString(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return storage_->value.Get(isolate)->IsString(); +} + +bool Value::isBigInt() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsBigInt(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return storage_->value.Get(isolate)->IsBigInt(); +} + +bool Value::isSymbol() const { + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + return !borrowedValue_.IsEmpty() && borrowedValue_->IsSymbol(); + } + if (kind_ != v8engine::ValueStorage::Kind::V8 || !storage_ || storage_->value.IsEmpty()) { + return false; + } + v8::Isolate* isolate = storage_->isolateOrCurrent(); + return storage_->value.Get(isolate)->IsSymbol(); +} + +Object Value::asObject(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Need to promote to storage for Object + auto s = std::make_shared(kind_); + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + s->kind = v8engine::ValueStorage::Kind::V8; + s->reset(runtime.isolate(), borrowedValue_); + } + return Object::fromValueStorage(std::move(s)); +} + +Object Value::asObjectBorrowed(Runtime& runtime) const { + if (storage_) { + return Object::fromValueStorage(storage_); + } + // Unlike asObject, a borrowed Value stays borrowed. + // + // asObject globalizes the handle -- make_shared plus a + // GlobalHandles::Create, released again a few statements later -- to give + // Object a persistent handle. Object reads its handle through local(), which + // already understands the borrowed kind, so for a caller that does not + // outlive the enclosing HandleScope that Global is pure cost: an allocation + // plus a GC root that every scavenge scans, on every property access made + // through a borrowed value. + // + // Kept as a separate entry point rather than folded into asObject because + // asObject has ~140 call sites in the Apple bridge whose lifetimes have not + // been audited, and this weakens the guarantee. The Node-API shim is the one + // caller that provably qualifies: a napi_value is scope-bounded by + // definition, and every napi handle scope is nested inside the v8::HandleScope + // that NapiScope opens. + auto s = std::make_shared(kind_); + if (kind_ == v8engine::ValueStorage::Kind::V8Borrowed) { + s->isolate = isolate_ != nullptr ? isolate_ : runtime.isolate(); + s->borrowedValue = borrowedValue_; + } + return Object::fromValueStorage(std::move(s)); +} + +String Value::asString(Runtime& runtime) const { + return String(runtime, local(runtime).As()); +} + +std::string Value::utf8(Runtime& runtime) const { + return v8engine::toUtf8(runtime.isolate(), local(runtime)); +} + +Value Value::createStringFromUtf8(Runtime& runtime, const char* data, size_t length) { + v8::Isolate* isolate = runtime.isolate(); + return Value::borrowed( + isolate, v8::String::NewFromUtf8(isolate, data != nullptr ? data : "", + v8::NewStringType::kNormal, static_cast(length)) + .ToLocalChecked()); +} + +BigInt Value::getBigInt(Runtime& runtime) const { + return BigInt(runtime, local(runtime).As()); +} + +Function Object::getPropertyAsFunction(Runtime& runtime, const char* name) const { + return getProperty(runtime, name).asObject(runtime).asFunction(runtime); +} + +Function Object::asFunction(Runtime& runtime) const { return Function(*this); } + +Array Object::getArray(Runtime& runtime) const { return Array(*this); } + +ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const { return ArrayBuffer(*this); } + +Array Object::getPropertyNames(Runtime& runtime) const { + v8::TryCatch tryCatch(runtime.isolate()); + v8::Local result; + if (!local(runtime)->GetPropertyNames(runtime.context()).ToLocal(&result)) { + throw v8engine::caughtError(runtime, tryCatch); + } + return Array(Object::fromValueStorage(Value(runtime, result).storage_)); +} + +void Object::setProperty(Runtime& runtime, const char* name, const Function& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +void Object::setProperty(Runtime& runtime, const char* name, const Array& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +void Object::setProperty(Runtime& runtime, const char* name, const ArrayBuffer& value) { + setProperty(runtime, name, Value(runtime, value)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/napi/common/bytecode_container.h b/NativeScript/napi/common/bytecode_container.h new file mode 100644 index 000000000..81854a423 --- /dev/null +++ b/NativeScript/napi/common/bytecode_container.h @@ -0,0 +1,53 @@ +// +// Shared helpers for the NativeScript bytecode container used by the QuickJS +// family (and any engine that wraps engine bytecode in our container). +// +// Container layout (see tools/bytecode-compiler/native/*-compile.c): +// [8-byte magic][4-byte format version, little-endian][engine payload] +// +// Hermes is the exception: it stores raw HBC (its own magic, no container), so +// it doesn't use these helpers. +// +#ifndef NS_BYTECODE_CONTAINER_H +#define NS_BYTECODE_CONTAINER_H + +#include +#include +#include +#include + +namespace nsbc { + +// magic(8) + version(4) +static const size_t kHeaderLen = 12; + +// Turn a script source URL into the on-disk path we read bytecode from. Returns +// false for synthetic / non-file sources (e.g. ""). +inline bool ResolvePath(const char *file, std::string &out) { + if (file == nullptr) return false; + std::string f(file); + static const std::string scheme = "file://"; + if (f.rfind(scheme, 0) == 0) { + out = f.substr(scheme.size()); + } else if (!f.empty() && f[0] == '/') { + out = f; + } else { + return false; + } + return !out.empty(); +} + +// Cheaply check whether `path` starts with the 8-byte container magic. `magic8` +// must point to 8 bytes (e.g. the string literal "NSBCQJS" — 7 chars + NUL). +inline bool HasMagic(const std::string &path, const char *magic8) { + uint8_t head[8]; + FILE *fp = fopen(path.c_str(), "rb"); + if (!fp) return false; + size_t n = fread(head, 1, sizeof(head), fp); + fclose(fp); + return n == sizeof(head) && memcmp(head, magic8, sizeof(head)) == 0; +} + +} // namespace nsbc + +#endif // NS_BYTECODE_CONTAINER_H diff --git a/NativeScript/napi/common/js_native_api.h b/NativeScript/napi/common/js_native_api.h index 314342ca1..4821a1746 100644 --- a/NativeScript/napi/common/js_native_api.h +++ b/NativeScript/napi/common/js_native_api.h @@ -1,10 +1,38 @@ #ifndef SRC_JS_NATIVE_API_H_ #define SRC_JS_NATIVE_API_H_ +// This runtime intentionally enables the experimental Node-API surface (see +// NAPI_EXPERIMENTAL below). Opt out of the informational #warning that +// js_native_api_types.h emits for it, rather than having every translation unit +// print it. Must be set before that header is first included. +#ifndef NODE_API_EXPERIMENTAL_NO_WARNING +#define NODE_API_EXPERIMENTAL_NO_WARNING +#endif + #include "js_native_api_types.h" -#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) -typedef uint16_t char16_t; +// If you need __declspec(dllimport), either include instead, or +// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. +#ifndef NAPI_EXTERN +#ifdef _WIN32 +#define NAPI_EXTERN __declspec(dllexport) +#elif defined(__wasm__) +#define NAPI_EXTERN \ + __attribute__((visibility("default"))) \ + __attribute__((__import_module__("napi"))) +#else +#define NAPI_EXTERN __attribute__((visibility("default"))) +#endif +#endif + +#define NAPI_AUTO_LENGTH SIZE_MAX + +#ifdef __cplusplus +#define EXTERN_C_START extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_START +#define EXTERN_C_END #endif EXTERN_C_START @@ -14,8 +42,20 @@ EXTERN_C_START #include // NOLINT(modernize-deprecated-headers) #define NAPI_AUTO_LENGTH SIZE_MAX + +// js_native_api_types.h (included above) now owns these, with the upstream +// #ifndef NAPI_VERSION / NAPI_EXPERIMENTAL logic. Defining them unconditionally +// here redefined them and warned in every translation unit. +#ifndef NAPI_VERSION_EXPERIMENTAL #define NAPI_VERSION_EXPERIMENTAL 2147483647 +#endif +#ifndef NAPI_VERSION #define NAPI_VERSION 8 +#endif + +#ifndef NAPI_EXPERIMENTAL +#define NAPI_EXPERIMENTAL 1 +#endif NAPI_EXTERN napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info **result); @@ -429,7 +469,12 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_reject_deferred(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_is_promise(napi_env env, napi_value value, bool *is_promise); - +#ifdef __PRIMJS__ +// Running a script +NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, const char* script, + size_t length, const char* filename, + napi_value* result); +#else // Running a script NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, napi_value script, @@ -439,6 +484,8 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script_source(napi_env env, napi_value script, const char* source_url, napi_value* result); +#endif + // ES Module support NAPI_EXTERN napi_status NAPI_CDECL napi_run_script_as_module(napi_env env, @@ -544,11 +591,35 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, napi_value object); #ifdef USE_HOST_OBJECT -NAPI_EXTERN napi_status NAPI_CDECL napi_create_host_object(napi_env env, napi_value value, napi_finalize finalize, void* data, bool is_array, napi_value getter, napi_value setter, napi_value* result); +// Creates a host object: a transparent proxy whose property operations are +// dispatched to the native `methods` (see napi_host_object_methods). `data` is +// passed to every callback and is also retrievable via +// napi_get_host_object_data; `finalize` (optional) runs when the host object is +// garbage-collected. `methods` and its `get`/`set` members are required. +NAPI_EXTERN napi_status NAPI_CDECL +napi_create_host_object(napi_env env, + napi_finalize finalize, + void* data, + const napi_host_object_methods* methods, + napi_value* result); + +NAPI_EXTERN napi_status NAPI_CDECL napi_get_host_object_data(napi_env env, + napi_value object, + void** data); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_host_object_data(napi_env env, napi_value object, void** data); +NAPI_EXTERN napi_status NAPI_CDECL napi_is_host_object(napi_env env, + napi_value object, + bool* result); +#endif -NAPI_EXTERN napi_status NAPI_CDECL napi_is_host_object(napi_env env, napi_value object, bool* result); +#ifdef NAPI_EXPERIMENTAL +// Defers `finalize_cb` to a safe pass after the GC finalizer, where reference +// and other JS/GC-state-affecting Node-API calls are allowed. Implemented for +// the V8 engine. +NAPI_EXTERN napi_status NAPI_CDECL node_api_post_finalizer(napi_env env, + napi_finalize finalize_cb, + void* finalize_data, + void* finalize_hint); #endif #endif // NAPI_VERSION >= 8 diff --git a/NativeScript/napi/common/js_native_api_types.h b/NativeScript/napi/common/js_native_api_types.h index 7bff81102..c6bc066cc 100644 --- a/NativeScript/napi/common/js_native_api_types.h +++ b/NativeScript/napi/common/js_native_api_types.h @@ -1,19 +1,42 @@ #ifndef SRC_JS_NATIVE_API_TYPES_H_ #define SRC_JS_NATIVE_API_TYPES_H_ -#include +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION_EXPERIMENTAL 2147483647 +#ifndef NAPI_VERSION +#ifdef NAPI_EXPERIMENTAL +#define NAPI_VERSION NAPI_VERSION_EXPERIMENTAL +#else +// The baseline version for Node-API. +// NAPI_VERSION controls which version is used by default when compiling +// a native addon. If the addon developer wants to use functions from a +// newer Node-API version not yet available in all LTS versions, they can +// set NAPI_VERSION to explicitly depend on that version. +#define NAPI_VERSION 8 +#endif +#endif -#ifdef __cplusplus -#define EXTERN_C_START \ -extern "C" \ -{ -#define EXTERN_C_END } +#if defined(NAPI_EXPERIMENTAL) && \ + !defined(NODE_API_EXPERIMENTAL_NO_WARNING) && \ + !defined(NODE_WANT_INTERNALS) +#ifdef _MSC_VER +#pragma message("NAPI_EXPERIMENTAL is enabled. " \ + "Experimental features may be unstable.") #else -#define EXTERN_C_START -#define EXTERN_C_END +#warning "NAPI_EXPERIMENTAL is enabled. " \ + "Experimental features may be unstable." #endif +#endif + +// This file needs to be compatible with C compilers. +// This is a public include file, and these includes have essentially +// become part of its API. +#include // NOLINT(modernize-deprecated-headers) +#include // NOLINT(modernize-deprecated-headers) -#define NAPI_EXTERN __attribute__((visibility("default"))) +#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) +typedef uint16_t char16_t; +#endif #ifndef NAPI_CDECL #ifdef _WIN32 @@ -23,56 +46,69 @@ extern "C" #endif #endif -EXTERN_C_START -typedef struct napi_runtime__ *napi_runtime; -typedef struct napi_env__ *napi_env; -typedef struct napi_value__ *napi_value; -typedef struct napi_ref__ *napi_ref; -typedef struct napi_handle_scope__ *napi_handle_scope; -typedef struct napi_handle_scope__ *napi_escapable_handle_scope; -typedef struct napi_callback_info__ *napi_callback_info; -typedef struct napi_deferred__* napi_deferred; +// JSVM API types are all opaque pointers for ABI stability +// typedef undefined structs instead of void* for compile time type safety +typedef struct napi_env__* napi_env; + +// We need to mark APIs which can be called during garbage collection (GC), +// meaning that they do not affect the state of the JS engine, and can +// therefore be called synchronously from a finalizer that itself runs +// synchronously during GC. Such APIs can receive either a `napi_env` or a +// `node_api_basic_env` as their first parameter, because we should be able to +// also call them during normal, non-garbage-collecting operations, whereas +// APIs that affect the state of the JS engine can only receive a `napi_env` as +// their first parameter, because we must not call them during GC. In lieu of +// inheritance, we use the properties of the const qualifier to accomplish +// this, because both a const and a non-const value can be passed to an API +// expecting a const value, but only a non-const value can be passed to an API +// expecting a non-const value. +// +// In conjunction with appropriate CFLAGS to warn us if we're passing a const +// (basic) environment into an API that expects a non-const environment, and +// the definition of basic finalizer function pointer types below, which +// receive a basic environment as their first parameter, and can thus only call +// basic APIs (unless the user explicitly casts the environment), we achieve +// the ability to ensure at compile time that we do not call APIs that affect +// the state of the JS engine from a synchronous (basic) finalizer. +#if !defined(NAPI_EXPERIMENTAL) || \ + (defined(NAPI_EXPERIMENTAL) && \ + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) +typedef struct napi_env__* node_api_nogc_env; +#else +typedef const struct napi_env__* node_api_nogc_env; +#endif +typedef node_api_nogc_env node_api_basic_env; +typedef struct napi_value__* napi_value; +typedef struct napi_ref__* napi_ref; +typedef struct napi_handle_scope__* napi_handle_scope; +typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; +typedef struct napi_callback_info__* napi_callback_info; +typedef struct napi_deferred__* napi_deferred; -typedef enum -{ +typedef enum { napi_default = 0, napi_writable = 1 << 0, napi_enumerable = 1 << 1, napi_configurable = 1 << 2, - + // Used with napi_define_class to distinguish static properties // from instance properties. Ignored by napi_define_properties. napi_static = 1 << 10, - + +#if NAPI_VERSION >= 8 // Default for class methods. napi_default_method = napi_writable | napi_configurable, - + // Default for object properties, like in JS obj[prop]. napi_default_jsproperty = napi_writable | napi_enumerable | napi_configurable, +#endif // NAPI_VERSION >= 8 } napi_property_attributes; -typedef napi_value (*napi_callback)(napi_env env, napi_callback_info callbackInfo); - -typedef void (*napi_finalize)(napi_env env, void *finalizeData, void *finalizeHint); - -typedef struct { - // One of utf8name or name should be NULL. - const char* utf8name; - napi_value name; - - napi_callback method; - napi_callback getter; - napi_callback setter; - napi_value value; - - napi_property_attributes attributes; - void* data; -} napi_property_descriptor; - -typedef enum -{ +typedef enum { + // ES6 types (corresponds to typeof) napi_undefined, napi_null, napi_boolean, @@ -85,6 +121,22 @@ typedef enum napi_bigint, } napi_valuetype; +typedef enum { + napi_int8_array, + napi_uint8_array, + napi_uint8_clamped_array, + napi_int16_array, + napi_uint16_array, + napi_int32_array, + napi_uint32_array, + napi_float32_array, + napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, +#define NODE_API_HAS_FLOAT16_ARRAY + napi_float16_array, +} napi_typedarray_type; + typedef enum { napi_ok, napi_invalid_arg, @@ -107,60 +159,140 @@ typedef enum { napi_date_expected, napi_arraybuffer_expected, napi_detachable_arraybuffer_expected, - napi_would_deadlock, /* unused */ + napi_would_deadlock, // unused napi_no_external_buffers_allowed, napi_cannot_run_js, - // Custom errors - napi_handle_scope_empty, - napi_memory_error, - napi_promise_exception } napi_status; +// Note: when adding a new enum value to `napi_status`, please also update +// * `const int last_status` in the definition of `napi_get_last_error_info()' +// in file js_native_api_v8.cc. +// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief +// message explaining the error. +// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly +// added value(s). -typedef enum { - napi_int8_array, - napi_uint8_array, - napi_uint8_clamped_array, - napi_int16_array, - napi_uint16_array, - napi_int32_array, - napi_uint32_array, - napi_float32_array, - napi_float64_array, - napi_bigint64_array, - napi_biguint64_array, -} napi_typedarray_type; +typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, + napi_callback_info info); +typedef void(NAPI_CDECL* napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); + +#if !defined(NAPI_EXPERIMENTAL) || \ + (defined(NAPI_EXPERIMENTAL) && \ + (defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT) || \ + defined(NODE_API_EXPERIMENTAL_BASIC_ENV_OPT_OUT))) +typedef napi_finalize node_api_nogc_finalize; +#else +typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env, + void* finalize_data, + void* finalize_hint); +#endif +typedef node_api_nogc_finalize node_api_basic_finalize; + +// A finalizer that can be called from any thread and at any time. +typedef void(NAPI_CDECL* node_api_noenv_finalize)(void* finalize_data, + void* finalize_hint); + +typedef struct { + // One of utf8name or name should be NULL. + const char* utf8name; + napi_value name; + + napi_callback method; + napi_callback getter; + napi_callback setter; + napi_value value; + + napi_property_attributes attributes; + void* data; +} napi_property_descriptor; + +typedef struct { + const char* error_message; + void* engine_reserved; + uint32_t engine_error_code; + napi_status error_code; +} napi_extended_error_info; +#if NAPI_VERSION >= 6 typedef enum { - napi_key_include_prototypes, - napi_key_own_only + napi_key_include_prototypes, + napi_key_own_only } napi_key_collection_mode; typedef enum { - napi_key_keep_numbers, - napi_key_numbers_to_strings -} napi_key_conversion; + napi_key_all_properties = 0, + napi_key_writable = 1, + napi_key_enumerable = 1 << 1, + napi_key_configurable = 1 << 2, + napi_key_skip_strings = 1 << 3, + napi_key_skip_symbols = 1 << 4 +} napi_key_filter; typedef enum { - napi_key_all_properties = 0, - napi_key_writable = 1, - napi_key_enumerable = 1 << 1, - napi_key_configurable = 1 << 2, - napi_key_skip_strings = 1 << 3, - napi_key_skip_symbols = 1 << 4 -} napi_key_filter; + napi_key_keep_numbers, + napi_key_numbers_to_strings +} napi_key_conversion; +#endif // NAPI_VERSION >= 6 +#if NAPI_VERSION >= 8 typedef struct { - uint64_t lower; - uint64_t upper; + uint64_t lower; + uint64_t upper; } napi_type_tag; +#endif // NAPI_VERSION >= 8 + + + +#ifdef USE_HOST_OBJECT +// Native handlers for a host object. The host object is a transparent proxy: +// every property operation is dispatched to these callbacks. Each receives the +// host object itself as `host_object` and the `data` pointer given to +// napi_create_host_object. `property` is the key as a napi_value (a number for +// indexed access, a string or symbol otherwise). +// +// `get` and `set` are required; `has`, `delete_property` and `own_keys` are +// optional (NULL means the operation is not intercepted / reports absent). +typedef napi_value(NAPI_CDECL* napi_host_object_get_cb)(napi_env env, + napi_value host_object, + napi_value property, + void* data); +typedef void(NAPI_CDECL* napi_host_object_set_cb)(napi_env env, + napi_value host_object, + napi_value property, + napi_value value, + void* data); +typedef int (NAPI_CDECL* napi_host_object_has_cb)(napi_env env, + napi_value host_object, + napi_value property, + void* data); +typedef int (NAPI_CDECL* napi_host_object_delete_cb)(napi_env env, + napi_value host_object, + napi_value property, + void* data); +typedef napi_value(NAPI_CDECL* napi_host_object_own_keys_cb)( + napi_env env, napi_value host_object, void* data); + +// Optional fast paths for integer-indexed access. +typedef napi_value(NAPI_CDECL* napi_host_object_indexed_get_cb)( + napi_env env, napi_value host_object, uint32_t index, void* data); +typedef void(NAPI_CDECL* napi_host_object_indexed_set_cb)( + napi_env env, + napi_value host_object, + uint32_t index, + napi_value value, + void* data); typedef struct { - const char* error_message; - void* engine_reserved; - uint32_t engine_error_code; - napi_status error_code; -} napi_extended_error_info; + napi_host_object_get_cb get; + napi_host_object_set_cb set; + napi_host_object_has_cb has; + napi_host_object_delete_cb delete_property; + napi_host_object_own_keys_cb own_keys; + napi_host_object_indexed_get_cb indexed_get; + napi_host_object_indexed_set_cb indexed_set; +} napi_host_object_methods; +#endif -EXTERN_C_END -#endif // SRC_JS_NATIVE_API_TYPES_H_ +#endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/NativeScript/napi/common/jsr_common.h b/NativeScript/napi/common/jsr_common.h index ed6b7a4d9..6c579a1c5 100644 --- a/NativeScript/napi/common/jsr_common.h +++ b/NativeScript/napi/common/jsr_common.h @@ -7,13 +7,15 @@ #include "js_native_api.h" -napi_status js_create_runtime(napi_runtime* runtime); -napi_status js_create_napi_env(napi_env* env, napi_runtime runtime); +typedef struct jsr_ns_runtime__ *jsr_ns_runtime; + +napi_status js_create_runtime(jsr_ns_runtime* runtime); +napi_status js_create_napi_env(napi_env* env, jsr_ns_runtime runtime); napi_status js_set_runtime_flags(const char* flags); napi_status js_lock_env(napi_env env); napi_status js_unlock_env(napi_env env); napi_status js_free_napi_env(napi_env env); -napi_status js_free_runtime(napi_runtime runtime); +napi_status js_free_runtime(jsr_ns_runtime runtime); napi_status js_execute_script(napi_env env, napi_value script, const char *file, @@ -26,6 +28,26 @@ napi_status js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64 napi_status js_cache_script(napi_env env, const char *source, const char *file); napi_status js_run_cached_script(napi_env env, const char * file, napi_value script, void* cache, napi_value *result); +/** + * Compile-time bytecode fast path. The runtime calls this BEFORE reading a + * module's source, so a precompiled module is never read/compiled as text. + * + * `file` is the module's source URL (e.g. "file:///.../app/foo.js"). If it holds + * precompiled bytecode for this engine, load + run it and set *result to the + * module wrapper function (mirroring js_execute_script for the equivalent source). + * + * The bytecode-loading implementation lives entirely in each engine's jsr.cpp; + * this is just the thin entry point the module loader calls. + * + * Returns: + * - napi_ok : `file` was bytecode; it ran; *result is set. + * - napi_cannot_run_js : `file` is NOT bytecode (caller compiles source). + * Detection only peeks the header — no full read — and + * engines without bytecode support always return this. + * - other : `file` was bytecode but failed/threw (do NOT fall back). + */ +napi_status js_run_bytecode_file(napi_env env, const char *file, napi_value *result); + napi_status js_get_runtime_version(napi_env env, napi_value* version); // Invoked by engine-specific env teardown to execute registered node-api diff --git a/NativeScript/napi/common/native_api_util.h b/NativeScript/napi/common/native_api_util.h index 2a1676eaa..701b31acc 100644 --- a/NativeScript/napi/common/native_api_util.h +++ b/NativeScript/napi/common/native_api_util.h @@ -62,6 +62,10 @@ struct char_traits { #include "js_native_api.h" #include "js_native_api_types.h" +#ifdef __ANDROID__ +#include +#endif + #ifndef NAPI_PREAMBLE #define NAPI_PREAMBLE napi_status status; #endif @@ -95,6 +99,34 @@ struct char_traits { } \ } +// Faster varargs prologue for hot callbacks. Reads arguments into a fixed stack +// buffer with a SINGLE napi_get_cb_info call (no heap allocation, no redundant +// argc-probe call), falling back to a heap vector only when the real arity +// exceeds the inline capacity `stackn`. Exposes `napi_value *argv` + `size_t +// argc`, so call sites use `argv`/`argv[i]` (a pointer) instead of a vector. +#define NAPI_CALLBACK_BEGIN_VARGS_FAST(stackn) \ + napi_status status; \ + size_t argc = (stackn); \ + void* data; \ + napi_value jsThis; \ + napi_value __argv_stack[(stackn)]; \ + NAPI_GUARD(napi_get_cb_info(env, info, &argc, __argv_stack, &jsThis, &data)) { \ + NAPI_THROW_LAST_ERROR \ + return NULL; \ + } \ + std::vector __argv_heap; \ + napi_value* argv = __argv_stack; \ + if (argc > (stackn)) { \ + __argv_heap.resize(argc); \ + NAPI_GUARD( \ + napi_get_cb_info(env, info, &argc, __argv_heap.data(), nullptr, nullptr)) \ + { \ + NAPI_THROW_LAST_ERROR \ + return NULL; \ + } \ + argv = __argv_heap.data(); \ + } + #define NAPI_ERROR_INFO \ const napi_extended_error_info* error_info = \ (napi_extended_error_info*)malloc(sizeof(napi_extended_error_info)); \ @@ -104,27 +136,30 @@ struct char_traits { NAPI_ERROR_INFO \ napi_throw_error(env, NULL, error_info->error_message); -#ifndef DEBUG - -#define NAPI_GUARD(expr) \ - status = expr; \ - if (status != napi_ok) { \ - NAPI_ERROR_INFO \ - std::stringstream msg; \ - msg << "Node-API returned error: " << status << "\n " << #expr \ - << "\n ^\n " \ - << "at " << __FILE__ << ":" << __LINE__ << ""; \ - } \ - if (status != napi_ok) - +#ifdef __ANDROID__ +#define NAPI_LOG_ERROR(status_val, expr_str) \ + __android_log_print(ANDROID_LOG_ERROR, "TNS.Native", \ + "Node-API returned error: %d\n %s\n ^\n at %s:%d", \ + (int)(status_val), (expr_str), __FILE__, __LINE__) #else +#define NAPI_LOG_ERROR(status_val, expr_str) ((void)0) +#endif -#define NAPI_GUARD(expr) \ - status = expr; \ +// NAPI_GUARD(expr) { ...on-error block... } +// Assigns the result of `expr` to the in-scope `status`, logs a diagnostic +// (status, expression, file:line) on failure so an invalid runtime state is +// traceable, and runs the trailing block when the call did not return napi_ok. +// napi_pending_exception is JS-level control flow (a callback threw), not an +// invalid runtime state, so it is intentionally not logged — the caller's +// exception handling deals with it. +#define NAPI_GUARD(expr) \ + status = expr; \ + if (status != napi_ok && status != napi_pending_exception) \ + { \ + NAPI_LOG_ERROR(status, #expr); \ + } \ if (status != napi_ok) -#endif - #define NAPI_FUNCTION(name) \ napi_value JS_##name(napi_env env, napi_callback_info cbinfo) @@ -341,23 +376,47 @@ inline napi_status define_property( return napi_define_properties(env, object, 1, &desc); } -inline void setPrototypeOf(napi_env env, napi_value object, - napi_value prototype) { +inline napi_status define_property_value( + napi_env env, napi_value object, const char* propertyName, + napi_value value = nullptr, + napi_property_attributes attributes = napi_default_jsproperty, + void* data = nullptr) { + return napi_util::define_property(env, object, propertyName, value, nullptr, + nullptr, data, attributes); +} + +inline napi_status define_property_get_set( + napi_env env, napi_value object, const char* propertyName, + napi_callback getter, napi_callback setter, + napi_property_attributes attributes = napi_default_jsproperty, + void* data = nullptr) { + return napi_util::define_property(env, object, propertyName, nullptr, getter, + setter, data, attributes); +} + +inline napi_status setPrototypeOf(napi_env env, napi_value object, + napi_value prototype) { + if (object == nullptr || prototype == nullptr) return napi_invalid_arg; + napi_value global, global_object, set_proto; // Get the global object - napi_get_global(env, &global); + auto status = napi_get_global(env, &global); + if (status != napi_ok) return status; // Get the Object global object - napi_get_named_property(env, global, OBJECT, &global_object); + status = napi_get_named_property(env, global, OBJECT, &global_object); + if (status != napi_ok) return status; // Get the setPrototypeOf function from the Object global object - napi_get_named_property(env, global_object, SET_PROTOTYPE_OF, &set_proto); + status = napi_get_named_property(env, global_object, SET_PROTOTYPE_OF, &set_proto); + if (status != napi_ok) return status; // Prepare the arguments for the setPrototypeOf call napi_value argv[]{object, prototype}; // Call setPrototypeOf(object, prototype) - napi_call_function(env, global, set_proto, 2, argv, nullptr); + napi_value result; + return napi_call_function(env, global, set_proto, 2, argv, &result); } inline bool is_object_explicit(napi_env env, napi_value value) { @@ -449,13 +508,14 @@ inline bool is_date(napi_env env, napi_value value) { inline bool is_undefined(napi_env env, napi_value value) { if (value == nullptr) return true; napi_valuetype type; - napi_typeof(env, value, &type); + if (napi_typeof(env, value, &type) != napi_ok) return false; return type == napi_undefined; } inline bool is_null(napi_env env, napi_value value) { + if (value == nullptr) return true; napi_valuetype type; - napi_typeof(env, value, &type); + if (napi_typeof(env, value, &type) != napi_ok) return false; return type == napi_null; } diff --git a/NativeScript/napi/hermes/README.md b/NativeScript/napi/hermes/README.md new file mode 100644 index 000000000..0251a3f34 --- /dev/null +++ b/NativeScript/napi/hermes/README.md @@ -0,0 +1,12 @@ +Hermes Node-API adapter +======================= + +`include/` is the single vendored Static Hermes header surface used by both +Apple and Android builds. It comes from `DjDeveloperr/build-hermes` and must +stay in sync with the Hermes binaries under `Frameworks/` and +`platforms/android/test-app/runtime/src/main/libs/hermes/`. + +Android still accepts the historical `SHERMES` engine selector so existing +test/build scripts keep working, but it is now only an alias for `HERMES`. +Both selectors compile the same adapter and link the same Static Hermes +artifact. diff --git a/NativeScript/napi/hermes/include/hermes/AsyncDebuggerAPI.h b/NativeScript/napi/hermes/include/hermes/AsyncDebuggerAPI.h deleted file mode 100644 index ea718dd4a..000000000 --- a/NativeScript/napi/hermes/include/hermes/AsyncDebuggerAPI.h +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_ASYNCDEBUGGERAPI_H -#define HERMES_ASYNCDEBUGGERAPI_H - -#ifdef HERMES_ENABLE_DEBUGGER - -#include -#include -#include -#include -#include - -#include -#include -#include - -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#include -#else -#ifndef TSA_GUARDED_BY -#define TSA_GUARDED_BY(x) -#endif -#ifndef TSA_NO_THREAD_SAFETY_ANALYSIS -#define TSA_NO_THREAD_SAFETY_ANALYSIS -#endif -#endif - -namespace facebook { -namespace hermes { -namespace debugger { - -class AsyncDebuggerAPI; - -enum class DebuggerEventType { - // Informational Events - ScriptLoaded, /// A script file was loaded, and the debugger has requested - /// pausing after script load. - Exception, /// An Exception was thrown. - Resumed, /// Script execution has resumed. - - // Events Requiring Next Command - DebuggerStatement, /// A debugger; statement was hit. - Breakpoint, /// A breakpoint was hit. - StepFinish, /// A Step operation completed. - ExplicitPause, /// A pause requested using Explicit AsyncBreak -}; - -/// This represents the list of possible commands that can be given to -/// \p resumeFromPaused. This is used instead of DebuggerAPI's Command class in -/// order to prevent callers from constructing an eval Command. The eval -/// functionality is implemented as a separate mechansim with -/// \p evalWhilePaused. -enum class AsyncDebugCommand { - Continue, /// Continues execution - StepInto, /// Perform a step into and then pause again - StepOver, /// Steps over the current instruction and then pause again - StepOut, /// Step out from the current scope and then pause again -}; - -using DebuggerEventCallback = std::function; -using DebuggerEventCallbackID = uint32_t; -constexpr const uint32_t kInvalidDebuggerEventCallbackID = 0; -using InterruptCallback = std::function; -using EvalCompleteCallback = std::function< - void(HermesRuntime &runtime, const debugger::EvalResult &result)>; - -/// This class wraps the DebuggerAPI to expose an asynchronous didPause -/// functionality as well as an interrupt API. This class must be constructed at -/// the same time as HermesRuntime. -/// -/// Functions in this class with the suffix "_TS" (Thread-Safe) are the only -/// functions that are safe to call on any thread. All other functions must be -/// called on the runtime thread. -class HERMES_EXPORT AsyncDebuggerAPI : private debugger::EventObserver { - /// Hide the constructor so users can only construct via static create - /// methods. - AsyncDebuggerAPI(HermesRuntime &runtime); - - public: - /// Creates an AsyncDebuggerAPI for use with the provided HermesRuntime. This - /// should be called and created at the same time as creating HermesRuntime. - static std::unique_ptr create(HermesRuntime &runtime); - - /// Must be destroyed on the runtime thread or when you're sure nothing is - /// interacting with the runtime. Must be destroyed before destroying - /// HermesRuntime. - ~AsyncDebuggerAPI() override; - - /// Add a callback function to invoke when the runtime pauses due to various - /// conditions such as hitting a "debugger;" statement. Can be called from any - /// thread. If there are no DebuggerEventCallback, then any reason that might - /// trigger a pause, such as a "debugger;" statement or breakpoints, will not - /// actually pause and will simply continue execution. Any caller that adds an - /// event callback cannot just be observing events and never call - /// \p resumeFromPaused in any of its code paths. The caller must either - /// expose UI enabling human action for controlling the debugger, or it must - /// have programmatic logic that controls the debugger via - /// \p resumeFromPaused. - DebuggerEventCallbackID addDebuggerEventCallback_TS( - DebuggerEventCallback callback); - - /// Remove a previously added callback function. If there is no callback - /// registered using the provided \p id, the function does nothing. - void removeDebuggerEventCallback_TS(DebuggerEventCallbackID id); - - /// Whether the runtime is currently paused waiting for the next action. - /// Should only be called from the runtime thread. - bool isWaitingForCommand(); - - /// Whether the runtime is currently paused for any reason (e.g. script - /// parsed, running interrupts, or waiting for a command). - /// Should only be called from the runtime thread. - bool isPaused(); - - /// Provide the next action to perform. Should only be called from the runtime - /// thread and only if the next command is expected to be set. - bool resumeFromPaused(AsyncDebugCommand command); - - /// Evaluate JavaScript code \p expression in the frame at index - /// \p frameIndex. Receives evaluation result in the \p callback. Should only - /// be called from the runtime thread and only if debugger is paused waiting - /// for the next action. - bool evalWhilePaused( - const std::string &expression, - uint32_t frameIndex, - EvalCompleteCallback callback); - - /// Request to interrupt the runtime at a convenient time and get a callback - /// on the runtime thread. Guaranteed to run "exactly once". This function can - /// be called from any thread, but cannot be called while inside a - /// DebuggerEventCallback. - void triggerInterrupt_TS(InterruptCallback callback); - - /// EventObserver implementation - debugger::Command didPause(debugger::Debugger &debugger) override; - - private: - struct EventCallbackEntry { - DebuggerEventCallbackID id; - DebuggerEventCallback callback; - }; - - /// This function infinite loops and uses \p signal_ to block the runtime - /// thread. It gets woken up if new InterruptCallback is queued or if - /// DebuggerEventCallback changes. - void processInterruptWhilePaused() TSA_NO_THREAD_SAFETY_ANALYSIS; - - /// Dequeues the next InterruptCallback if any. - std::optional takeNextInterruptCallback(); - - /// If \p ignoreNextCommand is true, then runs every InterruptCallback that - /// has been queued up so far. If \p ignoreNextCommand is false, then attempt - /// to run all interrupts, but will stop if any interrupt sets a next command. - void runInterrupts(bool ignoreNextCommand = true); - - /// Returns the next DebuggerEventCallback to execute if any. - std::optional takeNextEventCallback(); - - /// Runs every DebuggerEventCallback that has been registered. - void runEventCallbacks(DebuggerEventType event); - - HermesRuntime &runtime_; - - /// Whether the runtime thread is currently paused in \p didPause and needs to - /// be told what action to take next. - bool isWaitingForCommand_; - - /// Stores the command to return from \p didPause. - debugger::Command nextCommand_; - - /// Callback function to invoke after getting EvalResult from EvalComplete in - /// didPause. Used once and then cleared out. - EvalCompleteCallback oneTimeEvalCompleteCallback_{}; - - /// Tracks whether we are already in a didPause callback to detect recursive - /// calls to didPause. - bool inDidPause_ = false; - - /// Next ID to use when adding a DebuggerEventCallback. - uint32_t nextEventCallbackID_ TSA_GUARDED_BY(mutex_); - - /// Callback functions to invoke to notify events in \p didPause. Using - /// std::list which requires O(N) search when removing an element, but removal - /// should be a rare event. So the choice of using std::list is to optimize - /// for typical usage. - std::list eventCallbacks_ TSA_GUARDED_BY(mutex_){}; - - /// Iterator for eventCallbacks_. Used to traverse through the list when - /// running the callbacks. - std::list::iterator eventCallbackIterator_ - TSA_GUARDED_BY(mutex_); - - /// Queue of interrupt callback functions to invoke. - std::queue interruptCallbacks_ TSA_GUARDED_BY(mutex_){}; - - /// Used as a mechanism to block the runtime thread in \p didPause and for - /// protecting variables used across threads. - std::mutex mutex_{}; - /// Used to implement \p triggerInterrupt while \p didPause is holding onto - /// the runtime thread. - std::condition_variable signal_{}; -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#else // !HERMES_ENABLE_DEBUGGER - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace debugger { - -class AsyncDebuggerAPI; - -enum class DebuggerEventType { - // Informational Events - ScriptLoaded, /// A script file was loaded, and the debugger has requested - /// pausing after script load. - Exception, /// An Exception was thrown. - Resumed, /// Script execution has resumed. - - // Events Requiring Next Command - DebuggerStatement, /// A debugger; statement was hit. - Breakpoint, /// A breakpoint was hit. - StepFinish, /// A Step operation completed. - ExplicitPause, /// A pause requested using Explicit AsyncBreak -}; - -/// This represents the list of possible commands that can be given to -/// \p resumeFromPaused. This is used instead of DebuggerAPI's Command class in -/// order to prevent callers from constructing an eval Command. The eval -/// functionality is implemented as a separate mechansim with -/// \p evalWhilePaused. -enum class AsyncDebugCommand { - Continue, /// Continues execution - StepInto, /// Perform a step into and then pause again - StepOver, /// Steps over the current instruction and then pause again - StepOut, /// Step out from the current scope and then pause again -}; - -using DebuggerEventCallback = std::function; -using DebuggerEventCallbackID = uint32_t; -constexpr const uint32_t kInvalidDebuggerEventCallbackID = 0; -using InterruptCallback = std::function; -using EvalCompleteCallback = std::function< - void(HermesRuntime &runtime, const debugger::EvalResult &result)>; - -class HERMES_EXPORT AsyncDebuggerAPI { - public: - static std::unique_ptr create(HermesRuntime &runtime) { - return nullptr; - } - - ~AsyncDebuggerAPI() {} - - DebuggerEventCallbackID addDebuggerEventCallback_TS( - DebuggerEventCallback callback) { - return kInvalidDebuggerEventCallbackID; - } - - void removeDebuggerEventCallback_TS(DebuggerEventCallbackID id) {} - - bool isWaitingForCommand() { - return false; - } - - bool isPaused() { - return false; - } - - bool resumeFromPaused(AsyncDebugCommand command) { - return false; - } - - bool evalWhilePaused( - const std::string &expression, - uint32_t frameIndex, - EvalCompleteCallback callback) { - return false; - } - - void triggerInterrupt_TS(InterruptCallback callback) {} -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif // !HERMES_ENABLE_DEBUGGER - -#endif // HERMES_ASYNCDEBUGGERAPI_H diff --git a/NativeScript/napi/hermes/include/hermes/CompileJS.h b/NativeScript/napi/hermes/include/hermes/CompileJS.h deleted file mode 100644 index 562eeae7f..000000000 --- a/NativeScript/napi/hermes/include/hermes/CompileJS.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_COMPILEJS_H -#define HERMES_COMPILEJS_H - -#include -#include -#include - -namespace hermes { - -/// Interface for receiving errors, warnings and notes produced by compileJS. -class DiagnosticHandler { - public: - enum Kind { - Error, - Warning, - Note, - }; - - struct Diagnostic { - Kind kind; - int line; /// 1-based index - int column; /// 1-based index - std::string message; - /// 0-based char indices in half-open intervals - std::vector> ranges; - }; - - /// Called once for each diagnostic message produced during compilation. - virtual void handle(const Diagnostic &diagnostic) = 0; - virtual ~DiagnosticHandler() = default; -}; - -/// Compiles JS source \p str and if compilation is successful, returns true -/// and outputs to \p bytecode otherwise returns false. -/// \param sourceURL this will be used as the "file name" of the buffer for -/// errors, stack traces, etc. -/// \param optimize this will enable optimizations. -/// \param emitAsyncBreakCheck this will make the bytecode interruptable. -/// \param diagHandler if not null, receives any and all errors, warnings and -/// notes produced during compilation. -/// \param sourceMapBuf optional source map string. -/// \param debug Wether to generate debugging information in generated bytecode. -bool compileJS( - const std::string &str, - const std::string &sourceURL, - std::string &bytecode, - bool optimize, - bool emitAsyncBreakCheck, - DiagnosticHandler *diagHandler, - std::optional sourceMapBuf = std::nullopt, - bool debug = false); - -bool compileJS( - const std::string &str, - std::string &bytecode, - bool optimize = true); - -bool compileJS( - const std::string &str, - const std::string &sourceURL, - std::string &bytecode, - bool optimize = true); - -} // namespace hermes - -#endif diff --git a/NativeScript/napi/hermes/include/hermes/DebuggerAPI.h b/NativeScript/napi/hermes/include/hermes/DebuggerAPI.h deleted file mode 100644 index e444c41cb..000000000 --- a/NativeScript/napi/hermes/include/hermes/DebuggerAPI.h +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_DEBUGGERAPI_H -#define HERMES_DEBUGGERAPI_H - -#ifdef HERMES_ENABLE_DEBUGGER - -#include -#include -#include -#include - -#include "hermes/Public/DebuggerTypes.h" - -// Forward declarations of internal types. -namespace hermes { -namespace vm { -class CodeBlock; -class Debugger; -class Runtime; -struct DebugCommand; -class HermesValue; -} // namespace vm -} // namespace hermes - -namespace facebook { -namespace hermes { -class HermesRuntime; - -namespace debugger { - -class Debugger; -class EventObserver; - -/// Represents a variable in the debugger. -struct HERMES_EXPORT VariableInfo { - /// Name of the variable in the source. - String name; - - /// Value of the variable. - ::facebook::jsi::Value value; -}; - -/// An EvalResult represents the result of an Eval command. -struct HERMES_EXPORT EvalResult { - /// The resulting JavaScript object, or the thrown exception. - ::facebook::jsi::Value value; - - /// Indicates that the result was an exception. - bool isException = false; - - /// If isException is true, details about the exception. - ExceptionDetails exceptionDetails; - - EvalResult(EvalResult &&) = default; - EvalResult() = default; - - EvalResult( - ::facebook::jsi::Value value, - bool isException, - ExceptionDetails exceptionDetails) - : value(std::move(value)), - isException(isException), - exceptionDetails(std::move(exceptionDetails)) {} -}; - -/// ProgramState represents the state of a paused program. An instance of -/// ProgramState is available as the getProgramState() member function of class -/// Debugger. -class HERMES_EXPORT ProgramState { - public: - /// \return the reason for the Pause. - PauseReason getPauseReason() const { - return pauseReason_; - } - - /// \return the breakpoint if the PauseReason is Breakpoint, otherwise - /// kInvalidBreakpoint. - BreakpointID getBreakpoint() const { - return breakpoint_; - } - - /// \return the evaluation result if the PauseReason is due to EvalComplete. - EvalResult getEvalResult() const; - - /// \returns a stack trace for the current execution. - const StackTrace &getStackTrace() const { - return stackTrace_; - } - - /// \returns lexical information about the state in a given frame. - LexicalInfo getLexicalInfo(uint32_t frameIndex) const; - - /// \return information about a variable in a given lexical scope, in a given - /// frame. - VariableInfo getVariableInfo( - uint32_t frameIndex, - ScopeDepth scopeDepth, - uint32_t variableIndexInScope) const; - - /// \return information about the `this` value at a given stack depth. - VariableInfo getVariableInfoForThis(uint32_t frameIndex) const; - - /// \return the number of variables in a given frame. - /// This is deprecated: prefer using getLexicalInfoInFrame(). - uint32_t getVariablesCountInFrame(uint32_t frameIndex) const { - auto info = getLexicalInfo(frameIndex); - uint32_t result = 0; - for (ScopeDepth i = 0, max = info.getScopesCount(); i < max; i++) - result += info.getVariablesCountInScope(i); - return result; - } - - /// \return info for a variable at a given index \p variableIndex, in a given - /// frame at index \p frameIndex. - /// This is deprecated. Prefer the getVariableInfo() that takes three - /// parameters. - VariableInfo getVariableInfo(uint32_t frameIndex, uint32_t variableIndex) - const { - LexicalInfo info = getLexicalInfo(frameIndex); - uint32_t remaining = variableIndex; - for (ScopeDepth scope = 0;; scope++) { - assert(scope < info.getScopesCount() && "Index out of bounds"); - uint32_t count = info.getVariablesCountInScope(scope); - if (remaining < count) { - return getVariableInfo(frameIndex, scope, remaining); - } - remaining -= count; - } - } - - private: - friend Debugger; - /// ProgramState must not be copied, because some of its implementation - /// requires querying the live program state and so the state must not be - /// retained after the pause returns. - /// ProgramState must not be copied. - ProgramState(const ProgramState &) = delete; - ProgramState &operator=(const ProgramState &) = delete; - - ::hermes::vm::Debugger *impl() const; - - ProgramState(Debugger *dbg) : dbg_(dbg) {} - Debugger *dbg_; - PauseReason pauseReason_{}; - StackTrace stackTrace_; - EvalResult evalResult_; - BreakpointID breakpoint_{kInvalidBreakpoint}; -}; - -/// Command represents an action that you can request the debugger to perform -/// when returned from didPause(). -class HERMES_EXPORT Command { - public: - /// Commands may be moved. - Command(Command &&); - Command &operator=(Command &&); - ~Command(); - - /// \return a Command that steps with the given StepMode \p mode. - static Command step(StepMode mode); - - /// \return a Command that continues execution. - static Command continueExecution(); - - /// \return a Command that evaluates JavaScript code \p src in the - /// frame at index \p frameIndex. - static Command eval(const String &src, uint32_t frameIndex); - - /// \return a boolean whether this Command was constructed using the static - /// eval() method - bool isEval(); - - private: - friend Debugger; - explicit Command(::hermes::vm::DebugCommand &&); - std::unique_ptr<::hermes::vm::DebugCommand> debugCommand_; -}; - -/// Debugger allows access to the Hermes debugging functionality. An instance of -/// Debugger is available from HermesRuntime, and also passed to your -/// EventObserver. -class HERMES_EXPORT Debugger { - public: - /// Set the Debugger event observer. The event observer is notified of - /// debugging event, specifically when the program pauses. This is simply a - /// raw pointer: it is the client's responsibility to clear the event observer - /// if the event observer is deallocated before the Debugger. - void setEventObserver(EventObserver *observer); - - /// Sets the property %isDebuggerAttached in %DebuggerInternal object. Can be - /// called from any thread. - void setIsDebuggerAttached(bool isAttached); - - /// Asynchronously triggers a pause. This may be called from any thread. This - /// is inherently racey and the exact point at which the program pauses is not - /// guaranteed. You can discover when the program has paused through the event - /// observer. - void triggerAsyncPause(AsyncPauseKind kind); - - /// \return the ProgramState representing the state of the paused program. - /// This may only be invoked when the program is paused. - const ProgramState &getProgramState() const { - return state_; - } - - /// \return the source map URL for the \p fileId. - String getSourceMappingUrl(uint32_t fileId) const; - - /// Gets the list of loaded scripts. The order of the scripts in the vector - /// will be the same across calls. - /// \return list of loaded scripts - std::vector getLoadedScripts() const; - - /// Gets the current stack trace. - /// \return stack trace with call frames if runtime is in the interpreter - /// loop, otherwise return no call frames - StackTrace captureStackTrace() const; - - /// -- Breakpoint Management -- - - /// Sets a breakpoint on a given SourceLocation. - /// \return the ID of the breakpoint, 0 if it wasn't created. - BreakpointID setBreakpoint(SourceLocation loc); - - /// Sets the condition on breakpoint \p breakpoint. - /// The condition will be stored with the breakpoint, - /// and if non-empty, will be executed to determine whether to actually - /// pause on the breakpoint; only if ToBoolean(condition) is true - /// and does not throw will the debugger pause on \p breakpoint. - /// \param condition the code to execute to determine whether to break; - /// if empty, the condition is considered to not be set. - void setBreakpointCondition(BreakpointID breakpoint, const String &condition); - - /// Deletes a breakpoint. - void deleteBreakpoint(BreakpointID breakpoint); - - /// Deletes all breakpoints. - void deleteAllBreakpoints(); - - /// Mark a breakpoint as enabled. Breakpoints are by default enabled. - void setBreakpointEnabled(BreakpointID breakpoint, bool enable); - - /// \return information on a breakpoint. - BreakpointInfo getBreakpointInfo(BreakpointID breakpoint); - - /// \return a list of extant breakpoints. - std::vector getBreakpoints(); - - /// Set whether the debugger should pause when an exception is thrown. - void setPauseOnThrowMode(PauseOnThrowMode mode); - - /// \return whether the debugger pauses when an exception is thrown. - PauseOnThrowMode getPauseOnThrowMode() const; - - /// Set whether the debugger should pause after a script was loaded. - void setShouldPauseOnScriptLoad(bool flag); - - /// \return whether the debugger should pause after a script was loaded. - bool getShouldPauseOnScriptLoad() const; - - /// \return the thrown value if paused on an exception, or - /// jsi::Value::undefined() if not. - ::facebook::jsi::Value getThrownValue(); - - private: - friend std::unique_ptr hermes::makeHermesRuntime( - const ::hermes::vm::RuntimeConfig &); - friend std::unique_ptr - hermes::makeThreadSafeHermesRuntime(const ::hermes::vm::RuntimeConfig &); - friend ProgramState; - - /// Debuggers may not be moved or copied. - Debugger(const Debugger &) = delete; - void operator=(const Debugger &) = delete; - Debugger(Debugger &&) = delete; - void operator=(Debugger &&) = delete; - - /// Implementation detail used by ProgramState. - ::facebook::jsi::Value jsiValueFromHermesValue(::hermes::vm::HermesValue hv); - - explicit Debugger( - ::facebook::hermes::HermesRuntime *runtime, - ::hermes::vm::Runtime &vmRuntime); - - ::facebook::hermes::HermesRuntime *const runtime_; - EventObserver *eventObserver_ = nullptr; - ::hermes::vm::Runtime &vmRuntime_; - ::hermes::vm::Debugger *impl_; - ProgramState state_; -}; - -/// A subclass of EventObserver may be set on the Debugger via -/// setEventObserver(). It receives notifications when the Debugger pauses. -class HERMES_EXPORT EventObserver { - public: - /// didPause() is invoked when the JavaScript program has paused. The - /// The Debugger \p debugger can be used to manipulate breakpoints and enqueue - /// debugger commands such as stepping, etc. It can also be used to discover - /// the call stack and variables via debugger.getProgramState(). - /// \return a Command for the debugger to perform. - virtual Command didPause(Debugger &debugger) = 0; - - /// Invoked when the debugger resolves a previously unresolved breakpoint. - /// Note that the debugger is *not* paused during this, - /// and thus debugger.getProgramState() is not valid. - /// This callback may not invoke JavaScript or enqueue debugger commands. - virtual void breakpointResolved(Debugger &debugger, BreakpointID breakpoint) { - } - - virtual ~EventObserver(); -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#else // !HERMES_ENABLE_DEBUGGER - -#include - -#include "hermes/Public/DebuggerTypes.h" - -namespace facebook { -namespace hermes { -namespace debugger { - -class EventObserver; - -struct VariableInfo { - String name; - ::facebook::jsi::Value value; -}; - -struct EvalResult { - ::facebook::jsi::Value value; - bool isException = false; - ExceptionDetails exceptionDetails; - - EvalResult(EvalResult &&) = default; - EvalResult() = default; - - EvalResult( - ::facebook::jsi::Value value, - bool isException, - ExceptionDetails exceptionDetails) - : value(std::move(value)), - isException(isException), - exceptionDetails(std::move(exceptionDetails)) {} -}; - -class ProgramState { - public: - ProgramState() {} - - PauseReason getPauseReason() const { - return PauseReason::Exception; - } - - BreakpointID getBreakpoint() const { - return 0; - } - - EvalResult getEvalResult() const { - return EvalResult(); - } - - const StackTrace &getStackTrace() const { - return stackTrace_; - } - - LexicalInfo getLexicalInfo(uint32_t frameIndex) const { - return LexicalInfo(); - } - - VariableInfo getVariableInfo( - uint32_t frameIndex, - ScopeDepth scopeDepth, - uint32_t variableIndexInScope) const { - return VariableInfo(); - } - - VariableInfo getVariableInfoForThis(uint32_t frameIndex) const { - return VariableInfo(); - } - - uint32_t getVariablesCountInFrame(uint32_t frameIndex) const { - return 0; - } - - VariableInfo getVariableInfo(uint32_t frameIndex, uint32_t variableIndex) - const { - return VariableInfo(); - } - - private: - ProgramState(const ProgramState &) = delete; - ProgramState &operator=(const ProgramState &) = delete; - - StackTrace stackTrace_; -}; - -class Command { - public: - Command(Command &&) {} - Command &operator=(Command &&); - ~Command() {} - - static Command step(StepMode mode) { - return Command(); - } - static Command continueExecution() { - return Command(); - } - static Command eval(const String &src, uint32_t frameIndex) { - return Command(); - } - bool isEval() { - return false; - } - - private: - Command() {} -}; - -class Debugger { - public: - explicit Debugger() {} - - void setEventObserver(EventObserver *observer) {} - void setIsDebuggerAttached(bool isAttached) {} - void triggerAsyncPause(AsyncPauseKind kind) {} - const ProgramState &getProgramState() const { - return programState_; - } - String getSourceMappingUrl(uint32_t fileId) const { - return ""; - }; - std::vector getLoadedScripts() const { - return {}; - } - StackTrace captureStackTrace() const { - return StackTrace{}; - } - BreakpointID setBreakpoint(SourceLocation loc) { - return 0; - } - void setBreakpointCondition( - BreakpointID breakpoint, - const String &condition) {} - void deleteBreakpoint(BreakpointID breakpoint) {} - void deleteAllBreakpoints() {} - void setBreakpointEnabled(BreakpointID breakpoint, bool enable) {} - BreakpointInfo getBreakpointInfo(BreakpointID breakpoint) { - return BreakpointInfo(); - } - std::vector getBreakpoints() { - return std::vector(); - } - void setPauseOnThrowMode(PauseOnThrowMode mode) {} - PauseOnThrowMode getPauseOnThrowMode() const { - return PauseOnThrowMode::None; - } - void setShouldPauseOnScriptLoad(bool flag) {} - bool getShouldPauseOnScriptLoad() const { - return false; - } - ::facebook::jsi::Value getThrownValue() { - return ::facebook::jsi::Value::undefined(); - } - - private: - Debugger(const Debugger &) = delete; - void operator=(const Debugger &) = delete; - Debugger(Debugger &&) = delete; - void operator=(Debugger &&) = delete; - - ProgramState programState_; -}; - -class EventObserver { - public: - virtual Command didPause(Debugger &debugger) = 0; - virtual void breakpointResolved(Debugger &debugger, BreakpointID breakpoint) { - } - - virtual ~EventObserver() {} -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif // !HERMES_ENABLE_DEBUGGER - -#endif // HERMES_DEBUGGERAPI_H diff --git a/NativeScript/napi/hermes/include/hermes/Public/Buffer.h b/NativeScript/napi/hermes/include/hermes/Public/Buffer.h deleted file mode 100644 index 3a4e8c267..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/Buffer.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_BUFFER_H -#define HERMES_PUBLIC_BUFFER_H - -#include - -#include -#include - -namespace hermes { - -/// A generic buffer interface. E.g. for memmapped bytecode. -class HERMES_EXPORT Buffer { - public: - Buffer() : data_(nullptr), size_(0) {} - - Buffer(const uint8_t *data, size_t size) : data_(data), size_(size) {} - - virtual ~Buffer(); - - const uint8_t *data() const { - return data_; - }; - - size_t size() const { - return size_; - } - - protected: - const uint8_t *data_ = nullptr; - size_t size_ = 0; -}; - -} // namespace hermes - -#endif diff --git a/NativeScript/napi/hermes/include/hermes/Public/CtorConfig.h b/NativeScript/napi/hermes/include/hermes/Public/CtorConfig.h deleted file mode 100644 index aff3f3989..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/CtorConfig.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_CTORCONFIG_H -#define HERMES_PUBLIC_CTORCONFIG_H - -#include - -/// Defines a new class, called \p NAME representing a constructor config, and -/// an associated builder class. -/// -/// The fields of the class (along with their types and default values) are -/// encoded in the \p FIELDS parameter, and any logic to be run whilst building -/// the config can be passed as a code block in \p BUILD_BODY. -/// -/// Example: -/// -/// Suppose we wish to define a configuration class called Foo, with the -/// following fields and default values: -/// -/// int A = 0; -/// int B = 42; -/// std::string C = "hello"; -/// -/// Such that the value in A is at most the length of \c C. -/// -/// We can do so with the following declaration: -/// -/// " #define FIELDS(F) \ " -/// " F(int, A) \ " -/// " F(int, B, 42) \ " -/// " F(std::string, C, "hello") " -/// " " -/// " _HERMES_CTORCONFIG_STRUCT(Foo, FIELDS, { " -/// " A_ = std::min(A_, C_.length()); " -/// " }); " -/// -/// N.B. -/// - The definition of A does not mention any value -- meaning it is -/// default initialised. -/// - References to the fields in the validation logic have a trailling -/// underscore. -/// -#define _HERMES_CTORCONFIG_STRUCT(NAME, FIELDS, BUILD_BODY) \ - class NAME { \ - FIELDS(_HERMES_CTORCONFIG_FIELD_DECL) \ - \ - public: \ - class Builder; \ - friend Builder; \ - FIELDS(_HERMES_CTORCONFIG_GETTER) \ - \ - /* returns a Builder that starts with the current config. */ \ - inline Builder rebuild() const; \ - \ - private: \ - inline void doBuild(const Builder &builder); \ - }; \ - \ - class NAME::Builder { \ - NAME config_; \ - \ - FIELDS(_HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_DECL) \ - \ - public: \ - Builder() = default; \ - \ - explicit Builder(const NAME &config) : config_(config) {} \ - \ - inline const NAME build() { \ - config_.doBuild(*this); \ - return config_; \ - } \ - \ - /* The explicitly set fields of \p newconfig update \ - * the corresponding fields of \p this. */ \ - inline Builder update(const NAME::Builder &newConfig); \ - \ - FIELDS(_HERMES_CTORCONFIG_SETTER) \ - FIELDS(_HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_ACCESSOR) \ - }; \ - \ - NAME::Builder NAME::rebuild() const { \ - return Builder(*this); \ - } \ - \ - NAME::Builder NAME::Builder::update(const NAME::Builder &newConfig) { \ - FIELDS(_HERMES_CTORCONFIG_UPDATE) \ - return *this; \ - } \ - \ - void NAME::doBuild(const NAME::Builder &builder) { \ - (void)builder; \ - BUILD_BODY \ - } - -/// Helper Macros - -#define _HERMES_CTORCONFIG_FIELD_DECL(CX, TYPE, NAME, ...) \ - TYPE NAME##_{__VA_ARGS__}; - -/// This ignores the first and trailing arguments, and defines a member -/// indicating whether field NAME was set explicitly. -#define _HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_DECL(CX, TYPE, NAME, ...) \ - bool NAME##Explicit_{false}; - -/// This defines an accessor for the "Explicit_" fields defined above. -#define _HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_ACCESSOR(CX, TYPE, NAME, ...) \ - bool has##NAME() const { \ - return NAME##Explicit_; \ - } - -/// Placeholder token for fields whose defaults are not constexpr, to make the -/// listings more readable. -#define HERMES_NON_CONSTEXPR - -#define _HERMES_CTORCONFIG_GETTER(CX, TYPE, NAME, ...) \ - inline TYPE get##NAME() const { \ - return NAME##_; \ - } \ - static CX TYPE getDefault##NAME() { \ - /* Instead of parens around TYPE (non-standard) */ \ - using TypeAsSingleToken = TYPE; \ - return TypeAsSingleToken{__VA_ARGS__}; \ - } - -#define _HERMES_CTORCONFIG_SETTER(CX, TYPE, NAME, ...) \ - inline auto with##NAME(TYPE NAME)->decltype(*this) { \ - config_.NAME##_ = std::move(NAME); \ - NAME##Explicit_ = true; \ - return *this; \ - } - -#define _HERMES_CTORCONFIG_BUILDER_GETTER(CX, TYPE, NAME, ...) \ - TYPE get##NAME() const { \ - return config_.NAME##_; \ - } - -#define _HERMES_CTORCONFIG_UPDATE(CX, TYPE, NAME, ...) \ - if (newConfig.has##NAME()) { \ - with##NAME(newConfig.config_.get##NAME()); \ - } - -#endif // HERMES_PUBLIC_CTORCONFIG_H diff --git a/NativeScript/napi/hermes/include/hermes/Public/DebuggerTypes.h b/NativeScript/napi/hermes/include/hermes/Public/DebuggerTypes.h deleted file mode 100644 index 88184c077..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/DebuggerTypes.h +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_DEBUGGERTYPES_H -#define HERMES_PUBLIC_DEBUGGERTYPES_H - -#include -#include -#include -#pragma GCC diagnostic push - -#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 -#pragma GCC diagnostic ignored "-Wshorten-64-to-32" -#endif -namespace hermes { -namespace vm { -class Debugger; -} -} // namespace hermes - -namespace facebook { -namespace hermes { -namespace debugger { - -class ProgramState; - -/// Strings in the Debugger are UTF-8 encoded. When converting from a JavaScript -/// string, valid UTF-16 surrogate pairs are decoded. Surrogate halves are -/// converted into the Unicode replacement character. -using String = std::string; - -/// Debugging entities like breakpoints are identified by a unique ID. The -/// Debugger will not re-use IDs even across different entity types. 0 is an -/// invalid ID. -using BreakpointID = uint64_t; -// NOTE: Can't be kInvalidID due to a clash with MacTypes.h's define kInvalidID. -constexpr uint64_t kInvalidBreakpoint = 0; - -/// Scripts when loaded are identified by a script ID. -/// These are not reused within one invocation of the VM. -using ScriptID = uint32_t; - -/// A SourceLocation is a small value-type representing a location in a source -/// file. -constexpr uint32_t kInvalidLocation = ~0u; -struct SourceLocation { - /// Line in the source. 1 based. - uint32_t line = kInvalidLocation; - - /// Column in the source. 1 based. - uint32_t column = kInvalidLocation; - - /// Identifier of the source file. - ScriptID fileId = kInvalidLocation; - - /// Name of the source file. - String fileName; -}; - -/// CallFrameInfo is a value type representing an entry in a call stack. -struct CallFrameInfo { - /// Name of the function executing in this frame. - String functionName; - - /// Source location of the program counter for this frame. - SourceLocation location; -}; - -/// StackTrace represents a list of call frames, either in the current execution -/// or captured in an exception. -struct StackTrace { - /// \return the number of call frames. - uint32_t callFrameCount() const { - return frames_.size(); - } - - /// \return call frame info at a given index. 0 represents the topmost - /// (current) frame on the call stack. - CallFrameInfo callFrameForIndex(uint32_t index) const { - return frames_.at(index); - } - - StackTrace() {} - - private: - explicit StackTrace(std::vector frames) - : frames_(std::move(frames)){}; - friend ProgramState; - friend ::hermes::vm::Debugger; - std::vector frames_; -}; - -/// ExceptionDetails is a value type describing an exception. -struct ExceptionDetails { - /// Textual description of the exception. - String text; - - /// Location where the exception was thrown. - SourceLocation location; - - /// Get the stack trace associated with the exception. - const StackTrace &getStackTrace() const { - return stackTrace_; - } - - private: - friend ::hermes::vm::Debugger; - StackTrace stackTrace_; -}; - -/// A list of possible reasons for a Pause. -enum class PauseReason { - ScriptLoaded, /// A script file was loaded, and the debugger has requested - /// pausing after script load. - DebuggerStatement, /// A debugger; statement was hit. - Breakpoint, /// A breakpoint was hit. - StepFinish, /// A Step operation completed. - Exception, /// An Exception was thrown. - AsyncTriggerImplicit, /// The Pause is the result of - /// triggerAsyncPause(Implicit). - AsyncTriggerExplicit, /// The Pause is the result of - /// triggerAsyncPause(Explicit). - EvalComplete, /// An eval() function finished. -}; - -/// When stepping, the mode with which to step. -enum class StepMode { - Into, /// Enter into any function calls. - Over, /// Skip over any function calls. - Out, /// Step until the current function exits. -}; - -/// When setting pause on throw, this specifies when to pause. -enum class PauseOnThrowMode { - None, /// Never pause on exceptions. - Uncaught, /// Only pause on uncaught exceptions. - All, /// Pause any time an exception is thrown. -}; - -/// When requesting an async break, this specifies whether it was an implicit -/// break from the inspector or a user-requested explicit break. -enum class AsyncPauseKind { - /// Implicit pause to allow movement of jsi::Value types between threads. - /// The user will not be running commands and the inspector will immediately - /// request a Continue. - Implicit, - - /// Explicit pause requested by the user. - /// Clears any stepping state and allows the user to run their own commands. - Explicit, -}; - -/// A type representing depth in a lexical scope chain. -using ScopeDepth = uint32_t; - -/// Information about lexical entities (for now, just variable names). -struct LexicalInfo { - /// \return the number of scopes. - ScopeDepth getScopesCount() const { - return variableCountsByScope_.size(); - } - - /// \return the number of variables in a given scope. - uint32_t getVariablesCountInScope(ScopeDepth depth) const { - return variableCountsByScope_.at(depth); - } - - private: - friend ::hermes::vm::Debugger; - std::vector variableCountsByScope_; -}; - -/// Information about a breakpoint. -struct BreakpointInfo { - /// ID of the breakpoint. - /// kInvalidBreakpoint if the info is not valid. - BreakpointID id; - - /// Whether the breakpoint is enabled. - bool enabled; - - /// Whether the breakpoint has been resolved. - bool resolved; - - /// The originally requested location of the breakpoint. - SourceLocation requestedLocation; - - /// The resolved location of the breakpoint if resolved is true. - SourceLocation resolvedLocation; -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include/hermes/Public/GCConfig.h b/NativeScript/napi/hermes/include/hermes/Public/GCConfig.h deleted file mode 100644 index 8d3f316f7..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/GCConfig.h +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_GCCONFIG_H -#define HERMES_PUBLIC_GCCONFIG_H - -#include "hermes/Public/CtorConfig.h" -#include "hermes/Public/GCTripwireContext.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace hermes { -namespace vm { - -/// A type big enough to accomodate the entire allocated address space. -/// Individual allocations are always 'uint32_t', but on a 64-bit machine we -/// might want to accommodate a larger total heap (or not, in which case we keep -/// it 32-bit). -using gcheapsize_t = uint32_t; - -/// Represents a value before and after an event. -/// NOTE: Not a std::pair because using the names are more readable than first -/// and second. -struct BeforeAndAfter { - uint64_t before; - uint64_t after; -}; - -struct GCAnalyticsEvent { - /// The same value as \p Name from GCConfig. Stored here for simplicity of - /// the API since this is passed in callbacks that might not be able to store - /// the name. For a given Runtime, this will be the same value every time. - std::string runtimeDescription; - - /// The kind of GC this was. For a given Runtime, this will be the same value - /// every time. - std::string gcKind; - - /// The type of collection that ran, typically differentiating a "young" - /// generation GC and an "old" generation GC. When other values say they're - /// "scoped to the collectionType", it means that for a generation GC - /// they're only reporting the numbers for that generation. - std::string collectionType; - - /// The cause of this GC. Can be an arbitrary string describing the cause. - /// Typically "natural" is used to mean that the GC decided it was time, and - /// other causes mean it was forced by some other condition. - std::string cause; - - /// The wall time a collection took from start to end. - std::chrono::milliseconds duration; - - /// The CPU time a collection took from start to end. This time measure will - /// exclude time waiting on disk, mutexes, or time spent not scheduled to run. - std::chrono::milliseconds cpuDuration; - - /// The number of bytes allocated in the heap before and after the collection. - /// measurement does not include fragmentation, and is the same as the sum of - /// all sizes in calls to \p GC::makeA into that generation (including any - /// rounding up the GC does). - /// The value is scoped to the \p collectionType. - BeforeAndAfter allocated; - - /// The number of bytes in use by the heap before and after the collection. - /// This measurement can include fragmentation if the \p gcKind has that - /// concept. - /// The value is scoped to the \p collectionType. - BeforeAndAfter size; - - /// The number of bytes external to the JS heap before and after the - /// collection. - /// The value is scoped to the \p collectionType. - BeforeAndAfter external; - - /// The ratio of cells that survived the collection to all cells before - /// the collection. Note that this is in term of sizes of cells, not the - /// numbers of cells. Excludes any cells not in direct use by the JS program, - /// such as FillerCell or FreelistCell. - /// The value is scoped to the \p collectionType. - double survivalRatio; - - /// A list of metadata tags to annotate this event with. - std::vector tags; -}; - -/// Parameters to control a tripwire function called when the live set size -/// surpasses a given threshold after collections. Check documentation in -/// README.md -#define GC_TRIPWIRE_FIELDS(F) \ - /* If the heap size is above this threshold after a collection, the tripwire \ - * is triggered. */ \ - F(constexpr, gcheapsize_t, Limit, std::numeric_limits::max()) \ - \ - /* The callback to call when the tripwire is considered triggered. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::function, \ - Callback, \ - nullptr) \ - /* GC_TRIPWIRE_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(GCTripwireConfig, GC_TRIPWIRE_FIELDS, {}) - -#undef HEAP_TRIPWIRE_FIELDS - -#define GC_HANDLESAN_FIELDS(F) \ - /* The probability with which the GC should keep moving the heap */ \ - /* to detect stale GC handles. */ \ - F(constexpr, double, SanitizeRate, 0.0) \ - /* Random seed to use for basis of decisions whether or not to */ \ - /* sanitize. A negative value will mean a seed will be chosen at */ \ - /* random. */ \ - F(constexpr, int64_t, RandomSeed, -1) \ - /* GC_HANDLESAN_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(GCSanitizeConfig, GC_HANDLESAN_FIELDS, {}) - -#undef GC_HANDLESAN_FIELDS - -/// How aggressively to return unused memory to the OS. -enum ReleaseUnused { - kReleaseUnusedNone = 0, /// Don't try to release unused memory. - kReleaseUnusedOld, /// Only old gen, on full collections. - kReleaseUnusedYoungOnFull, /// Also young gen, but only on full collections. - kReleaseUnusedYoungAlways /// Also young gen, also on young gen collections. -}; - -enum class GCEventKind { - CollectionStart, - CollectionEnd, -}; - -/// Parameters for GC Initialisation. Check documentation in README.md -/// constexpr indicates that the default value is constexpr. -#define GC_FIELDS(F) \ - /* Minimum heap size hint. */ \ - F(constexpr, gcheapsize_t, MinHeapSize, 0) \ - \ - /* Initial heap size hint. */ \ - F(constexpr, gcheapsize_t, InitHeapSize, 32 << 20) \ - \ - /* Maximum heap size hint. */ \ - F(constexpr, gcheapsize_t, MaxHeapSize, 3u << 30) \ - \ - /* Sizing heuristic: fraction of heap to be occupied by live data. */ \ - F(constexpr, double, OccupancyTarget, 0.5) \ - \ - /* Number of consecutive full collections considered to be an OOM. */ \ - F(constexpr, \ - unsigned, \ - EffectiveOOMThreshold, \ - std::numeric_limits::max()) \ - \ - /* Sanitizer configuration for the GC. */ \ - F(constexpr, GCSanitizeConfig, SanitizeConfig) \ - \ - /* Whether to Keep track of GC Statistics. */ \ - F(constexpr, bool, ShouldRecordStats, false) \ - \ - /* How aggressively to return unused memory to the OS. */ \ - F(constexpr, ReleaseUnused, ShouldReleaseUnused, kReleaseUnusedOld) \ - \ - /* Name for this heap in logs. */ \ - F(HERMES_NON_CONSTEXPR, std::string, Name, "") \ - \ - /* Configuration for the Heap Tripwire. */ \ - F(HERMES_NON_CONSTEXPR, GCTripwireConfig, TripwireConfig) \ - \ - /* Whether to (initially) allocate from the young gen (true) or the */ \ - /* old gen (false). */ \ - F(constexpr, bool, AllocInYoung, true) \ - \ - /* Whether to fill the YG with invalid data after each collection. */ \ - F(constexpr, bool, OverwriteDeadYGObjects, false) \ - \ - /* Whether to revert, if necessary, to young-gen allocation at TTI. */ \ - F(constexpr, bool, RevertToYGAtTTI, false) \ - \ - /* Whether to use mprotect on GC metadata between GCs. */ \ - F(constexpr, bool, ProtectMetadata, false) \ - \ - /* Callout for an analytics event. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::function, \ - AnalyticsCallback, \ - nullptr) \ - \ - /* Called at GC events (see GCEventKind enum for the list). The */ \ - /* second argument contains human-readable details about the event. */ \ - /* NOTE: The function MUST NOT invoke any methods on the Runtime. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::function, \ - Callback, \ - nullptr) \ - /* GC_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(GCConfig, GC_FIELDS, { - if (builder.hasMinHeapSize()) { - if (builder.hasInitHeapSize()) { - // If both are specified, normalize the initial size up to the minimum, - // if necessary. - InitHeapSize_ = std::max(MinHeapSize_, InitHeapSize_); - } else { - // If the minimum is set explicitly, but the initial heap size is not, - // use the minimum as the initial size. - InitHeapSize_ = MinHeapSize_; - } - } - assert(InitHeapSize_ >= MinHeapSize_); - - // Make sure the max is at least the Init. - MaxHeapSize_ = std::max(InitHeapSize_, MaxHeapSize_); -}) - -#undef GC_FIELDS - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_GCCONFIG_H diff --git a/NativeScript/napi/hermes/include/hermes/Public/GCTripwireContext.h b/NativeScript/napi/hermes/include/hermes/Public/GCTripwireContext.h deleted file mode 100644 index 4a8f500f8..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/GCTripwireContext.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_GCTRIPWIRECONTEXT_H -#define HERMES_PUBLIC_GCTRIPWIRECONTEXT_H - -#include - -#include -#include -#include - -namespace hermes { -namespace vm { - -/// Interface passed to the GC tripwire callback when it fires. -class HERMES_EXPORT GCTripwireContext { - public: - virtual ~GCTripwireContext(); - - /// Captures the heap to a file. - /// \param path to save the heap capture. - /// \return Empty error code if the heap capture succeeded, else a real error - /// code. - virtual std::error_code createSnapshotToFile(const std::string &path) = 0; - - /// Captures the heap to a stream. - /// \param os stream to save the heap capture to. - /// \return Empty error code if the heap capture succeeded, else a real error - /// code. - virtual std::error_code createSnapshot( - std::ostream &os, - bool captureNumericValue) = 0; -}; - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_GCTRIPWIRECONTEXT_H diff --git a/NativeScript/napi/hermes/include/hermes/Public/JSOutOfMemoryError.h b/NativeScript/napi/hermes/include/hermes/Public/JSOutOfMemoryError.h deleted file mode 100644 index 95093ab76..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/JSOutOfMemoryError.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_JSOUTOFMEMORYERROR_H -#define HERMES_PUBLIC_JSOUTOFMEMORYERROR_H - -#include - -#include -#include - -namespace hermes { -namespace vm { - -/// A std::runtime_error class for out-of-memory. -class HERMES_EXPORT JSOutOfMemoryError : public std::runtime_error { - friend class GCBase; - JSOutOfMemoryError(const std::string &what_arg) - : std::runtime_error(what_arg) {} - ~JSOutOfMemoryError() override; -}; - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_JSOUTOFMEMORYERROR_H diff --git a/NativeScript/napi/hermes/include/hermes/Public/RuntimeConfig.h b/NativeScript/napi/hermes/include/hermes/Public/RuntimeConfig.h deleted file mode 100644 index 858f1f502..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/RuntimeConfig.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_RUNTIMECONFIG_H -#define HERMES_PUBLIC_RUNTIMECONFIG_H - -#include "hermes/Public/CrashManager.h" -#include "hermes/Public/CtorConfig.h" -#include "hermes/Public/GCConfig.h" - -#include -#include - -namespace hermes { -namespace vm { - -enum CompilationMode { - SmartCompilation, - ForceEagerCompilation, - ForceLazyCompilation -}; - -enum class SynthTraceMode : int8_t { - None, - Replaying, - Tracing, - TracingAndReplaying, -}; - -class PinnedHermesValue; - -// Parameters for Runtime initialisation. Check documentation in README.md -// constexpr indicates that the default value is constexpr. -#define RUNTIME_FIELDS(F) \ - /* Parameters to be passed on to the GC. */ \ - F(HERMES_NON_CONSTEXPR, vm::GCConfig, GCConfig) \ - \ - /* Pre-allocated Register Stack */ \ - F(constexpr, PinnedHermesValue *, RegisterStack, nullptr) \ - \ - /* Register Stack Size */ \ - F(constexpr, unsigned, MaxNumRegisters, 128 * 1024) \ - \ - /* Native stack remaining before assuming overflow */ \ - F(constexpr, unsigned, NativeStackGap, 64 * 1024) \ - \ - /* Whether to allow eval and Function ctor */ \ - F(constexpr, bool, EnableEval, true) \ - \ - /* Whether to verify the IR generated by eval and Function ctor */ \ - F(constexpr, bool, VerifyEvalIR, false) \ - \ - /* Whether to optimize the code inside eval and Function ctor */ \ - F(constexpr, bool, OptimizedEval, false) \ - \ - /* Whether to emit async break check instructions in eval code */ \ - F(constexpr, bool, AsyncBreakCheckInEval, true) \ - \ - /* Support for ES6 Promise. */ \ - F(constexpr, bool, ES6Promise, true) \ - \ - /* Support for ES6 Proxy. */ \ - F(constexpr, bool, ES6Proxy, true) \ - \ - /* Support for ES6 Class. */ \ - F(constexpr, bool, ES6Class, false) \ - \ - /* Support for ECMA-402 Intl APIs. */ \ - F(constexpr, bool, Intl, true) \ - \ - /* Support for ArrayBuffer, DataView and typed arrays. */ \ - F(constexpr, bool, ArrayBuffer, true) \ - \ - /* Support for using microtasks. */ \ - F(constexpr, bool, MicrotaskQueue, false) \ - \ - /* Runtime set up for synth trace. */ \ - F(constexpr, SynthTraceMode, SynthTraceMode, SynthTraceMode::None) \ - \ - /* Enable sampling certain statistics. */ \ - F(constexpr, bool, EnableSampledStats, false) \ - \ - /* Whether to enable automatic sampling profiler registration */ \ - F(constexpr, bool, EnableSampleProfiling, false) \ - \ - /* Whether to randomize stack placement etc. */ \ - F(constexpr, bool, RandomizeMemoryLayout, false) \ - \ - /* Eagerly read bytecode into page cache. */ \ - F(constexpr, unsigned, BytecodeWarmupPercent, 0) \ - \ - /* Signal-based I/O tracking. Slows down execution. If enabled, */ \ - /* all bytecode buffers > 64 kB passed to Hermes must be mmap:ed. */ \ - F(constexpr, bool, TrackIO, false) \ - \ - /* Enable contents of HermesInternal */ \ - F(constexpr, bool, EnableHermesInternal, true) \ - \ - /* Enable methods exposed to JS for testing */ \ - F(constexpr, bool, EnableHermesInternalTestMethods, false) \ - \ - /* Choose lazy/eager compilation mode. */ \ - F(constexpr, \ - CompilationMode, \ - CompilationMode, \ - CompilationMode::SmartCompilation) \ - \ - /* Choose whether generators are enabled. */ \ - F(constexpr, bool, EnableGenerator, true) \ - \ - /* An interface for managing crashes. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::shared_ptr, \ - CrashMgr, \ - new NopCrashManager) \ - \ - /* The flags passed from a VM experiment */ \ - F(constexpr, uint32_t, VMExperimentFlags, 0) \ - \ - /* Whether or not block scoping is enabled */ \ - F(constexpr, bool, EnableBlockScoping, false) \ - /* RUNTIME_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(RuntimeConfig, RUNTIME_FIELDS, {}) - -#undef RUNTIME_FIELDS - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_RUNTIMECONFIG_H diff --git a/NativeScript/napi/hermes/include/hermes/Public/SamplingProfiler.h b/NativeScript/napi/hermes/include/hermes/Public/SamplingProfiler.h deleted file mode 100644 index 4184964e9..000000000 --- a/NativeScript/napi/hermes/include/hermes/Public/SamplingProfiler.h +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_SAMPLINGPROFILER_H -#define HERMES_PUBLIC_SAMPLINGPROFILER_H - -#include - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace sampling_profiler { - -/// Represents a single frame inside the captured sample stack. -/// Base struct for different kinds of frames. -struct HERMES_EXPORT ProfileSampleCallStackFrame { - /// Represents type of frame inside of recorded call stack. - enum class Kind { - JSFunction, /// JavaScript function frame. - NativeFunction, /// Native built-in functions, like arrayPrototypeMap. - HostFunction, /// Native functions, defined by Host, a.k.a. Host functions. - Suspend, /// Frame that suspends the execution of the VM: GC or Debugger. - }; - - public: - explicit ProfileSampleCallStackFrame(const Kind kind) : kind_(kind) {} - - /// \return type of the call stack frame. - Kind getKind() const { - return kind_; - } - - private: - Kind kind_; -}; - -/// Extends ProfileSampleCallStackFrame with an information about JavaScript -/// function frame: function name, and possibly scriptId, url, line and column -/// numbers. -struct HERMES_EXPORT ProfileSampleCallStackJSFunctionFrame - : public ProfileSampleCallStackFrame { - explicit ProfileSampleCallStackJSFunctionFrame( - const std::string &functionName, - const std::optional &scriptId = std::nullopt, - const std::optional &url = std::nullopt, - const std::optional &lineNumber = std::nullopt, - const std::optional &columnNumber = std::nullopt) - : ProfileSampleCallStackFrame( - ProfileSampleCallStackFrame::Kind::JSFunction), - functionName_(functionName), - scriptId_(scriptId), - url_(url), - lineNumber_(lineNumber), - columnNumber_(columnNumber) {} - - /// \return name of the function that represents call frame. - const std::string &getFunctionName() const { - return functionName_; - } - - bool hasScriptId() const { - return scriptId_.has_value(); - } - - /// \return id of the corresponding script in the VM. - uint32_t getScriptId() const { - return scriptId_.value(); - } - - bool hasUrl() const { - return url_.has_value(); - } - - /// \return source url of the corresponding script in the VM. - const std::string &getUrl() const { - return url_.value(); - } - - bool hasLineNumber() const { - return lineNumber_.has_value(); - } - - /// \return 1-based line number of the corresponding call frame. - uint32_t getLineNumber() const { - return lineNumber_.value(); - } - - bool hasColumnNumber() const { - return columnNumber_.has_value(); - } - - /// \return 1-based column number of the corresponding call frame. - uint32_t getColumnNumber() const { - return columnNumber_.value(); - } - - private: - std::string functionName_; - std::optional scriptId_; - std::optional url_; - std::optional lineNumber_; - std::optional columnNumber_; -}; - -/// Extends ProfileSampleCallStackFrame with a function name. -struct HERMES_EXPORT ProfileSampleCallStackNativeFunctionFrame - : public ProfileSampleCallStackFrame { - public: - explicit ProfileSampleCallStackNativeFunctionFrame( - const std::string &functionName) - : ProfileSampleCallStackFrame( - ProfileSampleCallStackFrame::Kind::NativeFunction), - functionName_(functionName) {} - - /// \return name of the function that represents call frame. - const std::string &getFunctionName() const { - return functionName_; - } - - private: - std::string functionName_; -}; - -/// Extends ProfileSampleCallStackFrame with a function name. -struct HERMES_EXPORT ProfileSampleCallStackHostFunctionFrame - : public ProfileSampleCallStackFrame { - public: - explicit ProfileSampleCallStackHostFunctionFrame( - const std::string &functionName) - : ProfileSampleCallStackFrame( - ProfileSampleCallStackFrame::Kind::HostFunction), - functionName_(functionName) {} - - /// \return name of the function that represents call frame. - const std::string &getFunctionName() const { - return functionName_; - } - - private: - std::string functionName_; -}; - -/// Extends ProfileSampleCallStackFrame with a suspend frame information. -struct HERMES_EXPORT ProfileSampleCallStackSuspendFrame - : public ProfileSampleCallStackFrame { - /// Subtype of the Suspend frame. - enum class SuspendFrameKind { - GC, /// Frame that suspends the execution of the VM due to GC. - Debugger, /// Frame that suspends the execution of the VM due to debugger. - Multiple, /// Multiple suspensions have occurred. - }; - - public: - explicit ProfileSampleCallStackSuspendFrame( - const SuspendFrameKind suspendFrameKind) - : ProfileSampleCallStackFrame(ProfileSampleCallStackFrame::Kind::Suspend), - suspendFrameKind_(suspendFrameKind) {} - - /// \return subtype of the suspend frame. - SuspendFrameKind getSuspendFrameKind() const { - return suspendFrameKind_; - } - - private: - SuspendFrameKind suspendFrameKind_; -}; - -/// A pair of a timestamp and a snapshot of the call stack at this point in -/// time. -struct HERMES_EXPORT ProfileSample { - public: - ProfileSample( - uint64_t timestamp, - uint64_t threadId, - std::vector callStack) - : timestamp_(timestamp), - threadId_(threadId), - callStack_(std::move(callStack)) {} - - /// \return serialized unix timestamp in microseconds granularity. The - /// moment when this sample was recorded. - uint64_t getTimestamp() const { - return timestamp_; - } - - /// \return thread id where sample was recorded. - uint64_t getThreadId() const { - return threadId_; - } - - /// \return a snapshot of the call stack. The first element of the vector is - /// the lowest frame in the stack. - const std::vector &getCallStack() const { - return callStack_; - } - - private: - /// When the call stack snapshot was taken (μs). - uint64_t timestamp_; - /// Thread id where sample was recorded. - uint64_t threadId_; - /// Snapshot of the call stack. The first element of the vector is - /// the lowest frame in the stack. - std::vector callStack_; -}; - -/// Contains relevant information about the sampled trace from start to finish. -struct HERMES_EXPORT Profile { - public: - explicit Profile(std::vector samples) - : samples_(std::move(samples)) {} - - /// \return list of recorded samples, should be chronologically sorted. - const std::vector &getSamples() const { - return samples_; - } - - private: - /// List of recorded samples, should be chronologically sorted. - std::vector samples_; -}; - -} // namespace sampling_profiler -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include/hermes/RuntimeTaskRunner.h b/NativeScript/napi/hermes/include/hermes/RuntimeTaskRunner.h deleted file mode 100644 index 367b267a4..000000000 --- a/NativeScript/napi/hermes/include/hermes/RuntimeTaskRunner.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_RUNTIMETASKRUNNER_H -#define HERMES_RUNTIMETASKRUNNER_H - -#include "AsyncDebuggerAPI.h" - -namespace facebook { -namespace hermes { -namespace debugger { - -using RuntimeTask = std::function; -using EnqueueRuntimeTaskFunc = std::function; - -enum class TaskQueues { - All, - Integrator, -}; - -/// Helper for users of AsyncDebuggerAPI that makes it easy to find the -/// earliest opportunity to use the runtime. There are two ways to become -/// the exclusive user of the runtime: -/// - Ask the AsyncDebuggerAPI to interrupt execution and provide a reference -/// to the runtime. Interrupting will only succeed when JavaScript is -/// running, so this method won't produce a prompt response if JavaScript is -/// not running. -/// - Ask the owner of the runtime to provide a reference to the runtime. If -/// the owner is currently running JavaScript (e.g. via a call to -/// evaluateJavaScript), this method won't produce a prompt response. -/// To cover both cases (when JavaScript is running, and when JavaScript isn't -/// running), this helper requests the runtime from both sources, executes the -/// task via the first responder, and sets a flag to indicate to the second -/// responder that nothing more needs to be done. -class RuntimeTaskRunner - : public std::enable_shared_from_this { - public: - RuntimeTaskRunner( - debugger::AsyncDebuggerAPI &debugger, - EnqueueRuntimeTaskFunc enqueueRuntimeTaskFunc); - ~RuntimeTaskRunner(); - - /// Schedule a task to be run with access to the runtime at the earliest - /// opportunity. Before returning, the task is added to the relevant task - /// queues managed by the \p AsyncDebuggerAPI and/or the intergator, with no - /// lingering references to the \p RuntimeTaskRunner. Thus, tasks can be - /// enqueued even if the task runner will be destroyed shortly after. - void enqueueTask(RuntimeTask task, TaskQueues queues = TaskQueues::All); - - private: - /// API where the runtime can be obtained when JavaScript is running. - debugger::AsyncDebuggerAPI &debugger_; - - /// Function provided by the integrator that enqueues a task to be run - /// when JavaScript is not running. - EnqueueRuntimeTaskFunc enqueueRuntimeTask_; -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif // HERMES_RUNTIMETASKRUNNER_H diff --git a/NativeScript/napi/hermes/include/hermes/SynthTrace.h b/NativeScript/napi/hermes/include/hermes/SynthTrace.h deleted file mode 100644 index 749887b6b..000000000 --- a/NativeScript/napi/hermes/include/hermes/SynthTrace.h +++ /dev/null @@ -1,1493 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_SYNTHTRACE_H -#define HERMES_SYNTHTRACE_H - -#include "hermes/Public/RuntimeConfig.h" -#include "hermes/Support/JSONEmitter.h" -#include "hermes/Support/SHA1.h" -#include "hermes/Support/StringSetVector.h" -#include "hermes/VM/GCExecTrace.h" - -#include -#include -#include -#include -#include -#include - -namespace llvh { -// Forward declaration to avoid including llvm headers. -class raw_ostream; -} // namespace llvh - -namespace facebook { -namespace hermes { -namespace tracing { - -/// A SynthTrace is a list of events that occur in a run of a JS file by a -/// runtime that uses JSI. -/// It can be serialized into JSON and written to a llvh::raw_ostream. -class SynthTrace { - public: - using ObjectID = uint64_t; - - /// A tagged union representing different types available in the trace. - /// We use a an API very similar to HermesValue, but: - /// a) also represent the JSI type PropNameID, and - /// b) the "payloads" for some the types (Objects, Strings, BigInts, Symbols - /// and PropNameIDs) are unique ObjectIDs, rather than actual values. - /// (This could probably become a std::variant when we could use C++17.) - class TraceValue { - public: - bool isUndefined() const { - return tag_ == Tag::Undefined; - } - - bool isNull() const { - return tag_ == Tag::Null; - } - - bool isNumber() const { - return tag_ == Tag::Number; - } - - bool isBool() const { - return tag_ == Tag::Bool; - } - - bool isObject() const { - return tag_ == Tag::Object; - } - - bool isBigInt() const { - return tag_ == Tag::BigInt; - } - - bool isString() const { - return tag_ == Tag::String; - } - - bool isPropNameID() const { - return tag_ == Tag::PropNameID; - } - - bool isSymbol() const { - return tag_ == Tag::Symbol; - } - - bool isUID() const { - return isObject() || isBigInt() || isString() || isPropNameID() || - isSymbol(); - } - - static TraceValue encodeUndefinedValue() { - return TraceValue(Tag::Undefined); - } - - static TraceValue encodeNullValue() { - return TraceValue(Tag::Null); - } - - static TraceValue encodeBoolValue(bool value) { - return TraceValue(value); - } - - static TraceValue encodeNumberValue(double value) { - return TraceValue(value); - } - - static TraceValue encodeObjectValue(uint64_t uid) { - return TraceValue(Tag::Object, uid); - } - - static TraceValue encodeBigIntValue(uint64_t uid) { - return TraceValue(Tag::BigInt, uid); - } - - static TraceValue encodeStringValue(uint64_t uid) { - return TraceValue(Tag::String, uid); - } - - static TraceValue encodePropNameIDValue(uint64_t uid) { - return TraceValue(Tag::PropNameID, uid); - } - - static TraceValue encodeSymbolValue(uint64_t uid) { - return TraceValue(Tag::Symbol, uid); - } - - bool operator==(const TraceValue &that) const; - - ObjectID getUID() const { - assert(isUID()); - return val_.uid; - } - - bool getBool() const { - assert(isBool()); - return val_.b; - } - - double getNumber() const { - assert(isNumber()); - return val_.n; - } - - private: - enum class Tag { - Undefined, - Null, - Bool, - Number, - Object, - String, - PropNameID, - Symbol, - BigInt, - }; - - explicit TraceValue(Tag tag) : tag_(tag) {} - TraceValue(bool b) : tag_(Tag::Bool) { - val_.b = b; - } - TraceValue(double n) : tag_(Tag::Number) { - val_.n = n; - } - TraceValue(Tag tag, uint64_t uid) : tag_(tag) { - val_.uid = uid; - } - - Tag tag_; - union { - bool b; - double n; - ObjectID uid; - } val_; - }; - - /// Represents the encoding type of a String or PropNameId - enum class StringEncodingType { ASCII, UTF8, UTF16 }; - - /// A TimePoint is a time when some event occurred. - using TimePoint = std::chrono::steady_clock::time_point; - using TimeSinceStart = std::chrono::milliseconds; - -#define SYNTH_TRACE_RECORD_TYPES(RECORD) \ - RECORD(BeginExecJS) \ - RECORD(EndExecJS) \ - RECORD(Marker) \ - RECORD(CreateObject) \ - RECORD(CreateObjectWithPrototype) \ - RECORD(CreateString) \ - RECORD(CreatePropNameID) \ - RECORD(CreatePropNameIDWithValue) \ - RECORD(CreateHostObject) \ - RECORD(CreateHostFunction) \ - RECORD(QueueMicrotask) \ - RECORD(DrainMicrotasks) \ - RECORD(GetProperty) \ - RECORD(SetProperty) \ - RECORD(HasProperty) \ - RECORD(GetPropertyNames) \ - RECORD(CreateArray) \ - RECORD(ArrayRead) \ - RECORD(ArrayWrite) \ - RECORD(CallFromNative) \ - RECORD(ConstructFromNative) \ - RECORD(ReturnFromNative) \ - RECORD(ReturnToNative) \ - RECORD(CallToNative) \ - RECORD(GetPropertyNative) \ - RECORD(GetPropertyNativeReturn) \ - RECORD(SetPropertyNative) \ - RECORD(SetPropertyNativeReturn) \ - RECORD(GetNativePropertyNames) \ - RECORD(GetNativePropertyNamesReturn) \ - RECORD(CreateBigInt) \ - RECORD(BigIntToString) \ - RECORD(SetExternalMemoryPressure) \ - RECORD(Utf8) \ - RECORD(Utf16) \ - RECORD(GetStringData) \ - RECORD(GetPrototype) \ - RECORD(SetPrototype) \ - RECORD(Global) - - /// RecordType is a tag used to differentiate which type of record it is. - /// There should be a unique tag for each record type. - enum class RecordType { -#define RECORD(name) name, - SYNTH_TRACE_RECORD_TYPES(RECORD) -#undef RECORD - }; - - /// A Record is one element of a trace. - struct Record { - /// The time at which this event occurred with respect to the start of - /// execution. - /// NOTE: This is not compared in the \c operator= in order for tests to - /// pass. - const TimeSinceStart time_; - explicit Record() = delete; - explicit Record(TimeSinceStart time) : time_(time) {} - virtual ~Record() = default; - - /// Write out a serialization of this Record. - /// \param json An emitter connected to an ostream which will write out - /// JSON. - void toJSON(::hermes::JSONEmitter &json) const; - virtual RecordType getType() const = 0; - - // If \p val is an object (that is, an Object or String), push its - // decoding onto objs. - static void pushIfTrackedValue( - const TraceValue &val, - std::vector &objs) { - if (val.isUID()) { - objs.push_back(val.getUID()); - } - } - - /// \return A list of object ids that are defined by this record. - /// Defined means that the record would produce that object, - /// string, or PropNameID as a locally accessible value if it were - /// executed. - virtual std::vector defs() const { - return {}; - } - - /// \return A list of object ids that are used by this record. - /// Used means that the record would use that object, string, or - /// PropNameID as a value if it were executed. - /// If a record uses an object id, then some preceding record - /// (either in the same function invocation, or somewhere - /// globally) must provide a definition. - virtual std::vector uses() const { - return {}; - } - - protected: - /// Emit JSON fields into \p os, excluding the closing curly brace. - /// NOTE: This is overridable, and non-abstract children should call the - /// parent. - virtual void toJSONInternal(::hermes::JSONEmitter &json) const; - }; - - /// If \p traceStream is non-null, the trace will be written to that - /// stream. Otherwise, no trace is written. - explicit SynthTrace( - const ::hermes::vm::RuntimeConfig &conf, - std::unique_ptr traceStream = nullptr, - std::optional = {}); - - template - void emplace_back(Args &&...args) { - records_.emplace_back(new T(std::forward(args)...)); - flushRecordsIfNecessary(); - } - - const std::vector> &records() const { - return records_; - } - - std::optional globalObjID() const { - return globalObjID_; - } - - /// Given a trace value, turn it into its typed string. - static std::string encode(TraceValue value); - /// Encode an undefined JS value for the trace. - static TraceValue encodeUndefined(); - /// Encode a null JS value for the trace. - static TraceValue encodeNull(); - /// Encode a boolean JS value for the trace. - static TraceValue encodeBool(bool value); - /// Encodes a numeric value for the trace. - static TraceValue encodeNumber(double value); - /// Encodes an object for the trace as a unique id. - static TraceValue encodeObject(ObjectID objID); - /// Encodes a bigint for the trace as a unique id. - static TraceValue encodeBigInt(ObjectID objID); - /// Encodes a string for the trace as a unique id. - static TraceValue encodeString(ObjectID objID); - /// Encodes a PropNameID for the trace as a unique id. - static TraceValue encodePropNameID(ObjectID objID); - /// Encodes a Symbol for the trace as a unique id. - static TraceValue encodeSymbol(ObjectID objID); - - /// Decodes a string into a trace value. - static TraceValue decode(const std::string &); - - /// The version of the Synth Benchmark - constexpr static uint32_t synthVersion() { - return 5; - } - - static const char *nameFromReleaseUnused(::hermes::vm::ReleaseUnused ru); - static ::hermes::vm::ReleaseUnused releaseUnusedFromName(const char *name); - - private: - llvh::raw_ostream &os() const { - return (*traceStream_); - } - - /// If we're tracing to a file, and the number of accumulated - /// records has reached the limit kTraceRecordsToFlush, below, - /// flush the records to the file, and reset the accumulated records - /// to be empty. - void flushRecordsIfNecessary(); - - /// Assumes we're tracing to a file; flush accumulated records to - /// the file, and reset the accumulated records to be empty. - void flushRecords(); - - static constexpr unsigned kTraceRecordsToFlush = 100; - - /// If we're tracing to a file, pointer to a stream onto - /// traceFilename_. Null otherwise. - std::unique_ptr traceStream_; - /// If we're tracing to a file, pointer to a JSONEmitter writting - /// into *traceStream_. Null otherwise. - std::unique_ptr<::hermes::JSONEmitter> json_; - /// The records currently being accumulated in the trace. If we are - /// tracing to a file, these will be only the records not yet - /// written to the file. - std::vector> records_; - /// The id of the global object. - /// Note: Keeping this as optional to support replaying the older trace - /// records before the change of TracingRuntime's PointerValue based ObjectID. - /// We can remove this once we remove old traces. - /// TODO: T189113203 - const std::optional globalObjID_; - - public: - /// @name Record classes - /// @{ - - /// A MarkerRecord is an event that simply records an interesting event that - /// is not necessarily meaningful to the interpreter. It comes with a tag that - /// says what type of marker it was. - struct MarkerRecord : public Record { - static constexpr RecordType type{RecordType::Marker}; - const std::string tag_; - explicit MarkerRecord(TimeSinceStart time, const std::string &tag) - : Record(time), tag_(tag) {} - RecordType getType() const override { - return type; - } - - protected: - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A BeginExecJSRecord is an event where execution begins of JS source - /// code. This is not necessarily the first record, since native code can - /// inject values into the VM before any source code is run. - struct BeginExecJSRecord final : public Record { - static constexpr RecordType type{RecordType::BeginExecJS}; - explicit BeginExecJSRecord( - TimeSinceStart time, - std::string sourceURL, - ::hermes::SHA1 sourceHash, - bool sourceIsBytecode) - : Record(time), - sourceURL_(std::move(sourceURL)), - sourceHash_(std::move(sourceHash)), - sourceIsBytecode_(sourceIsBytecode) {} - - RecordType getType() const override { - return type; - } - - const std::string &sourceURL() const { - return sourceURL_; - } - - const ::hermes::SHA1 &sourceHash() const { - return sourceHash_; - } - - private: - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - /// The URL providing the source file mapping for the file being executed. - /// Can be empty. - std::string sourceURL_; - - /// A hash of the source that was executed. The source hash must match up - /// when the file is replayed. - /// The hash is optional, and will be all zeros if not provided. - ::hermes::SHA1 sourceHash_; - - /// Whether the input file was source or bytecode. - bool sourceIsBytecode_; - }; - - struct ReturnMixin { - const TraceValue retVal_; - - explicit ReturnMixin(TraceValue value) : retVal_(value) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const; - }; - - /// A EndExecJSRecord is an event where execution of JS source code stops. - /// This does not mean that the source code will never be entered again, just - /// that it has an entered a phase where it is waiting for native code to call - /// into the JS. This event is not guaranteed to be the last event, for the - /// aforementioned reason. The logged retVal is the result of the evaluation - /// ("undefined" in the majority of cases). - struct EndExecJSRecord final : public MarkerRecord, public ReturnMixin { - static constexpr RecordType type{RecordType::EndExecJS}; - EndExecJSRecord(TimeSinceStart time, TraceValue retVal) - : MarkerRecord(time, "end_global_code"), ReturnMixin(retVal) {} - - RecordType getType() const override { - return type; - } - virtual void toJSONInternal(::hermes::JSONEmitter &json) const final; - std::vector defs() const override { - auto defs = MarkerRecord::defs(); - pushIfTrackedValue(retVal_, defs); - return defs; - } - }; - - /// A CreateObjectRecord is an event where an empty object is created by the - /// native code. - struct CreateObjectRecord : public Record { - static constexpr RecordType type{RecordType::CreateObject}; - /// The ObjectID of the object that was created by native function calls - /// like Runtime::createObject(). - const ObjectID objID_; - - explicit CreateObjectRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A CreateBigIntRecord is an event where a jsi::BigInt (and thus a - /// Hermes BigIntPrimitive) is created by the native code. - struct CreateBigIntRecord : public Record { - static constexpr RecordType type{RecordType::CreateBigInt}; - /// The ObjectID of the BigInt that was created by - /// Runtime::createBigIntFromInt64() or Runtime::createBigIntFromUint64(). - const ObjectID objID_; - enum class Method { - FromInt64, - FromUint64, - }; - /// The method used for creating the BigInt. - Method method_; - /// The value used for creating the BigInt. - uint64_t bits_; - - CreateBigIntRecord( - TimeSinceStart time, - ObjectID objID, - Method m, - uint64_t bits) - : Record(time), objID_(objID), method_(m), bits_(bits) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A BigIntToStringRecord is an event where a jsi::BigInt is converted to a - /// string by native code - struct BigIntToStringRecord : public Record { - static constexpr RecordType type{RecordType::BigIntToString}; - /// The ObjectID of the string that was returned from - /// Runtime::bigintToString(). - const ObjectID strID_; - /// The ObjectID of the BigInt that was passed to Runtime::bigintToString(). - const ObjectID bigintID_; - /// The radix used for converting the BigInt to a string. - int radix_; - - BigIntToStringRecord( - TimeSinceStart time, - ObjectID strID, - ObjectID bigintID, - int radix) - : Record(time), strID_(strID), bigintID_(bigintID), radix_(radix) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {strID_}; - } - - std::vector uses() const override { - return {bigintID_}; - } - }; - - /// A CreateStringRecord is an event where a jsi::String (and thus a - /// Hermes StringPrimitive) is created by the native code. - struct CreateStringRecord : public Record { - static constexpr RecordType type{RecordType::CreateString}; - /// The ObjectID of the string that was created by - /// Runtime::createStringFromAscii() or Runtime::createStringFromUtf8(). - const ObjectID objID_; - /// The string that was passed to Runtime::createStringFromAscii() or - /// Runtime::createStringFromUtf8() when the string was created. - std::string chars_; - /// The string that was passed to Runtime::createStringFromUtf16() - std::u16string chars16_; - /// Whether the String was created from ASCII, UTF-8 or UTF-16 - StringEncodingType encodingType_; - - // General UTF-8. - CreateStringRecord( - TimeSinceStart time, - ObjectID objID, - const uint8_t *chars, - size_t length) - : Record(time), - objID_(objID), - chars_(reinterpret_cast(chars), length), - encodingType_(StringEncodingType::UTF8) {} - // Ascii. - CreateStringRecord( - TimeSinceStart time, - ObjectID objID, - const char *chars, - size_t length) - : Record(time), - objID_(objID), - chars_(chars, length), - encodingType_(StringEncodingType::ASCII) {} - // UTF-16. - CreateStringRecord( - TimeSinceStart time, - ObjectID objID, - const char16_t *chars, - size_t length) - : Record(time), - objID_(objID), - chars16_(chars, length), - encodingType_(StringEncodingType::UTF16) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A CreatePropNameIDRecord is an event where a jsi::PropNameID is - /// created by the native code. - struct CreatePropNameIDRecord : public Record { - static constexpr RecordType type{RecordType::CreatePropNameID}; - /// The ObjectID of the PropNameID that was created. - const ObjectID propNameID_; - /// The string that was passed to Runtime::createPropNameIDFromAscii() or - /// Runtime::createPropNameIDFromUtf8(). - std::string chars_; - /// The string that was passed to Runtime::createPropNameIDFromUtf16() - std::u16string chars16_; - /// Whether the PropNameID was created from ASCII, UTF-8, or UTF-16 - StringEncodingType encodingType_; - - // General UTF-8. - CreatePropNameIDRecord( - TimeSinceStart time, - ObjectID propNameID, - const uint8_t *chars, - size_t length) - : Record(time), - propNameID_(propNameID), - chars_(reinterpret_cast(chars), length), - encodingType_(StringEncodingType::UTF8) {} - // Ascii. - CreatePropNameIDRecord( - TimeSinceStart time, - ObjectID propNameID, - const char *chars, - size_t length) - : Record(time), - propNameID_(propNameID), - chars_(chars, length), - encodingType_(StringEncodingType::ASCII) {} - // UTF16 - CreatePropNameIDRecord( - TimeSinceStart time, - ObjectID propNameID, - const char16_t *chars, - size_t length) - : Record(time), - propNameID_(propNameID), - chars16_(chars, length), - encodingType_(StringEncodingType::UTF16) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {propNameID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A CreatePropNameIDWithValueRecord is an event where a jsi::PropNameID is - /// created by the native code from JSI Value - struct CreatePropNameIDWithValueRecord : public Record { - static constexpr RecordType type{RecordType::CreatePropNameIDWithValue}; - /// The ObjectID of the PropNameID that was created. - const ObjectID propNameID_; - /// The String or Symbol that was passed to - /// Runtime::createPropNameIDFromString() or - /// Runtime::createPropNameIDFromSymbol(). - const TraceValue traceValue_; - - // jsi::String or jsi::Symbol. - CreatePropNameIDWithValueRecord( - TimeSinceStart time, - ObjectID propNameID, - TraceValue traceValue) - : Record(time), propNameID_(propNameID), traceValue_(traceValue) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {propNameID_}; - } - - std::vector uses() const override { - std::vector vec; - pushIfTrackedValue(traceValue_, vec); - return vec; - } - }; - - struct CreateObjectWithPrototypeRecord : public Record { - static constexpr RecordType type{RecordType::CreateObjectWithPrototype}; - const ObjectID objID_; - /// The prototype being assigned - const TraceValue prototype_; - - CreateObjectWithPrototypeRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue prototype) - : Record(time), objID_(objID), prototype_(prototype) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(prototype_, uses); - return uses; - } - }; - - struct CreateHostObjectRecord final : public CreateObjectRecord { - static constexpr RecordType type{RecordType::CreateHostObject}; - using CreateObjectRecord::CreateObjectRecord; - RecordType getType() const override { - return type; - } - }; - - struct CreateHostFunctionRecord final : public CreateObjectRecord { - static constexpr RecordType type{RecordType::CreateHostFunction}; - /// The ObjectID of the PropNameID that was passed to - /// Runtime::createFromHostFunction(). - uint32_t propNameID_; -#ifdef HERMESVM_API_TRACE_DEBUG - const std::string functionName_; -#endif - /// The number of parameters that the created host function takes. - const unsigned paramCount_; - - CreateHostFunctionRecord( - TimeSinceStart time, - ObjectID objID, - ObjectID propNameID, -#ifdef HERMESVM_API_TRACE_DEBUG - std::string functionName, -#endif - unsigned paramCount) - : CreateObjectRecord(time, objID), - propNameID_(propNameID), -#ifdef HERMESVM_API_TRACE_DEBUG - functionName_(std::move(functionName)), -#endif - paramCount_(paramCount) { - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - return {propNameID_}; - } - }; - - struct QueueMicrotaskRecord : public Record { - static constexpr RecordType type{RecordType::QueueMicrotask}; - /// The ObjectID of the callback function that was queued. - const ObjectID callbackID_; - - QueueMicrotaskRecord(TimeSinceStart time, ObjectID callbackID) - : Record(time), callbackID_(callbackID) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - std::vector uses() const override { - return {callbackID_}; - } - }; - - struct DrainMicrotasksRecord : public Record { - static constexpr RecordType type{RecordType::DrainMicrotasks}; - /// maxMicrotasksHint value passed to Runtime::drainMicrotasks() call. - int maxMicrotasksHint_; - - DrainMicrotasksRecord(TimeSinceStart time, int tasksHint = -1) - : Record(time), maxMicrotasksHint_(tasksHint) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A GetPropertyRecord is an event where native code accesses the property - /// of a JS object. - struct GetPropertyRecord : public Record { - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; - /// String or PropNameID passed to getProperty. - const TraceValue propID_; -#ifdef HERMESVM_API_TRACE_DEBUG - std::string propNameDbg_; -#endif - - GetPropertyRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue propID -#ifdef HERMESVM_API_TRACE_DEBUG - , - const std::string &propNameDbg -#endif - ) - : Record(time), - objID_(objID), - propID_(propID) -#ifdef HERMESVM_API_TRACE_DEBUG - , - propNameDbg_(propNameDbg) -#endif - { - } - - static constexpr RecordType type{RecordType::GetProperty}; - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(propID_, uses); - return uses; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A SetPropertyRecord is an event where native code writes to the property - /// of a JS object. - struct SetPropertyRecord : public Record { - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; - /// String or PropNameID passed to setProperty. - const TraceValue propID_; -#ifdef HERMESVM_API_TRACE_DEBUG - std::string propNameDbg_; -#endif - /// The value being assigned. - const TraceValue value_; - - SetPropertyRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue propID, -#ifdef HERMESVM_API_TRACE_DEBUG - const std::string &propNameDbg, -#endif - TraceValue value) - : Record(time), - objID_(objID), - propID_(propID), -#ifdef HERMESVM_API_TRACE_DEBUG - propNameDbg_(propNameDbg), -#endif - value_(value) { - } - - static constexpr RecordType type{RecordType::SetProperty}; - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(propID_, uses); - pushIfTrackedValue(value_, uses); - return uses; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A HasPropertyRecord is an event where native code queries whether a - /// property exists on an object. (We don't care about the result because - /// it cannot influence the trace.) - struct HasPropertyRecord final : public Record { - static constexpr RecordType type{RecordType::HasProperty}; - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; -#ifdef HERMESVM_API_TRACE_DEBUG - std::string propNameDbg_; -#endif - /// The property name that was passed to hasProperty(). - const TraceValue propID_; - - HasPropertyRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue propID -#ifdef HERMESVM_API_TRACE_DEBUG - , - const std::string &propNameDbg -#endif - ) - : Record(time), - objID_(objID), -#ifdef HERMESVM_API_TRACE_DEBUG - propNameDbg_(propNameDbg), -#endif - propID_(propID) { - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - std::vector vec{objID_}; - pushIfTrackedValue(propID_, vec); - return vec; - } - }; - - struct GetPropertyNamesRecord final : public Record { - static constexpr RecordType type{RecordType::GetPropertyNames}; - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; - - explicit GetPropertyNamesRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - return {objID_}; - } - }; - - /// A SetPrototypeRecord is an event where native code sets the prototype of a - /// JS Object - struct SetPrototypeRecord : public Record { - static constexpr RecordType type{RecordType::SetPrototype}; - /// The ObjectID of the object that was accessed for its prototype. - const ObjectID objID_; - /// The custom prototype being assigned - const TraceValue value_; - SetPrototypeRecord(TimeSinceStart time, ObjectID objID, TraceValue value) - : Record(time), objID_(objID), value_(value) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(value_, uses); - return uses; - } - }; - - /// A GetPrototypeRecord is an event where native code gets the prototype of a - /// JS Object - struct GetPrototypeRecord : public Record { - static constexpr RecordType type{RecordType::GetPrototype}; - /// The ObjectID of the object that was accessed for its prototype. - const ObjectID objID_; - GetPrototypeRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - std::vector uses() const override { - return {objID_}; - } - }; - - /// A CreateArrayRecord is an event where a new array is created of a specific - /// length. - struct CreateArrayRecord final : public Record { - static constexpr RecordType type{RecordType::CreateArray}; - /// The ObjectID of the array that was created by the createArray(). - const ObjectID objID_; - /// The length of the array that was passed to createArray(). - const size_t length_; - - explicit CreateArrayRecord( - TimeSinceStart time, - ObjectID objID, - size_t length) - : Record(time), objID_(objID), length_(length) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector defs() const override { - return {objID_}; - } - }; - - /// An ArrayReadRecord is an event where a value was read from an index - /// of an array. - /// It is modeled separately from GetProperty because it is more efficient to - /// read from a numeric index on an array than a string. - struct ArrayReadRecord final : public Record { - /// The ObjectID of the array that was accessed. - const ObjectID objID_; - /// The index of the element that was accessed in the array. - const size_t index_; - - explicit ArrayReadRecord(TimeSinceStart time, ObjectID objID, size_t index) - : Record(time), objID_(objID), index_(index) {} - - static constexpr RecordType type{RecordType::ArrayRead}; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - return {objID_}; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// An ArrayWriteRecord is an event where a value was written into an index - /// of an array. - struct ArrayWriteRecord final : public Record { - /// The ObjectID of the array that was accessed. - const ObjectID objID_; - /// The index of the element that was accessed in the array. - const size_t index_; - /// The value that was written to the array. - const TraceValue value_; - - explicit ArrayWriteRecord( - TimeSinceStart time, - ObjectID objID, - size_t index, - TraceValue value) - : Record(time), objID_(objID), index_(index), value_(value) {} - - static constexpr RecordType type{RecordType::ArrayWrite}; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(value_, uses); - return uses; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - struct CallRecord : public Record { - /// The ObjectID of the function JS object that was called from - /// JS or native. - const ObjectID functionID_; - /// The value of the this argument passed to the function call. - const TraceValue thisArg_; - /// The arguments given to a call (excluding the this parameter), - /// already JSON stringified. - const std::vector args_; - - explicit CallRecord( - TimeSinceStart time, - ObjectID functionID, - TraceValue thisArg, - const std::vector &args) - : Record(time), - functionID_(functionID), - thisArg_(thisArg), - args_(args) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - std::vector uses() const override { - // The function is used regardless of direction. - return {functionID_}; - } - - protected: - std::vector getArgTrackedIDs() const { - std::vector objs; - pushIfTrackedValue(thisArg_, objs); - for (const auto &arg : args_) { - pushIfTrackedValue(arg, objs); - } - return objs; - } - }; - - /// A CallFromNativeRecord is an event where native code calls into a JS - /// function. - struct CallFromNativeRecord : public CallRecord { - static constexpr RecordType type{RecordType::CallFromNative}; - using CallRecord::CallRecord; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - auto uses = CallRecord::uses(); - auto objs = CallRecord::getArgTrackedIDs(); - uses.insert(uses.end(), objs.begin(), objs.end()); - return uses; - } - }; - - /// A ConstructFromNativeRecord is the same as \c CallFromNativeRecord, except - /// the function is called with the new operator. - struct ConstructFromNativeRecord final : public CallFromNativeRecord { - static constexpr RecordType type{RecordType::ConstructFromNative}; - using CallFromNativeRecord::CallFromNativeRecord; - RecordType getType() const override { - return type; - } - }; - - /// A ReturnFromNativeRecord is an event where a native function returns to a - /// JS caller. - /// It pairs with \c CallToNativeRecord. - struct ReturnFromNativeRecord final : public Record, public ReturnMixin { - static constexpr RecordType type{RecordType::ReturnFromNative}; - ReturnFromNativeRecord(TimeSinceStart time, TraceValue retVal) - : Record(time), ReturnMixin(retVal) {} - RecordType getType() const override { - return type; - } - std::vector uses() const override { - auto uses = Record::uses(); - pushIfTrackedValue(retVal_, uses); - return uses; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A ReturnToNativeRecord is an event where a JS function returns to a native - /// caller. - /// It pairs with \c CallFromNativeRecord. - struct ReturnToNativeRecord final : public Record, public ReturnMixin { - static constexpr RecordType type{RecordType::ReturnToNative}; - ReturnToNativeRecord(TimeSinceStart time, TraceValue retVal) - : Record(time), ReturnMixin(retVal) {} - RecordType getType() const override { - return type; - } - std::vector defs() const override { - auto defs = Record::defs(); - pushIfTrackedValue(retVal_, defs); - return defs; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A CallToNativeRecord is an event where JS code calls into a natively - /// defined function. - struct CallToNativeRecord final : public CallRecord { - static constexpr RecordType type{RecordType::CallToNative}; - using CallRecord::CallRecord; - RecordType getType() const override { - return type; - } - std::vector defs() const override { - auto defs = CallRecord::defs(); - auto objs = CallRecord::getArgTrackedIDs(); - defs.insert(defs.end(), objs.begin(), objs.end()); - return defs; - } - }; - - struct GetOrSetPropertyNativeRecord : public Record { - /// The ObjectID of the host object that was being accessed for its - /// property. - const ObjectID hostObjectID_; - /// The ObjectID of the PropNameID that was passed to HostObject::get() - /// or HostObject::set(). - const ObjectID propNameID_; - /// The UTF-8 string of the PropNameID that was passed to HostObject::get() - /// or HostObject::set(). - const std::string propName_; - - GetOrSetPropertyNativeRecord( - TimeSinceStart time, - ObjectID hostObjectID, - ObjectID propNameID, - const std::string &propName) - : Record(time), - hostObjectID_(hostObjectID), - propNameID_(propNameID), - propName_(propName) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - std::vector defs() const override { - return {propNameID_}; - } - std::vector uses() const override { - return {hostObjectID_}; - } - - protected: - }; - - /// A GetPropertyNativeRecord is an event where JS tries to access a property - /// on a native object. - /// This needs to be modeled as a call with no arguments, since native code - /// can arbitrarily affect the JS heap during the accessor. - struct GetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord { - static constexpr RecordType type{RecordType::GetPropertyNative}; - using GetOrSetPropertyNativeRecord::GetOrSetPropertyNativeRecord; - RecordType getType() const override { - return type; - } - }; - - struct GetPropertyNativeReturnRecord final : public Record, - public ReturnMixin { - static constexpr RecordType type{RecordType::GetPropertyNativeReturn}; - GetPropertyNativeReturnRecord(TimeSinceStart time, TraceValue retVal) - : Record(time), ReturnMixin(retVal) {} - RecordType getType() const override { - return type; - } - std::vector uses() const override { - auto uses = Record::uses(); - pushIfTrackedValue(retVal_, uses); - return uses; - } - - protected: - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A SetPropertyNativeRecord is an event where JS code writes to the property - /// of a Native object. - /// This needs to be modeled as a call with one argument, since native code - /// can arbitrarily affect the JS heap during the accessor. - struct SetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord { - static constexpr RecordType type{RecordType::SetPropertyNative}; - /// The value that was passed to HostObject::set() call. - TraceValue value_; - - SetPropertyNativeRecord( - TimeSinceStart time, - ObjectID hostObjectID, - ObjectID propNameID, - const std::string &propName, - TraceValue value) - : GetOrSetPropertyNativeRecord( - time, - hostObjectID, - propNameID, - propName), - value_(value) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector defs() const override { - auto defs = GetOrSetPropertyNativeRecord::defs(); - pushIfTrackedValue(value_, defs); - return defs; - } - }; - - /// A SetPropertyNativeReturnRecord needs to record no extra information - struct SetPropertyNativeReturnRecord final : public Record { - static constexpr RecordType type{RecordType::SetPropertyNativeReturn}; - using Record::Record; - RecordType getType() const override { - return type; - } - }; - - /// A GetNativePropertyNamesRecord records an event where JS asked for a list - /// of property names available on a host object. It records the object, and - /// the returned list of property names. - struct GetNativePropertyNamesRecord : public Record { - static constexpr RecordType type{RecordType::GetNativePropertyNames}; - /// The ObjectID of the host object that was being accessed for - /// HostObjet::getPropertyNames() call. - const ObjectID hostObjectID_; - - explicit GetNativePropertyNamesRecord( - TimeSinceStart time, - ObjectID hostObjectID) - : Record(time), hostObjectID_(hostObjectID) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - std::vector uses() const override { - return {hostObjectID_}; - } - }; - - /// A GetNativePropertyNamesReturnRecord records what property names were - /// returned by the GetNativePropertyNames query. - struct GetNativePropertyNamesReturnRecord final : public Record { - static constexpr RecordType type{RecordType::GetNativePropertyNamesReturn}; - - /// Returned list of property names - const std::vector propNameIDs_; - - explicit GetNativePropertyNamesReturnRecord( - TimeSinceStart time, - const std::vector &propNameIDs) - : Record(time), propNameIDs_(propNameIDs) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - std::vector uses() const override { - auto uses = Record::uses(); - for (const auto &val : propNameIDs_) { - pushIfTrackedValue(val, uses); - } - return uses; - } - }; - - struct SetExternalMemoryPressureRecord final : public Record { - static constexpr RecordType type{RecordType::SetExternalMemoryPressure}; - /// The ObjectID of the object that was passed to - /// Runtime::setExternalMemoryPressure() call. - const ObjectID objID_; - /// The value passed to Runtime::setExternalMemoryPressure() call. - const size_t amount_; - - explicit SetExternalMemoryPressureRecord( - TimeSinceStart time, - const ObjectID objID, - const size_t amount) - : Record(time), objID_(objID), amount_(amount) {} - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - return {objID_}; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// An Utf8Record is an event where a PropNameID or String or Symbol was - /// converted to utf8. - struct Utf8Record final : public Record { - static constexpr RecordType type{RecordType::Utf8}; - /// PropNameID, String or Symbol passed to utf8() or symbolToString() as an - /// argument - const TraceValue objID_; - /// Returned string from utf8() or symbolToString() - const std::string retVal_; - - explicit Utf8Record( - TimeSinceStart time, - const TraceValue objID, - std::string retval) - : Record(time), objID_(objID), retVal_(std::move(retval)) {} - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector vec; - pushIfTrackedValue(objID_, vec); - return vec; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A Utf16Record is an event where a PropNameID or String was converted to - /// UTF-16. - struct Utf16Record final : public Record { - static constexpr RecordType type{RecordType::Utf16}; - /// PropNameID, String passed to utf16() as an argument - const TraceValue objID_; - /// Returned string from utf16(). - const std::u16string retVal_; - - explicit Utf16Record( - TimeSinceStart time, - const TraceValue objID, - std::u16string retval) - : Record(time), objID_(objID), retVal_(std::move(retval)) {} - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector vec; - pushIfTrackedValue(objID_, vec); - return vec; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A GetStringData is an event where getStringData or getPropNameIdData was - /// invoked. - struct GetStringDataRecord final : public Record { - static constexpr RecordType type{RecordType::GetStringData}; - /// The String or PropNameID passed into getStringData or getPropNameIdData - const TraceValue objID_; - /// The string content in the String or PropNameID that was passed into the - /// callback - const std::u16string strData_; - - explicit GetStringDataRecord( - TimeSinceStart time, - const TraceValue objID, - std::u16string strData) - : Record(time), objID_(objID), strData_(std::move(strData)) {} - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector vec; - pushIfTrackedValue(objID_, vec); - return vec; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - struct GlobalRecord final : public Record { - static constexpr RecordType type{RecordType::Global}; - const ObjectID objID_; // global's ObjectID returned from Runtime::global(). - - explicit GlobalRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// Completes writing of the trace to the trace stream. If writing - /// to a file, disables further writing to the file, or accumulation - /// of data. - void flushAndDisable(const ::hermes::vm::GCExecTrace &gcTrace); -}; - -} // namespace tracing -} // namespace hermes -} // namespace facebook - -#endif // HERMES_SYNTHTRACE_H diff --git a/NativeScript/napi/hermes/include/hermes/SynthTraceParser.h b/NativeScript/napi/hermes/include/hermes/SynthTraceParser.h deleted file mode 100644 index 7844ee50e..000000000 --- a/NativeScript/napi/hermes/include/hermes/SynthTraceParser.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_SYNTHTRACEPARSER_H -#define HERMES_SYNTHTRACEPARSER_H - -#include - -#include "hermes/Public/RuntimeConfig.h" -#include "hermes/SynthTrace.h" - -#include "llvh/Support/MemoryBuffer.h" - -namespace facebook { -namespace hermes { -namespace tracing { - -/// Parse a trace from a JSON string stored in a MemoryBuffer. -std::tuple< - SynthTrace, - ::hermes::vm::RuntimeConfig::Builder, - ::hermes::vm::GCConfig::Builder> -parseSynthTrace(std::unique_ptr trace); - -/// Parse a trace from a JSON string stored in the given file name. -std::tuple< - SynthTrace, - ::hermes::vm::RuntimeConfig::Builder, - ::hermes::vm::GCConfig::Builder> -parseSynthTrace(const std::string &tracefile); - -} // namespace tracing -} // namespace hermes -} // namespace facebook - -#endif // HERMES_SYNTHTRACEPARSER_H diff --git a/NativeScript/napi/hermes/include/hermes/ThreadSafetyAnalysis.h b/NativeScript/napi/hermes/include/hermes/ThreadSafetyAnalysis.h deleted file mode 100644 index 39e6cf661..000000000 --- a/NativeScript/napi/hermes/include/hermes/ThreadSafetyAnalysis.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// Based on mutex.h from https://clang.llvm.org/docs/ThreadSafetyAnalysis.html - -#ifndef THREAD_SAFETY_ANALYSIS_MUTEX_H -#define THREAD_SAFETY_ANALYSIS_MUTEX_H - -// Enable thread safety attributes only with clang. -// The attributes can be safely erased when compiling with other compilers. -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#define TSA_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) -#else -#define TSA_THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op -#endif - -#define TSA_CAPABILITY(x) TSA_THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) - -#define TSA_SCOPED_CAPABILITY TSA_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) - -#define TSA_GUARDED_BY(x) TSA_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) - -#define TSA_PT_GUARDED_BY(x) TSA_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) - -#define TSA_ACQUIRED_BEFORE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) - -#define TSA_ACQUIRED_AFTER(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) - -#define TSA_REQUIRES(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) - -#define TSA_REQUIRES_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) - -#define TSA_ACQUIRE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) - -#define TSA_ACQUIRE_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) - -#define TSA_RELEASE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) - -#define TSA_RELEASE_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) - -#define TSA_RELEASE_GENERIC(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) - -#define TSA_TRY_ACQUIRE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) - -#define TSA_TRY_ACQUIRE_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) - -#define TSA_EXCLUDES(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) - -#define TSA_ASSERT_CAPABILITY(x) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) - -#define TSA_ASSERT_SHARED_CAPABILITY(x) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) - -#define TSA_RETURN_CAPABILITY(x) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) - -#define TSA_NO_THREAD_SAFETY_ANALYSIS \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) - -#endif // THREAD_SAFETY_ANALYSIS_MUTEX_H diff --git a/NativeScript/napi/hermes/include/hermes/TraceInterpreter.h b/NativeScript/napi/hermes/include/hermes/TraceInterpreter.h deleted file mode 100644 index 0a1240c1f..000000000 --- a/NativeScript/napi/hermes/include/hermes/TraceInterpreter.h +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { - -namespace tracing { - -class TraceInterpreter final { - public: - /// Options for executing the trace. - struct ExecuteOptions { - /// Customizes the GCConfig of the Runtime. - ::hermes::vm::GCConfig::Builder gcConfigBuilder; - - /// If true, trace again while replaying. After normalization (see - /// hermes/tools/synth/trace_normalize.py) the output trace should be - /// identical to the input trace. If they're not, there was a bug in replay. - mutable bool traceEnabled{false}; - - /// If true, verify that the replay results such as returned values from JS - /// execution, inputs from JS to native function calls are matching with the - /// trace record. - bool verificationEnabled{false}; - - /// If true, command-line options override the config options recorded in - /// the trace. If false, start from the default config. - bool useTraceConfig{false}; - - /// Number of initial executions whose stats are discarded. - int warmupReps{0}; - - /// Number of repetitions of execution. Stats returned are those for the rep - /// with the median totalTime. - int reps{1}; - - /// If true, run a complete collection before printing stats. Useful for - /// guaranteeing there's no garbage in heap size numbers. - bool forceGCBeforeStats{false}; - - /// If true, remove the requirement that the input bytecode was compiled - /// from the same source used to record the trace. There must only be one - /// input bytecode file in this case. If its observable behavior deviates - /// from the trace, the results are undefined. - bool disableSourceHashCheck{false}; - - /// A trace contains many MarkerRecords which have a name used to identify - /// them. If the replay encounters this given marker, perform an action - /// described by MarkerAction. All actions will stop the trace early and - /// collect stats at the marker point, unless the marker is set to the - /// special marker "end". In that case the trace will run to completion. - std::string marker{"end"}; - - enum class MarkerAction { - NONE, - /// Take a snapshot at marker. - SNAPSHOT, - /// Take a heap timeline that ends at marker. - TIMELINE, - /// Take a sampling heap profile that ends at marker. - SAMPLE_MEMORY, - /// Take a sampling time profile that ends at marker. - SAMPLE_TIME, - }; - - /// Sets the action to take upon encountering the marker. The action will - /// write results into the \p profileFileName. - MarkerAction action{MarkerAction::NONE}; - - /// Output file name for any profiling information. - std::string profileFileName; - - // These are the config parameters. We wrap them in llvh::Optional - // to indicate whether the corresponding command line flag was set - // explicitly. We override the trace's config only when that is true. - - /// If true, track all disk I/O done by the runtime and print a report at - /// the end to stdout. - llvh::Optional shouldTrackIO; - - /// If present, do a bytecode warmup run that touches a percentage of the - /// bytecode. A value of 50 here means 50% of the bytecode should be warmed. - llvh::Optional bytecodeWarmupPercent; - }; - - private: - jsi::Runtime &rt_; - ExecuteOptions options_; - llvh::raw_ostream *traceStream_; - // Map from source hash to source file to run. - std::map<::hermes::SHA1, std::shared_ptr> bundles_; - const SynthTrace &trace_; - - /// The last use of each object. - std::unordered_map lastUsePerObj_; - - /// The list of pairs from record index to ObjectID. Each record index is the - /// lastly used position of each Object, at which we can remove the object - /// from gom_ and gpnm_. - std::vector> lastUses_; - /// Index of lastUses_ vector that the interpreter is currently processing. - uint64_t lastUsesIndex_{0}; - - // Invariant: the value is either jsi::Object, jsi::String, jsi::Symbol, - // jsi::BigInt. - std::unordered_map gom_; - // For the PropNameIDs, which are not representable as jsi::Value. - std::unordered_map gpnm_; - - std::string stats_; - /// Whether the marker was reached. - bool markerFound_{false}; - /// Depth in the execution stack. Zero is the outermost function. - uint64_t depth_{0}; - - /// The index of the record that the TraceInterpreter is executing. - uint64_t nextExecIndex_{0}; - - public: - /// Execute the trace given by \p traceFile, that was the trace of executing - /// the bundle given by \p bytecodeFile. - /// \return The stats collected by the runtime about times and memory usage. - static std::string execAndGetStats( - const std::string &traceFile, - const std::vector &bytecodeFiles, - const ExecuteOptions &options); - - /// Same as execAndGetStats, except it additionally accepts a function to - /// create the runtime instance for replaying. This can be used to pass, for - /// example, TracingRuntime to trace while replaying. - static std::string execWithRuntime( - const std::string &traceFile, - const std::vector &bytecodeFiles, - const ExecuteOptions &options, - const std::function( - const ::hermes::vm::RuntimeConfig &runtimeConfig)> &createRuntime); - - /// \param traceStream If non-null, write a trace of the execution into this - /// stream. - /// \return Tuple of GC stats and the runtime instance used for replaying. - static std::tuple> - execFromMemoryBuffer( - std::unique_ptr &&traceBuf, - std::vector> &&codeBufs, - const ExecuteOptions &options, - const std::function( - const ::hermes::vm::RuntimeConfig &runtimeConfig)> &createRuntime); - - private: - TraceInterpreter( - jsi::Runtime &rt, - const ExecuteOptions &options, - const SynthTrace &trace, - std::map<::hermes::SHA1, std::shared_ptr> bundles); - - static std::string exec( - jsi::Runtime &rt, - const ExecuteOptions &options, - const SynthTrace &trace, - std::map<::hermes::SHA1, std::shared_ptr> bundles); - - static ::hermes::vm::RuntimeConfig merge( - ::hermes::vm::RuntimeConfig::Builder &, - const ::hermes::vm::GCConfig::Builder &, - const ExecuteOptions &, - bool, - bool); - - /// Requires \p codeBufs to be the memory buffers containing the code - /// referenced (via source hash) by the given \p trace. Returns a map from - /// the source hash to the memory buffer. In addition, if \p codeIsMmapped is - /// non-null, sets \p *codeIsMmapped to indicate whether all the code is - /// mmapped, and, if \p isBytecode is non-null, sets \p *isBytecode - /// to indicate whether all the code is bytecode. - static std::map<::hermes::SHA1, std::shared_ptr> - getSourceHashToBundleMap( - std::vector> &&codeBufs, - const SynthTrace &trace, - const ExecuteOptions &options, - bool *codeIsMmapped = nullptr, - bool *isBytecode = nullptr); - - jsi::Function createHostFunction( - const SynthTrace::CreateHostFunctionRecord &rec, - const jsi::PropNameID &propNameID); - - jsi::Object createHostObject(SynthTrace::ObjectID objID); - - /// Execute the records with the given ExecuteOptions::MarkerOption - std::string executeRecordsWithMarkerOptions(); - - /// Execute the records. JS might call this recursively when HostFunction or - /// HostObject's functions are called. - void executeRecords(); - - /// Requires that \p valID is the proper id for \p val, and that a - /// defining occurrence of \p valID occurs at the current \p defIndex. Decides - /// whether the definition should be recorded, and, if so, adds the - /// association between \p valID and \p val \p gom_ as appropriate. - void addToObjectMap( - SynthTrace::ObjectID valID, - jsi::Value &&val, - uint64_t defIndex); - - /// Similar to addToObjectMap, but for PropNameIDs. - void addToPropNameIDMap( - SynthTrace::ObjectID id, - jsi::PropNameID &&val, - uint64_t defIndex); - - /// If \p traceValue specifies an Object, String, BigInt or Symbol, requires - /// \p val to be of the corresponding runtime type. Adds this \p val to gom_. - /// - /// \p isThis should be true if and only if the value is a 'this' in a call - /// (only used for validation). TODO(T84791675): Remove this parameter. - /// - /// N.B. This method should be called even if you happen to know that the - /// value cannot be an Object, String, Symbol or BigInt, since it performs - /// useful validation. - void ifObjectAddToObjectMap( - SynthTrace::TraceValue traceValue, - const jsi::Value &val, - uint64_t defIndex, - bool isThis = false); - - /// Same as above, except it avoids copies on temporary objects. - void ifObjectAddToObjectMap( - SynthTrace::TraceValue traceValue, - jsi::Value &&val, - uint64_t defIndex, - bool isThis = false); - - /// Check if the \p marker is the one that is being searched for. If this is - /// the first time encountering the matching marker, perform the actions set - /// up for that marker. - void checkMarker(const std::string &marker); - - /// Get a jsi::Value from gom_ for given ObjectID. - jsi::Value getJSIValueForUse(SynthTrace::ObjectID id); - - /// Get a jsi::PropNameID from gpnm_ for given ObjectID. - jsi::PropNameID getPropNameIDForUse(SynthTrace::ObjectID id); - - /// Convert a TraceValue to a jsi::Value. This calls \p getJSIValueForUse, - /// which will remove the entry from gom_ and globalDefsAndUses_. - jsi::Value traceValueToJSIValue(SynthTrace::TraceValue value); - - /// Erase all references to objects of which last use is before the given - /// record index. - void eraseRefsBefore(uint64_t index); - - std::string printStats(); - - LLVM_ATTRIBUTE_NORETURN void crashOnException( - const std::exception &e, - ::hermes::OptValue globalRecordNum); - - void assertMatch( - const SynthTrace::TraceValue &traceValue, - const jsi::Value &val) const; -}; - -} // namespace tracing -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/TracingRuntime.h b/NativeScript/napi/hermes/include/hermes/TracingRuntime.h deleted file mode 100644 index 14fb20eb2..000000000 --- a/NativeScript/napi/hermes/include/hermes/TracingRuntime.h +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_TRACINGRUNTIME_H -#define HERMES_TRACINGRUNTIME_H - -#include "SynthTrace.h" - -#include -#include -#include "llvh/Support/raw_ostream.h" - -namespace facebook { -namespace hermes { -namespace tracing { - -class TracingRuntime : public jsi::RuntimeDecorator { - public: - using RD = RuntimeDecorator; - - TracingRuntime( - std::unique_ptr runtime, - const ::hermes::vm::RuntimeConfig &conf, - std::unique_ptr traceStream); - - /// Assign a new ObjectID for given jsi::Pointer. - SynthTrace::ObjectID defObjectID(const jsi::Pointer &p); - /// Get the ObjectID for given jsi::Pointer. - SynthTrace::ObjectID useObjectID(const jsi::Pointer &p) const; - - virtual void flushAndDisableTrace() = 0; - - /// @name jsi::Runtime methods. - /// @{ - - jsi::Value evaluateJavaScript( - const std::shared_ptr &buffer, - const std::string &sourceURL) override; - - void queueMicrotask(const jsi::Function &callback) override; - bool drainMicrotasks(int maxMicrotasksHint = -1) override; - - jsi::Object global() override; - - jsi::Object createObject() override; - jsi::Object createObjectWithPrototype(const jsi::Value &prototype) override; - jsi::Object createObject(std::shared_ptr ho) override; - - // Note that the NativeState methods do not need to be traced since they - // cannot be observed in JS. - - jsi::BigInt createBigIntFromInt64(int64_t value) override; - jsi::BigInt createBigIntFromUint64(uint64_t value) override; - jsi::String bigintToString(const jsi::BigInt &bigint, int radix) override; - - jsi::String createStringFromAscii(const char *str, size_t length) override; - jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) override; - jsi::String createStringFromUtf16(const char16_t *utf16, size_t length) - override; - std::string utf8(const jsi::PropNameID &) override; - - jsi::PropNameID createPropNameIDFromAscii(const char *str, size_t length) - override; - jsi::PropNameID createPropNameIDFromUtf8(const uint8_t *utf8, size_t length) - override; - jsi::PropNameID createPropNameIDFromUtf16( - const char16_t *utf16, - size_t length) override; - std::string utf8(const jsi::String &) override; - - std::u16string utf16(const jsi::PropNameID &) override; - std::u16string utf16(const jsi::String &) override; - - void getStringData( - const jsi::String &str, - void *ctx, - void (*cb)(void *ctx, bool ascii, const void *data, size_t num)) override; - - void getPropNameIdData( - const jsi::PropNameID &sym, - void *ctx, - void (*cb)(void *ctx, bool ascii, const void *data, size_t num)) override; - - std::string symbolToString(const jsi::Symbol &) override; - - jsi::PropNameID createPropNameIDFromString(const jsi::String &str) override; - jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol &sym) override; - - jsi::Value getProperty(const jsi::Object &obj, const jsi::String &name) - override; - jsi::Value getProperty(const jsi::Object &obj, const jsi::PropNameID &name) - override; - - bool hasProperty(const jsi::Object &obj, const jsi::String &name) override; - bool hasProperty(const jsi::Object &obj, const jsi::PropNameID &name) - override; - - void setPropertyValue( - const jsi::Object &obj, - const jsi::String &name, - const jsi::Value &value) override; - void setPropertyValue( - const jsi::Object &obj, - const jsi::PropNameID &name, - const jsi::Value &value) override; - - void setPrototypeOf(const jsi::Object &object, const jsi::Value &prototype) - override; - jsi::Value getPrototypeOf(const jsi::Object &object) override; - - jsi::Array getPropertyNames(const jsi::Object &o) override; - - jsi::WeakObject createWeakObject(const jsi::Object &o) override; - - jsi::Value lockWeakObject(const jsi::WeakObject &wo) override; - - jsi::Array createArray(size_t length) override; - jsi::ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) override; - - size_t size(const jsi::Array &arr) override; - size_t size(const jsi::ArrayBuffer &buf) override; - - uint8_t *data(const jsi::ArrayBuffer &buf) override; - - jsi::Value getValueAtIndex(const jsi::Array &arr, size_t i) override; - - void setValueAtIndexImpl( - const jsi::Array &arr, - size_t i, - const jsi::Value &value) override; - - jsi::Function createFunctionFromHostFunction( - const jsi::PropNameID &name, - unsigned int paramCount, - jsi::HostFunctionType func) override; - - jsi::Value call( - const jsi::Function &func, - const jsi::Value &jsThis, - const jsi::Value *args, - size_t count) override; - - jsi::Value callAsConstructor( - const jsi::Function &func, - const jsi::Value *args, - size_t count) override; - - void setExternalMemoryPressure(const jsi::Object &obj, size_t amount) - override; - - /// @} - - void addMarker(const std::string &marker); - - SynthTrace &trace() { - return trace_; - } - - const SynthTrace &trace() const { - return trace_; - } - - void replaceNondeterministicFuncs(); - - // This is the number of records recorded as part of the 'preamble' of a synth - // trace. This means all the records after this amount are from the actual - // execution of the trace. - uint32_t getNumPreambleRecordsForTest() const { - assert( - numPreambleRecords_ > 0 && - "Only call this method if the preamble has been executed"); - return numPreambleRecords_; - } - - private: - SynthTrace::TraceValue defTraceValue(const jsi::Value &value) { - return toTraceValue(value, true); - } - SynthTrace::TraceValue useTraceValue(const jsi::Value &value) { - return toTraceValue(value, false); - } - SynthTrace::TraceValue toTraceValue( - const jsi::Value &value, - bool assignNewUID = false); - - std::vector argStringifyer( - const jsi::Value *args, - size_t count, - bool assignNewUID = false); - - SynthTrace::TimeSinceStart getTimeSinceStart() const; - - std::unique_ptr runtime_; - SynthTrace trace_; - std::deque savedFunctions; - const SynthTrace::TimePoint startTime_{std::chrono::steady_clock::now()}; - uint32_t numPreambleRecords_; - - SynthTrace::ObjectID currentUniqueID_{0}; - - /// Map from PointerValue* to ObjectID. Except WeakRef case (see below), we - /// assign a new ObjectID whenever we see a new def of jsi::Pointer Value. - std::unordered_map - uniqueIDs_; - - /// WeakObject's PointerValue* to ObjectID mapping. - /// The key is the PointerValue of the WeakObject at the time of - /// it is created. - /// The value is newly assign ObjectID for that PointerValue. - std::unordered_map - weakRefIDs_; -}; - -// TracingRuntime is *almost* vm independent. This provides the -// vm-specific bits. And, it's not a HermesRuntime, but it holds one. -class TracingHermesRuntime final : public TracingRuntime { - public: - /// This constructor is not intended to be invoked directly. - /// Use makeTracingHermesRuntime instead. - /// - /// \p traceStream the stream to write trace to. - /// \p commitAction is invoked on completion of tracing. - /// Completion can be triggered implicitly by crash (if crash manager is - /// provided) or explicitly by invocation of flush. If the committed trace - /// can be found in a file, the callback returns the file name. Otherwise, - /// the callback returns empty. - /// \p rollbackAction is invoked if the runtime is destructed prior to - /// completion of tracing. It may or may not invoked if completion failed. - TracingHermesRuntime( - std::unique_ptr runtime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - std::unique_ptr traceStream, - std::function commitAction, - std::function rollbackAction); - - ~TracingHermesRuntime() override; - - void flushAndDisableTrace() override; - - std::string flushAndDisableBridgeTrafficTrace() override; - - jsi::Value evaluateJavaScript( - const std::shared_ptr &buffer, - const std::string &sourceURL) override; - - HermesRuntime &hermesRuntime() { - return static_cast(plain()); - } - - const HermesRuntime &hermesRuntime() const { - return static_cast(plain()); - } - - private: - void crashCallback(int fd); - - const ::hermes::vm::RuntimeConfig conf_; - const std::function commitAction_; - const std::function rollbackAction_; - const llvh::Optional<::hermes::vm::CrashManager::CallbackKey> - crashCallbackKey_; - - bool flushedAndDisabled_{false}; - std::string committedTraceFilename_; -}; - -/// Creates and returns a HermesRuntime that traces JSI interactions. -/// The trace will be written to \p traceScratchPath incrementally. -/// On completion, the file will be renamed to \p traceResultPath, and -/// \p traceCompletionCallback (for post-processing) will be invoked. -/// Completion can be triggered implicitly by crash (if crash manager is -/// provided) or explicitly by invocation of flush. -/// If the runtime is destructed without triggering trace completion, -/// the file at \p traceScratchPath will be deleted. -/// The return value of \p traceCompletionCallback indicates whether the -/// invocation completed successfully. -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - const std::string &traceScratchPath, - const std::string &traceResultPath, - std::function traceCompletionCallback); - -/// Creates and returns a HermesRuntime that traces JSI interactions. -/// If \p traceStream is non-null, writes the trace to \p traceStream. -/// The \p forReplay parameter indicates whether the runtime is being used -/// in trace replay. (Its behavior can differ slightly in that case.) -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - std::unique_ptr traceStream, - bool forReplay = false); - -} // namespace tracing -} // namespace hermes -} // namespace facebook - -#endif // HERMES_TRACINGRUNTIME_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/CDPAgent.h b/NativeScript/napi/hermes/include/hermes/cdp/CDPAgent.h deleted file mode 100644 index fc89c3b32..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/CDPAgent.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CDPAGENT_H -#define HERMES_CDP_CDPAGENT_H - -#include -#include - -#include -#include -#include -#include - -class CDPAgentTest; - -namespace facebook { -namespace hermes { -namespace cdp { - -using OutboundMessageFunc = std::function; - -class CDPAgentImpl; -class CDPDebugAPI; - -/// Public-facing wrapper for internal CDP state that can be preserved across -/// reloads. -struct HERMES_EXPORT State { - /// Incomplete type that stores the actual state. - struct Private; - - /// Create a new empty wrapper. - State(); - /// Create a new wrapper with the provided \p privateState. - explicit State(std::unique_ptr privateState); - - State(const State &other) = delete; - State &operator=(const State &other) = delete; - State(State &&other) noexcept; - State &operator=(State &&other) noexcept; - ~State(); - - inline operator bool() const { - return privateState_ != nullptr; - } - - /// Get the wrapped state. - inline Private &operator*() { - return *privateState_.get(); - } - - /// Get the wrapped state. - inline Private *operator->() { - return privateState_.get(); - } - - private: - /// Pointer to the actual stored state, hidden from users of this wrapper. - std::unique_ptr privateState_; -}; - -/// An agent for interacting with the provided \p runtime and -/// \p asyncDebuggerAPI via CDP messages in the Debugger, Runtime, Profiler, -/// HeapProfiler domains. -/// The integrator of the agent is expected to manage a queue of tasks to be -/// executed with exclusive access to the runtime (i.e. executed when -/// JavaScript is not running). Tasks to be run are delivered to the integrator -/// via the provided \p enqueueRuntimeTaskCallback, and should be executed in -/// order, at the first opportunity between evaluating JavaScript. -/// The integrator can deliver CDP commands to the agent via the -/// \p handleCommand method. When a CDP response or event is generated, it will -/// be delivered to the integrator via the provided \p messageCallback. -/// Both callbacks may be invoked from arbitrary threads. -class HERMES_EXPORT CDPAgent { - friend class ::CDPAgentTest; - - /// Hide the constructor so users can only construct via static create - /// methods. - CDPAgent( - int32_t executionContextID, - CDPDebugAPI &cdpDebugAPI, - debugger::EnqueueRuntimeTaskFunc enqueueRuntimeTaskCallback, - OutboundMessageFunc messageCallback, - State state, - std::shared_ptr destroyedDomainAgents); - - public: - /// Create a new CDP Agent. This can be done on an arbitrary thread; the - /// runtime will not be accessed during execution of this function. - static std::unique_ptr create( - int32_t executionContextID, - CDPDebugAPI &cdpDebugAPI, - debugger::EnqueueRuntimeTaskFunc enqueueRuntimeTaskCallback, - OutboundMessageFunc messageCallback, - State state = {}); - - /// Destroy the CDP Agent. This can be done on an arbitrary thread. - /// It's expected that the integrator will continue to process any runtime - /// tasks enqueued during destruction. - ~CDPAgent(); - - /// This function can be called from arbitrary threads. It processes a CDP - /// command encoded in \p json as UTF-8 in accordance with RFC-8259. See: - // https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/public/devtools_protocol/#wire-format_strings-and-binary-values - void handleCommand(std::string json); - - /// Enable the Runtime domain without processing a CDP command or sending a - /// CDP response. This can be called from arbitrary threads. - void enableRuntimeDomain(); - - /// Enable the Debugger domain without processing a CDP command or sending a - /// CDP response. This can be called from arbitrary threads. - void enableDebuggerDomain(); - - /// Extract state to be persisted across reloads. This can be called from - /// arbitrary threads. - State getState(); - - private: - /// This should be a unique_ptr to provide predictable destruction time lined - /// up with when CDPAgent is destroyed. Do not use shared_ptr. - std::unique_ptr impl_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CDPAGENT_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/CDPDebugAPI.h b/NativeScript/napi/hermes/include/hermes/cdp/CDPDebugAPI.h deleted file mode 100644 index 9809ec9a4..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/CDPDebugAPI.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CDPDEBUGAPI_H -#define HERMES_CDP_CDPDEBUGAPI_H - -#include - -#include "ConsoleMessage.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -class CDPAgentImpl; - -/// Storage and interfaces for carrying out a CDP debug session. Contains -/// information and operations that correspond to a single runtime being -/// debugged, independent of any particular CDPAgent. -class HERMES_EXPORT CDPDebugAPI { - public: - /// Create a new CDPDebugAPI instance. The provided runtime must remain valid - /// until the returned CDPDebugAPI is destroyed. - static std::unique_ptr create( - HermesRuntime &runtime, - size_t maxCachedMessages = kMaxCachedConsoleMessages); - ~CDPDebugAPI(); - - /// Gets the runtime originally passed into this instance. - HermesRuntime &runtime() { - return runtime_; - } - - /// Gets the AsyncDebuggerAPI associated with this instance. - debugger::AsyncDebuggerAPI &asyncDebuggerAPI() { - return *asyncDebuggerAPI_; - } - - /// Adds a console message to the current CDPDebugAPI instance, - /// broadcasting it to all current agents, and storing it for - /// future agents (within buffer limitations). This function - /// must only be called from the runtime thread. - void addConsoleMessage(ConsoleMessage message); - - private: - /// Allow CDPAgentImpl (but not integrators) to access - /// consoleMessageStorage_. - friend class CDPAgentImpl; - - CDPDebugAPI(HermesRuntime &runtime, size_t maxCachedMessages); - - HermesRuntime &runtime_; - std::unique_ptr asyncDebuggerAPI_; - ConsoleMessageStorage consoleMessageStorage_; - ConsoleMessageDispatcher consoleMessageDispatcher_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CDPDEBUGAPI_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/CallbackOStream.h b/NativeScript/napi/hermes/include/hermes/cdp/CallbackOStream.h deleted file mode 100644 index b8a4eb3bb..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/CallbackOStream.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CALLBACKOSTREAM_H -#define HERMES_CDP_CALLBACKOSTREAM_H - -#include -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Subclass of \c std::ostream where flushing is implemented through a -/// callback. Writes are collected in a buffer. When filled, the buffer's -/// contents are emptied out and sent to a callback. -struct CallbackOStream : public std::ostream { - /// Signature of callback called to flush buffer contents. Accepts the buffer - /// as a string. Returns a boolean indicating whether flushing succeeded. - /// Callback failure will be translated to stream failure. If the callback - /// throws an exception it will be swallowed and translated into stream - /// failure. - using Fn = std::function; - - /// Construct a new stream. - /// - /// \p sz The size of the buffer -- how large it can get before it must be - /// flushed. Must be non-zero. - /// \p cb The callback function. - CallbackOStream(size_t sz, Fn cb); - - /// This class is neither movable nor copyable. - CallbackOStream(CallbackOStream &&that) = delete; - CallbackOStream &operator=(CallbackOStream &&that) = delete; - CallbackOStream(const CallbackOStream &that) = delete; - CallbackOStream &operator=(const CallbackOStream &that) = delete; - - private: - /// \c std::streambuf sub-class backed by a std::string buffer and - /// implementing overflow by calling a callback. - struct StreamBuf : public std::streambuf { - /// Construct a new streambuf. Parameters are the same as those of - /// \c CallbackOStream . - StreamBuf(size_t sz, Fn cb); - - /// Destruction will flush any remaining buffer contents. - ~StreamBuf() override; - - /// StreamBufs are not copyable, to avoid the flush callback receiving - /// the contents of multiple streams. - StreamBuf(const StreamBuf &) = delete; - StreamBuf &operator=(const StreamBuf &) = delete; - - protected: - /// std::streambuf overrides - int_type overflow(int_type ch) override; - int sync() override; - - private: - /// The size of the backing buffer. Fixed for an instance of the streambuf. - size_t sz_; - - /// The backing buffer that writes will go to until full. - std::unique_ptr buf_; - - /// The function called when buf_ has been filled. - Fn cb_; - - /// Clears the backing buffer. - void reset(); - - /// Clears the backing buffer and returns it contents in a string. - std::string take(); - }; - - StreamBuf sbuf_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CALLBACKOSTREAM_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/ConsoleMessage.h b/NativeScript/napi/hermes/include/hermes/cdp/ConsoleMessage.h deleted file mode 100644 index 906dbb9a8..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/ConsoleMessage.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H -#define HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H - -#include -#include -#include - -#include - -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Controls the max number of message to cached in \p consoleMessageCache_. The -/// value here is chosen to match what Chromium uses in their CDP -/// implementation. -static const int kMaxCachedConsoleMessages = 1000; - -enum class ConsoleAPIType { - kLog, - kDebug, - kInfo, - kError, - kWarning, - kDir, - kDirXML, - kTable, - kTrace, - kStartGroup, - kStartGroupCollapsed, - kEndGroup, - kClear, - kAssert, - kTimeEnd, - kCount -}; - -struct ConsoleMessage { - double timestamp; - ConsoleAPIType type; - std::vector args; - debugger::StackTrace stackTrace; - - ConsoleMessage( - double timestamp, - ConsoleAPIType type, - std::vector args, - debugger::StackTrace stackTrace = {}) - : timestamp(timestamp), - type(type), - args(std::move(args)), - stackTrace(stackTrace) {} -}; - -class ConsoleMessageStorage { - public: - ConsoleMessageStorage(size_t maxCachedMessages = kMaxCachedConsoleMessages); - - void addMessage(ConsoleMessage message); - void clear(); - - const std::deque &messages() const; - size_t discarded() const; - std::optional oldestTimestamp() const; - - private: - /// Maximum number of messages to cache. - size_t maxCachedMessages_; - /// Counts the number of console messages discarded when - /// \p consoleMessageCache_ is full. - size_t numConsoleMessagesDiscardedFromCache_ = 0; - /// Cache for storing console messages. Earlier messages are discarded when - /// the cache is full. The choice to use a std::deque is for fast operations - /// at the beginning and the end, so that adding to the cache and discarding - /// from the cache are fast. - std::deque consoleMessageCache_{}; -}; - -class CDPAgent; - -/// Token that identifies a specific subscription to console messages. -using ConsoleMessageRegistration = uint32_t; - -/// Dispatcher to deliver console messages to all registered subscribers. -/// Everything in this class must be used exclusively from the runtime thread. -class ConsoleMessageDispatcher { - public: - ConsoleMessageDispatcher() {} - ~ConsoleMessageDispatcher() {} - - /// Register a subscriber and return a token that can be used to - /// unregister in the future. Must only be called from the runtime thread. - ConsoleMessageRegistration subscribe( - std::function handler) { - auto token = ++tokenCounter_; - subscribers_[token] = handler; - return token; - } - - /// Unregister a subscriber using the token returned from registration. - /// Must only be called from the runtime thread. - void unsubscribe(ConsoleMessageRegistration token) { - subscribers_.erase(token); - } - - /// Deliver a new console message to each subscriber. Must only be called - /// from the runtime thread. - void deliverMessage(const ConsoleMessage &message) { - for (auto &pair : subscribers_) { - pair.second(message); - } - } - - private: - /// Collection of subscribers, identified by registration token. - std::unordered_map< - ConsoleMessageRegistration, - std::function> - subscribers_; - - /// Counter to generate unique registration tokens. - ConsoleMessageRegistration tokenCounter_ = 0; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/DebuggerDomainAgent.h b/NativeScript/napi/hermes/include/hermes/cdp/DebuggerDomainAgent.h deleted file mode 100644 index c54b8983b..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/DebuggerDomainAgent.h +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_DEBUGGERDOMAINAGENT_H -#define HERMES_CDP_DEBUGGERDOMAINAGENT_H - -#include -#include - -#include -#include -#include - -#include "DomainAgent.h" -#include "DomainState.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -enum class PausedNotificationReason; - -/// Last explicit debugger step command issued by the user. -enum class LastUserStepRequest { - StepInto, - StepOver, - StepOut, -}; - -namespace m = ::facebook::hermes::cdp::message; - -/// Details about a single Hermes breakpoint, implied by a CDP breakpoint. -struct HermesBreakpoint { - debugger::BreakpointID breakpointID; - debugger::ScriptID scriptID; -}; - -/// Type used to store CDP breakpoint identifiers. These IDs are generated by -/// the CDP Handler, so we can constrain them to a specific range. -using CDPBreakpointID = uint32_t; - -/// Description of where breakpoints should be created. -struct CDPBreakpointDescription : public StateValue { - ~CDPBreakpointDescription() override = default; - std::unique_ptr copy() const override { - auto value = std::make_unique(); - value->line = line; - value->column = column; - value->condition = condition; - value->url = url; - return value; - } - - /// Determines whether this breakpoint can be persisted across sessions - bool persistable() const { - // Only persist breakpoints that can apply to future scripts (i.e. - // breakpoints set on a set of files specified by script URL, not - // breakpoints set on an exact, session-specific script ID). - return url.has_value(); - } - - std::optional url; - long long line; - std::optional column; - std::optional condition; -}; - -/// Details of each existing CDP breakpoint, which may correspond to multiple -/// Hermes breakpoints. -struct CDPBreakpoint { - explicit CDPBreakpoint(CDPBreakpointDescription description) - : description(description) {} - - // Description of where the breakpoint should be applied - CDPBreakpointDescription description; - - // Registered breakpoints in Hermes - std::vector hermesBreakpoints; -}; - -struct HermesBreakpointLocation { - debugger::BreakpointID id; - debugger::SourceLocation location; -}; - -/// Handler for the "Debugger" domain of CDP. Accepts events from the runtime, -/// and CDP requests from the debug client belonging to the "Debugger" domain. -/// Produces CDP responses and events belonging to the "Debugger" domain. All -/// methods expect to be invoked with exclusive access to the runtime. -class DebuggerDomainAgent : public DomainAgent { - public: - DebuggerDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - debugger::AsyncDebuggerAPI &asyncDebugger, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable_, - DomainState &state); - ~DebuggerDomainAgent(); - - /// Enables the Debugger domain without processing CDP message or sending a - /// CDP response. It will still send CDP notifications if needed. - void enable(); - /// Handles Debugger.enable request - /// @cdp Debugger.enable If domain is already enabled, will return success. - void enable(const m::debugger::EnableRequest &req); - /// Handles Debugger.disable request - /// @cdp Debugger.disable If domain is already disabled, will return success. - void disable(const m::debugger::DisableRequest &req); - - /// Handles Debugger.pause request - void pause(const m::debugger::PauseRequest &req); - /// Handles Debugger.resume request - void resume(const m::debugger::ResumeRequest &req); - - /// Handles Debugger.stepInto request - void stepInto(const m::debugger::StepIntoRequest &req); - /// Handles Debugger.stepOut request - void stepOut(const m::debugger::StepOutRequest &req); - /// Handles Debugger.stepOver request - void stepOver(const m::debugger::StepOverRequest &req); - - /// Handles Debugger.setBlackboxedRanges request - void setBlackboxedRanges(const m::debugger::SetBlackboxedRangesRequest &req); - /// Handles Debugger.setBlackboxPatterns request - void setBlackboxPatterns(const m::debugger::SetBlackboxPatternsRequest &req); - /// Handles Debugger.setPauseOnExceptions - void setPauseOnExceptions( - const m::debugger::SetPauseOnExceptionsRequest &req); - - /// Handles Debugger.evaluateOnCallFrame - void evaluateOnCallFrame(const m::debugger::EvaluateOnCallFrameRequest &req); - - /// Debugger.setBreakpoint creates a CDP breakpoint that applies to exactly - /// one script (identified by script ID) that does not survive reloads. - void setBreakpoint(const m::debugger::SetBreakpointRequest &req); - // Debugger.setBreakpointByUrl creates a CDP breakpoint that may apply to - // multiple scripts (identified by URL), and survives reloads. - void setBreakpointByUrl(const m::debugger::SetBreakpointByUrlRequest &req); - /// Handles Debugger.removeBreakpoint - void removeBreakpoint(const m::debugger::RemoveBreakpointRequest &req); - /// Handles Debugger.setBreakpointsActive - /// @cdp Debugger.setBreakpointsActive Allowed even if domain is not enabled. - void setBreakpointsActive( - const m::debugger::SetBreakpointsActiveRequest &req); - - private: - /// Handle an event originating from the runtime. - void handleDebuggerEvent( - HermesRuntime &runtime, - debugger::AsyncDebuggerAPI &asyncDebugger, - debugger::DebuggerEventType event); - - /// Send a Debugger.paused notification to the debug client - void sendPausedNotificationToClient(PausedNotificationReason reason); - /// Send a Debugger.scriptParsed notification to the debug client - void sendScriptParsedNotificationToClient( - const debugger::SourceLocation srcLoc); - - /// Obtain the newly loaded script and send a ScriptParsed notification to the - /// debug client - void processNewLoadedScript(); - - std::pair createCDPBreakpoint( - CDPBreakpointDescription &&description, - std::optional hermesBreakpoint = std::nullopt); - - std::optional createHermesBreakpoint( - debugger::ScriptID scriptID, - const CDPBreakpointDescription &description); - - void applyBreakpointAndSendNotification( - CDPBreakpointID cdpBreakpointID, - CDPBreakpoint &cdpBreakpoint, - const debugger::SourceLocation &srcLoc); - - std::optional applyBreakpoint( - CDPBreakpoint &cdpBreakpoint, - debugger::ScriptID scriptID); - - /// Holds a boolean that determines if scripts without a script url - /// (e.g. anonymous scripts) should be blackboxed. - /// Same as V8: - /// https://source.chromium.org/chromium/chromium/src/+/fef5d519bab86dbd712d76bfca5be90a6e03459c:v8/src/inspector/v8-debugger-agent-impl.cc;l=997-999 - bool blackboxAnonymousScripts_ = false; - /// Optionally, holds a compiled regex pattern that is used to test if - /// script urls should be blackboxed. - /// See isLocationBlackboxed below for more details. Same as V8: - /// https://source.chromium.org/chromium/chromium/src/+/fef5d519bab86dbd712d76bfca5be90a6e03459c:v8/src/inspector/v8-debugger-agent-impl.cc;l=993-996 - /// Matching using the compiled regex should be done with - /// ::hermes::regex::searchWithBytecode. - std::optional> compiledBlackboxPatternRegex_; - - /// A vector of 1-based positions per script id indicating where blackbox - /// state changes using [from inclusive, to exclusive) pairs. - /// [ (start) ... position[0]) range is not blackboxed - /// [position[0] ... position[1]) range is blackboxed - /// [position[1] ... position[2]) range is not blackboxed ... ... - /// [position[n] ... (end) ) range is blackboxed if n is even, not - /// blackboxed if odd. - /// This is used to determine if the debugger is paused on one of these - /// blackboxed ranges, to prevent the user from stopping there in the - /// following scenarios: - /// 1. Step out- repeats stepping out until reaches a non-blackboxed range. - /// 2. Step over- stepping over to a blackboxed range meaning that - /// the next un-blackboxed range would be after all the stepping in the - /// function are done (because blackboxing is per file, meaning per function - /// as well) so we can execute step out as well in this case until we - /// step out of blackboxed ranges. - /// Comparing with v8, we don’t check if the user comes from a blackboxed - /// range, but only if a stepover got you to a blackboxed range. However - /// both results in the same thing which is stepping out until reaching a - /// non-blackboxed range. - /// 3. Step into- execute another step into. - /// Repeat this step until outside of a blackboxed range. - /// 4. Exceptions triggering the debugger pause- - /// (uncaught or if the user chooses to stop on all exceptions)- - /// ignore and continue execution - /// 5. Debugger statements- ignore and continue execution - /// 6. Explicit pause- keep stepping in until reaching a non-blackboxed range - /// 7. Manual breakpoints- allow stopping in blackboxed ranges - std::unordered_map>> - blackboxedRanges_; - /// Checks whether the passed location falls within a blackboxed range - /// in blackboxedRanges_. - /// Chrome looks at full functions ("frames") to detemine this. See: - /// https://source.chromium.org/chromium/chromium/src/+/318e9cfd9fbbbc70906f6a78d017a2708248dc6d:v8/src/inspector/v8-debugger-agent-impl.cc;l=984-1026 - /// We, on the other hand, look at individual lines since there's no - /// difference in practise because the current way functions are blackboxed is - /// by using ignoreList in source maps, which blackboxes full files, which - /// means also it blackboxes full functions, so there's no difference between - /// checking if a line in a function is blackboxed or if the whole function is - /// blackboxed. - /// This means that we receive one "Debugger.setBlackboxedRanges" per bundle - /// file comprised of source js files. - /// For each file appearing in the "ignoreList" in source maps, we receive the - /// start positions and end positions of the file inside the bundle file: - /// [ file 1 start position, - /// file 1 end position, - /// file 2 start position, - /// file 2 end position, - /// ... ] - bool isLocationBlackboxed( - debugger::ScriptID scriptID, - std::string scriptName, - int lineNumber, - int columnNumber); - /// Checks whether the location of the top frame of the call stack is - /// blackboxed or not using isLocationBlackboxed - bool isTopFrameLocationBlackboxed(); - - bool checkDebuggerEnabled(const m::Request &req); - bool checkDebuggerPaused(const m::Request &req); - - /// Removes any modifications this agent made to Hermes in order to enable - /// debugging - void cleanUp(); - - HermesRuntime &runtime_; - debugger::AsyncDebuggerAPI &asyncDebugger_; - - /// ID for the registered DebuggerEventCallback - debugger::DebuggerEventCallbackID debuggerEventCallbackId_; - - /// Details of each CDP breakpoint that has been created, and not - /// yet destroyed. - std::unordered_map cdpBreakpoints_{}; - - /// CDP breakpoint IDs are assigned by the DebuggerDomainAgent. Keep track of - /// the next available ID. Starts with 100 to avoid confusion with Hermes - /// breakpoints IDs that start with 1. - CDPBreakpointID nextBreakpointID_ = 100; - - DomainState &state_; - - /// Whether the currently installed breakpoints actually take effect. If - /// they're supposed to be inactive, then debugger agent will automatically - /// resume execution when breakpoints are hit. - bool breakpointsActive_ = true; - - /// Whether Debugger.enable was received and wasn't disabled by receiving - /// Debugger.disable - bool enabled_; - - /// Whether to consider the debugger as currently paused. There are some - /// debugger events such as ScriptLoaded where we don't consider the debugger - /// to be paused. - /// Should only be set using setPaused and setUnpaused. - bool paused_; - - /// Called when the runtime is paused. - void setPaused(PausedNotificationReason pausedNotificationReason); - - /// Called when the runtime is resumed. - void setUnpaused(); - - /// Set to true when the user selects to explicitly pause execution. - /// This is set back to false when the execution is paused. - bool explicitPausePending_ = false; - - /// Last explicit step type issued by the user. - /// * This is never reset because cdp can't tell if a step command was - /// completed since a step command that does not result in further operations - /// resolves to a "resume" without "stepFinished" or debugger pause. - /// That means that this member should only be used in situations where we are - /// sure that a step command was issued in the given scenario. For example, a - /// step into command followed by a resume would leave this member holding an - /// "StepInto" even when minutes later the execution stops on a breakpoint. - std::optional lastUserStepRequest_ = std::nullopt; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_DEBUGGERDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/DomainAgent.h b/NativeScript/napi/hermes/include/hermes/cdp/DomainAgent.h deleted file mode 100644 index 6770e829f..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/DomainAgent.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_DOMAINAGENT_H -#define HERMES_CDP_DOMAINAGENT_H - -#include -#include - -#include -#include - -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#include -#else -#ifndef TSA_GUARDED_BY -#define TSA_GUARDED_BY(x) -#endif -#endif - -namespace facebook { -namespace hermes { -namespace cdp { - -namespace m = ::facebook::hermes::cdp::message; - -/// A wrapper around std::function to make it safe to use from -/// multiple threads. The wrapper implements an invalidate function so that one -/// thread can clean up the underlying std::function in a thread-safe way. -template -class SynchronizedCallback { - public: - SynchronizedCallback(std::function func) - : funcContainer_(std::make_shared(func)) {} - - /// Thread-safe version that calls the underlying std::function. If the - /// underlying std::function is empty, this function is a no-op. - void operator()(Args... args) const { - std::lock_guard lock(funcContainer_->mutex); - if (funcContainer_->func) { - funcContainer_->func(args...); - } - } - - /// Reset the underlying std::function so that future invocations of - /// operator() would just be a no-op. - void invalidate() { - std::lock_guard lock(funcContainer_->mutex); - funcContainer_->func = std::function(); - } - - private: - struct FunctionContainer { - FunctionContainer(std::function func) : func(func) {} - - std::mutex mutex{}; - - /// The actual std::function to be invoked by operator() - std::function func TSA_GUARDED_BY(mutex); - }; - std::shared_ptr funcContainer_; -}; - -using SynchronizedOutboundCallback = SynchronizedCallback; - -class DomainAgent { - protected: - DomainAgent( - int32_t executionContextID, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable) - : executionContextID_(executionContextID), - messageCallback_(messageCallback), - objTable_(objTable) {} - virtual ~DomainAgent() {} - - /// Sends the provided string back to the debug client - void sendToClient(const std::string &str) { - messageCallback_(str); - } - - /// Sends the provided \p Response back to the debug client - void sendResponseToClient(const m::Response &resp) { - sendToClient(resp.toJsonStr()); - } - - /// Sends the provided \p Notification back to the debug client - void sendNotificationToClient(const m::Notification ¬e) { - sendToClient(note.toJsonStr()); - } - - /// Execution context ID associated with the HermesRuntime - int32_t executionContextID_; - - /// Callback function to send CDP response back to the debug client - SynchronizedOutboundCallback messageCallback_; - - std::shared_ptr objTable_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_DOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/DomainState.h b/NativeScript/napi/hermes/include/hermes/cdp/DomainState.h deleted file mode 100644 index 4c21603cb..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/DomainState.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_DOMAINSTATE_H -#define HERMES_CDP_DOMAINSTATE_H - -#include -#include -#include -#include -#include - -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#include -#else -#ifndef TSA_GUARDED_BY -#define TSA_GUARDED_BY(x) -#endif -#ifndef TSA_REQUIRES -#define TSA_REQUIRES(x) -#endif -#endif - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Base class for data to be stored in DomainState. -struct StateValue { - public: - virtual ~StateValue() = default; - virtual std::unique_ptr copy() const = 0; -}; - -/// StateValue that can be used as a dictionary. Used as the main storage value -/// of DomainState so that modifications can be based on keys of the dictionary -/// hierarchy. -struct DictionaryStateValue : public StateValue { - ~DictionaryStateValue() override = default; - std::unique_ptr copy() const override; - - std::unordered_map> values; -}; - -using StateModification = - std::pair, std::unique_ptr>; - -/// This class acts as container for saving state that CDP agents need after a -/// reload. Its main purpose is to synchronize the manipulation of state on the -/// runtime thread and when CDPAgent::getState() gets called on arbitrary -/// thread. Functions in this class specifically do not contain callbacks to -/// ensure the mutex locking usage remain simple with no reentrancy to think -/// about. -class DomainState { - public: - DomainState(); - explicit DomainState(std::unique_ptr dict); - - /// TSA doesn't get applied to constructors, so delete the normal mechanism. - /// There is a separate copy() function instead. - DomainState(const DomainState &) = delete; - DomainState &operator=(const DomainState &) = delete; - - /// Deep copy of the data and make a new instance. Used by - /// CDPAgent::getState() to get the state in a thread-safe manner. - std::unique_ptr copy(); - - /// This function allows the caller to access values in the saved state. This - /// obtains a copy of the data so that no further synchronization is required - /// after calling this function. This function is expected to only be called a - /// few times after reload, so it isn't used frequently. All entries in the - /// \p paths vector are expected to be pointing to DictionaryStateValue(s) - /// except the last entry, which is a key to any StateValue. - /// \return a copy of the StateValue stored at \p paths, nullptr if no value - /// exists at paths - std::unique_ptr getCopy(std::vector paths); - - /// This class is the only way for callers to manipulate the DomainState. It - /// is a scope-based commit where the modifications get saved upon the class's - /// destruction. The class must not be saved elsewhere and outlive the - /// DomainState where it came from. The intent is to nudge the caller to batch - /// modifications and commit the changes in one go. Because we make a copy of - /// the state with copy(), we want state changes to be atomic. Caller can - /// still break things up into multiple transactions, but the hope is that - /// this nudges them to think about modifications as one atomic unit. - class Transaction { - public: - explicit Transaction(DomainState &state); - ~Transaction(); - - /// Adds a value to the container. All entries in the \p paths vector are - /// expected to be pointing to DictionaryStateValue(s) except the last - /// entry, which is a key to any StateValue. - void add(std::vector paths, const StateValue &value); - - /// Removes a value from the container. All entries in the \p paths vector - /// are expected to be pointing to DictionaryStateValue(s) except the last - /// entry, which is a key to any StateValue. - void remove(std::vector paths); - - private: - friend DomainState; - - DomainState &state_; - std::vector modifications_{}; - }; - - /// Gets a Transaction for modification. - Transaction transaction(); - - private: - /// Helper function for traversing the dictionary hierarchy. - DictionaryStateValue *getDict( - const std::vector &paths, - bool createMissingDict) TSA_REQUIRES(mutex_); - - /// Save modifications to \p dict_. - void commitTransaction(Transaction &transaction); - - std::mutex mutex_{}; - - /// The actual value container. TSA doesn't work if this is just a direct - /// value on the class, so using an unique_ptr. - std::unique_ptr dict_ TSA_GUARDED_BY(mutex_){}; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_DOMAINSTATE_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/HeapProfilerDomainAgent.h b/NativeScript/napi/hermes/include/hermes/cdp/HeapProfilerDomainAgent.h deleted file mode 100644 index 227214bcc..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/HeapProfilerDomainAgent.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_HEAPPROFILERDOMAINAGENT_H -#define HERMES_CDP_HEAPPROFILERDOMAINAGENT_H - -#include - -#include "DomainAgent.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Handler for the "HeapProfiler" domain of CDP. All methods expect to be -/// invoked with exclusive access to the runtime. -class HeapProfilerDomainAgent : public DomainAgent { - public: - HeapProfilerDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable); - ~HeapProfilerDomainAgent(); - - /// Handles HeapProfiler.takeHeapSnapshot request - void takeHeapSnapshot(const m::heapProfiler::TakeHeapSnapshotRequest &req); - - /// Handle HeapProfiler.getObjectByHeapObjectId - void getObjectByHeapObjectId( - const m::heapProfiler::GetObjectByHeapObjectIdRequest &req); - - /// Handle HeapProfiler.getObjectByHeapObjectId - void getHeapObjectId(const m::heapProfiler::GetHeapObjectIdRequest &req); - - /// Handle HeapProfiler.collectGarbage - void collectGarbage(const m::heapProfiler::CollectGarbageRequest &req); - - /// Handle HeapProfiler.startTrackingHeapObjects - void startTrackingHeapObjects( - const m::heapProfiler::StartTrackingHeapObjectsRequest &req); - - /// Handle HeapProfiler.stopTrackingHeapObjects - void stopTrackingHeapObjects( - const m::heapProfiler::StopTrackingHeapObjectsRequest &req); - - /// Handle HeapProfiler.startSampling - void startSampling(const m::heapProfiler::StartSamplingRequest &req); - - /// Handle HeapProfiler.stopSampling - void stopSampling(const m::heapProfiler::StopSamplingRequest &req); - - private: - void sendSnapshot(int reqId, bool reportProgress, bool captureNumericValue); - - HermesRuntime &runtime_; - - /// Flag indicating whether this agent is registered to receive heap object - /// tracking callbacks. - bool trackingHeapObjectStackTraces_ = false; - - /// Flag indicating whether this agent is currently running a heap sampling - /// session. - bool samplingHeap_ = false; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_HEAPPROFILERDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/JSONValueInterfaces.h b/NativeScript/napi/hermes/include/hermes/cdp/JSONValueInterfaces.h deleted file mode 100644 index 23a12ba8c..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/JSONValueInterfaces.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_JSONVALUEINTERFACES_H -#define HERMES_CDP_JSONVALUEINTERFACES_H - -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace cdp { -using namespace ::hermes::parser; - -/// Convert a string to a JSONValue. Will return nullopt if parsing is not -/// successful. -std::optional parseStr( - const std::string &str, - JSONFactory &factory); - -/// Convert a string to a JSON object. Will return nullopt if parsing is not -/// successful, or the resulting JSON value is not an object. -std::optional parseStrAsJsonObj( - const std::string &str, - JSONFactory &factory); - -/// Convert a JSONValue to a string. -std::string jsonValToStr(const JSONValue *v); - -/// Check if two JSONValues are equal. -bool jsonValsEQ(const JSONValue *A, const JSONValue *B); - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_JSONVALUEINTERFACES_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/MessageConverters.h b/NativeScript/napi/hermes/include/hermes/cdp/MessageConverters.h deleted file mode 100644 index 7397bd1d0..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/MessageConverters.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_MESSAGECONVERTERS_H -#define HERMES_CDP_MESSAGECONVERTERS_H - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { - -template -void setChromeLocation( - T &chromeLoc, - const facebook::hermes::debugger::SourceLocation &hermesLoc) { - if (hermesLoc.line != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.lineNumber = hermesLoc.line - 1; - } - - if (hermesLoc.column != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.columnNumber = hermesLoc.column - 1; - } -} - -/// ErrorCode magic numbers match JSC's (see InspectorBackendDispatcher.cpp) -enum class ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - ServerError = -32000 -}; - -ErrorResponse -makeErrorResponse(int id, ErrorCode code, const std::string &message); - -OkResponse makeOkResponse(int id); - -namespace debugger { - -Location makeLocation(const facebook::hermes::debugger::SourceLocation &loc); - -} // namespace debugger - -namespace runtime { - -CallFrame makeCallFrame(const facebook::hermes::debugger::CallFrameInfo &info); - -std::vector makeCallFrames( - const facebook::hermes::debugger::StackTrace &stackTrace); - -} // namespace runtime - -namespace heapProfiler { - -std::unique_ptr makeSamplingHeapProfile( - const std::string &value); - -} // namespace heapProfiler - -namespace profiler { - -std::unique_ptr makeProfile(const std::string &value); - -} // namespace profiler - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_MESSAGECONVERTERS_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/MessageInterfaces.h b/NativeScript/napi/hermes/include/hermes/cdp/MessageInterfaces.h deleted file mode 100644 index f19418f57..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/MessageInterfaces.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_MESSAGEINTERFACES_H -#define HERMES_CDP_MESSAGEINTERFACES_H - -#include -#include -#include -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { -using namespace ::hermes::parser; - -struct RequestHandler; - -/// Serializable is an interface for objects that can be serialized to and from -/// JSON. -struct Serializable { - virtual ~Serializable() = default; - virtual JSONValue *toJsonVal(JSONFactory &factory) const = 0; - - std::string toJsonStr() const; -}; - -/// Requests are sent from the debugger to the target. -struct Request : public Serializable { - using ParseResult = std::variant, std::string>; - static std::unique_ptr fromJson(const std::string &str); - - Request() = default; - explicit Request(std::string method) : method(method) {} - - // accept dispatches to the appropriate handler method in RequestHandler based - // on the type of the request. - virtual void accept(RequestHandler &handler) const = 0; - - long long id = 0; - std::string method; -}; - -/// Responses are sent from the target to the debugger in response to a Request. -struct Response : public Serializable { - Response() = default; - - std::optional id = std::nullopt; -}; - -/// Notifications are sent from the target to the debugger. This is used to -/// notify the debugger about events that occur in the target, e.g. stopping -/// at a breakpoint. -struct Notification : public Serializable { - Notification() = default; - explicit Notification(std::string method) : method(method) {} - - std::string method; -}; - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_MESSAGEINTERFACES_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/MessageTypes.h b/NativeScript/napi/hermes/include/hermes/cdp/MessageTypes.h deleted file mode 100644 index bdc14d394..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/MessageTypes.h +++ /dev/null @@ -1,1279 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved. -// @generated SignedSource<<1284c402aedd087ebdf70e9e76596f1c>> - -#pragma once - -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { - -template -void deleter(T *p); -using JSONBlob = std::string; -struct UnknownRequest; - -namespace debugger { -using BreakpointId = std::string; -struct BreakpointResolvedNotification; -struct CallFrame; -using CallFrameId = std::string; -struct DisableRequest; -struct EnableRequest; -struct EvaluateOnCallFrameRequest; -struct EvaluateOnCallFrameResponse; -struct Location; -struct PauseRequest; -struct PausedNotification; -struct RemoveBreakpointRequest; -struct ResumeRequest; -struct ResumedNotification; -struct Scope; -using ScriptLanguage = std::string; -struct ScriptParsedNotification; -struct ScriptPosition; -struct SetBlackboxPatternsRequest; -struct SetBlackboxedRangesRequest; -struct SetBreakpointByUrlRequest; -struct SetBreakpointByUrlResponse; -struct SetBreakpointRequest; -struct SetBreakpointResponse; -struct SetBreakpointsActiveRequest; -struct SetInstrumentationBreakpointRequest; -struct SetInstrumentationBreakpointResponse; -struct SetPauseOnExceptionsRequest; -struct StepIntoRequest; -struct StepOutRequest; -struct StepOverRequest; -} // namespace debugger - -namespace runtime { -struct CallArgument; -struct CallFrame; -struct CallFunctionOnRequest; -struct CallFunctionOnResponse; -struct CompileScriptRequest; -struct CompileScriptResponse; -struct ConsoleAPICalledNotification; -struct CustomPreview; -struct DisableRequest; -struct DiscardConsoleEntriesRequest; -struct EnableRequest; -struct EntryPreview; -struct EvaluateRequest; -struct EvaluateResponse; -struct ExceptionDetails; -struct ExecutionContextCreatedNotification; -struct ExecutionContextDescription; -using ExecutionContextId = long long; -struct GetHeapUsageRequest; -struct GetHeapUsageResponse; -struct GetPropertiesRequest; -struct GetPropertiesResponse; -struct GlobalLexicalScopeNamesRequest; -struct GlobalLexicalScopeNamesResponse; -struct InspectRequestedNotification; -struct InternalPropertyDescriptor; -struct ObjectPreview; -struct PropertyDescriptor; -struct PropertyPreview; -struct ReleaseObjectGroupRequest; -struct ReleaseObjectRequest; -struct RemoteObject; -using RemoteObjectId = std::string; -struct RunIfWaitingForDebuggerRequest; -using ScriptId = std::string; -struct StackTrace; -using Timestamp = double; -using UnserializableValue = std::string; -} // namespace runtime - -namespace heapProfiler { -struct AddHeapSnapshotChunkNotification; -struct CollectGarbageRequest; -struct GetHeapObjectIdRequest; -struct GetHeapObjectIdResponse; -struct GetObjectByHeapObjectIdRequest; -struct GetObjectByHeapObjectIdResponse; -using HeapSnapshotObjectId = std::string; -struct HeapStatsUpdateNotification; -struct LastSeenObjectIdNotification; -struct ReportHeapSnapshotProgressNotification; -struct SamplingHeapProfile; -struct SamplingHeapProfileNode; -struct SamplingHeapProfileSample; -struct StartSamplingRequest; -struct StartTrackingHeapObjectsRequest; -struct StopSamplingRequest; -struct StopSamplingResponse; -struct StopTrackingHeapObjectsRequest; -struct TakeHeapSnapshotRequest; -} // namespace heapProfiler - -namespace profiler { -struct PositionTickInfo; -struct Profile; -struct ProfileNode; -struct StartRequest; -struct StopRequest; -struct StopResponse; -} // namespace profiler - -/// RequestHandler handles requests via the visitor pattern. -struct RequestHandler { - virtual ~RequestHandler() = default; - - virtual void handle(const UnknownRequest &req) = 0; - virtual void handle(const debugger::DisableRequest &req) = 0; - virtual void handle(const debugger::EnableRequest &req) = 0; - virtual void handle(const debugger::EvaluateOnCallFrameRequest &req) = 0; - virtual void handle(const debugger::PauseRequest &req) = 0; - virtual void handle(const debugger::RemoveBreakpointRequest &req) = 0; - virtual void handle(const debugger::ResumeRequest &req) = 0; - virtual void handle(const debugger::SetBlackboxPatternsRequest &req) = 0; - virtual void handle(const debugger::SetBlackboxedRangesRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointByUrlRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointsActiveRequest &req) = 0; - virtual void handle( - const debugger::SetInstrumentationBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetPauseOnExceptionsRequest &req) = 0; - virtual void handle(const debugger::StepIntoRequest &req) = 0; - virtual void handle(const debugger::StepOutRequest &req) = 0; - virtual void handle(const debugger::StepOverRequest &req) = 0; - virtual void handle(const heapProfiler::CollectGarbageRequest &req) = 0; - virtual void handle(const heapProfiler::GetHeapObjectIdRequest &req) = 0; - virtual void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) = 0; - virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::StopSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0; - virtual void handle(const profiler::StartRequest &req) = 0; - virtual void handle(const profiler::StopRequest &req) = 0; - virtual void handle(const runtime::CallFunctionOnRequest &req) = 0; - virtual void handle(const runtime::CompileScriptRequest &req) = 0; - virtual void handle(const runtime::DisableRequest &req) = 0; - virtual void handle(const runtime::DiscardConsoleEntriesRequest &req) = 0; - virtual void handle(const runtime::EnableRequest &req) = 0; - virtual void handle(const runtime::EvaluateRequest &req) = 0; - virtual void handle(const runtime::GetHeapUsageRequest &req) = 0; - virtual void handle(const runtime::GetPropertiesRequest &req) = 0; - virtual void handle(const runtime::GlobalLexicalScopeNamesRequest &req) = 0; - virtual void handle(const runtime::ReleaseObjectRequest &req) = 0; - virtual void handle(const runtime::ReleaseObjectGroupRequest &req) = 0; - virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0; -}; - -/// NoopRequestHandler can be subclassed to only handle some requests. -struct NoopRequestHandler : public RequestHandler { - void handle(const UnknownRequest &req) override {} - void handle(const debugger::DisableRequest &req) override {} - void handle(const debugger::EnableRequest &req) override {} - void handle(const debugger::EvaluateOnCallFrameRequest &req) override {} - void handle(const debugger::PauseRequest &req) override {} - void handle(const debugger::RemoveBreakpointRequest &req) override {} - void handle(const debugger::ResumeRequest &req) override {} - void handle(const debugger::SetBlackboxPatternsRequest &req) override {} - void handle(const debugger::SetBlackboxedRangesRequest &req) override {} - void handle(const debugger::SetBreakpointRequest &req) override {} - void handle(const debugger::SetBreakpointByUrlRequest &req) override {} - void handle(const debugger::SetBreakpointsActiveRequest &req) override {} - void handle( - const debugger::SetInstrumentationBreakpointRequest &req) override {} - void handle(const debugger::SetPauseOnExceptionsRequest &req) override {} - void handle(const debugger::StepIntoRequest &req) override {} - void handle(const debugger::StepOutRequest &req) override {} - void handle(const debugger::StepOverRequest &req) override {} - void handle(const heapProfiler::CollectGarbageRequest &req) override {} - void handle(const heapProfiler::GetHeapObjectIdRequest &req) override {} - void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) override {} - void handle(const heapProfiler::StartSamplingRequest &req) override {} - void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::StopSamplingRequest &req) override {} - void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {} - void handle(const profiler::StartRequest &req) override {} - void handle(const profiler::StopRequest &req) override {} - void handle(const runtime::CallFunctionOnRequest &req) override {} - void handle(const runtime::CompileScriptRequest &req) override {} - void handle(const runtime::DisableRequest &req) override {} - void handle(const runtime::DiscardConsoleEntriesRequest &req) override {} - void handle(const runtime::EnableRequest &req) override {} - void handle(const runtime::EvaluateRequest &req) override {} - void handle(const runtime::GetHeapUsageRequest &req) override {} - void handle(const runtime::GetPropertiesRequest &req) override {} - void handle(const runtime::GlobalLexicalScopeNamesRequest &req) override {} - void handle(const runtime::ReleaseObjectRequest &req) override {} - void handle(const runtime::ReleaseObjectGroupRequest &req) override {} - void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {} -}; - -/// Types -struct debugger::Location : public Serializable { - Location() = default; - Location(Location &&) = default; - Location(const Location &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Location &operator=(const Location &) = delete; - Location &operator=(Location &&) = default; - - runtime::ScriptId scriptId{}; - long long lineNumber{}; - std::optional columnNumber; -}; - -struct runtime::PropertyPreview : public Serializable { - PropertyPreview() = default; - PropertyPreview(PropertyPreview &&) = default; - PropertyPreview(const PropertyPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyPreview &operator=(const PropertyPreview &) = delete; - PropertyPreview &operator=(PropertyPreview &&) = default; - - std::string name; - std::string type; - std::optional value; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - valuePreview{nullptr, deleter}; - std::optional subtype; -}; - -struct runtime::EntryPreview : public Serializable { - EntryPreview() = default; - EntryPreview(EntryPreview &&) = default; - EntryPreview(const EntryPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - EntryPreview &operator=(const EntryPreview &) = delete; - EntryPreview &operator=(EntryPreview &&) = default; - - std::unique_ptr< - runtime::ObjectPreview, - std::function> - key{nullptr, deleter}; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - value{nullptr, deleter}; -}; - -struct runtime::ObjectPreview : public Serializable { - ObjectPreview() = default; - ObjectPreview(ObjectPreview &&) = default; - ObjectPreview(const ObjectPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ObjectPreview &operator=(const ObjectPreview &) = delete; - ObjectPreview &operator=(ObjectPreview &&) = default; - - std::string type; - std::optional subtype; - std::optional description; - bool overflow{}; - std::vector properties; - std::optional> entries; -}; - -struct runtime::CustomPreview : public Serializable { - CustomPreview() = default; - CustomPreview(CustomPreview &&) = default; - CustomPreview(const CustomPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CustomPreview &operator=(const CustomPreview &) = delete; - CustomPreview &operator=(CustomPreview &&) = default; - - std::string header; - std::optional bodyGetterId; -}; - -struct runtime::RemoteObject : public Serializable { - RemoteObject() = default; - RemoteObject(RemoteObject &&) = default; - RemoteObject(const RemoteObject &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - RemoteObject &operator=(const RemoteObject &) = delete; - RemoteObject &operator=(RemoteObject &&) = default; - - std::string type; - std::optional subtype; - std::optional className; - std::optional value; - std::optional unserializableValue; - std::optional description; - std::optional objectId; - std::optional preview; - std::optional customPreview; -}; - -struct runtime::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - std::string functionName; - runtime::ScriptId scriptId{}; - std::string url; - long long lineNumber{}; - long long columnNumber{}; -}; - -struct runtime::StackTrace : public Serializable { - StackTrace() = default; - StackTrace(StackTrace &&) = default; - StackTrace(const StackTrace &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - StackTrace &operator=(const StackTrace &) = delete; - StackTrace &operator=(StackTrace &&) = default; - - std::optional description; - std::vector callFrames; - std::unique_ptr parent; -}; - -struct runtime::ExceptionDetails : public Serializable { - ExceptionDetails() = default; - ExceptionDetails(ExceptionDetails &&) = default; - ExceptionDetails(const ExceptionDetails &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExceptionDetails &operator=(const ExceptionDetails &) = delete; - ExceptionDetails &operator=(ExceptionDetails &&) = default; - - long long exceptionId{}; - std::string text; - long long lineNumber{}; - long long columnNumber{}; - std::optional scriptId; - std::optional url; - std::optional stackTrace; - std::optional exception; - std::optional executionContextId; -}; - -struct debugger::Scope : public Serializable { - Scope() = default; - Scope(Scope &&) = default; - Scope(const Scope &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Scope &operator=(const Scope &) = delete; - Scope &operator=(Scope &&) = default; - - std::string type; - runtime::RemoteObject object{}; - std::optional name; - std::optional startLocation; - std::optional endLocation; -}; - -struct debugger::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - debugger::CallFrameId callFrameId{}; - std::string functionName; - std::optional functionLocation; - debugger::Location location{}; - std::string url; - std::vector scopeChain; - runtime::RemoteObject thisObj{}; - std::optional returnValue; -}; - -struct debugger::ScriptPosition : public Serializable { - ScriptPosition() = default; - ScriptPosition(ScriptPosition &&) = default; - ScriptPosition(const ScriptPosition &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ScriptPosition &operator=(const ScriptPosition &) = delete; - ScriptPosition &operator=(ScriptPosition &&) = default; - - long long lineNumber{}; - long long columnNumber{}; -}; - -struct heapProfiler::SamplingHeapProfileNode : public Serializable { - SamplingHeapProfileNode() = default; - SamplingHeapProfileNode(SamplingHeapProfileNode &&) = default; - SamplingHeapProfileNode(const SamplingHeapProfileNode &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileNode &operator=(const SamplingHeapProfileNode &) = delete; - SamplingHeapProfileNode &operator=(SamplingHeapProfileNode &&) = default; - - runtime::CallFrame callFrame{}; - double selfSize{}; - long long id{}; - std::vector children; -}; - -struct heapProfiler::SamplingHeapProfileSample : public Serializable { - SamplingHeapProfileSample() = default; - SamplingHeapProfileSample(SamplingHeapProfileSample &&) = default; - SamplingHeapProfileSample(const SamplingHeapProfileSample &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileSample &operator=(const SamplingHeapProfileSample &) = - delete; - SamplingHeapProfileSample &operator=(SamplingHeapProfileSample &&) = default; - - double size{}; - long long nodeId{}; - double ordinal{}; -}; - -struct heapProfiler::SamplingHeapProfile : public Serializable { - SamplingHeapProfile() = default; - SamplingHeapProfile(SamplingHeapProfile &&) = default; - SamplingHeapProfile(const SamplingHeapProfile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfile &operator=(const SamplingHeapProfile &) = delete; - SamplingHeapProfile &operator=(SamplingHeapProfile &&) = default; - - heapProfiler::SamplingHeapProfileNode head{}; - std::vector samples; -}; - -struct profiler::PositionTickInfo : public Serializable { - PositionTickInfo() = default; - PositionTickInfo(PositionTickInfo &&) = default; - PositionTickInfo(const PositionTickInfo &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PositionTickInfo &operator=(const PositionTickInfo &) = delete; - PositionTickInfo &operator=(PositionTickInfo &&) = default; - - long long line{}; - long long ticks{}; -}; - -struct profiler::ProfileNode : public Serializable { - ProfileNode() = default; - ProfileNode(ProfileNode &&) = default; - ProfileNode(const ProfileNode &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ProfileNode &operator=(const ProfileNode &) = delete; - ProfileNode &operator=(ProfileNode &&) = default; - - long long id{}; - runtime::CallFrame callFrame{}; - std::optional hitCount; - std::optional> children; - std::optional deoptReason; - std::optional> positionTicks; -}; - -struct profiler::Profile : public Serializable { - Profile() = default; - Profile(Profile &&) = default; - Profile(const Profile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Profile &operator=(const Profile &) = delete; - Profile &operator=(Profile &&) = default; - - std::vector nodes; - double startTime{}; - double endTime{}; - std::optional> samples; - std::optional> timeDeltas; -}; - -struct runtime::CallArgument : public Serializable { - CallArgument() = default; - CallArgument(CallArgument &&) = default; - CallArgument(const CallArgument &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallArgument &operator=(const CallArgument &) = delete; - CallArgument &operator=(CallArgument &&) = default; - - std::optional value; - std::optional unserializableValue; - std::optional objectId; -}; - -struct runtime::ExecutionContextDescription : public Serializable { - ExecutionContextDescription() = default; - ExecutionContextDescription(ExecutionContextDescription &&) = default; - ExecutionContextDescription(const ExecutionContextDescription &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExecutionContextDescription &operator=(const ExecutionContextDescription &) = - delete; - ExecutionContextDescription &operator=(ExecutionContextDescription &&) = - default; - - runtime::ExecutionContextId id{}; - std::string origin; - std::string name; - std::optional auxData; -}; - -struct runtime::PropertyDescriptor : public Serializable { - PropertyDescriptor() = default; - PropertyDescriptor(PropertyDescriptor &&) = default; - PropertyDescriptor(const PropertyDescriptor &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyDescriptor &operator=(const PropertyDescriptor &) = delete; - PropertyDescriptor &operator=(PropertyDescriptor &&) = default; - - std::string name; - std::optional value; - std::optional writable; - std::optional get; - std::optional set; - bool configurable{}; - bool enumerable{}; - std::optional wasThrown; - std::optional isOwn; - std::optional symbol; -}; - -struct runtime::InternalPropertyDescriptor : public Serializable { - InternalPropertyDescriptor() = default; - InternalPropertyDescriptor(InternalPropertyDescriptor &&) = default; - InternalPropertyDescriptor(const InternalPropertyDescriptor &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - InternalPropertyDescriptor &operator=(const InternalPropertyDescriptor &) = - delete; - InternalPropertyDescriptor &operator=(InternalPropertyDescriptor &&) = - default; - - std::string name; - std::optional value; -}; - -/// Requests -struct UnknownRequest : public Request { - UnknownRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional params; -}; - -struct debugger::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EvaluateOnCallFrameRequest : public Request { - EvaluateOnCallFrameRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::CallFrameId callFrameId{}; - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional throwOnSideEffect; -}; - -struct debugger::PauseRequest : public Request { - PauseRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::RemoveBreakpointRequest : public Request { - RemoveBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct debugger::ResumeRequest : public Request { - ResumeRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional terminateOnResume; -}; - -struct debugger::SetBlackboxPatternsRequest : public Request { - SetBlackboxPatternsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::vector patterns; - std::optional skipAnonymous; -}; - -struct debugger::SetBlackboxedRangesRequest : public Request { - SetBlackboxedRangesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::ScriptId scriptId{}; - std::vector positions; -}; - -struct debugger::SetBreakpointRequest : public Request { - SetBreakpointRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::Location location{}; - std::optional condition; -}; - -struct debugger::SetBreakpointByUrlRequest : public Request { - SetBreakpointByUrlRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - long long lineNumber{}; - std::optional url; - std::optional urlRegex; - std::optional scriptHash; - std::optional columnNumber; - std::optional condition; -}; - -struct debugger::SetBreakpointsActiveRequest : public Request { - SetBreakpointsActiveRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - bool active{}; -}; - -struct debugger::SetInstrumentationBreakpointRequest : public Request { - SetInstrumentationBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string instrumentation; -}; - -struct debugger::SetPauseOnExceptionsRequest : public Request { - SetPauseOnExceptionsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string state; -}; - -struct debugger::StepIntoRequest : public Request { - StepIntoRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOutRequest : public Request { - StepOutRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOverRequest : public Request { - StepOverRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::CollectGarbageRequest : public Request { - CollectGarbageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::GetHeapObjectIdRequest : public Request { - GetHeapObjectIdRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdRequest : public Request { - GetObjectByHeapObjectIdRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - heapProfiler::HeapSnapshotObjectId objectId{}; - std::optional objectGroup; -}; - -struct heapProfiler::StartSamplingRequest : public Request { - StartSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional samplingInterval; - std::optional includeObjectsCollectedByMajorGC; - std::optional includeObjectsCollectedByMinorGC; -}; - -struct heapProfiler::StartTrackingHeapObjectsRequest : public Request { - StartTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional trackAllocations; -}; - -struct heapProfiler::StopSamplingRequest : public Request { - StopSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::StopTrackingHeapObjectsRequest : public Request { - StopTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct heapProfiler::TakeHeapSnapshotRequest : public Request { - TakeHeapSnapshotRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct profiler::StartRequest : public Request { - StartRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct profiler::StopRequest : public Request { - StopRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::CallFunctionOnRequest : public Request { - CallFunctionOnRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string functionDeclaration; - std::optional objectId; - std::optional> arguments; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; - std::optional executionContextId; - std::optional objectGroup; -}; - -struct runtime::CompileScriptRequest : public Request { - CompileScriptRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::string sourceURL; - bool persistScript{}; - std::optional executionContextId; -}; - -struct runtime::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::DiscardConsoleEntriesRequest : public Request { - DiscardConsoleEntriesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EvaluateRequest : public Request { - EvaluateRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional contextId; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; -}; - -struct runtime::GetHeapUsageRequest : public Request { - GetHeapUsageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::GetPropertiesRequest : public Request { - GetPropertiesRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; - std::optional ownProperties; - std::optional accessorPropertiesOnly; - std::optional generatePreview; -}; - -struct runtime::GlobalLexicalScopeNamesRequest : public Request { - GlobalLexicalScopeNamesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional executionContextId; -}; - -struct runtime::ReleaseObjectRequest : public Request { - ReleaseObjectRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; -}; - -struct runtime::ReleaseObjectGroupRequest : public Request { - ReleaseObjectGroupRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string objectGroup; -}; - -struct runtime::RunIfWaitingForDebuggerRequest : public Request { - RunIfWaitingForDebuggerRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -/// Responses -struct ErrorResponse : public Response { - ErrorResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long code; - std::string message; - std::optional data; -}; - -struct OkResponse : public Response { - OkResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::EvaluateOnCallFrameResponse : public Response { - EvaluateOnCallFrameResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct debugger::SetBreakpointResponse : public Response { - SetBreakpointResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location actualLocation{}; -}; - -struct debugger::SetBreakpointByUrlResponse : public Response { - SetBreakpointByUrlResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - std::vector locations; -}; - -struct debugger::SetInstrumentationBreakpointResponse : public Response { - SetInstrumentationBreakpointResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct heapProfiler::GetHeapObjectIdResponse : public Response { - GetHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::HeapSnapshotObjectId heapSnapshotObjectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdResponse : public Response { - GetObjectByHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; -}; - -struct heapProfiler::StopSamplingResponse : public Response { - StopSamplingResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::SamplingHeapProfile profile{}; -}; - -struct profiler::StopResponse : public Response { - StopResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - profiler::Profile profile{}; -}; - -struct runtime::CallFunctionOnResponse : public Response { - CallFunctionOnResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::CompileScriptResponse : public Response { - CompileScriptResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::optional scriptId; - std::optional exceptionDetails; -}; - -struct runtime::EvaluateResponse : public Response { - EvaluateResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::GetHeapUsageResponse : public Response { - GetHeapUsageResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - double usedSize{}; - double totalSize{}; -}; - -struct runtime::GetPropertiesResponse : public Response { - GetPropertiesResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector result; - std::optional> - internalProperties; - std::optional exceptionDetails; -}; - -struct runtime::GlobalLexicalScopeNamesResponse : public Response { - GlobalLexicalScopeNamesResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector names; -}; - -/// Notifications -struct debugger::BreakpointResolvedNotification : public Notification { - BreakpointResolvedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location location{}; -}; - -struct debugger::PausedNotification : public Notification { - PausedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector callFrames; - std::string reason; - std::optional data; - std::optional> hitBreakpoints; - std::optional asyncStackTrace; -}; - -struct debugger::ResumedNotification : public Notification { - ResumedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::ScriptParsedNotification : public Notification { - ScriptParsedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ScriptId scriptId{}; - std::string url; - long long startLine{}; - long long startColumn{}; - long long endLine{}; - long long endColumn{}; - runtime::ExecutionContextId executionContextId{}; - std::string hash; - std::optional executionContextAuxData; - std::optional sourceMapURL; - std::optional hasSourceURL; - std::optional isModule; - std::optional length; - std::optional scriptLanguage; -}; - -struct heapProfiler::AddHeapSnapshotChunkNotification : public Notification { - AddHeapSnapshotChunkNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string chunk; -}; - -struct heapProfiler::HeapStatsUpdateNotification : public Notification { - HeapStatsUpdateNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector statsUpdate; -}; - -struct heapProfiler::LastSeenObjectIdNotification : public Notification { - LastSeenObjectIdNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long lastSeenObjectId{}; - double timestamp{}; -}; - -struct heapProfiler::ReportHeapSnapshotProgressNotification - : public Notification { - ReportHeapSnapshotProgressNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long done{}; - long long total{}; - std::optional finished; -}; - -struct runtime::ConsoleAPICalledNotification : public Notification { - ConsoleAPICalledNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string type; - std::vector args; - runtime::ExecutionContextId executionContextId{}; - runtime::Timestamp timestamp{}; - std::optional stackTrace; -}; - -struct runtime::ExecutionContextCreatedNotification : public Notification { - ExecutionContextCreatedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ExecutionContextDescription context{}; -}; - -struct runtime::InspectRequestedNotification : public Notification { - InspectRequestedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject object{}; - JSONBlob hints; - std::optional executionContextId; -}; - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/cdp/MessageTypesInlines.h b/NativeScript/napi/hermes/include/hermes/cdp/MessageTypesInlines.h deleted file mode 100644 index fe765f935..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/MessageTypesInlines.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_MESSAGETYPESINLINES_H -#define HERMES_CDP_MESSAGETYPESINLINES_H - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { - -template -using optional = std::optional; - -template -struct is_vector : std::false_type {}; - -template -struct is_vector> : std::true_type {}; - -/// valueFromJson - -/// Convert JSONValue to a Serializable type. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return T::tryMake(res); -} - -/// Convert JSONValue to a bool. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a long long. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a double. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a string. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->c_str()); -} - -/// Convert JSONValue to a vector. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *items) { - auto *arr = llvh::dyn_cast(items); - std::unique_ptr result = std::make_unique(); - result->reserve(arr->size()); - for (const auto &item : *arr) { - auto itemResult = valueFromJson(item); - if (!itemResult) { - return nullptr; - } - result->push_back(std::move(*itemResult)); - } - return result; -} - -/// Convert JSONValue to a JSONObject. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - auto *res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res); -} - -/// Pass through JSONValues. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - return std::make_unique(v); -} - -/// assign(lhs, obj, key) is a wrapper for: -/// -/// lhs = obj[key] -/// -/// It mainly exists so that we can choose the right version of valueFromJson -/// based on the type of lhs. - -template -bool assign(T &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v == nullptr) { - return false; - } - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; -} - -template -bool assign(optional &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign(std::unique_ptr &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign( - std::unique_ptr> &lhs, - const JSONObject *obj, - const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -/// valueToJson - -inline JSONValue *valueToJson(const Serializable &value, JSONFactory &factory) { - return value.toJsonVal(factory); -} - -// Convert a bool to JSONValue. -inline JSONValue *valueToJson(bool b, JSONFactory &factory) { - return factory.getBoolean(b); -} - -// Convert a long long to JSONValue. -inline JSONValue *valueToJson(long long num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a double to JSONValue. -inline JSONValue *valueToJson(double num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a string to JSONValue. -inline JSONValue *valueToJson(const std::string &str, JSONFactory &factory) { - return factory.getString(str); -} - -// Convert a vector to JSONValue. -template -JSONValue *valueToJson(const std::vector &items, JSONFactory &factory) { - llvh::SmallVector storage; - for (const auto &item : items) { - storage.push_back(valueToJson(item, factory)); - } - return factory.newArray(storage.size(), storage.begin(), storage.end()); -} - -// Cast a JSONObject to JSONValue. -inline JSONValue *valueToJson(JSONObject *obj, JSONFactory &factory) { - return llvh::cast(obj); -} - -// Pass through JSONValues. -inline JSONValue *valueToJson(JSONValue *v, JSONFactory &factory) { - return v; -} - -/// put(obj, key, value) is meant to be a wrapper for: -/// obj[key] = valueToJson(value); -/// However, JSONObjects are immutable, so we represent a 'put' operation as -/// pushing a new element onto a vector of JSONFactory::Props. - -using Properties = llvh::SmallVectorImpl; - -template -void put( - Properties &props, - const std::string &key, - const V &value, - JSONFactory &factory) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(value, factory); - props.push_back({jsStr, jsVal}); -} - -template -void put( - Properties &props, - const std::string &key, - const optional &optValue, - JSONFactory &factory) { - if (optValue.has_value()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(optValue.value(), factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr> &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void deleter(T *p) { - delete p; -} - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_MESSAGETYPESINLINES_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/ProfilerDomainAgent.h b/NativeScript/napi/hermes/include/hermes/cdp/ProfilerDomainAgent.h deleted file mode 100644 index 6c62b9c8a..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/ProfilerDomainAgent.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_PROFILERDOMAINAGENT_H -#define HERMES_CDP_PROFILERDOMAINAGENT_H - -#include -#include - -#include "DomainAgent.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Handler for the "Profiler" domain of CDP. All methods expect to be invoked -/// with exclusive access to the runtime. -class ProfilerDomainAgent : public DomainAgent { - public: - ProfilerDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable); - ~ProfilerDomainAgent() = default; - - void start(const m::profiler::StartRequest &req); - void stop(const m::profiler::StopRequest &req); - - private: - HermesRuntime &runtime_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_PROFILERDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/RemoteObjectConverters.h b/NativeScript/napi/hermes/include/hermes/cdp/RemoteObjectConverters.h deleted file mode 100644 index ae688884e..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/RemoteObjectConverters.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_REMOTEOBJECTCONVERTERS_H -#define HERMES_CDP_REMOTEOBJECTCONVERTERS_H - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -struct ObjectSerializationOptions { - bool returnByValue = false; - bool generatePreview = false; -}; - -namespace message { - -namespace debugger { - -CallFrame makeCallFrame( - uint32_t callFrameIndex, - const facebook::hermes::debugger::CallFrameInfo &callFrameInfo, - const facebook::hermes::debugger::LexicalInfo &lexicalInfo, - cdp::RemoteObjectsTable &objTable, - jsi::Runtime &runtime, - const facebook::hermes::debugger::ProgramState &state); - -std::vector makeCallFrames( - const facebook::hermes::debugger::ProgramState &state, - cdp::RemoteObjectsTable &objTable, - jsi::Runtime &runtime); - -} // namespace debugger - -namespace runtime { - -RemoteObject makeRemoteObject( - facebook::jsi::Runtime &runtime, - const facebook::jsi::Value &value, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup, - const cdp::ObjectSerializationOptions &serializationOptions); - -RemoteObject makeRemoteObjectForError( - facebook::jsi::Runtime &runtime, - const facebook::jsi::Value &value, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup); - -ExceptionDetails makeExceptionDetails( - jsi::Runtime &runtime, - const jsi::JSError &error, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup); - -ExceptionDetails makeExceptionDetails(const jsi::JSIException &err); - -ExceptionDetails makeExceptionDetails( - facebook::jsi::Runtime &runtime, - const facebook::hermes::debugger::EvalResult &result, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup); - -} // namespace runtime - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_REMOTEOBJECTCONVERTERS_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/RemoteObjectsTable.h b/NativeScript/napi/hermes/include/hermes/cdp/RemoteObjectsTable.h deleted file mode 100644 index 1b8fff5a2..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/RemoteObjectsTable.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_REMOTEOBJECTSTABLE_H -#define HERMES_CDP_REMOTEOBJECTSTABLE_H - -#include -#include -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Well-known object group names - -/** - * Objects created as a result of the Debugger.paused notification (e.g. scope - * objects) are placed in the "backtrace" object group. This object group is - * cleared when the VM resumes. - */ -extern const char *BacktraceObjectGroup; - -/** - * Objects that are created as a result of a console evaluation are placed in - * the "console" object group. This object group is cleared when the client - * clears the console. - */ -extern const char *ConsoleObjectGroup; - -/** - * RemoteObjectsTable manages the mapping of string object ids to scope metadata - * or actual JSI objects. The debugger vends these ids to the client so that the - * client can perform operations on the ids (e.g. enumerate properties on the - * object backed by the id). See Runtime.RemoteObjectId in the CDT docs for - * more details. - * - * Note that object handles are not ref-counted. Suppose an object foo is mapped - * to object id "objId" and is also in object group "objGroup". Then *either* of - * `releaseObject("objId")` or `releaseObjectGroup("objGroup")` will remove foo - * from the table. This matches the behavior of object groups in CDT. - */ -class RemoteObjectsTable { - public: - RemoteObjectsTable(); - ~RemoteObjectsTable(); - - RemoteObjectsTable(const RemoteObjectsTable &) = delete; - RemoteObjectsTable &operator=(const RemoteObjectsTable &) = delete; - - /** - * addScope adds the provided (frameIndex, scopeIndex) mapping to the table. - * If objectGroup is non-empty, then the scope object is also added to that - * object group for releasing via releaseObjectGroup. Returns an object id. - */ - std::string addScope( - std::pair frameAndScopeIndex, - const std::string &objectGroup); - - /** - * addValue adds the JSI value to the table. If objectGroup is non-empty, then - * the scope object is also added to that object group for releasing via - * releaseObjectGroup. Returns an object id. - */ - std::string addValue( - ::facebook::jsi::Value value, - const std::string &objectGroup); - - /// /param objId The object ID. - /// /return true if object ID represents a scope in the scope chain of a call - /// frame. - bool isScopeId(const std::string &objId) const; - - /** - * Retrieves the (frameIndex, scopeIndex) associated with this object id, or - * nullptr if no mapping exists. The pointer stays valid as long as you only - * call const methods on this class. - */ - const std::pair *getScope(const std::string &objId) const; - - /** - * Retrieves the JSI value associated with this object id, or nullptr if no - * mapping exists. The pointer stays valid as long as you only call const - * methods on this class. - */ - const ::facebook::jsi::Value *getValue(const std::string &objId) const; - - /** - * Retrieves the object group that this object id is in, or empty string if it - * isn't in an object group. The returned pointer is only guaranteed to be - * valid until the next call to this class. - */ - std::string getObjectGroup(const std::string &objId) const; - - /** - * Removes the scope or JSI value backed by the provided object ID from the - * table. \return true if the object was removed, false if it was not found. - */ - bool releaseObject(const std::string &objId); - - /** - * Removes all objects that are part of the provided object group from the - * table. - */ - void releaseObjectGroup(const std::string &objectGroup); - - private: - bool releaseObject(int64_t id); - - int64_t scopeId_ = -1; - int64_t valueId_ = 1; - - std::unordered_map> scopes_; - std::unordered_map values_; - std::unordered_map idToGroup_; - std::unordered_map> groupToIds_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_REMOTEOBJECTSTABLE_H diff --git a/NativeScript/napi/hermes/include/hermes/cdp/RuntimeDomainAgent.h b/NativeScript/napi/hermes/include/hermes/cdp/RuntimeDomainAgent.h deleted file mode 100644 index 9c8142aab..000000000 --- a/NativeScript/napi/hermes/include/hermes/cdp/RuntimeDomainAgent.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_RUNTIMEDOMAINAGENT_H -#define HERMES_CDP_RUNTIMEDOMAINAGENT_H - -#include - -#include "CDPDebugAPI.h" -#include "DomainAgent.h" -#include "RemoteObjectConverters.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -namespace m = ::facebook::hermes::cdp::message; - -/// Handler for the "Runtime" domain of CDP. Accepts CDP requests belonging to -/// the "Runtime" domain from the debug client. Produces CDP responses and -/// events belonging to the "Runtime" domain. All methods expect to be invoked -/// with exclusive access to the runtime. -class RuntimeDomainAgent : public DomainAgent { - public: - RuntimeDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - debugger::AsyncDebuggerAPI &asyncDebuggerAPI, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable, - ConsoleMessageStorage &consoleMessageStorage, - ConsoleMessageDispatcher &consoleMessageDispatcher); - ~RuntimeDomainAgent(); - - /// Enables the Runtime domain without processing CDP message or sending a CDP - /// response. It will still send CDP notifications if needed. - void enable(); - /// Handles Runtime.enable request - /// @cdp Runtime.enable If domain is already enabled, will return success. - void enable(const m::runtime::EnableRequest &req); - /// @cdp Runtime.discardConsoleEntries - void discardConsoleEntries( - const m::runtime::DiscardConsoleEntriesRequest &req); - /// Handles Runtime.disable request - /// @cdp Runtime.disable If domain is already disabled, will return success. - void disable(const m::runtime::DisableRequest &req); - /// Handles Runtime.getHeapUsage request - /// @cdp Runtime.getHeapUsage Allowed even if domain is not enabled. - void getHeapUsage(const m::runtime::GetHeapUsageRequest &req); - /// Handles Runtime.globalLexicalScopeNames request - /// @cdp Runtime.globalLexicalScopeNames Allowed even if domain is not - /// enabled. - void globalLexicalScopeNames( - const m::runtime::GlobalLexicalScopeNamesRequest &req); - /// Handles Runtime.compileScript request - /// @cdp Runtime.compileScript Not allowed if domain is not enabled. - void compileScript(const m::runtime::CompileScriptRequest &req); - /// Handles Runtime.getProperties request - /// @cdp Runtime.getProperties Allowed even if domain is not enabled. - void getProperties(const m::runtime::GetPropertiesRequest &req); - /// Handles Runtime.evaluate request - /// @cdp Runtime.evaluate Allowed even if domain is not enabled. - void evaluate(const m::runtime::EvaluateRequest &req); - /// Handles Runtime.callFunctionOn request - /// @cdp Runtime.callFunctionOn Allowed even if domain is not enabled. - void callFunctionOn(const m::runtime::CallFunctionOnRequest &req); - /// Dispatches a Runtime.consoleAPICalled notification - void consoleAPICalled(const ConsoleMessage &message, bool isBuffered); - /// Handles Runtime.releaseObject request - /// @cdp Runtime.releaseObject Allowed even if domain is not enabled. - void releaseObject(const m::runtime::ReleaseObjectRequest &req); - /// Handles Runtime.releaseObjectGroup request - /// @cdp Runtime.releaseObjectGroup Allowed even if domain is not enabled. - void releaseObjectGroup(const m::runtime::ReleaseObjectGroupRequest &req); - - private: - struct Helpers { - jsi::Function objectGetOwnPropertySymbols; - jsi::Function objectGetOwnPropertyNames; - jsi::Function objectGetOwnPropertyDescriptor; - jsi::Function objectGetPrototypeOf; - - explicit Helpers(jsi::Runtime &runtime); - }; - - bool checkRuntimeEnabled(const m::Request &req); - - /// Ensure the provided \p executionContextId matches the one - /// indicated via the constructor. Returns true if they match. - /// Sends an error message with the specified \p commandId - /// and returns false otherwise. - bool validateExecutionContextId( - m::runtime::ExecutionContextId executionContextId, - long long commandId); - - std::optional> makePropsFromScope( - std::pair frameAndScopeIndex, - const std::string &objectGroup, - const debugger::ProgramState &state, - const ObjectSerializationOptions &serializationOptions); - std::vector makePropsFromValue( - const jsi::Value &value, - const std::string &objectGroup, - bool onlyOwnProperties, - bool accessorPropertiesOnly, - const ObjectSerializationOptions &serializationOptions); - std::vector - makeInternalPropsFromValue( - const jsi::Value &value, - const std::string &objectGroup, - const ObjectSerializationOptions &serializationOptions); - - HermesRuntime &runtime_; - debugger::AsyncDebuggerAPI &asyncDebuggerAPI_; - ConsoleMessageStorage &consoleMessageStorage_; - ConsoleMessageDispatcher &consoleMessageDispatcher_; - - /// Whether Runtime.enable was received and wasn't disabled by receiving - /// Runtime.disable - bool enabled_; - - // preparedScripts_ stores user-entered scripts that have been prepared for - // execution, and may be invoked by a later command. - std::vector> preparedScripts_; - - /// Console message subscription token, used to unsubscribe during shutdown. - ConsoleMessageRegistration consoleMessageRegistration_; - - /// Cached helper JS functions used by agent methods. - const Helpers helpers_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_RUNTIMEDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include/hermes/hermes.h b/NativeScript/napi/hermes/include/hermes/hermes.h deleted file mode 100644 index e34009ecd..000000000 --- a/NativeScript/napi/hermes/include/hermes/hermes.h +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_HERMES_H -#define HERMES_HERMES_H - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -struct HermesTestHelper; -struct SHUnit; -struct SHRuntime; - -namespace hermes { -namespace vm { -class GCExecTrace; -class Runtime; -} // namespace vm -} // namespace hermes - -namespace facebook { -namespace jsi { - -class ThreadSafeRuntime; - -} - -namespace hermes { - -namespace debugger { -class Debugger; -} - -class HermesRuntimeImpl; - -/// Represents a Hermes JS runtime. -class HERMES_EXPORT HermesRuntime : public jsi::Runtime { - public: - static bool isHermesBytecode(const uint8_t *data, size_t len); - // Returns the supported bytecode version. - static uint32_t getBytecodeVersion(); - // (EXPERIMENTAL) Issues madvise calls for portions of the given - // bytecode file that will likely be used when loading the bytecode - // file and running its global function. - static void prefetchHermesBytecode(const uint8_t *data, size_t len); - // Returns whether the data is valid HBC with more extensive checks than - // isHermesBytecode and returns why it isn't in errorMessage (if nonnull) - // if not. - static bool hermesBytecodeSanityCheck( - const uint8_t *data, - size_t len, - std::string *errorMessage = nullptr); - static void setFatalHandler(void (*handler)(const std::string &)); - - // Assuming that \p data is valid HBC bytecode data, returns a pointer to the - // first element of the epilogue, data append to the end of the bytecode - // stream. Return pair contain ptr to data and header. - static std::pair getBytecodeEpilogue( - const uint8_t *data, - size_t len); - - /// Enable sampling profiler. - /// Starts a separate thread that polls VM state with \p meanHzFreq frequency. - /// Any subsequent call to \c enableSamplingProfiler() is ignored until - /// next call to \c disableSamplingProfiler() - static void enableSamplingProfiler(double meanHzFreq = 100); - - /// Disable the sampling profiler - static void disableSamplingProfiler(); - - /// Dump sampled stack trace to the given file name. - static void dumpSampledTraceToFile(const std::string &fileName); - - /// Dump sampled stack trace to the given stream. - static void dumpSampledTraceToStream(std::ostream &stream); - - /// Return the executed JavaScript function info. - /// This information holds the segmentID, Virtualoffset and sourceURL. - /// This information is needed specifically to be able to symbolicate non-CJS - /// bundles correctly. This API will be simplified later to simply return a - /// segmentID and virtualOffset, when we are able to only support CJS bundles. - static std::unordered_map> - getExecutedFunctions(); - - /// \return whether code coverage profiler is enabled or not. - static bool isCodeCoverageProfilerEnabled(); - - /// Enable code coverage profiler. - static void enableCodeCoverageProfiler(); - - /// Disable code coverage profiler. - static void disableCodeCoverageProfiler(); - - /// Define a destructor to serve as the key function. - ~HermesRuntime() override; - - /// Serialize the sampled stack to the format expected by DevTools' - /// Profiler.stop return type. - virtual void sampledTraceToStreamInDevToolsFormat(std::ostream &stream) = 0; - - /// Dump sampled stack trace for a given runtime to a data structure that can - /// be used by third parties. - virtual sampling_profiler::Profile dumpSampledTraceToProfile() = 0; - - // The base class declares most of the interesting methods. This - // just declares new methods which are specific to HermesRuntime. - // The actual implementations of the pure virtual methods are - // provided by a class internal to the .cpp file, which is created - // by the factory. - - /// Load a new segment into the Runtime. - /// The \param context must be a valid RequireContext retrieved from JS - /// using `require.context`. - virtual void loadSegment( - std::unique_ptr buffer, - const jsi::Value &context) = 0; - - /// Gets a guaranteed unique id for an Object (or, respectively, String - /// or PropNameId), which is assigned at allocation time and is - /// static throughout that object's (or string's, or PropNameID's) - /// lifetime. - virtual uint64_t getUniqueID(const jsi::Object &o) const = 0; - virtual uint64_t getUniqueID(const jsi::BigInt &s) const = 0; - virtual uint64_t getUniqueID(const jsi::String &s) const = 0; - virtual uint64_t getUniqueID(const jsi::PropNameID &pni) const = 0; - virtual uint64_t getUniqueID(const jsi::Symbol &sym) const = 0; - - /// Same as the other \c getUniqueID, except it can return 0 for some values. - /// 0 means there is no ID associated with the value. - virtual uint64_t getUniqueID(const jsi::Value &val) const = 0; - - /// From an ID retrieved from \p getUniqueID, go back to the object. - /// NOTE: This is much slower in general than the reverse operation, and takes - /// up more memory. Don't use this unless it's absolutely necessary. - /// \return a jsi::Object if a matching object is found, else returns null. - virtual jsi::Value getObjectForID(uint64_t id) = 0; - - /// Get a structure representing the execution history (currently just of - /// GC, but will be generalized as necessary), to aid in debugging - /// non-deterministic execution. - virtual const ::hermes::vm::GCExecTrace &getGCExecTrace() const = 0; - - /// Get IO tracking (aka HBC page access) info as a JSON string. - /// See hermes::vm::Runtime::getIOTrackingInfoJSON() for conditions - /// needed for there to be useful output. - virtual std::string getIOTrackingInfoJSON() = 0; - -#ifdef HERMESVM_PROFILER_BB - /// Write the trace to the given stream. - virtual void dumpBasicBlockProfileTrace(std::ostream &os) const = 0; -#endif - -#ifdef HERMESVM_PROFILER_OPCODE - /// Write the opcode stats to the given stream. - virtual void dumpOpcodeStats(std::ostream &os) const = 0; -#endif - - /// \return a reference to the Debugger for this Runtime. - virtual debugger::Debugger &getDebugger() = 0; - -#ifdef HERMES_ENABLE_DEBUGGER - - struct DebugFlags { - // Looking for the .lazy flag? It's no longer necessary. - // Source is evaluated lazily by default. See - // RuntimeConfig::CompilationMode. - }; - - /// Evaluate the given code in an unoptimized form, - /// used for debugging. - virtual void debugJavaScript( - const std::string &src, - const std::string &sourceURL, - const DebugFlags &debugFlags) = 0; -#endif - - /// Register this runtime and thread for sampling profiler. Before using the - /// runtime on another thread, invoke this function again from the new thread - /// to make the sampling profiler target the new thread (and forget the old - /// thread). - virtual void registerForProfiling() = 0; - /// Unregister this runtime for sampling profiler. - virtual void unregisterForProfiling() = 0; - - /// Define methods to interrupt JS execution and set time limits. - /// All JS compiled to bytecode via prepareJS, or evaluateJS, will support - /// interruption and time limit monitoring if the runtime is configured with - /// AsyncBreakCheckInEval. If JS prepared in other ways is executed, care must - /// be taken to ensure that it is compiled in a mode that supports it (i.e., - /// the emitted code contains async break checks). - - /// Asynchronously terminates the current execution. This can be called on - /// any thread. - virtual void asyncTriggerTimeout() = 0; - - /// Register this runtime for execution time limit monitoring, with a time - /// limit of \p timeoutInMs milliseconds. - /// See compilation notes above. - virtual void watchTimeLimit(uint32_t timeoutInMs) = 0; - /// Unregister this runtime for execution time limit monitoring. - virtual void unwatchTimeLimit() = 0; - - /// Same as \c evaluate JavaScript but with a source map, which will be - /// applied to exception traces and debug information. - /// - /// This is an experimental Hermes-specific API. In the future it may be - /// renamed, moved or combined with another API, but the provided - /// functionality will continue to be available in some form. - virtual jsi::Value evaluateJavaScriptWithSourceMap( - const std::shared_ptr &buffer, - const std::shared_ptr &sourceMapBuf, - const std::string &sourceURL) = 0; - - /// Provided for compatibility with Static Hermes, but should not be called. - virtual jsi::Value evaluateSHUnit(SHUnit *(*shUnitCreator)()) = 0; - virtual SHRuntime *getSHRuntime() noexcept = 0; - - /// Returns the underlying low level Hermes VM runtime instance. - /// This function is considered unsafe and unstable. - /// Direct use of a vm::Runtime should be avoided as the lower level APIs are - /// unsafe and they can change without notice. - virtual ::hermes::vm::Runtime *getVMRuntimeUnsafe() const = 0; - - private: - // Only HermesRuntimeImpl can subclass this. - HermesRuntime() = default; - friend class HermesRuntimeImpl; - - friend struct ::HermesTestHelper; - virtual size_t rootsListLengthForTests() const = 0; - - // Do not add any members here. This ensures that there are no - // object size inconsistencies. All data should be in the impl - // class in the .cpp file. -}; - -/// Return a RuntimeConfig that is more suited for running untrusted JS than -/// the default config. Disables some language features and may trade off some -/// performance for security. -/// -/// Can serve as a starting point with tweaks to re-enable needed features: -/// auto conf = hardenedHermesRuntimeConfig().rebuild(); -/// conf.withArrayBuffer(true); -/// ... -/// auto runtime = makeHermesRuntime(conf.build()); -HERMES_EXPORT ::hermes::vm::RuntimeConfig hardenedHermesRuntimeConfig(); - -HERMES_EXPORT std::unique_ptr makeHermesRuntime( - const ::hermes::vm::RuntimeConfig &runtimeConfig = - ::hermes::vm::RuntimeConfig()); -HERMES_EXPORT std::unique_ptr -makeThreadSafeHermesRuntime( - const ::hermes::vm::RuntimeConfig &runtimeConfig = - ::hermes::vm::RuntimeConfig()); -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include/hermes/hermes_tracing.h b/NativeScript/napi/hermes/include/hermes/hermes_tracing.h deleted file mode 100644 index 470e82d9c..000000000 --- a/NativeScript/napi/hermes/include/hermes/hermes_tracing.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_HERMES_TRACING_H -#define HERMES_HERMES_TRACING_H - -#include - -namespace llvh { -class raw_ostream; -} // namespace llvh - -namespace facebook { -namespace hermes { - -/// Creates and returns a tracing runtime if \p runtimeConfig.SynthTraceMode is -/// either SynthTraceMode::Tracing or SynthTraceMode::TracingAndReplaying. -/// Otherwise, returns the passed \n hermesRuntime as is. -/// The trace will be written to \p traceScratchPath incrementally. -/// On completion, the file will be renamed to \p traceResultPath, and -/// \p traceCompletionCallback (for post-processing) will be invoked. -/// Completion can be triggered implicitly by crash (if crash manager is -/// provided) or explicitly by invocation of flush. -/// If the runtime is destructed without triggering trace completion, -/// the file at \p traceScratchPath will be deleted. -/// The return value of \p traceCompletionCallback indicates whether the -/// invocation completed successfully. If \p traceCompletionCallback is null, it -/// also assumes as if the callback is successful. -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - const std::string &traceScratchPath, - const std::string &traceResultPath, - std::function traceCompletionCallback); - -/// Creates and returns a tracing runtime that wrapps the passed -/// \p hermesRuntime. This API is mainly for Synth Trace replay (and tracing), -/// and for testing. -/// \p traceStream the stream to write trace to. -/// \p forReplay indicates whether the runtime is being used in trace replay and -/// tracing. -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - std::unique_ptr traceStream, - bool forReplay = false); - -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include/hermes/inspector/RuntimeAdapter.h b/NativeScript/napi/hermes/include/hermes/inspector/RuntimeAdapter.h deleted file mode 100644 index 64396f2cc..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/RuntimeAdapter.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include - -#ifndef INSPECTOR_EXPORT -#ifdef _MSC_VER -#ifdef CREATE_SHARED_LIBRARY -#define INSPECTOR_EXPORT __declspec(dllexport) -#else -#define INSPECTOR_EXPORT -#endif // CREATE_SHARED_LIBRARY -#else // _MSC_VER -#define INSPECTOR_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(INSPECTOR_EXPORT) - -namespace facebook { -namespace hermes { -namespace inspector_modern { - -/** - * RuntimeAdapter encapsulates a HermesRuntime object. The underlying Hermes - * runtime object should stay alive for at least as long as the RuntimeAdapter - * is alive. - */ -class INSPECTOR_EXPORT RuntimeAdapter { - public: - virtual ~RuntimeAdapter() = 0; - - /// getRuntime should return the runtime encapsulated by this adapter. The - /// CDP Handler will only invoke this function from the runtime thread. - virtual HermesRuntime &getRuntime() = 0; - - /// \p tickleJs is a method that subclasses can choose to override to make - /// the inspector more responsive. If overridden, it should call the - /// \p __tickleJs JavaScript function. Calling JavaScript functions must be - /// done on the runtime thread, and \p tickleJs() may be invoked from an - /// arbitrary thread. Thus, the call to \p __tickleJs should occur with - /// appropriate locking (e.g. via a thread-safe runtime instance, or by - /// enqueuing the call on to a dedicated JS thread). - /// - /// This makes the inspector more responsive because it gives the inspector - /// the ability to force the process to enter the Hermes interpreter loop - /// soon. This is important because the inspector can only do a number of - /// important operations (like manipulating breakpoints) within the context of - /// a Hermes interperter loop. - /// - /// The default implementation does nothing. - virtual void tickleJs(); -}; - -/** - * SharedRuntimeAdapter is a simple implementation of RuntimeAdapter that - * uses shared_ptr to hold on to the runtime. It's generally only used in tests, - * since it does not implement tickleJs. - */ -class INSPECTOR_EXPORT SharedRuntimeAdapter : public RuntimeAdapter { - public: - SharedRuntimeAdapter(std::shared_ptr runtime); - ~SharedRuntimeAdapter() override; - - HermesRuntime &getRuntime() override; - - private: - std::shared_ptr runtime_; -}; - -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/CDPHandler.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/CDPHandler.h deleted file mode 100644 index 01fe26eb4..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/CDPHandler.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// using include guards instead of #pragma once due to compile issues -// with MSVC and BUCK -#ifndef HERMES_INSPECTOR_CDPHANDLER_H -#define HERMES_INSPECTOR_CDPHANDLER_H - -#include -#include -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -using CDPMessageCallbackFunction = std::function; -using OnUnregisterFunction = std::function; - -class CDPHandlerImpl; - -struct State; - -/// Utility struct to configure the initial state of the CDP session. -struct INSPECTOR_EXPORT CDPHandlerSessionConfig { - bool isRuntimeDomainEnabled{false}; -}; - -/// Configuration for the execution context managed by the CDPHandler. -struct INSPECTOR_EXPORT CDPHandlerExecutionContextDescription { - int32_t id{}; - std::string origin; - std::string name; - std::optional auxData; - bool shouldSendNotifications{}; -}; - -/// CDPHandler processes CDP messages between the client and the debugger. -/// It performs no networking or connection logic itself. -/// The CDP Handler is invoked from multiple threads. The locking strategy is -/// to acquire the lock at each entry point into the class, and hold it until -/// the entry function has returned. In practice, these functions fall into 2 -/// categories: public functions invoked by the creator of this instance, and -/// callbacks invoked by the runtime to report events. -/// Once the lock is held, most members are safe to use from any thread, with -/// the notable exception of the runtime (and debugger retrieved from the -/// runtime). Most runtime methods must only be invoked when running on the -/// runtime thread, which occurs in the CDP Handler constructor/destructor, and -/// callbacks from the runtime thread (e.g. host functions, instrumentation -/// callbacks, and pause callback). -class INSPECTOR_EXPORT CDPHandler { - /// Hide the constructor so users can only construct via static create - /// methods. - CDPHandler( - std::unique_ptr adapter, - const std::string &title, - bool waitForDebugger, - bool processConsoleAPI, - std::shared_ptr state, - const CDPHandlerSessionConfig &sessionConfig, - std::optional - executionContextDescription); - - public: - /// Creating a CDPHandler enables the debugger on the provided runtime. This - /// should generally called before you start running any JS in the runtime. - /// This should also be called on the runtime thread, as methods are invoked - /// on the given \p adapter. - static std::shared_ptr create( - std::unique_ptr adapter, - bool waitForDebugger = false, - bool processConsoleAPI = true, - std::shared_ptr state = nullptr, - const CDPHandlerSessionConfig &sessionConfig = {}, - std::optional - executionContextDescription = std::nullopt); - /// Temporarily kept to allow React Native build to still work - static std::shared_ptr create( - std::unique_ptr adapter, - const std::string &title, - bool waitForDebugger = false, - bool processConsoleAPI = true, - std::shared_ptr state = nullptr, - const CDPHandlerSessionConfig &sessionConfig = {}, - std::optional - executionContextDescription = std::nullopt); - ~CDPHandler(); - - /// getTitle returns the name of the friendly name of the runtime that's shown - /// to users in the CDP frontend (e.g. Chrome DevTools). - std::string getTitle() const; - - /// Provide a callback to receive replies and notifications from the debugger, - /// and optionally provide a function to be called during - /// unregisterCallbacks(). - /// \param msgCallback Function to receive replies and notifications from the - /// debugger - /// \param onDisconnect Function that will be invoked upon calling - /// unregisterCallbacks - /// \return true if there wasn't a previously registered callback - bool registerCallbacks( - CDPMessageCallbackFunction msgCallback, - OnUnregisterFunction onUnregister); - - /// Unregister any previously registered callbacks. - /// \return true if there were previously registered callbacks - bool unregisterCallbacks(); - - /// Process a JSON-encoded Chrome DevTools Protocol request. - void handle(std::string str); - - /// Extract state to be persisted across reloads. - std::unique_ptr getState(); - - private: - std::shared_ptr impl_; - const std::string title_; -}; - -/// Public-facing wrapper for internal CDP state that can be preserved across -/// reloads. -struct INSPECTOR_EXPORT State { - /// Incomplete type that stores the actual state. - struct Private; - - /// Create a new wrapper with the provided \p privateState. - explicit State(std::unique_ptr privateState); - ~State(); - - /// Get the wrapped state. - Private &get() { - return *privateState_.get(); - } - - private: - /// Pointer to the actual stored state, hidden from users of this wrapper. - std::unique_ptr privateState_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook - -#endif // HERMES_INSPECTOR_CDPHandler_H diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/CallbackOStream.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/CallbackOStream.h deleted file mode 100644 index a9831555a..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/CallbackOStream.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -/// Subclass of \c std::ostream where flushing is implemented through a -/// callback. Writes are collected in a buffer. When filled, the buffer's -/// contents are emptied out and sent to a callback. -struct CallbackOStream : public std::ostream { - /// Signature of callback called to flush buffer contents. Accepts the buffer - /// as a string. Returns a boolean indicating whether flushing succeeded. - /// Callback failure will be translated to stream failure. If the callback - /// throws an exception it will be swallowed and translated into stream - /// failure. - using Fn = std::function; - - /// Construct a new stream. - /// - /// \p sz The size of the buffer -- how large it can get before it must be - /// flushed. Must be non-zero. - /// \p cb The callback function. - CallbackOStream(size_t sz, Fn cb); - - /// This class is neither movable nor copyable. - CallbackOStream(CallbackOStream &&that) = delete; - CallbackOStream &operator=(CallbackOStream &&that) = delete; - CallbackOStream(const CallbackOStream &that) = delete; - CallbackOStream &operator=(const CallbackOStream &that) = delete; - - private: - /// \c std::streambuf sub-class backed by a std::string buffer and - /// implementing overflow by calling a callback. - struct StreamBuf : public std::streambuf { - /// Construct a new streambuf. Parameters are the same as those of - /// \c CallbackOStream . - StreamBuf(size_t sz, Fn cb); - - /// Destruction will flush any remaining buffer contents. - ~StreamBuf() override; - - /// StreamBufs are not copyable, to avoid the flush callback receiving - /// the contents of multiple streams. - StreamBuf(const StreamBuf &) = delete; - StreamBuf &operator=(const StreamBuf &) = delete; - - protected: - /// std::streambuf overrides - int_type overflow(int_type ch) override; - int sync() override; - - private: - /// The size of the backing buffer. Fixed for an instance of the streambuf. - size_t sz_; - - /// The backing buffer that writes will go to until full. - std::unique_ptr buf_; - - /// The function called when buf_ has been filled. - Fn cb_; - - /// Clears the backing buffer. - void reset(); - - /// Clears the backing buffer and returns it contents in a string. - std::string take(); - }; - - StreamBuf sbuf_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/JSONValueInterfaces.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/JSONValueInterfaces.h deleted file mode 100644 index 263313810..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/JSONValueInterfaces.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -using namespace ::hermes::parser; - -/// Convert a string to a JSONValue. Will return nullopt if parsing is not -/// successful. -std::optional parseStr( - const std::string &str, - JSONFactory &factory); - -/// Convert a string to a JSON object. Will return nullopt if parsing is not -/// successful, or the resulting JSON value is not an object. -std::optional parseStrAsJsonObj( - const std::string &str, - JSONFactory &factory); - -/// Convert a JSONValue to a string. -std::string jsonValToStr(const JSONValue *v); - -/// Check if two JSONValues are equal. -bool jsonValsEQ(const JSONValue *A, const JSONValue *B); - -}; // namespace chrome -}; // namespace inspector_modern -}; // namespace hermes -}; // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageConverters.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageConverters.h deleted file mode 100644 index fd26c9ed5..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageConverters.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -template -void setChromeLocation( - T &chromeLoc, - const facebook::hermes::debugger::SourceLocation &hermesLoc) { - if (hermesLoc.line != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.lineNumber = hermesLoc.line - 1; - } - - if (hermesLoc.column != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.columnNumber = hermesLoc.column - 1; - } -} - -/// ErrorCode magic numbers match JSC's (see InspectorBackendDispatcher.cpp) -enum class ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - ServerError = -32000 -}; - -ErrorResponse -makeErrorResponse(int id, ErrorCode code, const std::string &message); - -OkResponse makeOkResponse(int id); - -namespace debugger { - -Location makeLocation(const facebook::hermes::debugger::SourceLocation &loc); - -} // namespace debugger - -namespace runtime { - -CallFrame makeCallFrame(const facebook::hermes::debugger::CallFrameInfo &info); - -std::vector makeCallFrames( - const facebook::hermes::debugger::StackTrace &stackTrace); - -ExceptionDetails makeExceptionDetails( - const facebook::hermes::debugger::ExceptionDetails &details); - -} // namespace runtime - -namespace heapProfiler { - -std::unique_ptr makeSamplingHeapProfile( - const std::string &value); - -} // namespace heapProfiler - -namespace profiler { - -std::unique_ptr makeProfile(const std::string &value); - -} // namespace profiler - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageInterfaces.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageInterfaces.h deleted file mode 100644 index 01e369e22..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageInterfaces.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { -using namespace ::hermes::parser; - -struct RequestHandler; - -/// Serializable is an interface for objects that can be serialized to and from -/// JSON. -struct Serializable { - virtual ~Serializable() = default; - virtual JSONValue *toJsonVal(JSONFactory &factory) const = 0; - - std::string toJsonStr() const; -}; - -/// Requests are sent from the debugger to the target. -struct Request : public Serializable { - using ParseResult = std::variant, std::string>; - static std::unique_ptr fromJson(const std::string &str); - - Request() = default; - explicit Request(std::string method) : method(method) {} - - // accept dispatches to the appropriate handler method in RequestHandler based - // on the type of the request. - virtual void accept(RequestHandler &handler) const = 0; - - long long id = 0; - std::string method; -}; - -/// Responses are sent from the target to the debugger in response to a Request. -struct Response : public Serializable { - Response() = default; - - long long id = 0; -}; - -/// Notifications are sent from the target to the debugger. This is used to -/// notify the debugger about events that occur in the target, e.g. stopping -/// at a breakpoint. -struct Notification : public Serializable { - Notification() = default; - explicit Notification(std::string method) : method(method) {} - - std::string method; -}; - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageTypes.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageTypes.h deleted file mode 100644 index e039758f6..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageTypes.h +++ /dev/null @@ -1,1183 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved. -// @generated SignedSource<<3ebea508f76e06269045891097f89eb5>> - -#pragma once - -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -template -void deleter(T *p); -using JSONBlob = std::string; -struct UnknownRequest; - -namespace debugger { -using BreakpointId = std::string; -struct BreakpointResolvedNotification; -struct CallFrame; -using CallFrameId = std::string; -struct DisableRequest; -struct EnableRequest; -struct EvaluateOnCallFrameRequest; -struct EvaluateOnCallFrameResponse; -struct Location; -struct PauseRequest; -struct PausedNotification; -struct RemoveBreakpointRequest; -struct ResumeRequest; -struct ResumedNotification; -struct Scope; -struct ScriptParsedNotification; -struct SetBreakpointByUrlRequest; -struct SetBreakpointByUrlResponse; -struct SetBreakpointRequest; -struct SetBreakpointResponse; -struct SetBreakpointsActiveRequest; -struct SetInstrumentationBreakpointRequest; -struct SetInstrumentationBreakpointResponse; -struct SetPauseOnExceptionsRequest; -struct StepIntoRequest; -struct StepOutRequest; -struct StepOverRequest; -} // namespace debugger - -namespace runtime { -struct CallArgument; -struct CallFrame; -struct CallFunctionOnRequest; -struct CallFunctionOnResponse; -struct CompileScriptRequest; -struct CompileScriptResponse; -struct ConsoleAPICalledNotification; -struct CustomPreview; -struct DisableRequest; -struct EnableRequest; -struct EntryPreview; -struct EvaluateRequest; -struct EvaluateResponse; -struct ExceptionDetails; -struct ExecutionContextCreatedNotification; -struct ExecutionContextDescription; -using ExecutionContextId = long long; -struct GetHeapUsageRequest; -struct GetHeapUsageResponse; -struct GetPropertiesRequest; -struct GetPropertiesResponse; -struct GlobalLexicalScopeNamesRequest; -struct GlobalLexicalScopeNamesResponse; -struct InternalPropertyDescriptor; -struct ObjectPreview; -struct PropertyDescriptor; -struct PropertyPreview; -struct RemoteObject; -using RemoteObjectId = std::string; -struct RunIfWaitingForDebuggerRequest; -using ScriptId = std::string; -struct StackTrace; -using Timestamp = double; -using UnserializableValue = std::string; -} // namespace runtime - -namespace heapProfiler { -struct AddHeapSnapshotChunkNotification; -struct CollectGarbageRequest; -struct GetHeapObjectIdRequest; -struct GetHeapObjectIdResponse; -struct GetObjectByHeapObjectIdRequest; -struct GetObjectByHeapObjectIdResponse; -using HeapSnapshotObjectId = std::string; -struct HeapStatsUpdateNotification; -struct LastSeenObjectIdNotification; -struct ReportHeapSnapshotProgressNotification; -struct SamplingHeapProfile; -struct SamplingHeapProfileNode; -struct SamplingHeapProfileSample; -struct StartSamplingRequest; -struct StartTrackingHeapObjectsRequest; -struct StopSamplingRequest; -struct StopSamplingResponse; -struct StopTrackingHeapObjectsRequest; -struct TakeHeapSnapshotRequest; -} // namespace heapProfiler - -namespace profiler { -struct PositionTickInfo; -struct Profile; -struct ProfileNode; -struct StartRequest; -struct StopRequest; -struct StopResponse; -} // namespace profiler - -/// RequestHandler handles requests via the visitor pattern. -struct RequestHandler { - virtual ~RequestHandler() = default; - - virtual void handle(const UnknownRequest &req) = 0; - virtual void handle(const debugger::DisableRequest &req) = 0; - virtual void handle(const debugger::EnableRequest &req) = 0; - virtual void handle(const debugger::EvaluateOnCallFrameRequest &req) = 0; - virtual void handle(const debugger::PauseRequest &req) = 0; - virtual void handle(const debugger::RemoveBreakpointRequest &req) = 0; - virtual void handle(const debugger::ResumeRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointByUrlRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointsActiveRequest &req) = 0; - virtual void handle( - const debugger::SetInstrumentationBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetPauseOnExceptionsRequest &req) = 0; - virtual void handle(const debugger::StepIntoRequest &req) = 0; - virtual void handle(const debugger::StepOutRequest &req) = 0; - virtual void handle(const debugger::StepOverRequest &req) = 0; - virtual void handle(const heapProfiler::CollectGarbageRequest &req) = 0; - virtual void handle(const heapProfiler::GetHeapObjectIdRequest &req) = 0; - virtual void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) = 0; - virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::StopSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0; - virtual void handle(const profiler::StartRequest &req) = 0; - virtual void handle(const profiler::StopRequest &req) = 0; - virtual void handle(const runtime::CallFunctionOnRequest &req) = 0; - virtual void handle(const runtime::CompileScriptRequest &req) = 0; - virtual void handle(const runtime::DisableRequest &req) = 0; - virtual void handle(const runtime::EnableRequest &req) = 0; - virtual void handle(const runtime::EvaluateRequest &req) = 0; - virtual void handle(const runtime::GetHeapUsageRequest &req) = 0; - virtual void handle(const runtime::GetPropertiesRequest &req) = 0; - virtual void handle(const runtime::GlobalLexicalScopeNamesRequest &req) = 0; - virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0; -}; - -/// NoopRequestHandler can be subclassed to only handle some requests. -struct NoopRequestHandler : public RequestHandler { - void handle(const UnknownRequest &req) override {} - void handle(const debugger::DisableRequest &req) override {} - void handle(const debugger::EnableRequest &req) override {} - void handle(const debugger::EvaluateOnCallFrameRequest &req) override {} - void handle(const debugger::PauseRequest &req) override {} - void handle(const debugger::RemoveBreakpointRequest &req) override {} - void handle(const debugger::ResumeRequest &req) override {} - void handle(const debugger::SetBreakpointRequest &req) override {} - void handle(const debugger::SetBreakpointByUrlRequest &req) override {} - void handle(const debugger::SetBreakpointsActiveRequest &req) override {} - void handle( - const debugger::SetInstrumentationBreakpointRequest &req) override {} - void handle(const debugger::SetPauseOnExceptionsRequest &req) override {} - void handle(const debugger::StepIntoRequest &req) override {} - void handle(const debugger::StepOutRequest &req) override {} - void handle(const debugger::StepOverRequest &req) override {} - void handle(const heapProfiler::CollectGarbageRequest &req) override {} - void handle(const heapProfiler::GetHeapObjectIdRequest &req) override {} - void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) override {} - void handle(const heapProfiler::StartSamplingRequest &req) override {} - void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::StopSamplingRequest &req) override {} - void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {} - void handle(const profiler::StartRequest &req) override {} - void handle(const profiler::StopRequest &req) override {} - void handle(const runtime::CallFunctionOnRequest &req) override {} - void handle(const runtime::CompileScriptRequest &req) override {} - void handle(const runtime::DisableRequest &req) override {} - void handle(const runtime::EnableRequest &req) override {} - void handle(const runtime::EvaluateRequest &req) override {} - void handle(const runtime::GetHeapUsageRequest &req) override {} - void handle(const runtime::GetPropertiesRequest &req) override {} - void handle(const runtime::GlobalLexicalScopeNamesRequest &req) override {} - void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {} -}; - -/// Types -struct debugger::Location : public Serializable { - Location() = default; - Location(Location &&) = default; - Location(const Location &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Location &operator=(const Location &) = delete; - Location &operator=(Location &&) = default; - - runtime::ScriptId scriptId{}; - long long lineNumber{}; - std::optional columnNumber; -}; - -struct runtime::PropertyPreview : public Serializable { - PropertyPreview() = default; - PropertyPreview(PropertyPreview &&) = default; - PropertyPreview(const PropertyPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyPreview &operator=(const PropertyPreview &) = delete; - PropertyPreview &operator=(PropertyPreview &&) = default; - - std::string name; - std::string type; - std::optional value; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - valuePreview{nullptr, deleter}; - std::optional subtype; -}; - -struct runtime::EntryPreview : public Serializable { - EntryPreview() = default; - EntryPreview(EntryPreview &&) = default; - EntryPreview(const EntryPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - EntryPreview &operator=(const EntryPreview &) = delete; - EntryPreview &operator=(EntryPreview &&) = default; - - std::unique_ptr< - runtime::ObjectPreview, - std::function> - key{nullptr, deleter}; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - value{nullptr, deleter}; -}; - -struct runtime::ObjectPreview : public Serializable { - ObjectPreview() = default; - ObjectPreview(ObjectPreview &&) = default; - ObjectPreview(const ObjectPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ObjectPreview &operator=(const ObjectPreview &) = delete; - ObjectPreview &operator=(ObjectPreview &&) = default; - - std::string type; - std::optional subtype; - std::optional description; - bool overflow{}; - std::vector properties; - std::optional> entries; -}; - -struct runtime::CustomPreview : public Serializable { - CustomPreview() = default; - CustomPreview(CustomPreview &&) = default; - CustomPreview(const CustomPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CustomPreview &operator=(const CustomPreview &) = delete; - CustomPreview &operator=(CustomPreview &&) = default; - - std::string header; - std::optional bodyGetterId; -}; - -struct runtime::RemoteObject : public Serializable { - RemoteObject() = default; - RemoteObject(RemoteObject &&) = default; - RemoteObject(const RemoteObject &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - RemoteObject &operator=(const RemoteObject &) = delete; - RemoteObject &operator=(RemoteObject &&) = default; - - std::string type; - std::optional subtype; - std::optional className; - std::optional value; - std::optional unserializableValue; - std::optional description; - std::optional objectId; - std::optional preview; - std::optional customPreview; -}; - -struct runtime::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - std::string functionName; - runtime::ScriptId scriptId{}; - std::string url; - long long lineNumber{}; - long long columnNumber{}; -}; - -struct runtime::StackTrace : public Serializable { - StackTrace() = default; - StackTrace(StackTrace &&) = default; - StackTrace(const StackTrace &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - StackTrace &operator=(const StackTrace &) = delete; - StackTrace &operator=(StackTrace &&) = default; - - std::optional description; - std::vector callFrames; - std::unique_ptr parent; -}; - -struct runtime::ExceptionDetails : public Serializable { - ExceptionDetails() = default; - ExceptionDetails(ExceptionDetails &&) = default; - ExceptionDetails(const ExceptionDetails &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExceptionDetails &operator=(const ExceptionDetails &) = delete; - ExceptionDetails &operator=(ExceptionDetails &&) = default; - - long long exceptionId{}; - std::string text; - long long lineNumber{}; - long long columnNumber{}; - std::optional scriptId; - std::optional url; - std::optional stackTrace; - std::optional exception; - std::optional executionContextId; -}; - -struct debugger::Scope : public Serializable { - Scope() = default; - Scope(Scope &&) = default; - Scope(const Scope &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Scope &operator=(const Scope &) = delete; - Scope &operator=(Scope &&) = default; - - std::string type; - runtime::RemoteObject object{}; - std::optional name; - std::optional startLocation; - std::optional endLocation; -}; - -struct debugger::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - debugger::CallFrameId callFrameId{}; - std::string functionName; - std::optional functionLocation; - debugger::Location location{}; - std::string url; - std::vector scopeChain; - runtime::RemoteObject thisObj{}; - std::optional returnValue; -}; - -struct heapProfiler::SamplingHeapProfileNode : public Serializable { - SamplingHeapProfileNode() = default; - SamplingHeapProfileNode(SamplingHeapProfileNode &&) = default; - SamplingHeapProfileNode(const SamplingHeapProfileNode &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileNode &operator=(const SamplingHeapProfileNode &) = delete; - SamplingHeapProfileNode &operator=(SamplingHeapProfileNode &&) = default; - - runtime::CallFrame callFrame{}; - double selfSize{}; - long long id{}; - std::vector children; -}; - -struct heapProfiler::SamplingHeapProfileSample : public Serializable { - SamplingHeapProfileSample() = default; - SamplingHeapProfileSample(SamplingHeapProfileSample &&) = default; - SamplingHeapProfileSample(const SamplingHeapProfileSample &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileSample &operator=(const SamplingHeapProfileSample &) = - delete; - SamplingHeapProfileSample &operator=(SamplingHeapProfileSample &&) = default; - - double size{}; - long long nodeId{}; - double ordinal{}; -}; - -struct heapProfiler::SamplingHeapProfile : public Serializable { - SamplingHeapProfile() = default; - SamplingHeapProfile(SamplingHeapProfile &&) = default; - SamplingHeapProfile(const SamplingHeapProfile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfile &operator=(const SamplingHeapProfile &) = delete; - SamplingHeapProfile &operator=(SamplingHeapProfile &&) = default; - - heapProfiler::SamplingHeapProfileNode head{}; - std::vector samples; -}; - -struct profiler::PositionTickInfo : public Serializable { - PositionTickInfo() = default; - PositionTickInfo(PositionTickInfo &&) = default; - PositionTickInfo(const PositionTickInfo &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PositionTickInfo &operator=(const PositionTickInfo &) = delete; - PositionTickInfo &operator=(PositionTickInfo &&) = default; - - long long line{}; - long long ticks{}; -}; - -struct profiler::ProfileNode : public Serializable { - ProfileNode() = default; - ProfileNode(ProfileNode &&) = default; - ProfileNode(const ProfileNode &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ProfileNode &operator=(const ProfileNode &) = delete; - ProfileNode &operator=(ProfileNode &&) = default; - - long long id{}; - runtime::CallFrame callFrame{}; - std::optional hitCount; - std::optional> children; - std::optional deoptReason; - std::optional> positionTicks; -}; - -struct profiler::Profile : public Serializable { - Profile() = default; - Profile(Profile &&) = default; - Profile(const Profile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Profile &operator=(const Profile &) = delete; - Profile &operator=(Profile &&) = default; - - std::vector nodes; - double startTime{}; - double endTime{}; - std::optional> samples; - std::optional> timeDeltas; -}; - -struct runtime::CallArgument : public Serializable { - CallArgument() = default; - CallArgument(CallArgument &&) = default; - CallArgument(const CallArgument &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallArgument &operator=(const CallArgument &) = delete; - CallArgument &operator=(CallArgument &&) = default; - - std::optional value; - std::optional unserializableValue; - std::optional objectId; -}; - -struct runtime::ExecutionContextDescription : public Serializable { - ExecutionContextDescription() = default; - ExecutionContextDescription(ExecutionContextDescription &&) = default; - ExecutionContextDescription(const ExecutionContextDescription &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExecutionContextDescription &operator=(const ExecutionContextDescription &) = - delete; - ExecutionContextDescription &operator=(ExecutionContextDescription &&) = - default; - - runtime::ExecutionContextId id{}; - std::string origin; - std::string name; - std::optional auxData; -}; - -struct runtime::PropertyDescriptor : public Serializable { - PropertyDescriptor() = default; - PropertyDescriptor(PropertyDescriptor &&) = default; - PropertyDescriptor(const PropertyDescriptor &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyDescriptor &operator=(const PropertyDescriptor &) = delete; - PropertyDescriptor &operator=(PropertyDescriptor &&) = default; - - std::string name; - std::optional value; - std::optional writable; - std::optional get; - std::optional set; - bool configurable{}; - bool enumerable{}; - std::optional wasThrown; - std::optional isOwn; - std::optional symbol; -}; - -struct runtime::InternalPropertyDescriptor : public Serializable { - InternalPropertyDescriptor() = default; - InternalPropertyDescriptor(InternalPropertyDescriptor &&) = default; - InternalPropertyDescriptor(const InternalPropertyDescriptor &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - InternalPropertyDescriptor &operator=(const InternalPropertyDescriptor &) = - delete; - InternalPropertyDescriptor &operator=(InternalPropertyDescriptor &&) = - default; - - std::string name; - std::optional value; -}; - -/// Requests -struct UnknownRequest : public Request { - UnknownRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional params; -}; - -struct debugger::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EvaluateOnCallFrameRequest : public Request { - EvaluateOnCallFrameRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::CallFrameId callFrameId{}; - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional throwOnSideEffect; -}; - -struct debugger::PauseRequest : public Request { - PauseRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::RemoveBreakpointRequest : public Request { - RemoveBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct debugger::ResumeRequest : public Request { - ResumeRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional terminateOnResume; -}; - -struct debugger::SetBreakpointRequest : public Request { - SetBreakpointRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::Location location{}; - std::optional condition; -}; - -struct debugger::SetBreakpointByUrlRequest : public Request { - SetBreakpointByUrlRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - long long lineNumber{}; - std::optional url; - std::optional urlRegex; - std::optional scriptHash; - std::optional columnNumber; - std::optional condition; -}; - -struct debugger::SetBreakpointsActiveRequest : public Request { - SetBreakpointsActiveRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - bool active{}; -}; - -struct debugger::SetInstrumentationBreakpointRequest : public Request { - SetInstrumentationBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string instrumentation; -}; - -struct debugger::SetPauseOnExceptionsRequest : public Request { - SetPauseOnExceptionsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string state; -}; - -struct debugger::StepIntoRequest : public Request { - StepIntoRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOutRequest : public Request { - StepOutRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOverRequest : public Request { - StepOverRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::CollectGarbageRequest : public Request { - CollectGarbageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::GetHeapObjectIdRequest : public Request { - GetHeapObjectIdRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdRequest : public Request { - GetObjectByHeapObjectIdRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - heapProfiler::HeapSnapshotObjectId objectId{}; - std::optional objectGroup; -}; - -struct heapProfiler::StartSamplingRequest : public Request { - StartSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional samplingInterval; - std::optional includeObjectsCollectedByMajorGC; - std::optional includeObjectsCollectedByMinorGC; -}; - -struct heapProfiler::StartTrackingHeapObjectsRequest : public Request { - StartTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional trackAllocations; -}; - -struct heapProfiler::StopSamplingRequest : public Request { - StopSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::StopTrackingHeapObjectsRequest : public Request { - StopTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct heapProfiler::TakeHeapSnapshotRequest : public Request { - TakeHeapSnapshotRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct profiler::StartRequest : public Request { - StartRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct profiler::StopRequest : public Request { - StopRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::CallFunctionOnRequest : public Request { - CallFunctionOnRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string functionDeclaration; - std::optional objectId; - std::optional> arguments; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; - std::optional executionContextId; - std::optional objectGroup; -}; - -struct runtime::CompileScriptRequest : public Request { - CompileScriptRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::string sourceURL; - bool persistScript{}; - std::optional executionContextId; -}; - -struct runtime::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EvaluateRequest : public Request { - EvaluateRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional contextId; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; -}; - -struct runtime::GetHeapUsageRequest : public Request { - GetHeapUsageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::GetPropertiesRequest : public Request { - GetPropertiesRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; - std::optional ownProperties; - std::optional generatePreview; -}; - -struct runtime::GlobalLexicalScopeNamesRequest : public Request { - GlobalLexicalScopeNamesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional executionContextId; -}; - -struct runtime::RunIfWaitingForDebuggerRequest : public Request { - RunIfWaitingForDebuggerRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -/// Responses -struct ErrorResponse : public Response { - ErrorResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long code; - std::string message; - std::optional data; -}; - -struct OkResponse : public Response { - OkResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::EvaluateOnCallFrameResponse : public Response { - EvaluateOnCallFrameResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct debugger::SetBreakpointResponse : public Response { - SetBreakpointResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location actualLocation{}; -}; - -struct debugger::SetBreakpointByUrlResponse : public Response { - SetBreakpointByUrlResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - std::vector locations; -}; - -struct debugger::SetInstrumentationBreakpointResponse : public Response { - SetInstrumentationBreakpointResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct heapProfiler::GetHeapObjectIdResponse : public Response { - GetHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::HeapSnapshotObjectId heapSnapshotObjectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdResponse : public Response { - GetObjectByHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; -}; - -struct heapProfiler::StopSamplingResponse : public Response { - StopSamplingResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::SamplingHeapProfile profile{}; -}; - -struct profiler::StopResponse : public Response { - StopResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - profiler::Profile profile{}; -}; - -struct runtime::CallFunctionOnResponse : public Response { - CallFunctionOnResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::CompileScriptResponse : public Response { - CompileScriptResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::optional scriptId; - std::optional exceptionDetails; -}; - -struct runtime::EvaluateResponse : public Response { - EvaluateResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::GetHeapUsageResponse : public Response { - GetHeapUsageResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - double usedSize{}; - double totalSize{}; -}; - -struct runtime::GetPropertiesResponse : public Response { - GetPropertiesResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector result; - std::optional> - internalProperties; - std::optional exceptionDetails; -}; - -struct runtime::GlobalLexicalScopeNamesResponse : public Response { - GlobalLexicalScopeNamesResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector names; -}; - -/// Notifications -struct debugger::BreakpointResolvedNotification : public Notification { - BreakpointResolvedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location location{}; -}; - -struct debugger::PausedNotification : public Notification { - PausedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector callFrames; - std::string reason; - std::optional data; - std::optional> hitBreakpoints; - std::optional asyncStackTrace; -}; - -struct debugger::ResumedNotification : public Notification { - ResumedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::ScriptParsedNotification : public Notification { - ScriptParsedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ScriptId scriptId{}; - std::string url; - long long startLine{}; - long long startColumn{}; - long long endLine{}; - long long endColumn{}; - runtime::ExecutionContextId executionContextId{}; - std::string hash; - std::optional executionContextAuxData; - std::optional sourceMapURL; - std::optional hasSourceURL; - std::optional isModule; - std::optional length; -}; - -struct heapProfiler::AddHeapSnapshotChunkNotification : public Notification { - AddHeapSnapshotChunkNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string chunk; -}; - -struct heapProfiler::HeapStatsUpdateNotification : public Notification { - HeapStatsUpdateNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector statsUpdate; -}; - -struct heapProfiler::LastSeenObjectIdNotification : public Notification { - LastSeenObjectIdNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long lastSeenObjectId{}; - double timestamp{}; -}; - -struct heapProfiler::ReportHeapSnapshotProgressNotification - : public Notification { - ReportHeapSnapshotProgressNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long done{}; - long long total{}; - std::optional finished; -}; - -struct runtime::ConsoleAPICalledNotification : public Notification { - ConsoleAPICalledNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string type; - std::vector args; - runtime::ExecutionContextId executionContextId{}; - runtime::Timestamp timestamp{}; - std::optional stackTrace; -}; - -struct runtime::ExecutionContextCreatedNotification : public Notification { - ExecutionContextCreatedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ExecutionContextDescription context{}; -}; - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageTypesInlines.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageTypesInlines.h deleted file mode 100644 index 49a4995dd..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/MessageTypesInlines.h +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -template -using optional = std::optional; - -template -struct is_vector : std::false_type {}; - -template -struct is_vector> : std::true_type {}; - -/// valueFromJson - -/// Convert JSONValue to a Serializable type. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return T::tryMake(res); -} - -/// Convert JSONValue to a bool. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a long long. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a double. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a string. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->c_str()); -} - -/// Convert JSONValue to a vector. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *items) { - auto *arr = llvh::dyn_cast(items); - std::unique_ptr result = std::make_unique(); - result->reserve(arr->size()); - for (const auto &item : *arr) { - auto itemResult = valueFromJson(item); - if (!itemResult) { - return nullptr; - } - result->push_back(std::move(*itemResult)); - } - return result; -} - -/// Convert JSONValue to a JSONObject. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - auto *res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res); -} - -/// Pass through JSONValues. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - return std::make_unique(v); -} - -/// assign(lhs, obj, key) is a wrapper for: -/// -/// lhs = obj[key] -/// -/// It mainly exists so that we can choose the right version of valueFromJson -/// based on the type of lhs. - -template -bool assign(T &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v == nullptr) { - return false; - } - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; -} - -template -bool assign(optional &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign(std::unique_ptr &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign( - std::unique_ptr> &lhs, - const JSONObject *obj, - const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -/// valueToJson - -inline JSONValue *valueToJson(const Serializable &value, JSONFactory &factory) { - return value.toJsonVal(factory); -} - -// Convert a bool to JSONValue. -inline JSONValue *valueToJson(bool b, JSONFactory &factory) { - return factory.getBoolean(b); -} - -// Convert a long long to JSONValue. -inline JSONValue *valueToJson(long long num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a double to JSONValue. -inline JSONValue *valueToJson(double num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a string to JSONValue. -inline JSONValue *valueToJson(const std::string &str, JSONFactory &factory) { - return factory.getString(str); -} - -// Convert a vector to JSONValue. -template -JSONValue *valueToJson(const std::vector &items, JSONFactory &factory) { - llvh::SmallVector storage; - for (const auto &item : items) { - storage.push_back(valueToJson(item, factory)); - } - return factory.newArray(storage.size(), storage.begin(), storage.end()); -} - -// Cast a JSONObject to JSONValue. -inline JSONValue *valueToJson(JSONObject *obj, JSONFactory &factory) { - return llvh::cast(obj); -} - -// Pass through JSONValues. -inline JSONValue *valueToJson(JSONValue *v, JSONFactory &factory) { - return v; -} - -/// put(obj, key, value) is meant to be a wrapper for: -/// obj[key] = valueToJson(value); -/// However, JSONObjects are immutable, so we represent a 'put' operation as -/// pushing a new element onto a vector of JSONFactory::Props. - -using Properties = llvh::SmallVectorImpl; - -template -void put( - Properties &props, - const std::string &key, - const V &value, - JSONFactory &factory) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(value, factory); - props.push_back({jsStr, jsVal}); -} - -template -void put( - Properties &props, - const std::string &key, - const optional &optValue, - JSONFactory &factory) { - if (optValue.has_value()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(optValue.value(), factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr> &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void deleter(T *p) { - delete p; -} - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/RemoteObjectConverters.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/RemoteObjectConverters.h deleted file mode 100644 index 89355dc3e..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/RemoteObjectConverters.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -namespace debugger { - -CallFrame makeCallFrame( - uint32_t callFrameIndex, - const facebook::hermes::debugger::CallFrameInfo &callFrameInfo, - const facebook::hermes::debugger::LexicalInfo &lexicalInfo, - facebook::hermes::inspector_modern::chrome::RemoteObjectsTable &objTable, - jsi::Runtime &runtime, - const facebook::hermes::debugger::ProgramState &state); - -std::vector makeCallFrames( - const facebook::hermes::debugger::ProgramState &state, - facebook::hermes::inspector_modern::chrome::RemoteObjectsTable &objTable, - jsi::Runtime &runtime); - -} // namespace debugger - -namespace runtime { - -RemoteObject makeRemoteObject( - facebook::jsi::Runtime &runtime, - const facebook::jsi::Value &value, - facebook::hermes::inspector_modern::chrome::RemoteObjectsTable &objTable, - const std::string &objectGroup, - bool byValue = false, - bool generatePreview = false); - -} // namespace runtime - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/RemoteObjectsTable.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/RemoteObjectsTable.h deleted file mode 100644 index d7a3370f6..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/RemoteObjectsTable.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -/// Well-known object group names - -/** - * Objects created as a result of the Debugger.paused notification (e.g. scope - * objects) are placed in the "backtrace" object group. This object group is - * cleared when the VM resumes. - */ -extern const char *BacktraceObjectGroup; - -/** - * Objects that are created as a result of a console evaluation are placed in - * the "console" object group. This object group is cleared when the client - * clears the console. - */ -extern const char *ConsoleObjectGroup; - -/** - * RemoteObjectsTable manages the mapping of string object ids to scope metadata - * or actual JSI objects. The debugger vends these ids to the client so that the - * client can perform operations on the ids (e.g. enumerate properties on the - * object backed by the id). See Runtime.RemoteObjectId in the CDT docs for - * more details. - * - * Note that object handles are not ref-counted. Suppose an object foo is mapped - * to object id "objId" and is also in object group "objGroup". Then *either* of - * `releaseObject("objId")` or `releaseObjectGroup("objGroup")` will remove foo - * from the table. This matches the behavior of object groups in CDT. - */ -class RemoteObjectsTable { - public: - RemoteObjectsTable(); - ~RemoteObjectsTable(); - - RemoteObjectsTable(const RemoteObjectsTable &) = delete; - RemoteObjectsTable &operator=(const RemoteObjectsTable &) = delete; - - /** - * addScope adds the provided (frameIndex, scopeIndex) mapping to the table. - * If objectGroup is non-empty, then the scope object is also added to that - * object group for releasing via releaseObjectGroup. Returns an object id. - */ - std::string addScope( - std::pair frameAndScopeIndex, - const std::string &objectGroup); - - /** - * addValue adds the JSI value to the table. If objectGroup is non-empty, then - * the scope object is also added to that object group for releasing via - * releaseObjectGroup. Returns an object id. - */ - std::string addValue( - ::facebook::jsi::Value value, - const std::string &objectGroup); - - /** - * Retrieves the (frameIndex, scopeIndex) associated with this object id, or - * nullptr if no mapping exists. The pointer stays valid as long as you only - * call const methods on this class. - */ - const std::pair *getScope(const std::string &objId) const; - - /** - * Retrieves the JSI value associated with this object id, or nullptr if no - * mapping exists. The pointer stays valid as long as you only call const - * methods on this class. - */ - const ::facebook::jsi::Value *getValue(const std::string &objId) const; - - /** - * Retrieves the object group that this object id is in, or empty string if it - * isn't in an object group. The returned pointer is only guaranteed to be - * valid until the next call to this class. - */ - std::string getObjectGroup(const std::string &objId) const; - - /** - * Removes the scope or JSI value backed by the provided object ID from the - * table. - */ - void releaseObject(const std::string &objId); - - /** - * Removes all objects that are part of the provided object group from the - * table. - */ - void releaseObjectGroup(const std::string &objectGroup); - - private: - void releaseObject(int64_t id); - - int64_t scopeId_ = -1; - int64_t valueId_ = 1; - - std::unordered_map> scopes_; - std::unordered_map values_; - std::unordered_map idToGroup_; - std::unordered_map> groupToIds_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/AsyncHermesRuntime.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/AsyncHermesRuntime.h deleted file mode 100644 index aaaf9cd04..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/AsyncHermesRuntime.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -/// URL assigned to scripts being executed in the absense of a caller-specified -/// URL. -constexpr auto kDefaultUrl = "url"; - -/** - * AsyncHermesRuntime is a helper class that runs JS scripts in a Hermes VM on - * a separate thread. This is useful for tests that want to test running JS - * in a multithreaded environment. - */ -class AsyncHermesRuntime { - public: - // Create a runtime. If veryLazy, configure the runtime to use completely - // lazy compilation. - AsyncHermesRuntime(bool veryLazy = false); - ~AsyncHermesRuntime(); - - std::shared_ptr runtime() { - return runtime_; - } - - /** - * stop sets the stop flag on this instance. JS scripts can get the current - * value of the stop flag by calling the global shouldStop() function. - */ - void stop(); - - /** - * start unsets the stop flag on this instance. JS scripts can get the current - * value of the stop flag by calling the global shouldStop() function. - */ - void start(); - - /** - * hasStoredValue returns whether or not a value has been stored yet - */ - bool hasStoredValue(); - - /** - * awaitStoredValue is a helper for getStoredValue that returns the value - * synchronously rather than in a future. - */ - jsi::Value awaitStoredValue( - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - /** - * tickleJsAsync evaluates '__tickleJs()' in the underlying Hermes runtime on - * a separate thread. - */ - void tickleJsAsync(); - - /** - * executeScriptAsync evaluates JS in the underlying Hermes runtime on a - * separate thread. - * - * This method should be called at most once during the lifetime of an - * AsyncHermesRuntime instance. - */ - void executeScriptAsync( - const std::string &str, - const std::string &url = kDefaultUrl, - facebook::hermes::HermesRuntime::DebugFlags flags = - facebook::hermes::HermesRuntime::DebugFlags{}); - - /** - * executeScriptSync evaluates JS in the underlying Hermes runtime on a - * separate thread. It will block the caller until execution completes. If - * this takes longer than \p timeout, an exception will be thrown. - */ - void executeScriptSync( - const std::string &script, - const std::string &url = kDefaultUrl, - facebook::hermes::HermesRuntime::DebugFlags flags = - facebook::hermes::HermesRuntime::DebugFlags{}, - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - /// Evaluates the given bytecode in the underlying Hermes runtime on a - /// separate thread. - /// \param bytecode Bytecode compiled with compileJS() API - /// \param url Corresponding source URL - void evaluateBytecodeAsync( - const std::string &bytecode, - const std::string &url = "url"); - - /** - * wait blocks until all previous executeScriptAsync calls finish. - */ - void wait( - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - /** - * returns the number of thrown exceptions. - */ - size_t getNumberOfExceptions(); - - /** - * returns the message of the last thrown exception. - */ - std::string getLastThrownExceptionMessage(); - - /** - * registers the runtime for profiling in the executor thread. - */ - void registerForProfilingInExecutor(); - - /** - * unregisters the runtime for profiling in the executor thread. - */ - void unregisterForProfilingInExecutor(); - - private: - jsi::Value shouldStop( - jsi::Runtime &runtime, - const jsi::Value &thisVal, - const jsi::Value *args, - size_t count); - - jsi::Value storeValue( - jsi::Runtime &runtime, - const jsi::Value &thisVal, - const jsi::Value *args, - size_t count); - - std::shared_ptr runtime_; - std::unique_ptr<::hermes::SerialExecutor> executor_; - std::atomic stopFlag_{}; - std::promise storedValue_; - bool hasStoredValue_{false}; - std::vector thrownExceptions_; -}; - -/// RAII-style class dealing with sampling profiler registration in tests. This -/// is especially important in tests -- if any test failure is caused by an -/// uncaught exception, stack unwinding will destroy a VM registered for -/// profiling in a thread that's not the one where registration happened, which -/// will lead to a hermes fatal error. Using this RAII class ensure that the -/// proper test failure cause is reported. -struct SamplingProfilerRAII { - explicit SamplingProfilerRAII(AsyncHermesRuntime &rt) : runtime_(rt) { - runtime_.registerForProfilingInExecutor(); - } - - ~SamplingProfilerRAII() { - runtime_.unregisterForProfilingInExecutor(); - } - - AsyncHermesRuntime &runtime_; -}; -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/SyncConnection.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/SyncConnection.h deleted file mode 100644 index d9ecc509f..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/SyncConnection.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -#include "AsyncHermesRuntime.h" - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -class ExecutorRuntimeAdapter - : public facebook::hermes::inspector_modern::RuntimeAdapter { - public: - explicit ExecutorRuntimeAdapter(AsyncHermesRuntime &runtime) - : runtime_(runtime) {} - - virtual ~ExecutorRuntimeAdapter() override = default; - - HermesRuntime &getRuntime() override { - return *runtime_.runtime(); - } - - void tickleJs() override; - - private: - AsyncHermesRuntime &runtime_; -}; - -/** - * SyncConnection provides a synchronous interface over Connection that is - * useful in tests. - */ -class SyncConnection { - public: - explicit SyncConnection( - AsyncHermesRuntime &runtime, - bool waitForDebugger = false); - ~SyncConnection(); - - /// sends a message to the debugger - void send(const std::string &str); - - /// waits for the next message of either kind (response or notification) - /// from the debugger. returns the message. throws on timeout. - std::string waitForMessage( - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - bool registerCallbacks(); - bool unregisterCallbacks(); - - /// \return True if onUnregister was called in a previous unregisterCallbacks - /// call. A registerCallbacks call will reset the status. - bool onUnregisterWasCalled(); - - private: - /// This function is given to the CDPHandler to receive replies in the form of - /// CDP messages - void onReply(const std::string &message); - - /// This function is given to the CDPHandler to be invoked upon - /// unregisterCallbacks call - void onUnregister(); - - std::shared_ptr cdpHandler_; - - bool onUnregisterCalled_ = false; - - std::mutex mutex_; - std::condition_variable hasMessage_; - std::queue messages_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/TestHelpers.h b/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/TestHelpers.h deleted file mode 100644 index 2f0e03992..000000000 --- a/NativeScript/napi/hermes/include/hermes/inspector/chrome/tests/TestHelpers.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -using namespace ::hermes::parser; - -inline JSONValue *mustParseStr(const std::string &str, JSONFactory &factory) { - std::optional v = parseStr(str, factory); - EXPECT_TRUE(v.has_value()); - return v.value(); -} - -inline JSONObject *mustParseStrAsJsonObj( - const std::string &str, - JSONFactory &factory) { - std::optional obj = parseStrAsJsonObj(str, factory); - EXPECT_TRUE(obj.has_value()); - return obj.value(); -} - -template -T mustMake(const JSONObject *obj) { - std::unique_ptr instance = T::tryMake(obj); - EXPECT_TRUE(instance != nullptr); - return std::move(*instance); -} - -namespace message { - -inline std::unique_ptr mustGetRequestFromJson(const std::string &str) { - std::unique_ptr req = Request::fromJson(str); - EXPECT_TRUE(req != nullptr); - return req; -} - -} // namespace message - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/jsi/decorator.h b/NativeScript/napi/hermes/include/jsi/decorator.h deleted file mode 100644 index 1940c3de3..000000000 --- a/NativeScript/napi/hermes/include/jsi/decorator.h +++ /dev/null @@ -1,977 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include - -// This file contains objects to help API users create their own -// runtime adapters, i.e. if you want to compose runtimes to add your -// own behavior. - -namespace facebook { -namespace jsi { - -// Use this to wrap host functions. It will pass the member runtime as -// the first arg to the callback. The first argument to the ctor -// should be the decorated runtime, not the plain one. -class DecoratedHostFunction { - public: - DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF) - : drt_(drt), plainHF_(std::move(plainHF)) {} - - Runtime& decoratedRuntime() { - return drt_; - } - - Value - operator()(Runtime&, const Value& thisVal, const Value* args, size_t count) { - return plainHF_(decoratedRuntime(), thisVal, args, count); - } - - private: - template - friend class RuntimeDecorator; - - Runtime& drt_; - HostFunctionType plainHF_; -}; - -// From the perspective of the caller, a plain HostObject is passed to -// the decorated Runtime, and the HostObject methods expect to get -// passed that Runtime. But the plain Runtime will pass itself to its -// callback, so we need a helper here which curries the decorated -// Runtime, and calls the plain HostObject with it. -// -// If the concrete RuntimeDecorator derives DecoratedHostObject, it -// should call the base class get() and set() to invoke the plain -// HostObject functionality. The Runtime& it passes does not matter, -// as it is not used. -class DecoratedHostObject : public HostObject { - public: - DecoratedHostObject(Runtime& drt, std::shared_ptr plainHO) - : drt_(drt), plainHO_(plainHO) {} - - // The derived class methods can call this to get a reference to the - // decorated runtime, since the rt passed to the callback will be - // the plain runtime. - Runtime& decoratedRuntime() { - return drt_; - } - - Value get(Runtime&, const PropNameID& name) override { - return plainHO_->get(decoratedRuntime(), name); - } - - void set(Runtime&, const PropNameID& name, const Value& value) override { - plainHO_->set(decoratedRuntime(), name, value); - } - - std::vector getPropertyNames(Runtime&) override { - return plainHO_->getPropertyNames(decoratedRuntime()); - } - - private: - template - friend class RuntimeDecorator; - - Runtime& drt_; - std::shared_ptr plainHO_; -}; - -/// C++ variant on a standard Decorator pattern, using template -/// parameters. The \c Plain template parameter type is the -/// undecorated Runtime type. You can usually use \c Runtime here, -/// but if you know the concrete type ahead of time and it's final, -/// the compiler can devirtualize calls to the decorated -/// implementation. The \c Base template parameter type will be used -/// as the base class of the decorated type. Here, too, you can -/// usually use \c Runtime, but if you want the decorated type to -/// implement a derived class of Runtime, you can specify that here. -/// For an example, see threadsafe.h. -template -class RuntimeDecorator : public Base, private jsi::Instrumentation { - public: - Plain& plain() { - static_assert( - std::is_base_of::value, - "RuntimeDecorator's Plain type must derive from jsi::Runtime"); - static_assert( - std::is_base_of::value, - "RuntimeDecorator's Base type must derive from jsi::Runtime"); - return plain_; - } - const Plain& plain() const { - return plain_; - } - - Value evaluateJavaScript( - const std::shared_ptr& buffer, - const std::string& sourceURL) override { - return plain().evaluateJavaScript(buffer, sourceURL); - } - std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, - std::string sourceURL) override { - return plain().prepareJavaScript(buffer, std::move(sourceURL)); - } - Value evaluatePreparedJavaScript( - const std::shared_ptr& js) override { - return plain().evaluatePreparedJavaScript(js); - } - void queueMicrotask(const jsi::Function& callback) override { - return plain().queueMicrotask(callback); - } - bool drainMicrotasks(int maxMicrotasksHint) override { - return plain().drainMicrotasks(maxMicrotasksHint); - } - Object global() override { - return plain().global(); - } - std::string description() override { - return plain().description(); - }; - bool isInspectable() override { - return plain().isInspectable(); - }; - Instrumentation& instrumentation() override { - return *this; - } - - protected: - // plain is generally going to be a reference to an object managed - // by a derived class. We cache it here so this class can be - // concrete, and avoid making virtual calls to find the plain - // Runtime. Note that the ctor and dtor do not access through the - // reference, so passing a reference to an object before its - // lifetime has started is ok. - RuntimeDecorator(Plain& plain) : plain_(plain) {} - - Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { - return plain_.cloneSymbol(pv); - }; - Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { - return plain_.cloneBigInt(pv); - }; - Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { - return plain_.cloneString(pv); - }; - Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { - return plain_.cloneObject(pv); - }; - Runtime::PointerValue* clonePropNameID( - const Runtime::PointerValue* pv) override { - return plain_.clonePropNameID(pv); - }; - - PropNameID createPropNameIDFromAscii(const char* str, size_t length) - override { - return plain_.createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) - override { - return plain_.createPropNameIDFromUtf8(utf8, length); - }; - PropNameID createPropNameIDFromString(const String& str) override { - return plain_.createPropNameIDFromString(str); - }; - PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) - override { - return plain_.createPropNameIDFromUtf16(utf16, length); - } - PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { - return plain_.createPropNameIDFromSymbol(sym); - }; - std::string utf8(const PropNameID& id) override { - return plain_.utf8(id); - }; - bool compare(const PropNameID& a, const PropNameID& b) override { - return plain_.compare(a, b); - }; - - std::string symbolToString(const Symbol& sym) override { - return plain_.symbolToString(sym); - } - - BigInt createBigIntFromInt64(int64_t value) override { - return plain_.createBigIntFromInt64(value); - } - BigInt createBigIntFromUint64(uint64_t value) override { - return plain_.createBigIntFromUint64(value); - } - bool bigintIsInt64(const BigInt& b) override { - return plain_.bigintIsInt64(b); - } - bool bigintIsUint64(const BigInt& b) override { - return plain_.bigintIsUint64(b); - } - uint64_t truncate(const BigInt& b) override { - return plain_.truncate(b); - } - String bigintToString(const BigInt& bigint, int radix) override { - return plain_.bigintToString(bigint, radix); - } - - String createStringFromAscii(const char* str, size_t length) override { - return plain_.createStringFromAscii(str, length); - }; - String createStringFromUtf8(const uint8_t* utf8, size_t length) override { - return plain_.createStringFromUtf8(utf8, length); - }; - String createStringFromUtf16(const char16_t* utf16, size_t length) override { - return plain_.createStringFromUtf16(utf16, length); - } - std::string utf8(const String& s) override { - return plain_.utf8(s); - } - - std::u16string utf16(const String& str) override { - return plain_.utf16(str); - } - std::u16string utf16(const PropNameID& sym) override { - return plain_.utf16(sym); - } - - void getStringData( - const jsi::String& str, - void* ctx, - void ( - *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { - plain_.getStringData(str, ctx, cb); - } - - void getPropNameIdData( - const jsi::PropNameID& sym, - void* ctx, - void ( - *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { - plain_.getPropNameIdData(sym, ctx, cb); - } - - Object createObjectWithPrototype(const Value& prototype) override { - return plain_.createObjectWithPrototype(prototype); - } - - Object createObject() override { - return plain_.createObject(); - }; - - Object createObject(std::shared_ptr ho) override { - return plain_.createObject( - std::make_shared(*this, std::move(ho))); - }; - std::shared_ptr getHostObject(const jsi::Object& o) override { - std::shared_ptr dho = plain_.getHostObject(o); - return static_cast(*dho).plainHO_; - }; - HostFunctionType& getHostFunction(const jsi::Function& f) override { - HostFunctionType& dhf = plain_.getHostFunction(f); - // This will fail if a cpp file including this header is not compiled - // with RTTI. - return dhf.target()->plainHF_; - }; - - bool hasNativeState(const Object& o) override { - return plain_.hasNativeState(o); - } - std::shared_ptr getNativeState(const Object& o) override { - return plain_.getNativeState(o); - } - void setNativeState(const Object& o, std::shared_ptr state) - override { - plain_.setNativeState(o, state); - } - - void setExternalMemoryPressure(const Object& obj, size_t amt) override { - plain_.setExternalMemoryPressure(obj, amt); - } - - void setPrototypeOf(const Object& object, const Value& prototype) override { - plain_.setPrototypeOf(object, prototype); - } - - Value getPrototypeOf(const Object& object) override { - return plain_.getPrototypeOf(object); - } - - Value getProperty(const Object& o, const PropNameID& name) override { - return plain_.getProperty(o, name); - }; - Value getProperty(const Object& o, const String& name) override { - return plain_.getProperty(o, name); - }; - bool hasProperty(const Object& o, const PropNameID& name) override { - return plain_.hasProperty(o, name); - }; - bool hasProperty(const Object& o, const String& name) override { - return plain_.hasProperty(o, name); - }; - void setPropertyValue( - const Object& o, - const PropNameID& name, - const Value& value) override { - plain_.setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, const Value& value) - override { - plain_.setPropertyValue(o, name, value); - }; - - bool isArray(const Object& o) const override { - return plain_.isArray(o); - }; - bool isArrayBuffer(const Object& o) const override { - return plain_.isArrayBuffer(o); - }; - bool isFunction(const Object& o) const override { - return plain_.isFunction(o); - }; - bool isHostObject(const jsi::Object& o) const override { - return plain_.isHostObject(o); - }; - bool isHostFunction(const jsi::Function& f) const override { - return plain_.isHostFunction(f); - }; - Array getPropertyNames(const Object& o) override { - return plain_.getPropertyNames(o); - }; - - WeakObject createWeakObject(const Object& o) override { - return plain_.createWeakObject(o); - }; - Value lockWeakObject(const WeakObject& wo) override { - return plain_.lockWeakObject(wo); - }; - - Array createArray(size_t length) override { - return plain_.createArray(length); - }; - ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) override { - return plain_.createArrayBuffer(std::move(buffer)); - }; - size_t size(const Array& a) override { - return plain_.size(a); - }; - size_t size(const ArrayBuffer& ab) override { - return plain_.size(ab); - }; - uint8_t* data(const ArrayBuffer& ab) override { - return plain_.data(ab); - }; - Value getValueAtIndex(const Array& a, size_t i) override { - return plain_.getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) - override { - plain_.setValueAtIndexImpl(a, i, value); - }; - - Function createFunctionFromHostFunction( - const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { - return plain_.createFunctionFromHostFunction( - name, paramCount, DecoratedHostFunction(*this, std::move(func))); - }; - Value call( - const Function& f, - const Value& jsThis, - const Value* args, - size_t count) override { - return plain_.call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, size_t count) - override { - return plain_.callAsConstructor(f, args, count); - }; - - // Private data for managing scopes. - Runtime::ScopeState* pushScope() override { - return plain_.pushScope(); - } - void popScope(Runtime::ScopeState* ss) override { - plain_.popScope(ss); - } - - bool strictEquals(const Symbol& a, const Symbol& b) const override { - return plain_.strictEquals(a, b); - }; - bool strictEquals(const BigInt& a, const BigInt& b) const override { - return plain_.strictEquals(a, b); - }; - bool strictEquals(const String& a, const String& b) const override { - return plain_.strictEquals(a, b); - }; - bool strictEquals(const Object& a, const Object& b) const override { - return plain_.strictEquals(a, b); - }; - - bool instanceOf(const Object& o, const Function& f) override { - return plain_.instanceOf(o, f); - }; - - // jsi::Instrumentation methods - - std::string getRecordedGCStats() override { - return plain().instrumentation().getRecordedGCStats(); - } - - std::unordered_map getHeapInfo( - bool includeExpensive) override { - return plain().instrumentation().getHeapInfo(includeExpensive); - } - - void collectGarbage(std::string cause) override { - plain().instrumentation().collectGarbage(std::move(cause)); - } - - void startTrackingHeapObjectStackTraces( - std::function)> callback) override { - plain().instrumentation().startTrackingHeapObjectStackTraces( - std::move(callback)); - } - - void stopTrackingHeapObjectStackTraces() override { - plain().instrumentation().stopTrackingHeapObjectStackTraces(); - } - - void startHeapSampling(size_t samplingInterval) override { - plain().instrumentation().startHeapSampling(samplingInterval); - } - - void stopHeapSampling(std::ostream& os) override { - plain().instrumentation().stopHeapSampling(os); - } - - void createSnapshotToFile( - const std::string& path, - const HeapSnapshotOptions& options) override { - plain().instrumentation().createSnapshotToFile(path, options); - } - - void createSnapshotToStream( - std::ostream& os, - const HeapSnapshotOptions& options) override { - plain().instrumentation().createSnapshotToStream(os, options); - } - - std::string flushAndDisableBridgeTrafficTrace() override { - return const_cast(plain()) - .instrumentation() - .flushAndDisableBridgeTrafficTrace(); - } - - void writeBasicBlockProfileTraceToFile( - const std::string& fileName) const override { - const_cast(plain()) - .instrumentation() - .writeBasicBlockProfileTraceToFile(fileName); - } - - /// Dump external profiler symbols to the given file name. - void dumpProfilerSymbolsToFile(const std::string& fileName) const override { - const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( - fileName); - } - - private: - Plain& plain_; -}; - -namespace detail { - -// This metaprogramming allows the With type's methods to be -// optional. - -template -struct BeforeCaller { - static void before(T&) {} -}; - -template -struct AfterCaller { - static void after(T&) {} -}; - -// decltype((void)&...) is either SFINAE, or void. -// So, if SFINAE does not happen for T, then this specialization exists -// for BeforeCaller, and always applies. If not, only the -// default above exists, and that is used instead. -template -struct BeforeCaller { - static void before(T& t) { - t.before(); - } -}; - -template -struct AfterCaller { - static void after(T& t) { - t.after(); - } -}; - -// It's possible to use multiple decorators by nesting -// WithRuntimeDecorator<...>, but this specialization allows use of -// std::tuple of decorator classes instead. See testlib.cpp for an -// example. -template -struct BeforeCaller> { - static void before(std::tuple& tuple) { - all_before<0, T...>(tuple); - } - - private: - template - static void all_before(std::tuple& tuple) { - detail::BeforeCaller::before(std::get(tuple)); - all_before(tuple); - } - - template - static void all_before(std::tuple&) {} -}; - -template -struct AfterCaller> { - static void after(std::tuple& tuple) { - all_after<0, T...>(tuple); - } - - private: - template - static void all_after(std::tuple& tuple) { - all_after(tuple); - detail::AfterCaller::after(std::get(tuple)); - } - - template - static void all_after(std::tuple&) {} -}; - -} // namespace detail - -// A decorator which implements an around idiom. A With instance is -// RAII constructed before each call to the undecorated class; the -// ctor is passed a single argument of type WithArg&. Plain and Base -// are used as in the base class. -template -class WithRuntimeDecorator : public RuntimeDecorator { - public: - using RD = RuntimeDecorator; - - // The reference arguments to the ctor are stored, but not used by - // the ctor, and there is no ctor, so they can be passed members of - // the derived class. - WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {} - - Value evaluateJavaScript( - const std::shared_ptr& buffer, - const std::string& sourceURL) override { - Around around{with_}; - return RD::evaluateJavaScript(buffer, sourceURL); - } - std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, - std::string sourceURL) override { - Around around{with_}; - return RD::prepareJavaScript(buffer, std::move(sourceURL)); - } - Value evaluatePreparedJavaScript( - const std::shared_ptr& js) override { - Around around{with_}; - return RD::evaluatePreparedJavaScript(js); - } - void queueMicrotask(const Function& callback) override { - Around around{with_}; - RD::queueMicrotask(callback); - } - bool drainMicrotasks(int maxMicrotasksHint) override { - Around around{with_}; - return RD::drainMicrotasks(maxMicrotasksHint); - } - Object global() override { - Around around{with_}; - return RD::global(); - } - std::string description() override { - Around around{with_}; - return RD::description(); - }; - bool isInspectable() override { - Around around{with_}; - return RD::isInspectable(); - }; - - // The jsi:: prefix is necessary because MSVC compiler complains C2247: - // Instrumentation is not accessible because RuntimeDecorator uses private - // to inherit from Instrumentation. - // TODO(T40821815) Consider removing this workaround when updating MSVC - jsi::Instrumentation& instrumentation() override { - Around around{with_}; - return RD::instrumentation(); - } - - protected: - Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneSymbol(pv); - }; - Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneBigInt(pv); - }; - Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneString(pv); - }; - Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneObject(pv); - }; - Runtime::PointerValue* clonePropNameID( - const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::clonePropNameID(pv); - }; - - PropNameID createPropNameIDFromAscii(const char* str, size_t length) - override { - Around around{with_}; - return RD::createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) - override { - Around around{with_}; - return RD::createPropNameIDFromUtf8(utf8, length); - }; - PropNameID createPropNameIDFromUtf16(const char16_t* utf16, size_t length) - override { - Around around{with_}; - return RD::createPropNameIDFromUtf16(utf16, length); - } - PropNameID createPropNameIDFromString(const String& str) override { - Around around{with_}; - return RD::createPropNameIDFromString(str); - }; - PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { - Around around{with_}; - return RD::createPropNameIDFromSymbol(sym); - }; - std::string utf8(const PropNameID& id) override { - Around around{with_}; - return RD::utf8(id); - }; - bool compare(const PropNameID& a, const PropNameID& b) override { - Around around{with_}; - return RD::compare(a, b); - }; - - std::string symbolToString(const Symbol& sym) override { - Around around{with_}; - return RD::symbolToString(sym); - }; - - BigInt createBigIntFromInt64(int64_t i) override { - Around around{with_}; - return RD::createBigIntFromInt64(i); - }; - BigInt createBigIntFromUint64(uint64_t i) override { - Around around{with_}; - return RD::createBigIntFromUint64(i); - }; - bool bigintIsInt64(const BigInt& bi) override { - Around around{with_}; - return RD::bigintIsInt64(bi); - }; - bool bigintIsUint64(const BigInt& bi) override { - Around around{with_}; - return RD::bigintIsUint64(bi); - }; - uint64_t truncate(const BigInt& bi) override { - Around around{with_}; - return RD::truncate(bi); - }; - String bigintToString(const BigInt& bi, int i) override { - Around around{with_}; - return RD::bigintToString(bi, i); - }; - - String createStringFromAscii(const char* str, size_t length) override { - Around around{with_}; - return RD::createStringFromAscii(str, length); - }; - String createStringFromUtf8(const uint8_t* utf8, size_t length) override { - Around around{with_}; - return RD::createStringFromUtf8(utf8, length); - }; - String createStringFromUtf16(const char16_t* utf16, size_t length) override { - Around around{with_}; - return RD::createStringFromUtf16(utf16, length); - } - std::string utf8(const String& s) override { - Around around{with_}; - return RD::utf8(s); - } - - std::u16string utf16(const String& str) override { - Around around{with_}; - return RD::utf16(str); - } - std::u16string utf16(const PropNameID& sym) override { - Around around{with_}; - return RD::utf16(sym); - } - - void getStringData( - const jsi::String& str, - void* ctx, - void ( - *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { - Around around{with_}; - RD::getStringData(str, ctx, cb); - } - - void getPropNameIdData( - const jsi::PropNameID& sym, - void* ctx, - void ( - *cb)(void* ctx, bool ascii, const void* data, size_t num)) override { - Around around{with_}; - RD::getPropNameIdData(sym, ctx, cb); - } - - Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override { - Around around{with_}; - return RD::createValueFromJsonUtf8(json, length); - }; - - Object createObjectWithPrototype(const Value& prototype) override { - Around around{with_}; - return RD::createObjectWithPrototype(prototype); - } - - Object createObject() override { - Around around{with_}; - return RD::createObject(); - }; - Object createObject(std::shared_ptr ho) override { - Around around{with_}; - return RD::createObject(std::move(ho)); - }; - std::shared_ptr getHostObject(const jsi::Object& o) override { - Around around{with_}; - return RD::getHostObject(o); - }; - HostFunctionType& getHostFunction(const jsi::Function& f) override { - Around around{with_}; - return RD::getHostFunction(f); - }; - - bool hasNativeState(const Object& o) override { - Around around{with_}; - return RD::hasNativeState(o); - }; - std::shared_ptr getNativeState(const Object& o) override { - Around around{with_}; - return RD::getNativeState(o); - }; - void setNativeState(const Object& o, std::shared_ptr state) - override { - Around around{with_}; - RD::setNativeState(o, state); - }; - - void setPrototypeOf(const Object& object, const Value& prototype) override { - Around around{with_}; - RD::setPrototypeOf(object, prototype); - } - - Value getPrototypeOf(const Object& object) override { - Around around{with_}; - return RD::getPrototypeOf(object); - } - - Value getProperty(const Object& o, const PropNameID& name) override { - Around around{with_}; - return RD::getProperty(o, name); - }; - Value getProperty(const Object& o, const String& name) override { - Around around{with_}; - return RD::getProperty(o, name); - }; - bool hasProperty(const Object& o, const PropNameID& name) override { - Around around{with_}; - return RD::hasProperty(o, name); - }; - bool hasProperty(const Object& o, const String& name) override { - Around around{with_}; - return RD::hasProperty(o, name); - }; - void setPropertyValue( - const Object& o, - const PropNameID& name, - const Value& value) override { - Around around{with_}; - RD::setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, const Value& value) - override { - Around around{with_}; - RD::setPropertyValue(o, name, value); - }; - - bool isArray(const Object& o) const override { - Around around{with_}; - return RD::isArray(o); - }; - bool isArrayBuffer(const Object& o) const override { - Around around{with_}; - return RD::isArrayBuffer(o); - }; - bool isFunction(const Object& o) const override { - Around around{with_}; - return RD::isFunction(o); - }; - bool isHostObject(const jsi::Object& o) const override { - Around around{with_}; - return RD::isHostObject(o); - }; - bool isHostFunction(const jsi::Function& f) const override { - Around around{with_}; - return RD::isHostFunction(f); - }; - Array getPropertyNames(const Object& o) override { - Around around{with_}; - return RD::getPropertyNames(o); - }; - - WeakObject createWeakObject(const Object& o) override { - Around around{with_}; - return RD::createWeakObject(o); - }; - Value lockWeakObject(const WeakObject& wo) override { - Around around{with_}; - return RD::lockWeakObject(wo); - }; - - Array createArray(size_t length) override { - Around around{with_}; - return RD::createArray(length); - }; - ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) override { - return RD::createArrayBuffer(std::move(buffer)); - }; - size_t size(const Array& a) override { - Around around{with_}; - return RD::size(a); - }; - size_t size(const ArrayBuffer& ab) override { - Around around{with_}; - return RD::size(ab); - }; - uint8_t* data(const ArrayBuffer& ab) override { - Around around{with_}; - return RD::data(ab); - }; - Value getValueAtIndex(const Array& a, size_t i) override { - Around around{with_}; - return RD::getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) - override { - Around around{with_}; - RD::setValueAtIndexImpl(a, i, value); - }; - - Function createFunctionFromHostFunction( - const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { - Around around{with_}; - return RD::createFunctionFromHostFunction( - name, paramCount, std::move(func)); - }; - Value call( - const Function& f, - const Value& jsThis, - const Value* args, - size_t count) override { - Around around{with_}; - return RD::call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, size_t count) - override { - Around around{with_}; - return RD::callAsConstructor(f, args, count); - }; - - // Private data for managing scopes. - Runtime::ScopeState* pushScope() override { - Around around{with_}; - return RD::pushScope(); - } - void popScope(Runtime::ScopeState* ss) override { - Around around{with_}; - RD::popScope(ss); - } - - bool strictEquals(const Symbol& a, const Symbol& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - bool strictEquals(const BigInt& a, const BigInt& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - - bool strictEquals(const String& a, const String& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - bool strictEquals(const Object& a, const Object& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - - bool instanceOf(const Object& o, const Function& f) override { - Around around{with_}; - return RD::instanceOf(o, f); - }; - - void setExternalMemoryPressure(const jsi::Object& obj, size_t amount) - override { - Around around{with_}; - RD::setExternalMemoryPressure(obj, amount); - }; - - private: - // Wrap an RAII type around With& to guarantee after always happens. - struct Around { - Around(With& with) : with_(with) { - detail::BeforeCaller::before(with_); - } - ~Around() { - detail::AfterCaller::after(with_); - } - - With& with_; - }; - - With& with_; -}; - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/jsi/instrumentation.h b/NativeScript/napi/hermes/include/jsi/instrumentation.h deleted file mode 100644 index 726858ccd..000000000 --- a/NativeScript/napi/hermes/include/jsi/instrumentation.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -namespace facebook { -namespace jsi { - -/// Methods for starting and collecting instrumentation, an \c Instrumentation -/// instance is associated with a particular \c Runtime instance, which it -/// controls the instrumentation of. -/// None of these functions should return newly created jsi values, nor should -/// it modify the values of any jsi values in the heap (although GCs are fine). -class JSI_EXPORT Instrumentation { - public: - /// Additional options controlling what to include when capturing a heap - /// snapshot. - struct HeapSnapshotOptions { - bool captureNumericValue{false}; - }; - - virtual ~Instrumentation() = default; - - /// Returns GC statistics as a JSON-encoded string, with an object containing - /// "type" and "version" fields outermost. "type" is a string, unique to a - /// particular implementation of \c jsi::Instrumentation, and "version" is a - /// number to indicate any revision to that implementation and its output - /// format. - /// - /// \pre This call can only be made on the instrumentation instance of a - /// runtime initialised to collect GC statistics. - /// - /// \post All cumulative measurements mentioned in the output are accumulated - /// across the entire lifetime of the Runtime. - /// - /// \return the GC statistics collected so far, as a JSON-encoded string. - virtual std::string getRecordedGCStats() = 0; - - /// Request statistics about the current state of the runtime's heap. This - /// function can be called at any time, and should produce information that is - /// correct at the instant it is called (i.e, not stale). - /// - /// \return a map from a string key to a number associated with that - /// statistic. - virtual std::unordered_map getHeapInfo( - bool includeExpensive) = 0; - - /// Perform a full garbage collection. - /// \param cause The cause of this collection, as it should be reported in - /// logs. - virtual void collectGarbage(std::string cause) = 0; - - /// A HeapStatsUpdate is a tuple of the fragment index, the number of objects - /// in that fragment, and the number of bytes used by those objects. - /// A "fragment" is a view of all objects allocated within a time slice. - using HeapStatsUpdate = std::tuple; - - /// Start capturing JS stack-traces for all JS heap allocated objects. These - /// can be accessed via \c ::createSnapshotToFile(). - /// \param fragmentCallback If present, invoke this callback every so often - /// with the most recently seen object ID, and a list of fragments that have - /// been updated. This callback will be invoked on the same thread that the - /// runtime is using. - virtual void startTrackingHeapObjectStackTraces( - std::function stats)> fragmentCallback) = 0; - - /// Stop capture JS stack-traces for JS heap allocated objects. - virtual void stopTrackingHeapObjectStackTraces() = 0; - - /// Start a heap sampling profiler that will sample heap allocations, and the - /// stack trace they were allocated at. Reports a summary of which functions - /// allocated the most. - /// \param samplingInterval The number of bytes allocated to wait between - /// samples. This will be used as the expected value of a poisson - /// distribution. - virtual void startHeapSampling(size_t samplingInterval) = 0; - - /// Turns off the heap sampling profiler previously enabled via - /// \c startHeapSampling. Writes the output of the sampling heap profiler to - /// \p os. The output is a JSON formatted string. - virtual void stopHeapSampling(std::ostream& os) = 0; - - /// Captures the heap to a file - /// - /// \param path to save the heap capture. - /// \param options additional options for what to capture. - virtual void createSnapshotToFile( - const std::string& path, - const HeapSnapshotOptions& options = {false}) = 0; - - /// Captures the heap to an output stream - /// - /// \param os output stream to write to. - /// \param options additional options for what to capture. - virtual void createSnapshotToStream( - std::ostream& os, - const HeapSnapshotOptions& options = {false}) = 0; - - /// If the runtime has been created to trace to a temp file, flush - /// any unwritten parts of the trace of bridge traffic to the file, - /// and return the name of the file. Otherwise, return the empty string. - /// Tracing is disabled after this call. - virtual std::string flushAndDisableBridgeTrafficTrace() = 0; - - /// Write basic block profile trace to the given file name. - virtual void writeBasicBlockProfileTraceToFile( - const std::string& fileName) const = 0; - - /// Dump external profiler symbols to the given file name. - virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; -}; - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/jsi/jsi-inl.h b/NativeScript/napi/hermes/include/jsi/jsi-inl.h deleted file mode 100644 index 6076c4955..000000000 --- a/NativeScript/napi/hermes/include/jsi/jsi-inl.h +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -namespace facebook { -namespace jsi { -namespace detail { - -inline Value toValue(Runtime&, std::nullptr_t) { - return Value::null(); -} -inline Value toValue(Runtime&, bool b) { - return Value(b); -} -inline Value toValue(Runtime&, double d) { - return Value(d); -} -inline Value toValue(Runtime&, float f) { - return Value(static_cast(f)); -} -inline Value toValue(Runtime&, int i) { - return Value(i); -} -inline Value toValue(Runtime& runtime, const char* str) { - return String::createFromAscii(runtime, str); -} -inline Value toValue(Runtime& runtime, const std::string& str) { - return String::createFromUtf8(runtime, str); -} -template -inline Value toValue(Runtime& runtime, const T& other) { - static_assert( - std::is_base_of::value, - "This type cannot be converted to Value"); - return Value(runtime, other); -} -inline Value toValue(Runtime& runtime, const Value& value) { - return Value(runtime, value); -} -inline Value&& toValue(Runtime&, Value&& value) { - return std::move(value); -} - -inline PropNameID toPropNameID(Runtime& runtime, const char* name) { - return PropNameID::forAscii(runtime, name); -} -inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) { - return PropNameID::forUtf8(runtime, name); -} -inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) { - return std::move(name); -} - -/// Helper to throw while still compiling with exceptions turned off. -template -[[noreturn]] inline void throwOrDie(Args&&... args) { - std::rethrow_exception( - std::make_exception_ptr(E{std::forward(args)...})); -} - -} // namespace detail - -template -inline T Runtime::make(Runtime::PointerValue* pv) { - return T(pv); -} - -inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) { - return pointer.ptr_; -} - -inline const Runtime::PointerValue* Runtime::getPointerValue( - const jsi::Pointer& pointer) { - return pointer.ptr_; -} - -inline const Runtime::PointerValue* Runtime::getPointerValue( - const jsi::Value& value) { - return value.data_.pointer.ptr_; -} - -Value Object::getPrototype(Runtime& runtime) const { - return runtime.getPrototypeOf(*this); -} - -inline Value Object::getProperty(Runtime& runtime, const char* name) const { - return getProperty(runtime, String::createFromAscii(runtime, name)); -} - -inline Value Object::getProperty(Runtime& runtime, const String& name) const { - return runtime.getProperty(*this, name); -} - -inline Value Object::getProperty(Runtime& runtime, const PropNameID& name) - const { - return runtime.getProperty(*this, name); -} - -inline bool Object::hasProperty(Runtime& runtime, const char* name) const { - return hasProperty(runtime, String::createFromAscii(runtime, name)); -} - -inline bool Object::hasProperty(Runtime& runtime, const String& name) const { - return runtime.hasProperty(*this, name); -} - -inline bool Object::hasProperty(Runtime& runtime, const PropNameID& name) - const { - return runtime.hasProperty(*this, name); -} - -template -void Object::setProperty(Runtime& runtime, const char* name, T&& value) const { - setProperty( - runtime, String::createFromAscii(runtime, name), std::forward(value)); -} - -template -void Object::setProperty(Runtime& runtime, const String& name, T&& value) - const { - setPropertyValue( - runtime, name, detail::toValue(runtime, std::forward(value))); -} - -template -void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value) - const { - setPropertyValue( - runtime, name, detail::toValue(runtime, std::forward(value))); -} - -inline Array Object::getArray(Runtime& runtime) const& { - assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - return Array(runtime.cloneObject(ptr_)); -} - -inline Array Object::getArray(Runtime& runtime) && { - assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - Runtime::PointerValue* value = ptr_; - ptr_ = nullptr; - return Array(value); -} - -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& { - assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - return ArrayBuffer(runtime.cloneObject(ptr_)); -} - -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && { - assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - Runtime::PointerValue* value = ptr_; - ptr_ = nullptr; - return ArrayBuffer(value); -} - -inline Function Object::getFunction(Runtime& runtime) const& { - assert(runtime.isFunction(*this)); - return Function(runtime.cloneObject(ptr_)); -} - -inline Function Object::getFunction(Runtime& runtime) && { - assert(runtime.isFunction(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - Runtime::PointerValue* value = ptr_; - ptr_ = nullptr; - return Function(value); -} - -template -inline bool Object::isHostObject(Runtime& runtime) const { - return runtime.isHostObject(*this) && - std::dynamic_pointer_cast(runtime.getHostObject(*this)); -} - -template <> -inline bool Object::isHostObject(Runtime& runtime) const { - return runtime.isHostObject(*this); -} - -template -inline std::shared_ptr Object::getHostObject(Runtime& runtime) const { - assert(isHostObject(runtime)); - return std::static_pointer_cast(runtime.getHostObject(*this)); -} - -template -inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { - if (!isHostObject(runtime)) { - detail::throwOrDie( - "Object is not a HostObject of desired type"); - } - return std::static_pointer_cast(runtime.getHostObject(*this)); -} - -template <> -inline std::shared_ptr Object::getHostObject( - Runtime& runtime) const { - assert(runtime.isHostObject(*this)); - return runtime.getHostObject(*this); -} - -template -inline bool Object::hasNativeState(Runtime& runtime) const { - return runtime.hasNativeState(*this) && - std::dynamic_pointer_cast(runtime.getNativeState(*this)); -} - -template <> -inline bool Object::hasNativeState(Runtime& runtime) const { - return runtime.hasNativeState(*this); -} - -template -inline std::shared_ptr Object::getNativeState(Runtime& runtime) const { - assert(hasNativeState(runtime)); - return std::static_pointer_cast(runtime.getNativeState(*this)); -} - -inline void Object::setNativeState( - Runtime& runtime, - std::shared_ptr state) const { - runtime.setNativeState(*this, state); -} - -inline void Object::setExternalMemoryPressure(Runtime& runtime, size_t amt) - const { - runtime.setExternalMemoryPressure(*this, amt); -} - -inline Array Object::getPropertyNames(Runtime& runtime) const { - return runtime.getPropertyNames(*this); -} - -inline Value WeakObject::lock(Runtime& runtime) const { - return runtime.lockWeakObject(*this); -} - -template -void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const { - setValueAtIndexImpl( - runtime, i, detail::toValue(runtime, std::forward(value))); -} - -inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const { - return runtime.getValueAtIndex(*this, i); -} - -inline Function Function::createFromHostFunction( - Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func) { - return runtime.createFunctionFromHostFunction( - name, paramCount, std::move(func)); -} - -inline Value Function::call(Runtime& runtime, const Value* args, size_t count) - const { - return runtime.call(*this, Value::undefined(), args, count); -} - -inline Value Function::call(Runtime& runtime, std::initializer_list args) - const { - return call(runtime, args.begin(), args.size()); -} - -template -inline Value Function::call(Runtime& runtime, Args&&... args) const { - // A more awesome version of this would be able to create raw values - // which can be used directly without wrapping and unwrapping, but - // this will do for now. - return call(runtime, {detail::toValue(runtime, std::forward(args))...}); -} - -inline Value Function::callWithThis( - Runtime& runtime, - const Object& jsThis, - const Value* args, - size_t count) const { - return runtime.call(*this, Value(runtime, jsThis), args, count); -} - -inline Value Function::callWithThis( - Runtime& runtime, - const Object& jsThis, - std::initializer_list args) const { - return callWithThis(runtime, jsThis, args.begin(), args.size()); -} - -template -inline Value Function::callWithThis( - Runtime& runtime, - const Object& jsThis, - Args&&... args) const { - // A more awesome version of this would be able to create raw values - // which can be used directly without wrapping and unwrapping, but - // this will do for now. - return callWithThis( - runtime, jsThis, {detail::toValue(runtime, std::forward(args))...}); -} - -template -inline Array Array::createWithElements(Runtime& runtime, Args&&... args) { - return createWithElements( - runtime, {detail::toValue(runtime, std::forward(args))...}); -} - -template -inline std::vector PropNameID::names( - Runtime& runtime, - Args&&... args) { - return names({detail::toPropNameID(runtime, std::forward(args))...}); -} - -template -inline std::vector PropNameID::names( - PropNameID (&&propertyNames)[N]) { - std::vector result; - result.reserve(N); - for (auto& name : propertyNames) { - result.push_back(std::move(name)); - } - return result; -} - -inline Value Function::callAsConstructor( - Runtime& runtime, - const Value* args, - size_t count) const { - return runtime.callAsConstructor(*this, args, count); -} - -inline Value Function::callAsConstructor( - Runtime& runtime, - std::initializer_list args) const { - return callAsConstructor(runtime, args.begin(), args.size()); -} - -template -inline Value Function::callAsConstructor(Runtime& runtime, Args&&... args) - const { - return callAsConstructor( - runtime, {detail::toValue(runtime, std::forward(args))...}); -} - -String BigInt::toString(Runtime& runtime, int radix) const { - return runtime.bigintToString(*this, radix); -} - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include/jsi/jsi.h b/NativeScript/napi/hermes/include/jsi/jsi.h deleted file mode 100644 index 4fbbaae30..000000000 --- a/NativeScript/napi/hermes/include/jsi/jsi.h +++ /dev/null @@ -1,1670 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#ifndef JSI_EXPORT -#ifdef _MSC_VER -#ifdef CREATE_SHARED_LIBRARY -#define JSI_EXPORT __declspec(dllexport) -#else -#define JSI_EXPORT -#endif // CREATE_SHARED_LIBRARY -#else // _MSC_VER -#define JSI_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(JSI_EXPORT) - -class FBJSRuntime; -namespace facebook { -namespace jsi { - -/// Base class for buffers of data or bytecode that need to be passed to the -/// runtime. The buffer is expected to be fully immutable, so the result of -/// size(), data(), and the contents of the pointer returned by data() must not -/// change after construction. -class JSI_EXPORT Buffer { - public: - virtual ~Buffer(); - virtual size_t size() const = 0; - virtual const uint8_t* data() const = 0; -}; - -class JSI_EXPORT StringBuffer : public Buffer { - public: - StringBuffer(std::string s) : s_(std::move(s)) {} - size_t size() const override { - return s_.size(); - } - const uint8_t* data() const override { - return reinterpret_cast(s_.data()); - } - - private: - std::string s_; -}; - -/// Base class for buffers of data that need to be passed to the runtime. The -/// result of size() and data() must not change after construction. However, the -/// region pointed to by data() may be modified by the user or the runtime. The -/// user must ensure that access to the contents of the buffer is properly -/// synchronised. -class JSI_EXPORT MutableBuffer { - public: - virtual ~MutableBuffer(); - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -/// PreparedJavaScript is a base class representing JavaScript which is in a -/// form optimized for execution, in a runtime-specific way. Construct one via -/// jsi::Runtime::prepareJavaScript(). -/// ** This is an experimental API that is subject to change. ** -class JSI_EXPORT PreparedJavaScript { - protected: - PreparedJavaScript() = default; - - public: - virtual ~PreparedJavaScript() = 0; -}; - -class Runtime; -class Pointer; -class PropNameID; -class Symbol; -class BigInt; -class String; -class Object; -class WeakObject; -class Array; -class ArrayBuffer; -class Function; -class Value; -class Instrumentation; -class Scope; -class JSIException; -class JSError; - -/// A function which has this type can be registered as a function -/// callable from JavaScript using Function::createFromHostFunction(). -/// When the function is called, args will point to the arguments, and -/// count will indicate how many arguments are passed. The function -/// can return a Value to the caller, or throw an exception. If a C++ -/// exception is thrown, a JS Error will be created and thrown into -/// JS; if the C++ exception extends std::exception, the Error's -/// message will be whatever what() returns. Note that it is undefined whether -/// HostFunctions may or may not be called in strict mode; that is `thisVal` -/// can be any value - it will not necessarily be coerced to an object or -/// or set to the global object. -using HostFunctionType = std::function< - Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>; - -/// An object which implements this interface can be registered as an -/// Object with the JS runtime. -class JSI_EXPORT HostObject { - public: - // The C++ object's dtor will be called when the GC finalizes this - // object. (This may be as late as when the Runtime is shut down.) - // You have no control over which thread it is called on. This will - // be called from inside the GC, so it is unsafe to do any VM - // operations which require a Runtime&. Derived classes' dtors - // should also avoid doing anything expensive. Calling the dtor on - // a jsi object is explicitly ok. If you want to do JS operations, - // or any nontrivial work, you should add it to a work queue, and - // manage it externally. - virtual ~HostObject(); - - // When JS wants a property with a given name from the HostObject, - // it will call this method. If it throws an exception, the call - // will throw a JS \c Error object. By default this returns undefined. - // \return the value for the property. - virtual Value get(Runtime&, const PropNameID& name); - - // When JS wants to set a property with a given name on the HostObject, - // it will call this method. If it throws an exception, the call will - // throw a JS \c Error object. By default this throws a type error exception - // mimicking the behavior of a frozen object in strict mode. - virtual void set(Runtime&, const PropNameID& name, const Value& value); - - // When JS wants a list of property names for the HostObject, it will - // call this method. If it throws an exception, the call will throw a - // JS \c Error object. The default implementation returns empty vector. - virtual std::vector getPropertyNames(Runtime& rt); -}; - -/// Native state (and destructor) that can be attached to any JS object -/// using setNativeState. -class JSI_EXPORT NativeState { - public: - virtual ~NativeState(); -}; - -/// Represents a JS runtime. Movable, but not copyable. Note that -/// this object may not be thread-aware, but cannot be used safely from -/// multiple threads at once. The application is responsible for -/// ensuring that it is used safely. This could mean using the -/// Runtime from a single thread, using a mutex, doing all work on a -/// serial queue, etc. This restriction applies to the methods of -/// this class, and any method in the API which take a Runtime& as an -/// argument. Destructors (all but ~Scope), operators, or other methods -/// which do not take Runtime& as an argument are safe to call from any -/// thread, but it is still forbidden to make write operations on a single -/// instance of any class from more than one thread. In addition, to -/// make shutdown safe, destruction of objects associated with the Runtime -/// must be destroyed before the Runtime is destroyed, or from the -/// destructor of a managed HostObject or HostFunction. Informally, this -/// means that the main source of unsafe behavior is to hold a jsi object -/// in a non-Runtime-managed object, and not clean it up before the Runtime -/// is shut down. If your lifecycle is such that avoiding this is hard, -/// you will probably need to do use your own locks. -class JSI_EXPORT Runtime { - public: - virtual ~Runtime(); - - /// Evaluates the given JavaScript \c buffer. \c sourceURL is used - /// to annotate the stack trace if there is an exception. The - /// contents may be utf8-encoded JS source code, or binary bytecode - /// whose format is specific to the implementation. If the input - /// format is unknown, or evaluation causes an error, a JSIException - /// will be thrown. - /// Note this function should ONLY be used when there isn't another means - /// through the JSI API. For example, it will be much slower to use this to - /// call a global function than using the JSI APIs to read the function - /// property from the global object and then calling it explicitly. - virtual Value evaluateJavaScript( - const std::shared_ptr& buffer, - const std::string& sourceURL) = 0; - - /// Prepares to evaluate the given JavaScript \c buffer by processing it into - /// a form optimized for execution. This may include pre-parsing, compiling, - /// etc. If the input is invalid (for example, cannot be parsed), a - /// JSIException will be thrown. The resulting object is tied to the - /// particular concrete type of Runtime from which it was created. It may be - /// used (via evaluatePreparedJavaScript) in any Runtime of the same concrete - /// type. - /// The PreparedJavaScript object may be passed to multiple VM instances, so - /// they can all share and benefit from the prepared script. - /// As with evaluateJavaScript(), using JavaScript code should be avoided - /// when the JSI API is sufficient. - virtual std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, - std::string sourceURL) = 0; - - /// Evaluates a PreparedJavaScript. If evaluation causes an error, a - /// JSIException will be thrown. - /// As with evaluateJavaScript(), using JavaScript code should be avoided - /// when the JSI API is sufficient. - virtual Value evaluatePreparedJavaScript( - const std::shared_ptr& js) = 0; - - /// Queues a microtask in the JavaScript VM internal Microtask (a.k.a. Job in - /// ECMA262) queue, to be executed when the host drains microtasks in - /// its event loop implementation. - /// - /// \param callback a function to be executed as a microtask. - virtual void queueMicrotask(const jsi::Function& callback) = 0; - - /// Drain the JavaScript VM internal Microtask (a.k.a. Job in ECMA262) queue. - /// - /// \param maxMicrotasksHint a hint to tell an implementation that it should - /// make a best effort not execute more than the given number. It's default - /// to -1 for infinity (unbounded execution). - /// \return true if the queue is drained or false if there is more work to do. - /// - /// When there were exceptions thrown from the execution of microtasks, - /// implementations shall discard the exceptional jobs. An implementation may - /// \throw a \c JSError object to signal the hosts to handle. In that case, an - /// implementation may or may not suspend the draining. - /// - /// Hosts may call this function again to resume the draining if it was - /// suspended due to either exceptions or the \p maxMicrotasksHint bound. - /// E.g. a host may repetitively invoke this function until the queue is - /// drained to implement the "microtask checkpoint" defined in WHATWG HTML - /// event loop: https://html.spec.whatwg.org/C#perform-a-microtask-checkpoint. - /// - /// Note that error propagation is only a concern if a host needs to implement - /// `queueMicrotask`, a recent API that allows enqueueing arbitrary functions - /// (hence may throw) as microtasks. Exceptions from ECMA-262 Promise Jobs are - /// handled internally to VMs and are never propagated to hosts. - /// - /// This API offers some queue management to hosts at its best effort due to - /// different behaviors and limitations imposed by different VMs and APIs. By - /// the time this is written, An implementation may swallow exceptions (JSC), - /// may not pause (V8), and may not support bounded executions. - virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0; - - /// \return the global object - virtual Object global() = 0; - - /// \return a short printable description of the instance. It should - /// at least include some human-readable indication of the runtime - /// implementation. This should only be used by logging, debugging, - /// and other developer-facing callers. - virtual std::string description() = 0; - - /// \return whether or not the underlying runtime supports debugging via the - /// Chrome remote debugging protocol. - /// - /// NOTE: the API for determining whether a runtime is debuggable and - /// registering a runtime with the debugger is still in flux, so please don't - /// use this API unless you know what you're doing. - virtual bool isInspectable() = 0; - - /// \return an interface to extract metrics from this \c Runtime. The default - /// implementation of this function returns an \c Instrumentation instance - /// which returns no metrics. - virtual Instrumentation& instrumentation(); - - /// Creates a Node-API environment. - /// \throw a \c JSINativeException if the runtime does not support Node-API. - /// \param apiVersion the version of Node-API to use. - /// \return the newly created Node-API environment. - virtual void* createNodeApiEnv(int32_t apiVersion); - - protected: - friend class Pointer; - friend class PropNameID; - friend class Symbol; - friend class BigInt; - friend class String; - friend class Object; - friend class WeakObject; - friend class Array; - friend class ArrayBuffer; - friend class Function; - friend class Value; - friend class Scope; - friend class JSError; - - // Potential optimization: avoid the cloneFoo() virtual dispatch, - // and instead just fix the number of fields, and copy them, since - // in practice they are trivially copyable. Sufficient use of - // rvalue arguments/methods would also reduce the number of clones. - - struct PointerValue { - virtual void invalidate() noexcept = 0; - - protected: - virtual ~PointerValue() = default; - }; - - virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0; - - virtual PropNameID createPropNameIDFromAscii( - const char* str, - size_t length) = 0; - virtual PropNameID createPropNameIDFromUtf8( - const uint8_t* utf8, - size_t length) = 0; - virtual PropNameID createPropNameIDFromUtf16( - const char16_t* utf16, - size_t length); - virtual PropNameID createPropNameIDFromString(const String& str) = 0; - virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; - virtual std::string utf8(const PropNameID&) = 0; - virtual bool compare(const PropNameID&, const PropNameID&) = 0; - - virtual std::string symbolToString(const Symbol&) = 0; - - virtual BigInt createBigIntFromInt64(int64_t) = 0; - virtual BigInt createBigIntFromUint64(uint64_t) = 0; - virtual bool bigintIsInt64(const BigInt&) = 0; - virtual bool bigintIsUint64(const BigInt&) = 0; - virtual uint64_t truncate(const BigInt&) = 0; - virtual String bigintToString(const BigInt&, int) = 0; - - virtual String createStringFromAscii(const char* str, size_t length) = 0; - virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; - virtual String createStringFromUtf16(const char16_t* utf16, size_t length); - virtual std::string utf8(const String&) = 0; - - // \return a \c Value created from a utf8-encoded JSON string. The default - // implementation creates a \c String and invokes JSON.parse. - virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length); - - virtual Object createObject() = 0; - virtual Object createObject(std::shared_ptr ho) = 0; - virtual std::shared_ptr getHostObject(const jsi::Object&) = 0; - virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; - - // Creates a new Object with the custom prototype - virtual Object createObjectWithPrototype(const Value& prototype); - - virtual bool hasNativeState(const jsi::Object&) = 0; - virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; - virtual void setNativeState( - const jsi::Object&, - std::shared_ptr state) = 0; - - virtual void setPrototypeOf(const Object& object, const Value& prototype); - virtual Value getPrototypeOf(const Object& object); - - virtual Value getProperty(const Object&, const PropNameID& name) = 0; - virtual Value getProperty(const Object&, const String& name) = 0; - virtual bool hasProperty(const Object&, const PropNameID& name) = 0; - virtual bool hasProperty(const Object&, const String& name) = 0; - virtual void setPropertyValue( - const Object&, - const PropNameID& name, - const Value& value) = 0; - virtual void - setPropertyValue(const Object&, const String& name, const Value& value) = 0; - - virtual bool isArray(const Object&) const = 0; - virtual bool isArrayBuffer(const Object&) const = 0; - virtual bool isFunction(const Object&) const = 0; - virtual bool isHostObject(const jsi::Object&) const = 0; - virtual bool isHostFunction(const jsi::Function&) const = 0; - virtual Array getPropertyNames(const Object&) = 0; - - virtual WeakObject createWeakObject(const Object&) = 0; - virtual Value lockWeakObject(const WeakObject&) = 0; - - virtual Array createArray(size_t length) = 0; - virtual ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) = 0; - virtual size_t size(const Array&) = 0; - virtual size_t size(const ArrayBuffer&) = 0; - virtual uint8_t* data(const ArrayBuffer&) = 0; - virtual Value getValueAtIndex(const Array&, size_t i) = 0; - virtual void - setValueAtIndexImpl(const Array&, size_t i, const Value& value) = 0; - - virtual Function createFunctionFromHostFunction( - const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) = 0; - virtual Value call( - const Function&, - const Value& jsThis, - const Value* args, - size_t count) = 0; - virtual Value - callAsConstructor(const Function&, const Value* args, size_t count) = 0; - - // Private data for managing scopes. - struct ScopeState; - virtual ScopeState* pushScope(); - virtual void popScope(ScopeState*); - - virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0; - virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0; - virtual bool strictEquals(const String& a, const String& b) const = 0; - virtual bool strictEquals(const Object& a, const Object& b) const = 0; - - virtual bool instanceOf(const Object& o, const Function& f) = 0; - - /// See Object::setExternalMemoryPressure. - virtual void setExternalMemoryPressure( - const jsi::Object& obj, - size_t amount) = 0; - - virtual std::u16string utf16(const String& str); - virtual std::u16string utf16(const PropNameID& sym); - - /// Invokes the provided callback \p cb with the String content in \p str. - /// The callback must take in three arguments: bool ascii, const void* data, - /// and size_t num, respectively. \p ascii indicates whether the \p data - /// passed to the callback should be interpreted as a pointer to a sequence of - /// \p num ASCII characters or UTF16 characters. Depending on the internal - /// representation of the string, the function may invoke the callback - /// multiple times, with a different format on each invocation. The callback - /// must not access runtime functionality, as any operation on the runtime may - /// invalidate the data pointers. - virtual void getStringData( - const jsi::String& str, - void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, size_t num)); - - /// Invokes the provided callback \p cb with the PropNameID content in \p sym. - /// The callback must take in three arguments: bool ascii, const void* data, - /// and size_t num, respectively. \p ascii indicates whether the \p data - /// passed to the callback should be interpreted as a pointer to a sequence of - /// \p num ASCII characters or UTF16 characters. Depending on the internal - /// representation of the string, the function may invoke the callback - /// multiple times, with a different format on each invocation. The callback - /// must not access runtime functionality, as any operation on the runtime may - /// invalidate the data pointers. - virtual void getPropNameIdData( - const jsi::PropNameID& sym, - void* ctx, - void (*cb)(void* ctx, bool ascii, const void* data, size_t num)); - - // These exist so derived classes can access the private parts of - // Value, Symbol, String, and Object, which are all friends of Runtime. - template - static T make(PointerValue* pv); - static PointerValue* getPointerValue(Pointer& pointer); - static const PointerValue* getPointerValue(const Pointer& pointer); - static const PointerValue* getPointerValue(const Value& value); - - friend class ::FBJSRuntime; - template - friend class RuntimeDecorator; -}; - -// Base class for pointer-storing types. -class JSI_EXPORT Pointer { - protected: - explicit Pointer(Pointer&& other) noexcept : ptr_(other.ptr_) { - other.ptr_ = nullptr; - } - - ~Pointer() { - if (ptr_) { - ptr_->invalidate(); - } - } - - Pointer& operator=(Pointer&& other) noexcept; - - friend class Runtime; - friend class Value; - - explicit Pointer(Runtime::PointerValue* ptr) : ptr_(ptr) {} - - typename Runtime::PointerValue* ptr_; -}; - -/// Represents something that can be a JS property key. Movable, not copyable. -class JSI_EXPORT PropNameID : public Pointer { - public: - using Pointer::Pointer; - - PropNameID(Runtime& runtime, const PropNameID& other) - : Pointer(runtime.clonePropNameID(other.ptr_)) {} - - PropNameID(PropNameID&& other) = default; - PropNameID& operator=(PropNameID&& other) = default; - - /// Create a JS property name id from ascii values. The data is - /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) { - return runtime.createPropNameIDFromAscii(str, length); - } - - /// Create a property name id from a nul-terminated C ascii name. The data is - /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str) { - return forAscii(runtime, str, strlen(str)); - } - - /// Create a PropNameID from a C++ string. The string is copied. - static PropNameID forAscii(Runtime& runtime, const std::string& str) { - return forAscii(runtime, str.c_str(), str.size()); - } - - /// Create a PropNameID from utf8 values. The data is copied. - /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID - forUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) { - return runtime.createPropNameIDFromUtf8(utf8, length); - } - - /// Create a PropNameID from utf8-encoded octets stored in a - /// std::string. The string data is transformed and copied. - /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) { - return runtime.createPropNameIDFromUtf8( - reinterpret_cast(utf8.data()), utf8.size()); - } - - /// Given a series of UTF-16 encoded code units, create a PropNameId. The - /// input may contain unpaired surrogates, which will be interpreted as a code - /// point of the same value. - static PropNameID - forUtf16(Runtime& runtime, const char16_t* utf16, size_t length) { - return runtime.createPropNameIDFromUtf16(utf16, length); - } - - /// Given a series of UTF-16 encoded code units stored inside std::u16string, - /// create a PropNameId. The input may contain unpaired surrogates, which - /// will be interpreted as a code point of the same value. - static PropNameID forUtf16(Runtime& runtime, const std::u16string& str) { - return runtime.createPropNameIDFromUtf16(str.data(), str.size()); - } - - /// Create a PropNameID from a JS string. - static PropNameID forString(Runtime& runtime, const jsi::String& str) { - return runtime.createPropNameIDFromString(str); - } - - /// Create a PropNameID from a JS symbol. - static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) { - return runtime.createPropNameIDFromSymbol(sym); - } - - // Creates a vector of PropNameIDs constructed from given arguments. - template - static std::vector names(Runtime& runtime, Args&&... args); - - // Creates a vector of given PropNameIDs. - template - static std::vector names(PropNameID (&&propertyNames)[N]); - - /// Copies the data in a PropNameID as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { - return runtime.utf8(*this); - } - - /// Copies the data in a PropNameID as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { - return runtime.utf16(*this); - } - - /// Invokes the user provided callback to process the content in PropNameId. - /// The callback must take in three arguments: bool ascii, const void* data, - /// and size_t num, respectively. \p ascii indicates whether the \p data - /// passed to the callback should be interpreted as a pointer to a sequence of - /// \p num ASCII characters or UTF16 characters. The function may invoke the - /// callback multiple times, with a different format on each invocation. The - /// callback must not access runtime functionality, as any operation on the - /// runtime may invalidate the data pointers. - template - void getPropNameIdData(Runtime& runtime, CB& cb) const { - runtime.getPropNameIdData( - *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { - (*((CB*)ctx))(ascii, data, num); - }); - } - - static bool compare( - Runtime& runtime, - const jsi::PropNameID& a, - const jsi::PropNameID& b) { - return runtime.compare(a, b); - } - - friend class Runtime; - friend class Value; -}; - -/// Represents a JS Symbol (es6). Movable, not copyable. -/// TODO T40778724: this is a limited implementation sufficient for -/// the debugger not to crash when a Symbol is a property in an Object -/// or element in an array. Complete support for creating will come -/// later. -class JSI_EXPORT Symbol : public Pointer { - public: - using Pointer::Pointer; - - Symbol(Symbol&& other) = default; - Symbol& operator=(Symbol&& other) = default; - - /// \return whether a and b refer to the same symbol. - static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) { - return runtime.strictEquals(a, b); - } - - /// Converts a Symbol into a C++ string as JS .toString would. The output - /// will look like \c Symbol(description) . - std::string toString(Runtime& runtime) const { - return runtime.symbolToString(*this); - } - - friend class Runtime; - friend class Value; -}; - -/// Represents a JS BigInt. Movable, not copyable. -class JSI_EXPORT BigInt : public Pointer { - public: - using Pointer::Pointer; - - BigInt(BigInt&& other) = default; - BigInt& operator=(BigInt&& other) = default; - - /// Create a BigInt representing the signed 64-bit \p value. - static BigInt fromInt64(Runtime& runtime, int64_t value) { - return runtime.createBigIntFromInt64(value); - } - - /// Create a BigInt representing the unsigned 64-bit \p value. - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - return runtime.createBigIntFromUint64(value); - } - - /// \return whether a === b. - static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) { - return runtime.strictEquals(a, b); - } - - /// \returns This bigint truncated to a signed 64-bit integer. - int64_t getInt64(Runtime& runtime) const { - return runtime.truncate(*this); - } - - /// \returns Whether this bigint can be losslessly converted to int64_t. - bool isInt64(Runtime& runtime) const { - return runtime.bigintIsInt64(*this); - } - - /// \returns This bigint truncated to a signed 64-bit integer. Throws a - /// JSIException if the truncation is lossy. - int64_t asInt64(Runtime& runtime) const; - - /// \returns This bigint truncated to an unsigned 64-bit integer. - uint64_t getUint64(Runtime& runtime) const { - return runtime.truncate(*this); - } - - /// \returns Whether this bigint can be losslessly converted to uint64_t. - bool isUint64(Runtime& runtime) const { - return runtime.bigintIsUint64(*this); - } - - /// \returns This bigint truncated to an unsigned 64-bit integer. Throws a - /// JSIException if the truncation is lossy. - uint64_t asUint64(Runtime& runtime) const; - - /// \returns this BigInt converted to a String in base \p radix. Throws a - /// JSIException if radix is not in the [2, 36] range. - inline String toString(Runtime& runtime, int radix = 10) const; - - friend class Runtime; - friend class Value; -}; - -/// Represents a JS String. Movable, not copyable. -class JSI_EXPORT String : public Pointer { - public: - using Pointer::Pointer; - - String(String&& other) = default; - String& operator=(String&& other) = default; - - /// Create a JS string from ascii values. The string data is - /// copied. - static String - createFromAscii(Runtime& runtime, const char* str, size_t length) { - return runtime.createStringFromAscii(str, length); - } - - /// Create a JS string from a nul-terminated C ascii string. The - /// string data is copied. - static String createFromAscii(Runtime& runtime, const char* str) { - return createFromAscii(runtime, str, strlen(str)); - } - - /// Create a JS string from a C++ string. The string data is - /// copied. - static String createFromAscii(Runtime& runtime, const std::string& str) { - return createFromAscii(runtime, str.c_str(), str.size()); - } - - /// Create a JS string from utf8-encoded octets. The string data is - /// transformed and copied. Results are undefined if \p utf8 contains invalid - /// code points. - static String - createFromUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) { - return runtime.createStringFromUtf8(utf8, length); - } - - /// Create a JS string from utf8-encoded octets stored in a - /// std::string. The string data is transformed and copied. Results are - /// undefined if \p utf8 contains invalid code points. - static String createFromUtf8(Runtime& runtime, const std::string& utf8) { - return runtime.createStringFromUtf8( - reinterpret_cast(utf8.data()), utf8.length()); - } - - /// Given a series of UTF-16 encoded code units, create a JS String. The input - /// may contain unpaired surrogates, which will be interpreted as a code point - /// of the same value. - static String - createFromUtf16(Runtime& runtime, const char16_t* utf16, size_t length) { - return runtime.createStringFromUtf16(utf16, length); - } - - /// Given a series of UTF-16 encoded code units stored inside std::u16string, - /// create a JS String. The input may contain unpaired surrogates, which will - /// be interpreted as a code point of the same value. - static String createFromUtf16(Runtime& runtime, const std::u16string& utf16) { - return runtime.createStringFromUtf16(utf16.data(), utf16.length()); - } - - /// \return whether a and b contain the same characters. - static bool strictEquals(Runtime& runtime, const String& a, const String& b) { - return runtime.strictEquals(a, b); - } - - /// Copies the data in a JS string as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { - return runtime.utf8(*this); - } - - /// Copies the data in a JS string as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { - return runtime.utf16(*this); - } - - /// Invokes the user provided callback to process content in String. The - /// callback must take in three arguments: bool ascii, const void* data, and - /// size_t num, respectively. \p ascii indicates whether the \p data passed to - /// the callback should be interpreted as a pointer to a sequence of \p num - /// ASCII characters or UTF16 characters. The function may invoke the callback - /// multiple times, with a different format on each invocation. The callback - /// must not access runtime functionality, as any operation on the runtime may - /// invalidate the data pointers. - template - void getStringData(Runtime& runtime, CB& cb) const { - runtime.getStringData( - *this, &cb, [](void* ctx, bool ascii, const void* data, size_t num) { - (*((CB*)ctx))(ascii, data, num); - }); - } - - friend class Runtime; - friend class Value; -}; - -class Array; -class Function; - -/// Represents a JS Object. Movable, not copyable. -class JSI_EXPORT Object : public Pointer { - public: - using Pointer::Pointer; - - Object(Object&& other) = default; - Object& operator=(Object&& other) = default; - - /// Creates a new Object instance, like '{}' in JS. - Object(Runtime& runtime) : Object(runtime.createObject()) {} - - static Object createFromHostObject( - Runtime& runtime, - std::shared_ptr ho) { - return runtime.createObject(ho); - } - - /// Creates a new Object with the custom prototype - static Object create(Runtime& runtime, const Value& prototype) { - return runtime.createObjectWithPrototype(prototype); - } - - /// \return whether this and \c obj are the same JSObject or not. - static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { - return runtime.strictEquals(a, b); - } - - /// \return the result of `this instanceOf ctor` in JS. - bool instanceOf(Runtime& rt, const Function& ctor) const { - return rt.instanceOf(*this, ctor); - } - - /// Sets \p prototype as the prototype of the object. The prototype must be - /// either an Object or null. If the prototype was not set successfully, this - /// method will throw. - void setPrototype(Runtime& runtime, const Value& prototype) const { - return runtime.setPrototypeOf(*this, prototype); - } - - /// \return the prototype of the object - inline Value getPrototype(Runtime& runtime) const; - - /// \return the property of the object with the given ascii name. - /// If the name isn't a property on the object, returns the - /// undefined value. - Value getProperty(Runtime& runtime, const char* name) const; - - /// \return the property of the object with the String name. - /// If the name isn't a property on the object, returns the - /// undefined value. - Value getProperty(Runtime& runtime, const String& name) const; - - /// \return the property of the object with the given JS PropNameID - /// name. If the name isn't a property on the object, returns the - /// undefined value. - Value getProperty(Runtime& runtime, const PropNameID& name) const; - - /// \return true if and only if the object has a property with the - /// given ascii name. - bool hasProperty(Runtime& runtime, const char* name) const; - - /// \return true if and only if the object has a property with the - /// given String name. - bool hasProperty(Runtime& runtime, const String& name) const; - - /// \return true if and only if the object has a property with the - /// given PropNameID name. - bool hasProperty(Runtime& runtime, const PropNameID& name) const; - - /// Sets the property value from a Value or anything which can be - /// used to make one: nullptr_t, bool, double, int, const char*, - /// String, or Object. - template - void setProperty(Runtime& runtime, const char* name, T&& value) const; - - /// Sets the property value from a Value or anything which can be - /// used to make one: nullptr_t, bool, double, int, const char*, - /// String, or Object. - template - void setProperty(Runtime& runtime, const String& name, T&& value) const; - - /// Sets the property value from a Value or anything which can be - /// used to make one: nullptr_t, bool, double, int, const char*, - /// String, or Object. - template - void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const; - - /// \return true iff JS \c Array.isArray() would return \c true. If - /// so, then \c getArray() will succeed. - bool isArray(Runtime& runtime) const { - return runtime.isArray(*this); - } - - /// \return true iff the Object is an ArrayBuffer. If so, then \c - /// getArrayBuffer() will succeed. - bool isArrayBuffer(Runtime& runtime) const { - return runtime.isArrayBuffer(*this); - } - - /// \return true iff the Object is callable. If so, then \c - /// getFunction will succeed. - bool isFunction(Runtime& runtime) const { - return runtime.isFunction(*this); - } - - /// \return true iff the Object was initialized with \c createFromHostObject - /// and the HostObject passed is of type \c T. If returns \c true then - /// \c getHostObject will succeed. - template - bool isHostObject(Runtime& runtime) const; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) const&; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) &&; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will throw - /// JSIException. - Array asArray(Runtime& runtime) const&; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will throw - /// JSIException. - Array asArray(Runtime& runtime) &&; - - /// \return an ArrayBuffer instance which refers to the same underlying - /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) const&; - - /// \return an ArrayBuffer instance which refers to the same underlying - /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) &&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) const&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) &&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will throw - /// JSIException. - Function asFunction(Runtime& runtime) const&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will throw - /// JSIException. - Function asFunction(Runtime& runtime) &&; - - /// \return a shared_ptr which refers to the same underlying - /// \c HostObject that was used to create this object. If \c isHostObject - /// is false, this will assert. Note that this does a type check and will - /// assert if the underlying HostObject isn't of type \c T - template - std::shared_ptr getHostObject(Runtime& runtime) const; - - /// \return a shared_ptr which refers to the same underlying - /// \c HostObject that was used to create this object. If \c isHostObject - /// is false, this will throw. - template - std::shared_ptr asHostObject(Runtime& runtime) const; - - /// \return whether this object has native state of type T previously set by - /// \c setNativeState. - template - bool hasNativeState(Runtime& runtime) const; - - /// \return a shared_ptr to the state previously set by \c setNativeState. - /// If \c hasNativeState is false, this will assert. Note that this does a - /// type check and will assert if the native state isn't of type \c T - template - std::shared_ptr getNativeState(Runtime& runtime) const; - - /// Set the internal native state property of this object, overwriting any old - /// value. Creates a new shared_ptr to the object managed by \p state, which - /// will live until the value at this property becomes unreachable. - /// - /// Throws a type error if this object is a proxy or host object. - void setNativeState(Runtime& runtime, std::shared_ptr state) - const; - - /// \return same as \c getProperty(name).asObject(), except with - /// a better exception message. - Object getPropertyAsObject(Runtime& runtime, const char* name) const; - - /// \return similar to \c - /// getProperty(name).getObject().getFunction(), except it will - /// throw JSIException instead of asserting if the property is - /// not an object, or the object is not callable. - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - /// \return an Array consisting of all enumerable property names in - /// the object and its prototype chain. All values in the return - /// will be isString(). (This is probably not optimal, but it - /// works. I only need it in one place.) - Array getPropertyNames(Runtime& runtime) const; - - /// Inform the runtime that there is additional memory associated with a given - /// JavaScript object that is not visible to the GC. This can be used if an - /// object is known to retain some native memory, and may be used to guide - /// decisions about when to run garbage collection. - /// This method may be invoked multiple times on an object, and subsequent - /// calls will overwrite any previously set value. Once the object is garbage - /// collected, the associated external memory will be considered freed and may - /// no longer factor into GC decisions. - void setExternalMemoryPressure(Runtime& runtime, size_t amt) const; - - protected: - void setPropertyValue( - Runtime& runtime, - const String& name, - const Value& value) const { - return runtime.setPropertyValue(*this, name, value); - } - - void setPropertyValue( - Runtime& runtime, - const PropNameID& name, - const Value& value) const { - return runtime.setPropertyValue(*this, name, value); - } - - friend class Runtime; - friend class Value; -}; - -/// Represents a weak reference to a JS Object. If the only reference -/// to an Object are these, the object is eligible for GC. Method -/// names are inspired by C++ weak_ptr. Movable, not copyable. -class JSI_EXPORT WeakObject : public Pointer { - public: - using Pointer::Pointer; - - WeakObject(WeakObject&& other) = default; - WeakObject& operator=(WeakObject&& other) = default; - - /// Create a WeakObject from an Object. - WeakObject(Runtime& runtime, const Object& o) - : WeakObject(runtime.createWeakObject(o)) {} - - /// \return a Value representing the underlying Object if it is still valid; - /// otherwise returns \c undefined. Note that this method has nothing to do - /// with threads or concurrency. The name is based on std::weak_ptr::lock() - /// which serves a similar purpose. - Value lock(Runtime& runtime) const; - - friend class Runtime; -}; - -/// Represents a JS Object which can be efficiently used as an array -/// with integral indices. -class JSI_EXPORT Array : public Object { - public: - Array(Array&&) = default; - /// Creates a new Array instance, with \c length undefined elements. - Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {} - - Array& operator=(Array&&) = default; - - /// \return the size of the Array, according to its length property. - /// (C++ naming convention) - size_t size(Runtime& runtime) const { - return runtime.size(*this); - } - - /// \return the size of the Array, according to its length property. - /// (JS naming convention) - size_t length(Runtime& runtime) const { - return size(runtime); - } - - /// \return the property of the array at index \c i. If there is no - /// such property, returns the undefined value. If \c i is out of - /// range [ 0..\c length ] throws a JSIException. - Value getValueAtIndex(Runtime& runtime, size_t i) const; - - /// Sets the property of the array at index \c i. The argument - /// value behaves as with Object::setProperty(). If \c i is out of - /// range [ 0..\c length ] throws a JSIException. - template - void setValueAtIndex(Runtime& runtime, size_t i, T&& value) const; - - /// There is no current API for changing the size of an array once - /// created. We'll probably need that eventually. - - /// Creates a new Array instance from provided values - template - static Array createWithElements(Runtime&, Args&&... args); - - /// Creates a new Array instance from initializer list. - static Array createWithElements( - Runtime& runtime, - std::initializer_list elements); - - private: - friend class Object; - friend class Value; - friend class Runtime; - - void setValueAtIndexImpl(Runtime& runtime, size_t i, const Value& value) - const { - return runtime.setValueAtIndexImpl(*this, i, value); - } - - Array(Runtime::PointerValue* value) : Object(value) {} -}; - -/// Represents a JSArrayBuffer -class JSI_EXPORT ArrayBuffer : public Object { - public: - ArrayBuffer(ArrayBuffer&&) = default; - ArrayBuffer& operator=(ArrayBuffer&&) = default; - - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} - - /// \return the size of the ArrayBuffer storage. This is not affected by - /// overriding the byteLength property. - /// (C++ naming convention) - size_t size(Runtime& runtime) const { - return runtime.size(*this); - } - - size_t length(Runtime& runtime) const { - return runtime.size(*this); - } - - uint8_t* data(Runtime& runtime) const { - return runtime.data(*this); - } - - private: - friend class Object; - friend class Value; - friend class Runtime; - - ArrayBuffer(Runtime::PointerValue* value) : Object(value) {} -}; - -/// Represents a JS Object which is guaranteed to be Callable. -class JSI_EXPORT Function : public Object { - public: - Function(Function&&) = default; - Function& operator=(Function&&) = default; - - /// Create a function which, when invoked, calls C++ code. If the - /// function throws an exception, a JS Error will be created and - /// thrown. - /// \param name the name property for the function. - /// \param paramCount the length property for the function, which - /// may not be the number of arguments the function is passed. - /// \note The std::function's dtor will be called when the GC finalizes this - /// function. As with HostObject, this may be as late as when the Runtime is - /// shut down, and may occur on an arbitrary thread. If the function contains - /// any captured values, you are responsible for ensuring that their - /// destructors are safe to call on any thread. - static Function createFromHostFunction( - Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func); - - /// Calls the function with \c count \c args. The \c this value of the JS - /// function will not be set by the C++ caller, similar to calling - /// Function.prototype.apply(undefined, args) in JS. - /// \b Note: as with Function.prototype.apply, \c this may not always be - /// \c undefined in the function itself. If the function is non-strict, - /// \c this will be set to the global object. - Value call(Runtime& runtime, const Value* args, size_t count) const; - - /// Calls the function with a \c std::initializer_list of Value - /// arguments. The \c this value of the JS function will not be set by the - /// C++ caller, similar to calling Function.prototype.apply(undefined, args) - /// in JS. - /// \b Note: as with Function.prototype.apply, \c this may not always be - /// \c undefined in the function itself. If the function is non-strict, - /// \c this will be set to the global object. - Value call(Runtime& runtime, std::initializer_list args) const; - - /// Calls the function with any number of arguments similarly to - /// Object::setProperty(). The \c this value of the JS function will not be - /// set by the C++ caller, similar to calling - /// Function.prototype.call(undefined, ...args) in JS. - /// \b Note: as with Function.prototype.call, \c this may not always be - /// \c undefined in the function itself. If the function is non-strict, - /// \c this will be set to the global object. - template - Value call(Runtime& runtime, Args&&... args) const; - - /// Calls the function with \c count \c args and \c jsThis value passed - /// as the \c this value. - Value callWithThis( - Runtime& Runtime, - const Object& jsThis, - const Value* args, - size_t count) const; - - /// Calls the function with a \c std::initializer_list of Value - /// arguments and \c jsThis passed as the \c this value. - Value callWithThis( - Runtime& runtime, - const Object& jsThis, - std::initializer_list args) const; - - /// Calls the function with any number of arguments similarly to - /// Object::setProperty(), and with \c jsThis passed as the \c this value. - template - Value callWithThis(Runtime& runtime, const Object& jsThis, Args&&... args) - const; - - /// Calls the function as a constructor with \c count \c args. Equivalent - /// to calling `new Func` where `Func` is the js function reqresented by - /// this. - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) - const; - - /// Same as above `callAsConstructor`, except use an initializer_list to - /// supply the arguments. - Value callAsConstructor(Runtime& runtime, std::initializer_list args) - const; - - /// Same as above `callAsConstructor`, but automatically converts/wraps - /// any argument with a jsi Value. - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const; - - /// Returns whether this was created with Function::createFromHostFunction. - /// If true then you can use getHostFunction to get the underlying - /// HostFunctionType. - bool isHostFunction(Runtime& runtime) const { - return runtime.isHostFunction(*this); - } - - /// Returns the underlying HostFunctionType iff isHostFunction returns true - /// and asserts otherwise. You can use this to use std::function<>::target - /// to get the object that was passed to create the HostFunctionType. - /// - /// Note: The reference returned is borrowed from the JS object underlying - /// \c this, and thus only lasts as long as the object underlying - /// \c this does. - HostFunctionType& getHostFunction(Runtime& runtime) const { - assert(isHostFunction(runtime)); - return runtime.getHostFunction(*this); - } - - private: - friend class Object; - friend class Value; - friend class Runtime; - - Function(Runtime::PointerValue* value) : Object(value) {} -}; - -/// Represents any JS Value (undefined, null, boolean, number, symbol, -/// string, or object). Movable, or explicitly copyable (has no copy -/// ctor). -class JSI_EXPORT Value { - public: - /// Default ctor creates an \c undefined JS value. - Value() noexcept : Value(UndefinedKind) {} - - /// Creates a \c null JS value. - /* implicit */ Value(std::nullptr_t) : kind_(NullKind) {} - - /// Creates a boolean JS value. - /* implicit */ Value(bool b) : Value(BooleanKind) { - data_.boolean = b; - } - - /// Creates a number JS value. - /* implicit */ Value(double d) : Value(NumberKind) { - data_.number = d; - } - - /// Creates a number JS value. - /* implicit */ Value(int i) : Value(NumberKind) { - data_.number = i; - } - - /// Moves a Symbol, String, or Object rvalue into a new JS value. - template < - typename T, - typename = std::enable_if_t< - std::is_base_of::value || - std::is_base_of::value || - std::is_base_of::value || - std::is_base_of::value>> - /* implicit */ Value(T&& other) : Value(kindOf(other)) { - new (&data_.pointer) T(std::move(other)); - } - - /// Value("foo") will treat foo as a bool. This makes doing that a - /// compile error. - template - Value(const char*) { - static_assert( - !std::is_same::value, - "Value cannot be constructed directly from const char*"); - } - - Value(Value&& other) noexcept; - - /// Copies a Symbol lvalue into a new JS value. - Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) { - new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_)); - } - - /// Copies a BigInt lvalue into a new JS value. - Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) { - new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_)); - } - - /// Copies a String lvalue into a new JS value. - Value(Runtime& runtime, const String& str) : Value(StringKind) { - new (&data_.pointer) String(runtime.cloneString(str.ptr_)); - } - - /// Copies a Object lvalue into a new JS value. - Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) { - new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_)); - } - - /// Creates a JS value from another Value lvalue. - Value(Runtime& runtime, const Value& value); - - /// Value(rt, "foo") will treat foo as a bool. This makes doing - /// that a compile error. - template - Value(Runtime&, const char*) { - static_assert( - !std::is_same::value, - "Value cannot be constructed directly from const char*"); - } - - ~Value(); - // \return the undefined \c Value. - static Value undefined() { - return Value(); - } - - // \return the null \c Value. - static Value null() { - return Value(nullptr); - } - - // \return a \c Value created from a utf8-encoded JSON string. - static Value - createFromJsonUtf8(Runtime& runtime, const uint8_t* json, size_t length) { - return runtime.createValueFromJsonUtf8(json, length); - } - - /// \return according to the Strict Equality Comparison algorithm, see: - /// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison - static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); - - Value& operator=(Value&& other) noexcept { - this->~Value(); - new (this) Value(std::move(other)); - return *this; - } - - bool isUndefined() const { - return kind_ == UndefinedKind; - } - - bool isNull() const { - return kind_ == NullKind; - } - - bool isBool() const { - return kind_ == BooleanKind; - } - - bool isNumber() const { - return kind_ == NumberKind; - } - - bool isString() const { - return kind_ == StringKind; - } - - bool isBigInt() const { - return kind_ == BigIntKind; - } - - bool isSymbol() const { - return kind_ == SymbolKind; - } - - bool isObject() const { - return kind_ == ObjectKind; - } - - /// \return the boolean value, or asserts if not a boolean. - bool getBool() const { - assert(isBool()); - return data_.boolean; - } - - /// \return the boolean value, or throws JSIException if not a - /// boolean. - bool asBool() const; - - /// \return the number value, or asserts if not a number. - double getNumber() const { - assert(isNumber()); - return data_.number; - } - - /// \return the number value, or throws JSIException if not a - /// number. - double asNumber() const; - - /// \return the Symbol value, or asserts if not a symbol. - Symbol getSymbol(Runtime& runtime) const& { - assert(isSymbol()); - return Symbol(runtime.cloneSymbol(data_.pointer.ptr_)); - } - - /// \return the Symbol value, or asserts if not a symbol. - /// Can be used on rvalue references to avoid cloning more symbols. - Symbol getSymbol(Runtime&) && { - assert(isSymbol()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the Symbol value, or throws JSIException if not a - /// symbol - Symbol asSymbol(Runtime& runtime) const&; - Symbol asSymbol(Runtime& runtime) &&; - - /// \return the BigInt value, or asserts if not a bigint. - BigInt getBigInt(Runtime& runtime) const& { - assert(isBigInt()); - return BigInt(runtime.cloneBigInt(data_.pointer.ptr_)); - } - - /// \return the BigInt value, or asserts if not a bigint. - /// Can be used on rvalue references to avoid cloning more bigints. - BigInt getBigInt(Runtime&) && { - assert(isBigInt()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the BigInt value, or throws JSIException if not a - /// bigint - BigInt asBigInt(Runtime& runtime) const&; - BigInt asBigInt(Runtime& runtime) &&; - - /// \return the String value, or asserts if not a string. - String getString(Runtime& runtime) const& { - assert(isString()); - return String(runtime.cloneString(data_.pointer.ptr_)); - } - - /// \return the String value, or asserts if not a string. - /// Can be used on rvalue references to avoid cloning more strings. - String getString(Runtime&) && { - assert(isString()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the String value, or throws JSIException if not a - /// string. - String asString(Runtime& runtime) const&; - String asString(Runtime& runtime) &&; - - /// \return the Object value, or asserts if not an object. - Object getObject(Runtime& runtime) const& { - assert(isObject()); - return Object(runtime.cloneObject(data_.pointer.ptr_)); - } - - /// \return the Object value, or asserts if not an object. - /// Can be used on rvalue references to avoid cloning more objects. - Object getObject(Runtime&) && { - assert(isObject()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the Object value, or throws JSIException if not an - /// object. - Object asObject(Runtime& runtime) const&; - Object asObject(Runtime& runtime) &&; - - // \return a String like JS .toString() would do. - String toString(Runtime& runtime) const; - - private: - friend class Runtime; - - enum ValueKind { - UndefinedKind, - NullKind, - BooleanKind, - NumberKind, - SymbolKind, - BigIntKind, - StringKind, - ObjectKind, - PointerKind = SymbolKind, - }; - - union Data { - // Value's ctor and dtor will manage the lifecycle of the contained Data. - Data() { - static_assert( - sizeof(Data) == sizeof(uint64_t), - "Value data should fit in a 64-bit register"); - } - ~Data() {} - - // scalars - bool boolean; - double number; - // pointers - Pointer pointer; // Symbol, String, Object, Array, Function - }; - - Value(ValueKind kind) : kind_(kind) {} - - constexpr static ValueKind kindOf(const Symbol&) { - return SymbolKind; - } - constexpr static ValueKind kindOf(const BigInt&) { - return BigIntKind; - } - constexpr static ValueKind kindOf(const String&) { - return StringKind; - } - constexpr static ValueKind kindOf(const Object&) { - return ObjectKind; - } - - ValueKind kind_; - Data data_; - - // In the future: Value becomes NaN-boxed. See T40538354. -}; - -/// Not movable and not copyable RAII marker advising the underlying -/// JavaScript VM to track resources allocated since creation until -/// destruction so that they can be recycled eagerly when the Scope -/// goes out of scope instead of floating in the air until the next -/// garbage collection or any other delayed release occurs. -/// -/// This API should be treated only as advice, implementations can -/// choose to ignore the fact that Scopes are created or destroyed. -/// -/// This class is an exception to the rule allowing destructors to be -/// called without proper synchronization (see Runtime documentation). -/// The whole point of this class is to enable all sorts of clean ups -/// when the destructor is called and this proper synchronization is -/// required at that time. -/// -/// Instances of this class are intended to be created as automatic stack -/// variables in which case destructor calls don't require any additional -/// locking, provided that the lock (if any) is managed with RAII helpers. -class JSI_EXPORT Scope { - public: - explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {} - ~Scope() { - rt_.popScope(prv_); - } - - Scope(const Scope&) = delete; - Scope(Scope&&) = delete; - - Scope& operator=(const Scope&) = delete; - Scope& operator=(Scope&&) = delete; - - template - static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) { - Scope s(rt); - return f(); - } - - private: - Runtime& rt_; - Runtime::ScopeState* prv_; -}; - -/// Base class for jsi exceptions -class JSI_EXPORT JSIException : public std::exception { - protected: - JSIException() {} - JSIException(std::string what) : what_(std::move(what)) {} - - public: - JSIException(const JSIException&) = default; - - virtual const char* what() const noexcept override { - return what_.c_str(); - } - - virtual ~JSIException() override; - - protected: - std::string what_; -}; - -/// This exception will be thrown by API functions on errors not related to -/// JavaScript execution. -class JSI_EXPORT JSINativeException : public JSIException { - public: - JSINativeException(std::string what) : JSIException(std::move(what)) {} - - JSINativeException(const JSINativeException&) = default; - - virtual ~JSINativeException(); -}; - -/// This exception will be thrown by API functions whenever a JS -/// operation causes an exception as described by the spec, or as -/// otherwise described. -class JSI_EXPORT JSError : public JSIException { - public: - /// Creates a JSError referring to provided \c value - JSError(Runtime& r, Value&& value); - - /// Creates a JSError referring to new \c Error instance capturing current - /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, std::string message); - - /// Creates a JSError referring to new \c Error instance capturing current - /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, const char* message) - : JSError(rt, std::string(message)) {} - - /// Creates a JSError referring to a JavaScript Object having message and - /// stack properties set to provided values. - JSError(Runtime& rt, std::string message, std::string stack); - - /// Creates a JSError referring to provided value and what string - /// set to provided message. This argument order is a bit weird, - /// but necessary to avoid ambiguity with the above. - JSError(std::string what, Runtime& rt, Value&& value); - - /// Creates a JSError referring to the provided value, message and stack. This - /// constructor does not take a Runtime parameter, and therefore cannot result - /// in recursively invoking the JSError constructor. - JSError(Value&& value, std::string message, std::string stack); - - JSError(const JSError&) = default; - - virtual ~JSError(); - - const std::string& getStack() const { - return stack_; - } - - const std::string& getMessage() const { - return message_; - } - - const jsi::Value& value() const { - assert(value_); - return *value_; - } - - private: - // This initializes the value_ member and does some other - // validation, so it must be called by every branch through the - // constructors. - void setValue(Runtime& rt, Value&& value); - - // This needs to be on the heap, because throw requires the object - // be copyable, and Value is not. - std::shared_ptr value_; - std::string message_; - std::string stack_; -}; - -} // namespace jsi -} // namespace facebook - -#include diff --git a/NativeScript/napi/hermes/include/old/js_native_api.h b/NativeScript/napi/hermes/include/old/js_native_api.h deleted file mode 100644 index 9e7073cf3..000000000 --- a/NativeScript/napi/hermes/include/old/js_native_api.h +++ /dev/null @@ -1,600 +0,0 @@ -#ifndef SRC_JS_NATIVE_API_H_ -#define SRC_JS_NATIVE_API_H_ - -// This file needs to be compatible with C compilers. -#include // NOLINT(modernize-deprecated-headers) -#include // NOLINT(modernize-deprecated-headers) - -// Use INT_MAX, this should only be consumed by the pre-processor anyway. -#define NAPI_VERSION_EXPERIMENTAL 2147483647 -#ifndef NAPI_VERSION -// The baseline version for N-API. -// The NAPI_VERSION controls which version will be used by default when -// compilling a native addon. If the addon developer specifically wants to use -// functions available in a new version of N-API that is not yet ported in all -// LTS versions, they can set NAPI_VERSION knowing that they have specifically -// depended on that version. -#define NAPI_VERSION 8 -#endif - -#include "js_native_api_types.h" - -// If you need __declspec(dllimport), either include instead, or -// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. -#ifndef NAPI_EXTERN -#ifdef _WIN32 -#define NAPI_EXTERN __declspec(dllexport) -#elif defined(__wasm__) -#define NAPI_EXTERN \ - __attribute__((visibility("default"))) \ - __attribute__((__import_module__("napi"))) -#else -#define NAPI_EXTERN __attribute__((visibility("default"))) -#endif -#endif - -#define NAPI_AUTO_LENGTH SIZE_MAX - -#ifdef __cplusplus -#define EXTERN_C_START extern "C" { -#define EXTERN_C_END } -#else -#define EXTERN_C_START -#define EXTERN_C_END -#endif - -EXTERN_C_START - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info( - node_api_basic_env env, const napi_extended_error_info** result); - -// Getters for defined singletons -NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_null(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_global(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env, - bool value, - napi_value* result); - -// Methods to create Primitive types/Objects -NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_array_with_length(napi_env env, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_double(napi_env env, - double value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_int32(napi_env env, - int32_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_uint32(napi_env env, - uint32_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_int64(napi_env env, - int64_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_latin1( - napi_env env, const char* str, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf8(napi_env env, - const char* str, - size_t length, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, - const char16_t* str, - size_t length, - napi_value* result); -#if NAPI_VERSION >= 10 -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1( - napi_env env, - char* str, - size_t length, - node_api_basic_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied); -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_external_string_utf16(napi_env env, - char16_t* str, - size_t length, - node_api_basic_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_latin1( - napi_env env, const char* str, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf8( - napi_env env, const char* str, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf16( - napi_env env, const char16_t* str, size_t length, napi_value* result); -#endif // NAPI_VERSION >= 10 - -NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env, - napi_value description, - napi_value* result); -#if NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL -node_api_symbol_for(napi_env env, - const char* utf8description, - size_t length, - napi_value* result); -#endif // NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL napi_create_function(napi_env env, - const char* utf8name, - size_t length, - napi_callback cb, - void* data, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_type_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_range_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -#if NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_syntax_error( - napi_env env, napi_value code, napi_value msg, napi_value* result); -#endif // NAPI_VERSION >= 9 - -// Methods to get the native napi_value from Primitive type -NAPI_EXTERN napi_status NAPI_CDECL napi_typeof(napi_env env, - napi_value value, - napi_valuetype* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_double(napi_env env, - napi_value value, - double* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int32(napi_env env, - napi_value value, - int32_t* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_uint32(napi_env env, - napi_value value, - uint32_t* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int64(napi_env env, - napi_value value, - int64_t* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bool(napi_env env, - napi_value value, - bool* result); - -// Copies LATIN-1 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_latin1( - napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); - -// Copies UTF-8 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf8( - napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); - -// Copies UTF-16 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env, - napi_value value, - char16_t* buf, - size_t bufsize, - size_t* result); - -// Methods to coerce values -// These APIs may execute user scripts -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_number(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_object(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_string(napi_env env, - napi_value value, - napi_value* result); - -// Methods to work with Objects -NAPI_EXTERN napi_status NAPI_CDECL napi_get_prototype(napi_env env, - napi_value object, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_property_names(napi_env env, - napi_value object, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_set_property(napi_env env, - napi_value object, - napi_value key, - napi_value value); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_property(napi_env env, - napi_value object, - napi_value key, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_own_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_set_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value value); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_named_property(napi_env env, - napi_value object, - const char* utf8name, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_set_element(napi_env env, - napi_value object, - uint32_t index, - napi_value value); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_element(napi_env env, - napi_value object, - uint32_t index, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_element(napi_env env, - napi_value object, - uint32_t index, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_element(napi_env env, - napi_value object, - uint32_t index, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_define_properties(napi_env env, - napi_value object, - size_t property_count, - const napi_property_descriptor* properties); - -// Methods to work with Arrays -NAPI_EXTERN napi_status NAPI_CDECL napi_is_array(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_array_length(napi_env env, - napi_value value, - uint32_t* result); - -// Methods to compare values -NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals(napi_env env, - napi_value lhs, - napi_value rhs, - bool* result); - -// Methods to work with Functions -NAPI_EXTERN napi_status NAPI_CDECL napi_call_function(napi_env env, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_new_instance(napi_env env, - napi_value constructor, - size_t argc, - const napi_value* argv, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env, - napi_value object, - napi_value constructor, - bool* result); - -// Methods to work with napi_callbacks - -// Gets all callback info in a single call. (Ugly, but faster.) -NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info( - napi_env env, // [in] Node-API environment handle - napi_callback_info cbinfo, // [in] Opaque callback-info handle - size_t* argc, // [in-out] Specifies the size of the provided argv array - // and receives the actual count of args. - napi_value* argv, // [out] Array of values - napi_value* this_arg, // [out] Receives the JS 'this' arg for the call - void** data); // [out] Receives the data pointer for the callback. - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_new_target( - napi_env env, napi_callback_info cbinfo, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_define_class(napi_env env, - const char* utf8name, - size_t length, - napi_callback constructor, - void* data, - size_t property_count, - const napi_property_descriptor* properties, - napi_value* result); - -// Methods to work with external data objects -NAPI_EXTERN napi_status NAPI_CDECL -napi_wrap(napi_env env, - napi_value js_object, - void* native_object, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, - napi_value js_object, - void** result); -NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, - napi_value js_object, - void** result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_external(napi_env env, - void* data, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, - napi_value value, - void** result); - -// Methods to control object lifespan - -// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_reference(napi_env env, - napi_value value, - uint32_t initial_refcount, - napi_ref* result); - -// Deletes a reference. The referenced value is released, and may -// be GC'd unless there are other references to it. -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_reference(napi_env env, - napi_ref ref); - -// Increments the reference count, optionally returning the resulting count. -// After this call the reference will be a strong reference because its -// refcount is >0, and the referenced object is effectively "pinned". -// Calling this when the refcount is 0 and the object is unavailable -// results in an error. -NAPI_EXTERN napi_status NAPI_CDECL napi_reference_ref(napi_env env, - napi_ref ref, - uint32_t* result); - -// Decrements the reference count, optionally returning the resulting count. -// If the result is 0 the reference is now weak and the object may be GC'd -// at any time if there are no other references. Calling this when the -// refcount is already 0 results in an error. -NAPI_EXTERN napi_status NAPI_CDECL napi_reference_unref(napi_env env, - napi_ref ref, - uint32_t* result); - -// Attempts to get a referenced value. If the reference is weak, -// the value might no longer be available, in that case the call -// is still successful but the result is NULL. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_reference_value(napi_env env, - napi_ref ref, - napi_value* result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_open_handle_scope(napi_env env, napi_handle_scope* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_close_handle_scope(napi_env env, napi_handle_scope scope); -NAPI_EXTERN napi_status NAPI_CDECL napi_open_escapable_handle_scope( - napi_env env, napi_escapable_handle_scope* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_close_escapable_handle_scope( - napi_env env, napi_escapable_handle_scope scope); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_escape_handle(napi_env env, - napi_escapable_handle_scope scope, - napi_value escapee, - napi_value* result); - -// Methods to support error handling -NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error); -NAPI_EXTERN napi_status NAPI_CDECL napi_throw_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status NAPI_CDECL napi_throw_type_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status NAPI_CDECL napi_throw_range_error(napi_env env, - const char* code, - const char* msg); -#if NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL node_api_throw_syntax_error(napi_env env, - const char* code, - const char* msg); -#endif // NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL napi_is_error(napi_env env, - napi_value value, - bool* result); - -// Methods to support catching exceptions -NAPI_EXTERN napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_and_clear_last_exception(napi_env env, napi_value* result); - -// Methods to work with array buffers and typed arrays -NAPI_EXTERN napi_status NAPI_CDECL napi_is_arraybuffer(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_arraybuffer(napi_env env, - size_t byte_length, - void** data, - napi_value* result); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_external_arraybuffer(napi_env env, - void* external_data, - size_t byte_length, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info( - napi_env env, napi_value arraybuffer, void** data, size_t* byte_length); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_typedarray(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_typedarray(napi_env env, - napi_typedarray_type type, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_typedarray_info(napi_env env, - napi_value typedarray, - napi_typedarray_type* type, - size_t* length, - void** data, - napi_value* arraybuffer, - size_t* byte_offset); - -NAPI_EXTERN napi_status NAPI_CDECL napi_create_dataview(napi_env env, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_dataview(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_dataview_info(napi_env env, - napi_value dataview, - size_t* bytelength, - void** data, - napi_value* arraybuffer, - size_t* byte_offset); - -// version management -NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env, - uint32_t* result); - -// Promises -NAPI_EXTERN napi_status NAPI_CDECL napi_create_promise(napi_env env, - napi_deferred* deferred, - napi_value* promise); -NAPI_EXTERN napi_status NAPI_CDECL napi_resolve_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution); -NAPI_EXTERN napi_status NAPI_CDECL napi_reject_deferred(napi_env env, - napi_deferred deferred, - napi_value rejection); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_promise(napi_env env, - napi_value value, - bool* is_promise); - -// Running a script -NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, - napi_value script, - napi_value* result); - -// Memory management -NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( - node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value); - -#if NAPI_VERSION >= 5 - -// Dates -NAPI_EXTERN napi_status NAPI_CDECL napi_create_date(napi_env env, - double time, - napi_value* result); - -NAPI_EXTERN napi_status NAPI_CDECL napi_is_date(napi_env env, - napi_value value, - bool* is_date); - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env, - napi_value value, - double* result); - -// Add finalizer for pointer -NAPI_EXTERN napi_status NAPI_CDECL -napi_add_finalizer(napi_env env, - napi_value js_object, - void* finalize_data, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); - -#endif // NAPI_VERSION >= 5 - -#if NAPI_VERSION >= 6 - -// BigInt -NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env, - int64_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_bigint_words(napi_env env, - int sign_bit, - size_t word_count, - const uint64_t* words, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env, - napi_value value, - int64_t* result, - bool* lossless); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_uint64( - napi_env env, napi_value value, uint64_t* result, bool* lossless); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_value_bigint_words(napi_env env, - napi_value value, - int* sign_bit, - size_t* word_count, - uint64_t* words); - -// Object -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_all_property_names(napi_env env, - napi_value object, - napi_key_collection_mode key_mode, - napi_key_filter key_filter, - napi_key_conversion key_conversion, - napi_value* result); - -// Instance data -NAPI_EXTERN napi_status NAPI_CDECL -napi_set_instance_data(node_api_basic_env env, - void* data, - napi_finalize finalize_cb, - void* finalize_hint); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_instance_data(node_api_basic_env env, void** data); -#endif // NAPI_VERSION >= 6 - -#if NAPI_VERSION >= 7 -// ArrayBuffer detaching -NAPI_EXTERN napi_status NAPI_CDECL -napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result); -#endif // NAPI_VERSION >= 7 - -#if NAPI_VERSION >= 8 -// Type tagging -NAPI_EXTERN napi_status NAPI_CDECL napi_type_tag_object( - napi_env env, napi_value value, const napi_type_tag* type_tag); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_check_object_type_tag(napi_env env, - napi_value value, - const napi_type_tag* type_tag, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_object_freeze(napi_env env, - napi_value object); -NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, - napi_value object); -#endif // NAPI_VERSION >= 8 - -EXTERN_C_END - -#endif // SRC_JS_NATIVE_API_H_ diff --git a/NativeScript/napi/hermes/include/old/js_native_api_types.h b/NativeScript/napi/hermes/include/old/js_native_api_types.h deleted file mode 100644 index 7853a8d7a..000000000 --- a/NativeScript/napi/hermes/include/old/js_native_api_types.h +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef SRC_JS_NATIVE_API_TYPES_H_ -#define SRC_JS_NATIVE_API_TYPES_H_ - -// This file needs to be compatible with C compilers. -// This is a public include file, and these includes have essentially -// became part of it's API. -#include // NOLINT(modernize-deprecated-headers) -#include // NOLINT(modernize-deprecated-headers) - -#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) -typedef uint16_t char16_t; -#endif - -#ifndef NAPI_CDECL -#ifdef _WIN32 -#define NAPI_CDECL __cdecl -#else -#define NAPI_CDECL -#endif -#endif - -// JSVM API types are all opaque pointers for ABI stability -// typedef undefined structs instead of void* for compile time type safety -typedef struct napi_env__* napi_env; - -// We need to mark APIs which can be called during garbage collection (GC), -// meaning that they do not affect the state of the JS engine, and can -// therefore be called synchronously from a finalizer that itself runs -// synchronously during GC. Such APIs can receive either a `napi_env` or a -// `node_api_basic_env` as their first parameter, because we should be able to -// also call them during normal, non-garbage-collecting operations, whereas -// APIs that affect the state of the JS engine can only receive a `napi_env` as -// their first parameter, because we must not call them during GC. In lieu of -// inheritance, we use the properties of the const qualifier to accomplish -// this, because both a const and a non-const value can be passed to an API -// expecting a const value, but only a non-const value can be passed to an API -// expecting a non-const value. -// -// In conjunction with appropriate CFLAGS to warn us if we're passing a const -// (basic) environment into an API that expects a non-const environment, and -// the definition of basic finalizer function pointer types below, which -// receive a basic environment as their first parameter, and can thus only call -// basic APIs (unless the user explicitly casts the environment), we achieve -// the ability to ensure at compile time that we do not call APIs that affect -// the state of the JS engine from a synchronous (basic) finalizer. -typedef struct napi_env__* node_api_nogc_env; -typedef node_api_nogc_env node_api_basic_env; - -typedef struct napi_value__* napi_value; -typedef struct napi_ref__* napi_ref; -typedef struct napi_handle_scope__* napi_handle_scope; -typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; -typedef struct napi_callback_info__* napi_callback_info; -typedef struct napi_deferred__* napi_deferred; - -typedef enum { - napi_default = 0, - napi_writable = 1 << 0, - napi_enumerable = 1 << 1, - napi_configurable = 1 << 2, - - // Used with napi_define_class to distinguish static properties - // from instance properties. Ignored by napi_define_properties. - napi_static = 1 << 10, - -#if NAPI_VERSION >= 8 - // Default for class methods. - napi_default_method = napi_writable | napi_configurable, - - // Default for object properties, like in JS obj[prop]. - napi_default_jsproperty = napi_writable | napi_enumerable | napi_configurable, -#endif // NAPI_VERSION >= 8 -} napi_property_attributes; - -typedef enum { - // ES6 types (corresponds to typeof) - napi_undefined, - napi_null, - napi_boolean, - napi_number, - napi_string, - napi_symbol, - napi_object, - napi_function, - napi_external, - napi_bigint, -} napi_valuetype; - -typedef enum { - napi_int8_array, - napi_uint8_array, - napi_uint8_clamped_array, - napi_int16_array, - napi_uint16_array, - napi_int32_array, - napi_uint32_array, - napi_float32_array, - napi_float64_array, - napi_bigint64_array, - napi_biguint64_array, -} napi_typedarray_type; - -typedef enum { - napi_ok, - napi_invalid_arg, - napi_object_expected, - napi_string_expected, - napi_name_expected, - napi_function_expected, - napi_number_expected, - napi_boolean_expected, - napi_array_expected, - napi_generic_failure, - napi_pending_exception, - napi_cancelled, - napi_escape_called_twice, - napi_handle_scope_mismatch, - napi_callback_scope_mismatch, - napi_queue_full, - napi_closing, - napi_bigint_expected, - napi_date_expected, - napi_arraybuffer_expected, - napi_detachable_arraybuffer_expected, - napi_would_deadlock, // unused - napi_no_external_buffers_allowed, - napi_cannot_run_js, -} napi_status; -// Note: when adding a new enum value to `napi_status`, please also update -// * `const int last_status` in the definition of `napi_get_last_error_info()' -// in file js_native_api_v8.cc. -// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief -// message explaining the error. -// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly -// added value(s). - -typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, - napi_callback_info info); -typedef void(NAPI_CDECL* napi_finalize)(napi_env env, - void* finalize_data, - void* finalize_hint); - -typedef napi_finalize node_api_nogc_finalize; -typedef node_api_nogc_finalize node_api_basic_finalize; - -typedef struct { - // One of utf8name or name should be NULL. - const char* utf8name; - napi_value name; - - napi_callback method; - napi_callback getter; - napi_callback setter; - napi_value value; - - napi_property_attributes attributes; - void* data; -} napi_property_descriptor; - -typedef struct { - const char* error_message; - void* engine_reserved; - uint32_t engine_error_code; - napi_status error_code; -} napi_extended_error_info; - -#if NAPI_VERSION >= 6 -typedef enum { - napi_key_include_prototypes, - napi_key_own_only -} napi_key_collection_mode; - -typedef enum { - napi_key_all_properties = 0, - napi_key_writable = 1, - napi_key_enumerable = 1 << 1, - napi_key_configurable = 1 << 2, - napi_key_skip_strings = 1 << 3, - napi_key_skip_symbols = 1 << 4 -} napi_key_filter; - -typedef enum { - napi_key_keep_numbers, - napi_key_numbers_to_strings -} napi_key_conversion; -#endif // NAPI_VERSION >= 6 - -#if NAPI_VERSION >= 8 -typedef struct { - uint64_t lower; - uint64_t upper; -} napi_type_tag; -#endif // NAPI_VERSION >= 8 - -#endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/NativeScript/napi/hermes/include/old/node_api.h b/NativeScript/napi/hermes/include/old/node_api.h deleted file mode 100644 index 4ebfbd46d..000000000 --- a/NativeScript/napi/hermes/include/old/node_api.h +++ /dev/null @@ -1,270 +0,0 @@ -#ifndef SRC_NODE_API_H_ -#define SRC_NODE_API_H_ - -#if defined(BUILDING_NODE_EXTENSION) && !defined(NAPI_EXTERN) -#ifdef _WIN32 -// Building native addon against node -#define NAPI_EXTERN __declspec(dllimport) -#elif defined(__wasm__) -#define NAPI_EXTERN __attribute__((__import_module__("napi"))) -#endif -#endif -#include "js_native_api.h" -#include "node_api_types.h" - -struct uv_loop_s; // Forward declaration. - -#ifdef _WIN32 -#define NAPI_MODULE_EXPORT __declspec(dllexport) -#else -#ifdef __EMSCRIPTEN__ -#define NAPI_MODULE_EXPORT \ - __attribute__((visibility("default"))) __attribute__((used)) -#else -#define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) -#endif -#endif - -#if defined(__GNUC__) -#define NAPI_NO_RETURN __attribute__((noreturn)) -#elif defined(_WIN32) -#define NAPI_NO_RETURN __declspec(noreturn) -#else -#define NAPI_NO_RETURN -#endif - -typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env, - napi_value exports); -typedef int32_t(NAPI_CDECL* node_api_addon_get_api_version_func)(void); - -// Used by deprecated registration method napi_module_register. -typedef struct napi_module { - int nm_version; - unsigned int nm_flags; - const char* nm_filename; - napi_addon_register_func nm_register_func; - const char* nm_modname; - void* nm_priv; - void* reserved[4]; -} napi_module; - -#define NAPI_MODULE_VERSION 1 - -#define NAPI_MODULE_INITIALIZER_X(base, version) \ - NAPI_MODULE_INITIALIZER_X_HELPER(base, version) -#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version - -#ifdef __wasm__ -#define NAPI_MODULE_INITIALIZER_BASE napi_register_wasm_v -#else -#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v -#endif - -#define NODE_API_MODULE_GET_API_VERSION_BASE node_api_module_get_api_version_v - -#define NAPI_MODULE_INITIALIZER \ - NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, NAPI_MODULE_VERSION) - -#define NODE_API_MODULE_GET_API_VERSION \ - NAPI_MODULE_INITIALIZER_X(NODE_API_MODULE_GET_API_VERSION_BASE, \ - NAPI_MODULE_VERSION) - -#define NAPI_MODULE_INIT() \ - EXTERN_C_START \ - NAPI_MODULE_EXPORT int32_t NODE_API_MODULE_GET_API_VERSION(void) { \ - return NAPI_VERSION; \ - } \ - NAPI_MODULE_EXPORT napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ - napi_value exports); \ - EXTERN_C_END \ - napi_value NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports) - -#define NAPI_MODULE(modname, regfunc) \ - NAPI_MODULE_INIT() { \ - return regfunc(env, exports); \ - } - -// Deprecated. Use NAPI_MODULE. -#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ - NAPI_MODULE(modname, regfunc) - -EXTERN_C_START - -// Deprecated. Replaced by symbol-based registration defined by NAPI_MODULE -// and NAPI_MODULE_INIT macros. -NAPI_EXTERN void NAPI_CDECL napi_module_register(napi_module* mod); - -NAPI_EXTERN NAPI_NO_RETURN void NAPI_CDECL -napi_fatal_error(const char* location, - size_t location_len, - const char* message, - size_t message_len); - -// Methods for custom handling of async operations -NAPI_EXTERN napi_status NAPI_CDECL -napi_async_init(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_context* result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_async_destroy(napi_env env, napi_async_context async_context); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_make_callback(napi_env env, - napi_async_context async_context, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result); - -// Methods to provide node::Buffer functionality with napi types -NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer(napi_env env, - size_t length, - void** data, - napi_value* result); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_external_buffer(napi_env env, - size_t length, - void* data, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - -#if NAPI_VERSION >= 10 - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_buffer_from_arraybuffer(napi_env env, - napi_value arraybuffer, - size_t byte_offset, - size_t byte_length, - napi_value* result); -#endif // NAPI_VERSION >= 10 - -NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer_copy(napi_env env, - size_t length, - const void* data, - void** result_data, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_buffer(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_buffer_info(napi_env env, - napi_value value, - void** data, - size_t* length); - -// Methods to manage simple async operations -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_async_work(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete, - void* data, - napi_async_work* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, - napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_basic_env env, - napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL -napi_cancel_async_work(node_api_basic_env env, napi_async_work work); - -// version management -NAPI_EXTERN napi_status NAPI_CDECL napi_get_node_version( - node_api_basic_env env, const napi_node_version** version); - -#if NAPI_VERSION >= 2 - -// Return the current libuv event loop for a given environment -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop); - -#endif // NAPI_VERSION >= 2 - -#if NAPI_VERSION >= 3 - -NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env, - napi_value err); - -NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook( - node_api_basic_env env, napi_cleanup_hook fun, void* arg); - -NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook( - node_api_basic_env env, napi_cleanup_hook fun, void* arg); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_open_callback_scope(napi_env env, - napi_value resource_object, - napi_async_context context, - napi_callback_scope* result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_close_callback_scope(napi_env env, napi_callback_scope scope); - -#endif // NAPI_VERSION >= 3 - -#if NAPI_VERSION >= 4 - -// Calling into JS from other threads -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_threadsafe_function(napi_env env, - napi_value func, - napi_value async_resource, - napi_value async_resource_name, - size_t max_queue_size, - size_t initial_thread_count, - void* thread_finalize_data, - napi_finalize thread_finalize_cb, - void* context, - napi_threadsafe_function_call_js call_js_cb, - napi_threadsafe_function* result); - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_threadsafe_function_context( - napi_threadsafe_function func, void** result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_call_threadsafe_function(napi_threadsafe_function func, - void* data, - napi_threadsafe_function_call_mode is_blocking); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_acquire_threadsafe_function(napi_threadsafe_function func); - -NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function( - napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); - -NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function( - node_api_basic_env env, napi_threadsafe_function func); - -NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function( - node_api_basic_env env, napi_threadsafe_function func); - -#endif // NAPI_VERSION >= 4 - -#if NAPI_VERSION >= 8 - -NAPI_EXTERN napi_status NAPI_CDECL -napi_add_async_cleanup_hook(node_api_basic_env env, - napi_async_cleanup_hook hook, - void* arg, - napi_async_cleanup_hook_handle* remove_handle); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle); - -#endif // NAPI_VERSION >= 8 - -#if NAPI_VERSION >= 9 - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_get_module_file_name(node_api_basic_env env, const char** result); - -#endif // NAPI_VERSION >= 9 - -EXTERN_C_END - -#endif // SRC_NODE_API_H_ diff --git a/NativeScript/napi/hermes/include/old/node_api_types.h b/NativeScript/napi/hermes/include/old/node_api_types.h deleted file mode 100644 index 9c2f03f4d..000000000 --- a/NativeScript/napi/hermes/include/old/node_api_types.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef SRC_NODE_API_TYPES_H_ -#define SRC_NODE_API_TYPES_H_ - -#include "js_native_api_types.h" - -typedef struct napi_callback_scope__* napi_callback_scope; -typedef struct napi_async_context__* napi_async_context; -typedef struct napi_async_work__* napi_async_work; - -#if NAPI_VERSION >= 3 -typedef void(NAPI_CDECL* napi_cleanup_hook)(void* arg); -#endif // NAPI_VERSION >= 3 - -#if NAPI_VERSION >= 4 -typedef struct napi_threadsafe_function__* napi_threadsafe_function; -#endif // NAPI_VERSION >= 4 - -#if NAPI_VERSION >= 4 -typedef enum { - napi_tsfn_release, - napi_tsfn_abort -} napi_threadsafe_function_release_mode; - -typedef enum { - napi_tsfn_nonblocking, - napi_tsfn_blocking -} napi_threadsafe_function_call_mode; -#endif // NAPI_VERSION >= 4 - -typedef void(NAPI_CDECL* napi_async_execute_callback)(napi_env env, void* data); -typedef void(NAPI_CDECL* napi_async_complete_callback)(napi_env env, - napi_status status, - void* data); -#if NAPI_VERSION >= 4 -typedef void(NAPI_CDECL* napi_threadsafe_function_call_js)( - napi_env env, napi_value js_callback, void* context, void* data); -#endif // NAPI_VERSION >= 4 - -typedef struct { - uint32_t major; - uint32_t minor; - uint32_t patch; - const char* release; -} napi_node_version; - -#if NAPI_VERSION >= 8 -typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle; -typedef void(NAPI_CDECL* napi_async_cleanup_hook)( - napi_async_cleanup_hook_handle handle, void* data); -#endif // NAPI_VERSION >= 8 - -#endif // SRC_NODE_API_TYPES_H_ diff --git a/NativeScript/napi/hermes/include_old/hermes/AsyncDebuggerAPI.h b/NativeScript/napi/hermes/include_old/hermes/AsyncDebuggerAPI.h deleted file mode 100644 index ea718dd4a..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/AsyncDebuggerAPI.h +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_ASYNCDEBUGGERAPI_H -#define HERMES_ASYNCDEBUGGERAPI_H - -#ifdef HERMES_ENABLE_DEBUGGER - -#include -#include -#include -#include -#include - -#include -#include -#include - -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#include -#else -#ifndef TSA_GUARDED_BY -#define TSA_GUARDED_BY(x) -#endif -#ifndef TSA_NO_THREAD_SAFETY_ANALYSIS -#define TSA_NO_THREAD_SAFETY_ANALYSIS -#endif -#endif - -namespace facebook { -namespace hermes { -namespace debugger { - -class AsyncDebuggerAPI; - -enum class DebuggerEventType { - // Informational Events - ScriptLoaded, /// A script file was loaded, and the debugger has requested - /// pausing after script load. - Exception, /// An Exception was thrown. - Resumed, /// Script execution has resumed. - - // Events Requiring Next Command - DebuggerStatement, /// A debugger; statement was hit. - Breakpoint, /// A breakpoint was hit. - StepFinish, /// A Step operation completed. - ExplicitPause, /// A pause requested using Explicit AsyncBreak -}; - -/// This represents the list of possible commands that can be given to -/// \p resumeFromPaused. This is used instead of DebuggerAPI's Command class in -/// order to prevent callers from constructing an eval Command. The eval -/// functionality is implemented as a separate mechansim with -/// \p evalWhilePaused. -enum class AsyncDebugCommand { - Continue, /// Continues execution - StepInto, /// Perform a step into and then pause again - StepOver, /// Steps over the current instruction and then pause again - StepOut, /// Step out from the current scope and then pause again -}; - -using DebuggerEventCallback = std::function; -using DebuggerEventCallbackID = uint32_t; -constexpr const uint32_t kInvalidDebuggerEventCallbackID = 0; -using InterruptCallback = std::function; -using EvalCompleteCallback = std::function< - void(HermesRuntime &runtime, const debugger::EvalResult &result)>; - -/// This class wraps the DebuggerAPI to expose an asynchronous didPause -/// functionality as well as an interrupt API. This class must be constructed at -/// the same time as HermesRuntime. -/// -/// Functions in this class with the suffix "_TS" (Thread-Safe) are the only -/// functions that are safe to call on any thread. All other functions must be -/// called on the runtime thread. -class HERMES_EXPORT AsyncDebuggerAPI : private debugger::EventObserver { - /// Hide the constructor so users can only construct via static create - /// methods. - AsyncDebuggerAPI(HermesRuntime &runtime); - - public: - /// Creates an AsyncDebuggerAPI for use with the provided HermesRuntime. This - /// should be called and created at the same time as creating HermesRuntime. - static std::unique_ptr create(HermesRuntime &runtime); - - /// Must be destroyed on the runtime thread or when you're sure nothing is - /// interacting with the runtime. Must be destroyed before destroying - /// HermesRuntime. - ~AsyncDebuggerAPI() override; - - /// Add a callback function to invoke when the runtime pauses due to various - /// conditions such as hitting a "debugger;" statement. Can be called from any - /// thread. If there are no DebuggerEventCallback, then any reason that might - /// trigger a pause, such as a "debugger;" statement or breakpoints, will not - /// actually pause and will simply continue execution. Any caller that adds an - /// event callback cannot just be observing events and never call - /// \p resumeFromPaused in any of its code paths. The caller must either - /// expose UI enabling human action for controlling the debugger, or it must - /// have programmatic logic that controls the debugger via - /// \p resumeFromPaused. - DebuggerEventCallbackID addDebuggerEventCallback_TS( - DebuggerEventCallback callback); - - /// Remove a previously added callback function. If there is no callback - /// registered using the provided \p id, the function does nothing. - void removeDebuggerEventCallback_TS(DebuggerEventCallbackID id); - - /// Whether the runtime is currently paused waiting for the next action. - /// Should only be called from the runtime thread. - bool isWaitingForCommand(); - - /// Whether the runtime is currently paused for any reason (e.g. script - /// parsed, running interrupts, or waiting for a command). - /// Should only be called from the runtime thread. - bool isPaused(); - - /// Provide the next action to perform. Should only be called from the runtime - /// thread and only if the next command is expected to be set. - bool resumeFromPaused(AsyncDebugCommand command); - - /// Evaluate JavaScript code \p expression in the frame at index - /// \p frameIndex. Receives evaluation result in the \p callback. Should only - /// be called from the runtime thread and only if debugger is paused waiting - /// for the next action. - bool evalWhilePaused( - const std::string &expression, - uint32_t frameIndex, - EvalCompleteCallback callback); - - /// Request to interrupt the runtime at a convenient time and get a callback - /// on the runtime thread. Guaranteed to run "exactly once". This function can - /// be called from any thread, but cannot be called while inside a - /// DebuggerEventCallback. - void triggerInterrupt_TS(InterruptCallback callback); - - /// EventObserver implementation - debugger::Command didPause(debugger::Debugger &debugger) override; - - private: - struct EventCallbackEntry { - DebuggerEventCallbackID id; - DebuggerEventCallback callback; - }; - - /// This function infinite loops and uses \p signal_ to block the runtime - /// thread. It gets woken up if new InterruptCallback is queued or if - /// DebuggerEventCallback changes. - void processInterruptWhilePaused() TSA_NO_THREAD_SAFETY_ANALYSIS; - - /// Dequeues the next InterruptCallback if any. - std::optional takeNextInterruptCallback(); - - /// If \p ignoreNextCommand is true, then runs every InterruptCallback that - /// has been queued up so far. If \p ignoreNextCommand is false, then attempt - /// to run all interrupts, but will stop if any interrupt sets a next command. - void runInterrupts(bool ignoreNextCommand = true); - - /// Returns the next DebuggerEventCallback to execute if any. - std::optional takeNextEventCallback(); - - /// Runs every DebuggerEventCallback that has been registered. - void runEventCallbacks(DebuggerEventType event); - - HermesRuntime &runtime_; - - /// Whether the runtime thread is currently paused in \p didPause and needs to - /// be told what action to take next. - bool isWaitingForCommand_; - - /// Stores the command to return from \p didPause. - debugger::Command nextCommand_; - - /// Callback function to invoke after getting EvalResult from EvalComplete in - /// didPause. Used once and then cleared out. - EvalCompleteCallback oneTimeEvalCompleteCallback_{}; - - /// Tracks whether we are already in a didPause callback to detect recursive - /// calls to didPause. - bool inDidPause_ = false; - - /// Next ID to use when adding a DebuggerEventCallback. - uint32_t nextEventCallbackID_ TSA_GUARDED_BY(mutex_); - - /// Callback functions to invoke to notify events in \p didPause. Using - /// std::list which requires O(N) search when removing an element, but removal - /// should be a rare event. So the choice of using std::list is to optimize - /// for typical usage. - std::list eventCallbacks_ TSA_GUARDED_BY(mutex_){}; - - /// Iterator for eventCallbacks_. Used to traverse through the list when - /// running the callbacks. - std::list::iterator eventCallbackIterator_ - TSA_GUARDED_BY(mutex_); - - /// Queue of interrupt callback functions to invoke. - std::queue interruptCallbacks_ TSA_GUARDED_BY(mutex_){}; - - /// Used as a mechanism to block the runtime thread in \p didPause and for - /// protecting variables used across threads. - std::mutex mutex_{}; - /// Used to implement \p triggerInterrupt while \p didPause is holding onto - /// the runtime thread. - std::condition_variable signal_{}; -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#else // !HERMES_ENABLE_DEBUGGER - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace debugger { - -class AsyncDebuggerAPI; - -enum class DebuggerEventType { - // Informational Events - ScriptLoaded, /// A script file was loaded, and the debugger has requested - /// pausing after script load. - Exception, /// An Exception was thrown. - Resumed, /// Script execution has resumed. - - // Events Requiring Next Command - DebuggerStatement, /// A debugger; statement was hit. - Breakpoint, /// A breakpoint was hit. - StepFinish, /// A Step operation completed. - ExplicitPause, /// A pause requested using Explicit AsyncBreak -}; - -/// This represents the list of possible commands that can be given to -/// \p resumeFromPaused. This is used instead of DebuggerAPI's Command class in -/// order to prevent callers from constructing an eval Command. The eval -/// functionality is implemented as a separate mechansim with -/// \p evalWhilePaused. -enum class AsyncDebugCommand { - Continue, /// Continues execution - StepInto, /// Perform a step into and then pause again - StepOver, /// Steps over the current instruction and then pause again - StepOut, /// Step out from the current scope and then pause again -}; - -using DebuggerEventCallback = std::function; -using DebuggerEventCallbackID = uint32_t; -constexpr const uint32_t kInvalidDebuggerEventCallbackID = 0; -using InterruptCallback = std::function; -using EvalCompleteCallback = std::function< - void(HermesRuntime &runtime, const debugger::EvalResult &result)>; - -class HERMES_EXPORT AsyncDebuggerAPI { - public: - static std::unique_ptr create(HermesRuntime &runtime) { - return nullptr; - } - - ~AsyncDebuggerAPI() {} - - DebuggerEventCallbackID addDebuggerEventCallback_TS( - DebuggerEventCallback callback) { - return kInvalidDebuggerEventCallbackID; - } - - void removeDebuggerEventCallback_TS(DebuggerEventCallbackID id) {} - - bool isWaitingForCommand() { - return false; - } - - bool isPaused() { - return false; - } - - bool resumeFromPaused(AsyncDebugCommand command) { - return false; - } - - bool evalWhilePaused( - const std::string &expression, - uint32_t frameIndex, - EvalCompleteCallback callback) { - return false; - } - - void triggerInterrupt_TS(InterruptCallback callback) {} -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif // !HERMES_ENABLE_DEBUGGER - -#endif // HERMES_ASYNCDEBUGGERAPI_H diff --git a/NativeScript/napi/hermes/include_old/hermes/CompileJS.h b/NativeScript/napi/hermes/include_old/hermes/CompileJS.h deleted file mode 100644 index 562eeae7f..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/CompileJS.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_COMPILEJS_H -#define HERMES_COMPILEJS_H - -#include -#include -#include - -namespace hermes { - -/// Interface for receiving errors, warnings and notes produced by compileJS. -class DiagnosticHandler { - public: - enum Kind { - Error, - Warning, - Note, - }; - - struct Diagnostic { - Kind kind; - int line; /// 1-based index - int column; /// 1-based index - std::string message; - /// 0-based char indices in half-open intervals - std::vector> ranges; - }; - - /// Called once for each diagnostic message produced during compilation. - virtual void handle(const Diagnostic &diagnostic) = 0; - virtual ~DiagnosticHandler() = default; -}; - -/// Compiles JS source \p str and if compilation is successful, returns true -/// and outputs to \p bytecode otherwise returns false. -/// \param sourceURL this will be used as the "file name" of the buffer for -/// errors, stack traces, etc. -/// \param optimize this will enable optimizations. -/// \param emitAsyncBreakCheck this will make the bytecode interruptable. -/// \param diagHandler if not null, receives any and all errors, warnings and -/// notes produced during compilation. -/// \param sourceMapBuf optional source map string. -/// \param debug Wether to generate debugging information in generated bytecode. -bool compileJS( - const std::string &str, - const std::string &sourceURL, - std::string &bytecode, - bool optimize, - bool emitAsyncBreakCheck, - DiagnosticHandler *diagHandler, - std::optional sourceMapBuf = std::nullopt, - bool debug = false); - -bool compileJS( - const std::string &str, - std::string &bytecode, - bool optimize = true); - -bool compileJS( - const std::string &str, - const std::string &sourceURL, - std::string &bytecode, - bool optimize = true); - -} // namespace hermes - -#endif diff --git a/NativeScript/napi/hermes/include_old/hermes/DebuggerAPI.h b/NativeScript/napi/hermes/include_old/hermes/DebuggerAPI.h deleted file mode 100644 index e444c41cb..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/DebuggerAPI.h +++ /dev/null @@ -1,501 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_DEBUGGERAPI_H -#define HERMES_DEBUGGERAPI_H - -#ifdef HERMES_ENABLE_DEBUGGER - -#include -#include -#include -#include - -#include "hermes/Public/DebuggerTypes.h" - -// Forward declarations of internal types. -namespace hermes { -namespace vm { -class CodeBlock; -class Debugger; -class Runtime; -struct DebugCommand; -class HermesValue; -} // namespace vm -} // namespace hermes - -namespace facebook { -namespace hermes { -class HermesRuntime; - -namespace debugger { - -class Debugger; -class EventObserver; - -/// Represents a variable in the debugger. -struct HERMES_EXPORT VariableInfo { - /// Name of the variable in the source. - String name; - - /// Value of the variable. - ::facebook::jsi::Value value; -}; - -/// An EvalResult represents the result of an Eval command. -struct HERMES_EXPORT EvalResult { - /// The resulting JavaScript object, or the thrown exception. - ::facebook::jsi::Value value; - - /// Indicates that the result was an exception. - bool isException = false; - - /// If isException is true, details about the exception. - ExceptionDetails exceptionDetails; - - EvalResult(EvalResult &&) = default; - EvalResult() = default; - - EvalResult( - ::facebook::jsi::Value value, - bool isException, - ExceptionDetails exceptionDetails) - : value(std::move(value)), - isException(isException), - exceptionDetails(std::move(exceptionDetails)) {} -}; - -/// ProgramState represents the state of a paused program. An instance of -/// ProgramState is available as the getProgramState() member function of class -/// Debugger. -class HERMES_EXPORT ProgramState { - public: - /// \return the reason for the Pause. - PauseReason getPauseReason() const { - return pauseReason_; - } - - /// \return the breakpoint if the PauseReason is Breakpoint, otherwise - /// kInvalidBreakpoint. - BreakpointID getBreakpoint() const { - return breakpoint_; - } - - /// \return the evaluation result if the PauseReason is due to EvalComplete. - EvalResult getEvalResult() const; - - /// \returns a stack trace for the current execution. - const StackTrace &getStackTrace() const { - return stackTrace_; - } - - /// \returns lexical information about the state in a given frame. - LexicalInfo getLexicalInfo(uint32_t frameIndex) const; - - /// \return information about a variable in a given lexical scope, in a given - /// frame. - VariableInfo getVariableInfo( - uint32_t frameIndex, - ScopeDepth scopeDepth, - uint32_t variableIndexInScope) const; - - /// \return information about the `this` value at a given stack depth. - VariableInfo getVariableInfoForThis(uint32_t frameIndex) const; - - /// \return the number of variables in a given frame. - /// This is deprecated: prefer using getLexicalInfoInFrame(). - uint32_t getVariablesCountInFrame(uint32_t frameIndex) const { - auto info = getLexicalInfo(frameIndex); - uint32_t result = 0; - for (ScopeDepth i = 0, max = info.getScopesCount(); i < max; i++) - result += info.getVariablesCountInScope(i); - return result; - } - - /// \return info for a variable at a given index \p variableIndex, in a given - /// frame at index \p frameIndex. - /// This is deprecated. Prefer the getVariableInfo() that takes three - /// parameters. - VariableInfo getVariableInfo(uint32_t frameIndex, uint32_t variableIndex) - const { - LexicalInfo info = getLexicalInfo(frameIndex); - uint32_t remaining = variableIndex; - for (ScopeDepth scope = 0;; scope++) { - assert(scope < info.getScopesCount() && "Index out of bounds"); - uint32_t count = info.getVariablesCountInScope(scope); - if (remaining < count) { - return getVariableInfo(frameIndex, scope, remaining); - } - remaining -= count; - } - } - - private: - friend Debugger; - /// ProgramState must not be copied, because some of its implementation - /// requires querying the live program state and so the state must not be - /// retained after the pause returns. - /// ProgramState must not be copied. - ProgramState(const ProgramState &) = delete; - ProgramState &operator=(const ProgramState &) = delete; - - ::hermes::vm::Debugger *impl() const; - - ProgramState(Debugger *dbg) : dbg_(dbg) {} - Debugger *dbg_; - PauseReason pauseReason_{}; - StackTrace stackTrace_; - EvalResult evalResult_; - BreakpointID breakpoint_{kInvalidBreakpoint}; -}; - -/// Command represents an action that you can request the debugger to perform -/// when returned from didPause(). -class HERMES_EXPORT Command { - public: - /// Commands may be moved. - Command(Command &&); - Command &operator=(Command &&); - ~Command(); - - /// \return a Command that steps with the given StepMode \p mode. - static Command step(StepMode mode); - - /// \return a Command that continues execution. - static Command continueExecution(); - - /// \return a Command that evaluates JavaScript code \p src in the - /// frame at index \p frameIndex. - static Command eval(const String &src, uint32_t frameIndex); - - /// \return a boolean whether this Command was constructed using the static - /// eval() method - bool isEval(); - - private: - friend Debugger; - explicit Command(::hermes::vm::DebugCommand &&); - std::unique_ptr<::hermes::vm::DebugCommand> debugCommand_; -}; - -/// Debugger allows access to the Hermes debugging functionality. An instance of -/// Debugger is available from HermesRuntime, and also passed to your -/// EventObserver. -class HERMES_EXPORT Debugger { - public: - /// Set the Debugger event observer. The event observer is notified of - /// debugging event, specifically when the program pauses. This is simply a - /// raw pointer: it is the client's responsibility to clear the event observer - /// if the event observer is deallocated before the Debugger. - void setEventObserver(EventObserver *observer); - - /// Sets the property %isDebuggerAttached in %DebuggerInternal object. Can be - /// called from any thread. - void setIsDebuggerAttached(bool isAttached); - - /// Asynchronously triggers a pause. This may be called from any thread. This - /// is inherently racey and the exact point at which the program pauses is not - /// guaranteed. You can discover when the program has paused through the event - /// observer. - void triggerAsyncPause(AsyncPauseKind kind); - - /// \return the ProgramState representing the state of the paused program. - /// This may only be invoked when the program is paused. - const ProgramState &getProgramState() const { - return state_; - } - - /// \return the source map URL for the \p fileId. - String getSourceMappingUrl(uint32_t fileId) const; - - /// Gets the list of loaded scripts. The order of the scripts in the vector - /// will be the same across calls. - /// \return list of loaded scripts - std::vector getLoadedScripts() const; - - /// Gets the current stack trace. - /// \return stack trace with call frames if runtime is in the interpreter - /// loop, otherwise return no call frames - StackTrace captureStackTrace() const; - - /// -- Breakpoint Management -- - - /// Sets a breakpoint on a given SourceLocation. - /// \return the ID of the breakpoint, 0 if it wasn't created. - BreakpointID setBreakpoint(SourceLocation loc); - - /// Sets the condition on breakpoint \p breakpoint. - /// The condition will be stored with the breakpoint, - /// and if non-empty, will be executed to determine whether to actually - /// pause on the breakpoint; only if ToBoolean(condition) is true - /// and does not throw will the debugger pause on \p breakpoint. - /// \param condition the code to execute to determine whether to break; - /// if empty, the condition is considered to not be set. - void setBreakpointCondition(BreakpointID breakpoint, const String &condition); - - /// Deletes a breakpoint. - void deleteBreakpoint(BreakpointID breakpoint); - - /// Deletes all breakpoints. - void deleteAllBreakpoints(); - - /// Mark a breakpoint as enabled. Breakpoints are by default enabled. - void setBreakpointEnabled(BreakpointID breakpoint, bool enable); - - /// \return information on a breakpoint. - BreakpointInfo getBreakpointInfo(BreakpointID breakpoint); - - /// \return a list of extant breakpoints. - std::vector getBreakpoints(); - - /// Set whether the debugger should pause when an exception is thrown. - void setPauseOnThrowMode(PauseOnThrowMode mode); - - /// \return whether the debugger pauses when an exception is thrown. - PauseOnThrowMode getPauseOnThrowMode() const; - - /// Set whether the debugger should pause after a script was loaded. - void setShouldPauseOnScriptLoad(bool flag); - - /// \return whether the debugger should pause after a script was loaded. - bool getShouldPauseOnScriptLoad() const; - - /// \return the thrown value if paused on an exception, or - /// jsi::Value::undefined() if not. - ::facebook::jsi::Value getThrownValue(); - - private: - friend std::unique_ptr hermes::makeHermesRuntime( - const ::hermes::vm::RuntimeConfig &); - friend std::unique_ptr - hermes::makeThreadSafeHermesRuntime(const ::hermes::vm::RuntimeConfig &); - friend ProgramState; - - /// Debuggers may not be moved or copied. - Debugger(const Debugger &) = delete; - void operator=(const Debugger &) = delete; - Debugger(Debugger &&) = delete; - void operator=(Debugger &&) = delete; - - /// Implementation detail used by ProgramState. - ::facebook::jsi::Value jsiValueFromHermesValue(::hermes::vm::HermesValue hv); - - explicit Debugger( - ::facebook::hermes::HermesRuntime *runtime, - ::hermes::vm::Runtime &vmRuntime); - - ::facebook::hermes::HermesRuntime *const runtime_; - EventObserver *eventObserver_ = nullptr; - ::hermes::vm::Runtime &vmRuntime_; - ::hermes::vm::Debugger *impl_; - ProgramState state_; -}; - -/// A subclass of EventObserver may be set on the Debugger via -/// setEventObserver(). It receives notifications when the Debugger pauses. -class HERMES_EXPORT EventObserver { - public: - /// didPause() is invoked when the JavaScript program has paused. The - /// The Debugger \p debugger can be used to manipulate breakpoints and enqueue - /// debugger commands such as stepping, etc. It can also be used to discover - /// the call stack and variables via debugger.getProgramState(). - /// \return a Command for the debugger to perform. - virtual Command didPause(Debugger &debugger) = 0; - - /// Invoked when the debugger resolves a previously unresolved breakpoint. - /// Note that the debugger is *not* paused during this, - /// and thus debugger.getProgramState() is not valid. - /// This callback may not invoke JavaScript or enqueue debugger commands. - virtual void breakpointResolved(Debugger &debugger, BreakpointID breakpoint) { - } - - virtual ~EventObserver(); -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#else // !HERMES_ENABLE_DEBUGGER - -#include - -#include "hermes/Public/DebuggerTypes.h" - -namespace facebook { -namespace hermes { -namespace debugger { - -class EventObserver; - -struct VariableInfo { - String name; - ::facebook::jsi::Value value; -}; - -struct EvalResult { - ::facebook::jsi::Value value; - bool isException = false; - ExceptionDetails exceptionDetails; - - EvalResult(EvalResult &&) = default; - EvalResult() = default; - - EvalResult( - ::facebook::jsi::Value value, - bool isException, - ExceptionDetails exceptionDetails) - : value(std::move(value)), - isException(isException), - exceptionDetails(std::move(exceptionDetails)) {} -}; - -class ProgramState { - public: - ProgramState() {} - - PauseReason getPauseReason() const { - return PauseReason::Exception; - } - - BreakpointID getBreakpoint() const { - return 0; - } - - EvalResult getEvalResult() const { - return EvalResult(); - } - - const StackTrace &getStackTrace() const { - return stackTrace_; - } - - LexicalInfo getLexicalInfo(uint32_t frameIndex) const { - return LexicalInfo(); - } - - VariableInfo getVariableInfo( - uint32_t frameIndex, - ScopeDepth scopeDepth, - uint32_t variableIndexInScope) const { - return VariableInfo(); - } - - VariableInfo getVariableInfoForThis(uint32_t frameIndex) const { - return VariableInfo(); - } - - uint32_t getVariablesCountInFrame(uint32_t frameIndex) const { - return 0; - } - - VariableInfo getVariableInfo(uint32_t frameIndex, uint32_t variableIndex) - const { - return VariableInfo(); - } - - private: - ProgramState(const ProgramState &) = delete; - ProgramState &operator=(const ProgramState &) = delete; - - StackTrace stackTrace_; -}; - -class Command { - public: - Command(Command &&) {} - Command &operator=(Command &&); - ~Command() {} - - static Command step(StepMode mode) { - return Command(); - } - static Command continueExecution() { - return Command(); - } - static Command eval(const String &src, uint32_t frameIndex) { - return Command(); - } - bool isEval() { - return false; - } - - private: - Command() {} -}; - -class Debugger { - public: - explicit Debugger() {} - - void setEventObserver(EventObserver *observer) {} - void setIsDebuggerAttached(bool isAttached) {} - void triggerAsyncPause(AsyncPauseKind kind) {} - const ProgramState &getProgramState() const { - return programState_; - } - String getSourceMappingUrl(uint32_t fileId) const { - return ""; - }; - std::vector getLoadedScripts() const { - return {}; - } - StackTrace captureStackTrace() const { - return StackTrace{}; - } - BreakpointID setBreakpoint(SourceLocation loc) { - return 0; - } - void setBreakpointCondition( - BreakpointID breakpoint, - const String &condition) {} - void deleteBreakpoint(BreakpointID breakpoint) {} - void deleteAllBreakpoints() {} - void setBreakpointEnabled(BreakpointID breakpoint, bool enable) {} - BreakpointInfo getBreakpointInfo(BreakpointID breakpoint) { - return BreakpointInfo(); - } - std::vector getBreakpoints() { - return std::vector(); - } - void setPauseOnThrowMode(PauseOnThrowMode mode) {} - PauseOnThrowMode getPauseOnThrowMode() const { - return PauseOnThrowMode::None; - } - void setShouldPauseOnScriptLoad(bool flag) {} - bool getShouldPauseOnScriptLoad() const { - return false; - } - ::facebook::jsi::Value getThrownValue() { - return ::facebook::jsi::Value::undefined(); - } - - private: - Debugger(const Debugger &) = delete; - void operator=(const Debugger &) = delete; - Debugger(Debugger &&) = delete; - void operator=(Debugger &&) = delete; - - ProgramState programState_; -}; - -class EventObserver { - public: - virtual Command didPause(Debugger &debugger) = 0; - virtual void breakpointResolved(Debugger &debugger, BreakpointID breakpoint) { - } - - virtual ~EventObserver() {} -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif // !HERMES_ENABLE_DEBUGGER - -#endif // HERMES_DEBUGGERAPI_H diff --git a/NativeScript/napi/hermes/include_old/hermes/MurmurHash.h b/NativeScript/napi/hermes/include_old/hermes/MurmurHash.h deleted file mode 100644 index 3d2e53ee9..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/MurmurHash.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -#pragma once - -#include -#include - -// Computes the hash of key using MurmurHash3 algorithm, the value is planced in the "hash" output parameter -// The function returns whether or not key is comprised of only ASCII characters (<=127) -bool murmurhash(const uint8_t *key, size_t length, uint64_t &hash); \ No newline at end of file diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/Buffer.h b/NativeScript/napi/hermes/include_old/hermes/Public/Buffer.h deleted file mode 100644 index 3a4e8c267..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/Buffer.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_BUFFER_H -#define HERMES_PUBLIC_BUFFER_H - -#include - -#include -#include - -namespace hermes { - -/// A generic buffer interface. E.g. for memmapped bytecode. -class HERMES_EXPORT Buffer { - public: - Buffer() : data_(nullptr), size_(0) {} - - Buffer(const uint8_t *data, size_t size) : data_(data), size_(size) {} - - virtual ~Buffer(); - - const uint8_t *data() const { - return data_; - }; - - size_t size() const { - return size_; - } - - protected: - const uint8_t *data_ = nullptr; - size_t size_ = 0; -}; - -} // namespace hermes - -#endif diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/CrashManager.h b/NativeScript/napi/hermes/include_old/hermes/Public/CrashManager.h deleted file mode 100644 index 07a9b5929..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/CrashManager.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_CRASHMANAGER_H -#define HERMES_PUBLIC_CRASHMANAGER_H - -#include - -#include -#include - -namespace hermes { -namespace vm { - -/// A CrashManager provides functions that determine what memory and data is -/// included in dumps in case of crashes. -class HERMES_EXPORT CrashManager { - public: - /// CallbackKey is the type of an identifier for a callback supplied to the - /// CrashManager. - using CallbackKey = int; - /// Type for the callback function invoked on crash. The fd supplied is a raw - /// file stream an implementation should write a JSON object to. - using CallbackFunc = std::function; - - /// Registers some memory to be included in any crash dump that occurs. - /// \param mem A pointer to allocated memory. It must be unregistered - /// before being freed. - /// \param length The number of bytes the memory controls. - virtual void registerMemory(void *mem, size_t length) = 0; - - /// Unregisters some memory from being included in any crash dump that occurs. - virtual void unregisterMemory(void *mem) = 0; - - /// Registers custom data to be included in any crash dump that occurs. - /// Calling \c setCustomData on the same key twice will overwrite the previous - /// value. - /// \param key A tag to look for in the custom data output. Distinguishes - /// between multiple values. - /// \param val The value to store for the given key. - virtual void setCustomData(const char *key, const char *val) = 0; - - /// If the given \p key has an associated custom data string, remove the - /// association. If the key hasn't been set before, is a no-op. - virtual void removeCustomData(const char *key) = 0; - - /// Same as \c setCustomData, except it is only set for the current thread. - virtual void setContextualCustomData(const char *key, const char *val) = 0; - - /// Same as \c removeCustomData, except it is for keys set with \c - /// setContextualCustomData. - virtual void removeContextualCustomData(const char *key) = 0; - - /// Registers a function to be called after a crash has occurred. This - /// function can examine memory and serialize this to a JSON output stream. - /// Implmentations decide where the stream is routed to. - /// \param callback A function to called after a crash. - /// \return A CallbackKey representing the function you provided. Pass this - /// key into unregisterCallback when it that callback is no longer needed. - virtual CallbackKey registerCallback(CallbackFunc callback) = 0; - - /// Unregisters a previously registered callback. After this function returns, - /// the previously registered function will not be executed by this - /// CrashManager during a crash. - virtual void unregisterCallback(CallbackKey key) = 0; - - /// the heap information. - struct HeapInformation { - /// The amount of memory that is currently in use - size_t used_{0}; - /// The amount of memory that can currently be allocated - /// before a full GC is triggered. - size_t size_{0}; - }; - - /// Record the heap information. - /// \param heapInfo The current heap information - virtual void setHeapInfo(const HeapInformation &heapInfo) = 0; - - virtual ~CrashManager(); -}; - -/// A CrashManager that does nothing. -class HERMES_EXPORT NopCrashManager final : public CrashManager { - public: - void registerMemory(void *, size_t) override {} - void unregisterMemory(void *) override {} - void setCustomData(const char *, const char *) override {} - void removeCustomData(const char *) override {} - void setContextualCustomData(const char *, const char *) override {} - void removeContextualCustomData(const char *) override {} - CallbackKey registerCallback(CallbackFunc /*callback*/) override { - return 0; - } - void unregisterCallback(CallbackKey /*key*/) override {} - void setHeapInfo(const HeapInformation & /*heapInfo*/) override {} - - ~NopCrashManager() override; -}; - -} // namespace vm -} // namespace hermes -#endif diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/CtorConfig.h b/NativeScript/napi/hermes/include_old/hermes/Public/CtorConfig.h deleted file mode 100644 index aff3f3989..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/CtorConfig.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_CTORCONFIG_H -#define HERMES_PUBLIC_CTORCONFIG_H - -#include - -/// Defines a new class, called \p NAME representing a constructor config, and -/// an associated builder class. -/// -/// The fields of the class (along with their types and default values) are -/// encoded in the \p FIELDS parameter, and any logic to be run whilst building -/// the config can be passed as a code block in \p BUILD_BODY. -/// -/// Example: -/// -/// Suppose we wish to define a configuration class called Foo, with the -/// following fields and default values: -/// -/// int A = 0; -/// int B = 42; -/// std::string C = "hello"; -/// -/// Such that the value in A is at most the length of \c C. -/// -/// We can do so with the following declaration: -/// -/// " #define FIELDS(F) \ " -/// " F(int, A) \ " -/// " F(int, B, 42) \ " -/// " F(std::string, C, "hello") " -/// " " -/// " _HERMES_CTORCONFIG_STRUCT(Foo, FIELDS, { " -/// " A_ = std::min(A_, C_.length()); " -/// " }); " -/// -/// N.B. -/// - The definition of A does not mention any value -- meaning it is -/// default initialised. -/// - References to the fields in the validation logic have a trailling -/// underscore. -/// -#define _HERMES_CTORCONFIG_STRUCT(NAME, FIELDS, BUILD_BODY) \ - class NAME { \ - FIELDS(_HERMES_CTORCONFIG_FIELD_DECL) \ - \ - public: \ - class Builder; \ - friend Builder; \ - FIELDS(_HERMES_CTORCONFIG_GETTER) \ - \ - /* returns a Builder that starts with the current config. */ \ - inline Builder rebuild() const; \ - \ - private: \ - inline void doBuild(const Builder &builder); \ - }; \ - \ - class NAME::Builder { \ - NAME config_; \ - \ - FIELDS(_HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_DECL) \ - \ - public: \ - Builder() = default; \ - \ - explicit Builder(const NAME &config) : config_(config) {} \ - \ - inline const NAME build() { \ - config_.doBuild(*this); \ - return config_; \ - } \ - \ - /* The explicitly set fields of \p newconfig update \ - * the corresponding fields of \p this. */ \ - inline Builder update(const NAME::Builder &newConfig); \ - \ - FIELDS(_HERMES_CTORCONFIG_SETTER) \ - FIELDS(_HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_ACCESSOR) \ - }; \ - \ - NAME::Builder NAME::rebuild() const { \ - return Builder(*this); \ - } \ - \ - NAME::Builder NAME::Builder::update(const NAME::Builder &newConfig) { \ - FIELDS(_HERMES_CTORCONFIG_UPDATE) \ - return *this; \ - } \ - \ - void NAME::doBuild(const NAME::Builder &builder) { \ - (void)builder; \ - BUILD_BODY \ - } - -/// Helper Macros - -#define _HERMES_CTORCONFIG_FIELD_DECL(CX, TYPE, NAME, ...) \ - TYPE NAME##_{__VA_ARGS__}; - -/// This ignores the first and trailing arguments, and defines a member -/// indicating whether field NAME was set explicitly. -#define _HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_DECL(CX, TYPE, NAME, ...) \ - bool NAME##Explicit_{false}; - -/// This defines an accessor for the "Explicit_" fields defined above. -#define _HERMES_CTORCONFIG_FIELD_EXPLICIT_BOOL_ACCESSOR(CX, TYPE, NAME, ...) \ - bool has##NAME() const { \ - return NAME##Explicit_; \ - } - -/// Placeholder token for fields whose defaults are not constexpr, to make the -/// listings more readable. -#define HERMES_NON_CONSTEXPR - -#define _HERMES_CTORCONFIG_GETTER(CX, TYPE, NAME, ...) \ - inline TYPE get##NAME() const { \ - return NAME##_; \ - } \ - static CX TYPE getDefault##NAME() { \ - /* Instead of parens around TYPE (non-standard) */ \ - using TypeAsSingleToken = TYPE; \ - return TypeAsSingleToken{__VA_ARGS__}; \ - } - -#define _HERMES_CTORCONFIG_SETTER(CX, TYPE, NAME, ...) \ - inline auto with##NAME(TYPE NAME)->decltype(*this) { \ - config_.NAME##_ = std::move(NAME); \ - NAME##Explicit_ = true; \ - return *this; \ - } - -#define _HERMES_CTORCONFIG_BUILDER_GETTER(CX, TYPE, NAME, ...) \ - TYPE get##NAME() const { \ - return config_.NAME##_; \ - } - -#define _HERMES_CTORCONFIG_UPDATE(CX, TYPE, NAME, ...) \ - if (newConfig.has##NAME()) { \ - with##NAME(newConfig.config_.get##NAME()); \ - } - -#endif // HERMES_PUBLIC_CTORCONFIG_H diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/DebuggerTypes.h b/NativeScript/napi/hermes/include_old/hermes/Public/DebuggerTypes.h deleted file mode 100644 index 88184c077..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/DebuggerTypes.h +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_DEBUGGERTYPES_H -#define HERMES_PUBLIC_DEBUGGERTYPES_H - -#include -#include -#include -#pragma GCC diagnostic push - -#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 -#pragma GCC diagnostic ignored "-Wshorten-64-to-32" -#endif -namespace hermes { -namespace vm { -class Debugger; -} -} // namespace hermes - -namespace facebook { -namespace hermes { -namespace debugger { - -class ProgramState; - -/// Strings in the Debugger are UTF-8 encoded. When converting from a JavaScript -/// string, valid UTF-16 surrogate pairs are decoded. Surrogate halves are -/// converted into the Unicode replacement character. -using String = std::string; - -/// Debugging entities like breakpoints are identified by a unique ID. The -/// Debugger will not re-use IDs even across different entity types. 0 is an -/// invalid ID. -using BreakpointID = uint64_t; -// NOTE: Can't be kInvalidID due to a clash with MacTypes.h's define kInvalidID. -constexpr uint64_t kInvalidBreakpoint = 0; - -/// Scripts when loaded are identified by a script ID. -/// These are not reused within one invocation of the VM. -using ScriptID = uint32_t; - -/// A SourceLocation is a small value-type representing a location in a source -/// file. -constexpr uint32_t kInvalidLocation = ~0u; -struct SourceLocation { - /// Line in the source. 1 based. - uint32_t line = kInvalidLocation; - - /// Column in the source. 1 based. - uint32_t column = kInvalidLocation; - - /// Identifier of the source file. - ScriptID fileId = kInvalidLocation; - - /// Name of the source file. - String fileName; -}; - -/// CallFrameInfo is a value type representing an entry in a call stack. -struct CallFrameInfo { - /// Name of the function executing in this frame. - String functionName; - - /// Source location of the program counter for this frame. - SourceLocation location; -}; - -/// StackTrace represents a list of call frames, either in the current execution -/// or captured in an exception. -struct StackTrace { - /// \return the number of call frames. - uint32_t callFrameCount() const { - return frames_.size(); - } - - /// \return call frame info at a given index. 0 represents the topmost - /// (current) frame on the call stack. - CallFrameInfo callFrameForIndex(uint32_t index) const { - return frames_.at(index); - } - - StackTrace() {} - - private: - explicit StackTrace(std::vector frames) - : frames_(std::move(frames)){}; - friend ProgramState; - friend ::hermes::vm::Debugger; - std::vector frames_; -}; - -/// ExceptionDetails is a value type describing an exception. -struct ExceptionDetails { - /// Textual description of the exception. - String text; - - /// Location where the exception was thrown. - SourceLocation location; - - /// Get the stack trace associated with the exception. - const StackTrace &getStackTrace() const { - return stackTrace_; - } - - private: - friend ::hermes::vm::Debugger; - StackTrace stackTrace_; -}; - -/// A list of possible reasons for a Pause. -enum class PauseReason { - ScriptLoaded, /// A script file was loaded, and the debugger has requested - /// pausing after script load. - DebuggerStatement, /// A debugger; statement was hit. - Breakpoint, /// A breakpoint was hit. - StepFinish, /// A Step operation completed. - Exception, /// An Exception was thrown. - AsyncTriggerImplicit, /// The Pause is the result of - /// triggerAsyncPause(Implicit). - AsyncTriggerExplicit, /// The Pause is the result of - /// triggerAsyncPause(Explicit). - EvalComplete, /// An eval() function finished. -}; - -/// When stepping, the mode with which to step. -enum class StepMode { - Into, /// Enter into any function calls. - Over, /// Skip over any function calls. - Out, /// Step until the current function exits. -}; - -/// When setting pause on throw, this specifies when to pause. -enum class PauseOnThrowMode { - None, /// Never pause on exceptions. - Uncaught, /// Only pause on uncaught exceptions. - All, /// Pause any time an exception is thrown. -}; - -/// When requesting an async break, this specifies whether it was an implicit -/// break from the inspector or a user-requested explicit break. -enum class AsyncPauseKind { - /// Implicit pause to allow movement of jsi::Value types between threads. - /// The user will not be running commands and the inspector will immediately - /// request a Continue. - Implicit, - - /// Explicit pause requested by the user. - /// Clears any stepping state and allows the user to run their own commands. - Explicit, -}; - -/// A type representing depth in a lexical scope chain. -using ScopeDepth = uint32_t; - -/// Information about lexical entities (for now, just variable names). -struct LexicalInfo { - /// \return the number of scopes. - ScopeDepth getScopesCount() const { - return variableCountsByScope_.size(); - } - - /// \return the number of variables in a given scope. - uint32_t getVariablesCountInScope(ScopeDepth depth) const { - return variableCountsByScope_.at(depth); - } - - private: - friend ::hermes::vm::Debugger; - std::vector variableCountsByScope_; -}; - -/// Information about a breakpoint. -struct BreakpointInfo { - /// ID of the breakpoint. - /// kInvalidBreakpoint if the info is not valid. - BreakpointID id; - - /// Whether the breakpoint is enabled. - bool enabled; - - /// Whether the breakpoint has been resolved. - bool resolved; - - /// The originally requested location of the breakpoint. - SourceLocation requestedLocation; - - /// The resolved location of the breakpoint if resolved is true. - SourceLocation resolvedLocation; -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/GCConfig.h b/NativeScript/napi/hermes/include_old/hermes/Public/GCConfig.h deleted file mode 100644 index 8d3f316f7..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/GCConfig.h +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_GCCONFIG_H -#define HERMES_PUBLIC_GCCONFIG_H - -#include "hermes/Public/CtorConfig.h" -#include "hermes/Public/GCTripwireContext.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace hermes { -namespace vm { - -/// A type big enough to accomodate the entire allocated address space. -/// Individual allocations are always 'uint32_t', but on a 64-bit machine we -/// might want to accommodate a larger total heap (or not, in which case we keep -/// it 32-bit). -using gcheapsize_t = uint32_t; - -/// Represents a value before and after an event. -/// NOTE: Not a std::pair because using the names are more readable than first -/// and second. -struct BeforeAndAfter { - uint64_t before; - uint64_t after; -}; - -struct GCAnalyticsEvent { - /// The same value as \p Name from GCConfig. Stored here for simplicity of - /// the API since this is passed in callbacks that might not be able to store - /// the name. For a given Runtime, this will be the same value every time. - std::string runtimeDescription; - - /// The kind of GC this was. For a given Runtime, this will be the same value - /// every time. - std::string gcKind; - - /// The type of collection that ran, typically differentiating a "young" - /// generation GC and an "old" generation GC. When other values say they're - /// "scoped to the collectionType", it means that for a generation GC - /// they're only reporting the numbers for that generation. - std::string collectionType; - - /// The cause of this GC. Can be an arbitrary string describing the cause. - /// Typically "natural" is used to mean that the GC decided it was time, and - /// other causes mean it was forced by some other condition. - std::string cause; - - /// The wall time a collection took from start to end. - std::chrono::milliseconds duration; - - /// The CPU time a collection took from start to end. This time measure will - /// exclude time waiting on disk, mutexes, or time spent not scheduled to run. - std::chrono::milliseconds cpuDuration; - - /// The number of bytes allocated in the heap before and after the collection. - /// measurement does not include fragmentation, and is the same as the sum of - /// all sizes in calls to \p GC::makeA into that generation (including any - /// rounding up the GC does). - /// The value is scoped to the \p collectionType. - BeforeAndAfter allocated; - - /// The number of bytes in use by the heap before and after the collection. - /// This measurement can include fragmentation if the \p gcKind has that - /// concept. - /// The value is scoped to the \p collectionType. - BeforeAndAfter size; - - /// The number of bytes external to the JS heap before and after the - /// collection. - /// The value is scoped to the \p collectionType. - BeforeAndAfter external; - - /// The ratio of cells that survived the collection to all cells before - /// the collection. Note that this is in term of sizes of cells, not the - /// numbers of cells. Excludes any cells not in direct use by the JS program, - /// such as FillerCell or FreelistCell. - /// The value is scoped to the \p collectionType. - double survivalRatio; - - /// A list of metadata tags to annotate this event with. - std::vector tags; -}; - -/// Parameters to control a tripwire function called when the live set size -/// surpasses a given threshold after collections. Check documentation in -/// README.md -#define GC_TRIPWIRE_FIELDS(F) \ - /* If the heap size is above this threshold after a collection, the tripwire \ - * is triggered. */ \ - F(constexpr, gcheapsize_t, Limit, std::numeric_limits::max()) \ - \ - /* The callback to call when the tripwire is considered triggered. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::function, \ - Callback, \ - nullptr) \ - /* GC_TRIPWIRE_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(GCTripwireConfig, GC_TRIPWIRE_FIELDS, {}) - -#undef HEAP_TRIPWIRE_FIELDS - -#define GC_HANDLESAN_FIELDS(F) \ - /* The probability with which the GC should keep moving the heap */ \ - /* to detect stale GC handles. */ \ - F(constexpr, double, SanitizeRate, 0.0) \ - /* Random seed to use for basis of decisions whether or not to */ \ - /* sanitize. A negative value will mean a seed will be chosen at */ \ - /* random. */ \ - F(constexpr, int64_t, RandomSeed, -1) \ - /* GC_HANDLESAN_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(GCSanitizeConfig, GC_HANDLESAN_FIELDS, {}) - -#undef GC_HANDLESAN_FIELDS - -/// How aggressively to return unused memory to the OS. -enum ReleaseUnused { - kReleaseUnusedNone = 0, /// Don't try to release unused memory. - kReleaseUnusedOld, /// Only old gen, on full collections. - kReleaseUnusedYoungOnFull, /// Also young gen, but only on full collections. - kReleaseUnusedYoungAlways /// Also young gen, also on young gen collections. -}; - -enum class GCEventKind { - CollectionStart, - CollectionEnd, -}; - -/// Parameters for GC Initialisation. Check documentation in README.md -/// constexpr indicates that the default value is constexpr. -#define GC_FIELDS(F) \ - /* Minimum heap size hint. */ \ - F(constexpr, gcheapsize_t, MinHeapSize, 0) \ - \ - /* Initial heap size hint. */ \ - F(constexpr, gcheapsize_t, InitHeapSize, 32 << 20) \ - \ - /* Maximum heap size hint. */ \ - F(constexpr, gcheapsize_t, MaxHeapSize, 3u << 30) \ - \ - /* Sizing heuristic: fraction of heap to be occupied by live data. */ \ - F(constexpr, double, OccupancyTarget, 0.5) \ - \ - /* Number of consecutive full collections considered to be an OOM. */ \ - F(constexpr, \ - unsigned, \ - EffectiveOOMThreshold, \ - std::numeric_limits::max()) \ - \ - /* Sanitizer configuration for the GC. */ \ - F(constexpr, GCSanitizeConfig, SanitizeConfig) \ - \ - /* Whether to Keep track of GC Statistics. */ \ - F(constexpr, bool, ShouldRecordStats, false) \ - \ - /* How aggressively to return unused memory to the OS. */ \ - F(constexpr, ReleaseUnused, ShouldReleaseUnused, kReleaseUnusedOld) \ - \ - /* Name for this heap in logs. */ \ - F(HERMES_NON_CONSTEXPR, std::string, Name, "") \ - \ - /* Configuration for the Heap Tripwire. */ \ - F(HERMES_NON_CONSTEXPR, GCTripwireConfig, TripwireConfig) \ - \ - /* Whether to (initially) allocate from the young gen (true) or the */ \ - /* old gen (false). */ \ - F(constexpr, bool, AllocInYoung, true) \ - \ - /* Whether to fill the YG with invalid data after each collection. */ \ - F(constexpr, bool, OverwriteDeadYGObjects, false) \ - \ - /* Whether to revert, if necessary, to young-gen allocation at TTI. */ \ - F(constexpr, bool, RevertToYGAtTTI, false) \ - \ - /* Whether to use mprotect on GC metadata between GCs. */ \ - F(constexpr, bool, ProtectMetadata, false) \ - \ - /* Callout for an analytics event. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::function, \ - AnalyticsCallback, \ - nullptr) \ - \ - /* Called at GC events (see GCEventKind enum for the list). The */ \ - /* second argument contains human-readable details about the event. */ \ - /* NOTE: The function MUST NOT invoke any methods on the Runtime. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::function, \ - Callback, \ - nullptr) \ - /* GC_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(GCConfig, GC_FIELDS, { - if (builder.hasMinHeapSize()) { - if (builder.hasInitHeapSize()) { - // If both are specified, normalize the initial size up to the minimum, - // if necessary. - InitHeapSize_ = std::max(MinHeapSize_, InitHeapSize_); - } else { - // If the minimum is set explicitly, but the initial heap size is not, - // use the minimum as the initial size. - InitHeapSize_ = MinHeapSize_; - } - } - assert(InitHeapSize_ >= MinHeapSize_); - - // Make sure the max is at least the Init. - MaxHeapSize_ = std::max(InitHeapSize_, MaxHeapSize_); -}) - -#undef GC_FIELDS - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_GCCONFIG_H diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/GCTripwireContext.h b/NativeScript/napi/hermes/include_old/hermes/Public/GCTripwireContext.h deleted file mode 100644 index 4a8f500f8..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/GCTripwireContext.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_GCTRIPWIRECONTEXT_H -#define HERMES_PUBLIC_GCTRIPWIRECONTEXT_H - -#include - -#include -#include -#include - -namespace hermes { -namespace vm { - -/// Interface passed to the GC tripwire callback when it fires. -class HERMES_EXPORT GCTripwireContext { - public: - virtual ~GCTripwireContext(); - - /// Captures the heap to a file. - /// \param path to save the heap capture. - /// \return Empty error code if the heap capture succeeded, else a real error - /// code. - virtual std::error_code createSnapshotToFile(const std::string &path) = 0; - - /// Captures the heap to a stream. - /// \param os stream to save the heap capture to. - /// \return Empty error code if the heap capture succeeded, else a real error - /// code. - virtual std::error_code createSnapshot( - std::ostream &os, - bool captureNumericValue) = 0; -}; - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_GCTRIPWIRECONTEXT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/HermesExport.h b/NativeScript/napi/hermes/include_old/hermes/Public/HermesExport.h deleted file mode 100644 index f9832cb5b..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/HermesExport.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_EXPORT -#ifdef _MSC_VER -#define HERMES_EXPORT __declspec(dllexport) -#else // _MSC_VER -#define HERMES_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(HERMES_EXPORT) diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/JSOutOfMemoryError.h b/NativeScript/napi/hermes/include_old/hermes/Public/JSOutOfMemoryError.h deleted file mode 100644 index 95093ab76..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/JSOutOfMemoryError.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_JSOUTOFMEMORYERROR_H -#define HERMES_PUBLIC_JSOUTOFMEMORYERROR_H - -#include - -#include -#include - -namespace hermes { -namespace vm { - -/// A std::runtime_error class for out-of-memory. -class HERMES_EXPORT JSOutOfMemoryError : public std::runtime_error { - friend class GCBase; - JSOutOfMemoryError(const std::string &what_arg) - : std::runtime_error(what_arg) {} - ~JSOutOfMemoryError() override; -}; - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_JSOUTOFMEMORYERROR_H diff --git a/NativeScript/napi/hermes/include_old/hermes/Public/RuntimeConfig.h b/NativeScript/napi/hermes/include_old/hermes/Public/RuntimeConfig.h deleted file mode 100644 index 858f1f502..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/Public/RuntimeConfig.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_PUBLIC_RUNTIMECONFIG_H -#define HERMES_PUBLIC_RUNTIMECONFIG_H - -#include "hermes/Public/CrashManager.h" -#include "hermes/Public/CtorConfig.h" -#include "hermes/Public/GCConfig.h" - -#include -#include - -namespace hermes { -namespace vm { - -enum CompilationMode { - SmartCompilation, - ForceEagerCompilation, - ForceLazyCompilation -}; - -enum class SynthTraceMode : int8_t { - None, - Replaying, - Tracing, - TracingAndReplaying, -}; - -class PinnedHermesValue; - -// Parameters for Runtime initialisation. Check documentation in README.md -// constexpr indicates that the default value is constexpr. -#define RUNTIME_FIELDS(F) \ - /* Parameters to be passed on to the GC. */ \ - F(HERMES_NON_CONSTEXPR, vm::GCConfig, GCConfig) \ - \ - /* Pre-allocated Register Stack */ \ - F(constexpr, PinnedHermesValue *, RegisterStack, nullptr) \ - \ - /* Register Stack Size */ \ - F(constexpr, unsigned, MaxNumRegisters, 128 * 1024) \ - \ - /* Native stack remaining before assuming overflow */ \ - F(constexpr, unsigned, NativeStackGap, 64 * 1024) \ - \ - /* Whether to allow eval and Function ctor */ \ - F(constexpr, bool, EnableEval, true) \ - \ - /* Whether to verify the IR generated by eval and Function ctor */ \ - F(constexpr, bool, VerifyEvalIR, false) \ - \ - /* Whether to optimize the code inside eval and Function ctor */ \ - F(constexpr, bool, OptimizedEval, false) \ - \ - /* Whether to emit async break check instructions in eval code */ \ - F(constexpr, bool, AsyncBreakCheckInEval, true) \ - \ - /* Support for ES6 Promise. */ \ - F(constexpr, bool, ES6Promise, true) \ - \ - /* Support for ES6 Proxy. */ \ - F(constexpr, bool, ES6Proxy, true) \ - \ - /* Support for ES6 Class. */ \ - F(constexpr, bool, ES6Class, false) \ - \ - /* Support for ECMA-402 Intl APIs. */ \ - F(constexpr, bool, Intl, true) \ - \ - /* Support for ArrayBuffer, DataView and typed arrays. */ \ - F(constexpr, bool, ArrayBuffer, true) \ - \ - /* Support for using microtasks. */ \ - F(constexpr, bool, MicrotaskQueue, false) \ - \ - /* Runtime set up for synth trace. */ \ - F(constexpr, SynthTraceMode, SynthTraceMode, SynthTraceMode::None) \ - \ - /* Enable sampling certain statistics. */ \ - F(constexpr, bool, EnableSampledStats, false) \ - \ - /* Whether to enable automatic sampling profiler registration */ \ - F(constexpr, bool, EnableSampleProfiling, false) \ - \ - /* Whether to randomize stack placement etc. */ \ - F(constexpr, bool, RandomizeMemoryLayout, false) \ - \ - /* Eagerly read bytecode into page cache. */ \ - F(constexpr, unsigned, BytecodeWarmupPercent, 0) \ - \ - /* Signal-based I/O tracking. Slows down execution. If enabled, */ \ - /* all bytecode buffers > 64 kB passed to Hermes must be mmap:ed. */ \ - F(constexpr, bool, TrackIO, false) \ - \ - /* Enable contents of HermesInternal */ \ - F(constexpr, bool, EnableHermesInternal, true) \ - \ - /* Enable methods exposed to JS for testing */ \ - F(constexpr, bool, EnableHermesInternalTestMethods, false) \ - \ - /* Choose lazy/eager compilation mode. */ \ - F(constexpr, \ - CompilationMode, \ - CompilationMode, \ - CompilationMode::SmartCompilation) \ - \ - /* Choose whether generators are enabled. */ \ - F(constexpr, bool, EnableGenerator, true) \ - \ - /* An interface for managing crashes. */ \ - F(HERMES_NON_CONSTEXPR, \ - std::shared_ptr, \ - CrashMgr, \ - new NopCrashManager) \ - \ - /* The flags passed from a VM experiment */ \ - F(constexpr, uint32_t, VMExperimentFlags, 0) \ - \ - /* Whether or not block scoping is enabled */ \ - F(constexpr, bool, EnableBlockScoping, false) \ - /* RUNTIME_FIELDS END */ - -_HERMES_CTORCONFIG_STRUCT(RuntimeConfig, RUNTIME_FIELDS, {}) - -#undef RUNTIME_FIELDS - -} // namespace vm -} // namespace hermes - -#endif // HERMES_PUBLIC_RUNTIMECONFIG_H diff --git a/NativeScript/napi/hermes/include_old/hermes/RuntimeTaskRunner.h b/NativeScript/napi/hermes/include_old/hermes/RuntimeTaskRunner.h deleted file mode 100644 index 367b267a4..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/RuntimeTaskRunner.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_RUNTIMETASKRUNNER_H -#define HERMES_RUNTIMETASKRUNNER_H - -#include "AsyncDebuggerAPI.h" - -namespace facebook { -namespace hermes { -namespace debugger { - -using RuntimeTask = std::function; -using EnqueueRuntimeTaskFunc = std::function; - -enum class TaskQueues { - All, - Integrator, -}; - -/// Helper for users of AsyncDebuggerAPI that makes it easy to find the -/// earliest opportunity to use the runtime. There are two ways to become -/// the exclusive user of the runtime: -/// - Ask the AsyncDebuggerAPI to interrupt execution and provide a reference -/// to the runtime. Interrupting will only succeed when JavaScript is -/// running, so this method won't produce a prompt response if JavaScript is -/// not running. -/// - Ask the owner of the runtime to provide a reference to the runtime. If -/// the owner is currently running JavaScript (e.g. via a call to -/// evaluateJavaScript), this method won't produce a prompt response. -/// To cover both cases (when JavaScript is running, and when JavaScript isn't -/// running), this helper requests the runtime from both sources, executes the -/// task via the first responder, and sets a flag to indicate to the second -/// responder that nothing more needs to be done. -class RuntimeTaskRunner - : public std::enable_shared_from_this { - public: - RuntimeTaskRunner( - debugger::AsyncDebuggerAPI &debugger, - EnqueueRuntimeTaskFunc enqueueRuntimeTaskFunc); - ~RuntimeTaskRunner(); - - /// Schedule a task to be run with access to the runtime at the earliest - /// opportunity. Before returning, the task is added to the relevant task - /// queues managed by the \p AsyncDebuggerAPI and/or the intergator, with no - /// lingering references to the \p RuntimeTaskRunner. Thus, tasks can be - /// enqueued even if the task runner will be destroyed shortly after. - void enqueueTask(RuntimeTask task, TaskQueues queues = TaskQueues::All); - - private: - /// API where the runtime can be obtained when JavaScript is running. - debugger::AsyncDebuggerAPI &debugger_; - - /// Function provided by the integrator that enqueues a task to be run - /// when JavaScript is not running. - EnqueueRuntimeTaskFunc enqueueRuntimeTask_; -}; - -} // namespace debugger -} // namespace hermes -} // namespace facebook - -#endif // HERMES_RUNTIMETASKRUNNER_H diff --git a/NativeScript/napi/hermes/include_old/hermes/ScriptStore.h b/NativeScript/napi/hermes/include_old/hermes/ScriptStore.h deleted file mode 100644 index e7365cc5b..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/ScriptStore.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -#pragma once - -#include -#include - -namespace facebook { -namespace jsi { - -// Integer type as it's persist friendly. -using ScriptVersion_t = uint64_t; // It should be std::optional once we have c++17 available everywhere. Until - // then, 0 implies versioning not available. -using JSRuntimeVersion_t = uint64_t; // 0 implies version can't be computed. We assert whenever that happens. - -struct VersionedBuffer { - std::shared_ptr buffer; - ScriptVersion_t version; -}; - -struct ScriptSignature { - std::string url; - ScriptVersion_t version; -}; - -struct JSRuntimeSignature { - std::string runtimeName; // e.g. Chakra, V8 - JSRuntimeVersion_t version; -}; - -// Most JSI::Runtime implementation offer some form of prepared JavaScript which offers better performance -// characteristics when loading comparing to plain JavaScript. Embedders can provide an instance of this interface -// (through JSI::Runtime implementation's factory method), to enable persistance of the prepared script and retrieval on -// subsequent evaluation of a script. -struct PreparedScriptStore { - virtual ~PreparedScriptStore() = default; - - // Try to retrieve the prepared javascript for a given combination of script & runtime. - // scriptSignature : Javascript url and version - // RuntimeSignature : Javascript engine type and version - // prepareTag : Custom tag to uniquely identify JS engine specific preparation schemes. It is usually useful while - // experimentation and can be null. It is possible that no prepared script is available for a given script & runtime - // signature. This method should null if so - virtual std::shared_ptr tryGetPreparedScript( - const ScriptSignature &scriptSignature, - const JSRuntimeSignature &runtimeSignature, - const char *prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. - ) noexcept = 0; - - // Persist the prepared javascript for a given combination of script & runtime. - // scriptSignature : Javascript url and version - // RuntimeSignature : Javascript engine type and version - // prepareTag : Custom tag to uniquely identify JS engine specific preparation schemes. It is usually useful while - // experimentation and can be null. It is possible that no prepared script is available for a given script & runtime - // signature. This method should null if so Any failure in persistance should be identified during the subsequent - // retrieval through the integrity mechanism which must be put into the storage. - virtual void persistPreparedScript( - std::shared_ptr preparedScript, - const ScriptSignature &scriptMetadata, - const JSRuntimeSignature &runtimeMetadata, - const char *prepareTag // Optional tag. For e.g. eagerly evaluated vs lazy cache. - ) noexcept = 0; -}; - -// JSI::Runtime implementation must be provided an instance on this interface to enable version sensitive capabilities -// such as usage of pre-prepared javascript script. Alternatively, this entity can be used to directly provide the -// Javascript buffer and rich metadata to the JSI::Runtime instance. -struct ScriptStore { - virtual ~ScriptStore() = default; - - // Return the Javascript buffer and version corresponding to a given url. - virtual VersionedBuffer getVersionedScript(const std::string &url) noexcept = 0; - - // Return the version of the Javascript buffer corresponding to a given url. - virtual ScriptVersion_t getScriptVersion(const std::string &url) noexcept = 0; -}; - -} // namespace jsi -} // namespace facebook \ No newline at end of file diff --git a/NativeScript/napi/hermes/include_old/hermes/SynthTrace.h b/NativeScript/napi/hermes/include_old/hermes/SynthTrace.h deleted file mode 100644 index f8d174c82..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/SynthTrace.h +++ /dev/null @@ -1,1316 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_SYNTHTRACE_H -#define HERMES_SYNTHTRACE_H - -#include "hermes/Public/RuntimeConfig.h" -#include "hermes/Support/JSONEmitter.h" -#include "hermes/Support/SHA1.h" -#include "hermes/Support/StringSetVector.h" -#include "hermes/VM/GCExecTrace.h" - -#include -#include -#include -#include -#include -#include - -namespace llvh { -// Forward declaration to avoid including llvm headers. -class raw_ostream; -} // namespace llvh - -namespace facebook { -namespace hermes { -namespace tracing { - -/// A SynthTrace is a list of events that occur in a run of a JS file by a -/// runtime that uses JSI. -/// It can be serialized into JSON and written to a llvh::raw_ostream. -class SynthTrace { - public: - using ObjectID = uint64_t; - - /// A tagged union representing different types available in the trace. - /// We use a an API very similar to HermesValue, but: - /// a) also represent the JSI type PropNameID, and - /// b) the "payloads" for some the types (Objects, Strings, BigInts, Symbols - /// and PropNameIDs) are unique ObjectIDs, rather than actual values. - /// (This could probably become a std::variant when we could use C++17.) - class TraceValue { - public: - bool isUndefined() const { - return tag_ == Tag::Undefined; - } - - bool isNull() const { - return tag_ == Tag::Null; - } - - bool isNumber() const { - return tag_ == Tag::Number; - } - - bool isBool() const { - return tag_ == Tag::Bool; - } - - bool isObject() const { - return tag_ == Tag::Object; - } - - bool isBigInt() const { - return tag_ == Tag::BigInt; - } - - bool isString() const { - return tag_ == Tag::String; - } - - bool isPropNameID() const { - return tag_ == Tag::PropNameID; - } - - bool isSymbol() const { - return tag_ == Tag::Symbol; - } - - bool isUID() const { - return isObject() || isBigInt() || isString() || isPropNameID() || - isSymbol(); - } - - static TraceValue encodeUndefinedValue() { - return TraceValue(Tag::Undefined); - } - - static TraceValue encodeNullValue() { - return TraceValue(Tag::Null); - } - - static TraceValue encodeBoolValue(bool value) { - return TraceValue(value); - } - - static TraceValue encodeNumberValue(double value) { - return TraceValue(value); - } - - static TraceValue encodeObjectValue(uint64_t uid) { - return TraceValue(Tag::Object, uid); - } - - static TraceValue encodeBigIntValue(uint64_t uid) { - return TraceValue(Tag::BigInt, uid); - } - - static TraceValue encodeStringValue(uint64_t uid) { - return TraceValue(Tag::String, uid); - } - - static TraceValue encodePropNameIDValue(uint64_t uid) { - return TraceValue(Tag::PropNameID, uid); - } - - static TraceValue encodeSymbolValue(uint64_t uid) { - return TraceValue(Tag::Symbol, uid); - } - - bool operator==(const TraceValue &that) const; - - ObjectID getUID() const { - assert(isUID()); - return val_.uid; - } - - bool getBool() const { - assert(isBool()); - return val_.b; - } - - double getNumber() const { - assert(isNumber()); - return val_.n; - } - - private: - enum class Tag { - Undefined, - Null, - Bool, - Number, - Object, - String, - PropNameID, - Symbol, - BigInt, - }; - - explicit TraceValue(Tag tag) : tag_(tag) {} - TraceValue(bool b) : tag_(Tag::Bool) { - val_.b = b; - } - TraceValue(double n) : tag_(Tag::Number) { - val_.n = n; - } - TraceValue(Tag tag, uint64_t uid) : tag_(tag) { - val_.uid = uid; - } - - Tag tag_; - union { - bool b; - double n; - ObjectID uid; - } val_; - }; - - /// A TimePoint is a time when some event occurred. - using TimePoint = std::chrono::steady_clock::time_point; - using TimeSinceStart = std::chrono::milliseconds; - -#define SYNTH_TRACE_RECORD_TYPES(RECORD) \ - RECORD(BeginExecJS) \ - RECORD(EndExecJS) \ - RECORD(Marker) \ - RECORD(CreateObject) \ - RECORD(CreateString) \ - RECORD(CreatePropNameID) \ - RECORD(CreateHostObject) \ - RECORD(CreateHostFunction) \ - RECORD(QueueMicrotask) \ - RECORD(DrainMicrotasks) \ - RECORD(GetProperty) \ - RECORD(SetProperty) \ - RECORD(HasProperty) \ - RECORD(GetPropertyNames) \ - RECORD(CreateArray) \ - RECORD(ArrayRead) \ - RECORD(ArrayWrite) \ - RECORD(CallFromNative) \ - RECORD(ConstructFromNative) \ - RECORD(ReturnFromNative) \ - RECORD(ReturnToNative) \ - RECORD(CallToNative) \ - RECORD(GetPropertyNative) \ - RECORD(GetPropertyNativeReturn) \ - RECORD(SetPropertyNative) \ - RECORD(SetPropertyNativeReturn) \ - RECORD(GetNativePropertyNames) \ - RECORD(GetNativePropertyNamesReturn) \ - RECORD(CreateBigInt) \ - RECORD(BigIntToString) \ - RECORD(SetExternalMemoryPressure) \ - RECORD(Utf8) \ - RECORD(Global) - - /// RecordType is a tag used to differentiate which type of record it is. - /// There should be a unique tag for each record type. - enum class RecordType { -#define RECORD(name) name, - SYNTH_TRACE_RECORD_TYPES(RECORD) -#undef RECORD - }; - - /// A Record is one element of a trace. - struct Record { - /// The time at which this event occurred with respect to the start of - /// execution. - /// NOTE: This is not compared in the \c operator= in order for tests to - /// pass. - const TimeSinceStart time_; - explicit Record() = delete; - explicit Record(TimeSinceStart time) : time_(time) {} - virtual ~Record() = default; - - /// Write out a serialization of this Record. - /// \param json An emitter connected to an ostream which will write out - /// JSON. - void toJSON(::hermes::JSONEmitter &json) const; - virtual RecordType getType() const = 0; - - // If \p val is an object (that is, an Object or String), push its - // decoding onto objs. - static void pushIfTrackedValue( - const TraceValue &val, - std::vector &objs) { - if (val.isUID()) { - objs.push_back(val.getUID()); - } - } - - /// \return A list of object ids that are defined by this record. - /// Defined means that the record would produce that object, - /// string, or PropNameID as a locally accessible value if it were - /// executed. - virtual std::vector defs() const { - return {}; - } - - /// \return A list of object ids that are used by this record. - /// Used means that the record would use that object, string, or - /// PropNameID as a value if it were executed. - /// If a record uses an object id, then some preceding record - /// (either in the same function invocation, or somewhere - /// globally) must provide a definition. - virtual std::vector uses() const { - return {}; - } - - protected: - /// Emit JSON fields into \p os, excluding the closing curly brace. - /// NOTE: This is overridable, and non-abstract children should call the - /// parent. - virtual void toJSONInternal(::hermes::JSONEmitter &json) const; - }; - - /// If \p traceStream is non-null, the trace will be written to that - /// stream. Otherwise, no trace is written. - explicit SynthTrace( - const ::hermes::vm::RuntimeConfig &conf, - std::unique_ptr traceStream = nullptr, - std::optional = {}); - - template - void emplace_back(Args &&...args) { - records_.emplace_back(new T(std::forward(args)...)); - flushRecordsIfNecessary(); - } - - const std::vector> &records() const { - return records_; - } - - std::optional globalObjID() const { - return globalObjID_; - } - - /// Given a trace value, turn it into its typed string. - static std::string encode(TraceValue value); - /// Encode an undefined JS value for the trace. - static TraceValue encodeUndefined(); - /// Encode a null JS value for the trace. - static TraceValue encodeNull(); - /// Encode a boolean JS value for the trace. - static TraceValue encodeBool(bool value); - /// Encodes a numeric value for the trace. - static TraceValue encodeNumber(double value); - /// Encodes an object for the trace as a unique id. - static TraceValue encodeObject(ObjectID objID); - /// Encodes a bigint for the trace as a unique id. - static TraceValue encodeBigInt(ObjectID objID); - /// Encodes a string for the trace as a unique id. - static TraceValue encodeString(ObjectID objID); - /// Encodes a PropNameID for the trace as a unique id. - static TraceValue encodePropNameID(ObjectID objID); - /// Encodes a Symbol for the trace as a unique id. - static TraceValue encodeSymbol(ObjectID objID); - - /// Decodes a string into a trace value. - static TraceValue decode(const std::string &); - - /// The version of the Synth Benchmark - constexpr static uint32_t synthVersion() { - return 4; - } - - static const char *nameFromReleaseUnused(::hermes::vm::ReleaseUnused ru); - static ::hermes::vm::ReleaseUnused releaseUnusedFromName(const char *name); - - private: - llvh::raw_ostream &os() const { - return (*traceStream_); - } - - /// If we're tracing to a file, and the number of accumulated - /// records has reached the limit kTraceRecordsToFlush, below, - /// flush the records to the file, and reset the accumulated records - /// to be empty. - void flushRecordsIfNecessary(); - - /// Assumes we're tracing to a file; flush accumulated records to - /// the file, and reset the accumulated records to be empty. - void flushRecords(); - - static constexpr unsigned kTraceRecordsToFlush = 100; - - /// If we're tracing to a file, pointer to a stream onto - /// traceFilename_. Null otherwise. - std::unique_ptr traceStream_; - /// If we're tracing to a file, pointer to a JSONEmitter writting - /// into *traceStream_. Null otherwise. - std::unique_ptr<::hermes::JSONEmitter> json_; - /// The records currently being accumulated in the trace. If we are - /// tracing to a file, these will be only the records not yet - /// written to the file. - std::vector> records_; - /// The id of the global object. - /// Note: Keeping this as optional to support replaying the older trace - /// records before the change of TracingRuntime's PointerValue based ObjectID. - /// We can remove this once we remove old traces. - /// TODO: T189113203 - const std::optional globalObjID_; - - public: - /// @name Record classes - /// @{ - - /// A MarkerRecord is an event that simply records an interesting event that - /// is not necessarily meaningful to the interpreter. It comes with a tag that - /// says what type of marker it was. - struct MarkerRecord : public Record { - static constexpr RecordType type{RecordType::Marker}; - const std::string tag_; - explicit MarkerRecord(TimeSinceStart time, const std::string &tag) - : Record(time), tag_(tag) {} - RecordType getType() const override { - return type; - } - - protected: - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A BeginExecJSRecord is an event where execution begins of JS source - /// code. This is not necessarily the first record, since native code can - /// inject values into the VM before any source code is run. - struct BeginExecJSRecord final : public Record { - static constexpr RecordType type{RecordType::BeginExecJS}; - explicit BeginExecJSRecord( - TimeSinceStart time, - std::string sourceURL, - ::hermes::SHA1 sourceHash, - bool sourceIsBytecode) - : Record(time), - sourceURL_(std::move(sourceURL)), - sourceHash_(std::move(sourceHash)), - sourceIsBytecode_(sourceIsBytecode) {} - - RecordType getType() const override { - return type; - } - - const std::string &sourceURL() const { - return sourceURL_; - } - - const ::hermes::SHA1 &sourceHash() const { - return sourceHash_; - } - - private: - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - /// The URL providing the source file mapping for the file being executed. - /// Can be empty. - std::string sourceURL_; - - /// A hash of the source that was executed. The source hash must match up - /// when the file is replayed. - /// The hash is optional, and will be all zeros if not provided. - ::hermes::SHA1 sourceHash_; - - /// Whether the input file was source or bytecode. - bool sourceIsBytecode_; - }; - - struct ReturnMixin { - const TraceValue retVal_; - - explicit ReturnMixin(TraceValue value) : retVal_(value) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const; - }; - - /// A EndExecJSRecord is an event where execution of JS source code stops. - /// This does not mean that the source code will never be entered again, just - /// that it has an entered a phase where it is waiting for native code to call - /// into the JS. This event is not guaranteed to be the last event, for the - /// aforementioned reason. The logged retVal is the result of the evaluation - /// ("undefined" in the majority of cases). - struct EndExecJSRecord final : public MarkerRecord, public ReturnMixin { - static constexpr RecordType type{RecordType::EndExecJS}; - EndExecJSRecord(TimeSinceStart time, TraceValue retVal) - : MarkerRecord(time, "end_global_code"), ReturnMixin(retVal) {} - - RecordType getType() const override { - return type; - } - virtual void toJSONInternal(::hermes::JSONEmitter &json) const final; - std::vector defs() const override { - auto defs = MarkerRecord::defs(); - pushIfTrackedValue(retVal_, defs); - return defs; - } - }; - - /// A CreateObjectRecord is an event where an empty object is created by the - /// native code. - struct CreateObjectRecord : public Record { - static constexpr RecordType type{RecordType::CreateObject}; - /// The ObjectID of the object that was created by native function calls - /// like Runtime::createObject(). - const ObjectID objID_; - - explicit CreateObjectRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A CreateBigIntRecord is an event where a jsi::BigInt (and thus a - /// Hermes BigIntPrimitive) is created by the native code. - struct CreateBigIntRecord : public Record { - static constexpr RecordType type{RecordType::CreateBigInt}; - /// The ObjectID of the BigInt that was created by - /// Runtime::createBigIntFromInt64() or Runtime::createBigIntFromUint64(). - const ObjectID objID_; - enum class Method { - FromInt64, - FromUint64, - }; - /// The method used for creating the BigInt. - Method method_; - /// The value used for creating the BigInt. - uint64_t bits_; - - CreateBigIntRecord( - TimeSinceStart time, - ObjectID objID, - Method m, - uint64_t bits) - : Record(time), objID_(objID), method_(m), bits_(bits) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A BigIntToStringRecord is an event where a jsi::BigInt is converted to a - /// string by native code - struct BigIntToStringRecord : public Record { - static constexpr RecordType type{RecordType::BigIntToString}; - /// The ObjectID of the string that was returned from - /// Runtime::bigintToString(). - const ObjectID strID_; - /// The ObjectID of the BigInt that was passed to Runtime::bigintToString(). - const ObjectID bigintID_; - /// The radix used for converting the BigInt to a string. - int radix_; - - BigIntToStringRecord( - TimeSinceStart time, - ObjectID strID, - ObjectID bigintID, - int radix) - : Record(time), strID_(strID), bigintID_(bigintID), radix_(radix) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {strID_}; - } - - std::vector uses() const override { - return {bigintID_}; - } - }; - - /// A CreateStringRecord is an event where a jsi::String (and thus a - /// Hermes StringPrimitive) is created by the native code. - struct CreateStringRecord : public Record { - static constexpr RecordType type{RecordType::CreateString}; - /// The ObjectID of the string that was created by - /// Runtime::createStringFromAscii() or Runtime::createStringFromUtf8(). - const ObjectID objID_; - /// The string that was passed to Runtime::createStringFromAscii() or - /// Runtime::createStringFromUtf8() when the string was created. - std::string chars_; - /// Whether the string was created from ASCII (true) or UTF8 (false). - bool ascii_; - - // General UTF-8. - CreateStringRecord( - TimeSinceStart time, - ObjectID objID, - const uint8_t *chars, - size_t length) - : Record(time), - objID_(objID), - chars_(reinterpret_cast(chars), length), - ascii_(false) {} - // Ascii. - CreateStringRecord( - TimeSinceStart time, - ObjectID objID, - const char *chars, - size_t length) - : Record(time), objID_(objID), chars_(chars, length), ascii_(true) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - std::vector uses() const override { - return {}; - } - }; - - /// A CreatePropNameIDRecord is an event where a jsi::PropNameID is - /// created by the native code. - struct CreatePropNameIDRecord : public Record { - static constexpr RecordType type{RecordType::CreatePropNameID}; - /// The ObjectID of the PropNameID that was created by - /// Runtime::createPropNameIDFromXxx() functions. - const ObjectID propNameID_; - /// The string that was passed to Runtime::createPropNameIDFromAscii() or - /// Runtime::createPropNameIDFromUtf8(). - std::string chars_; - /// The String for Symbol that was passed to - /// Runtime::createPropNameIDFromString() or - /// Runtime::createPropNameIDFromSymbol(). - const TraceValue traceValue_{TraceValue::encodeUndefinedValue()}; - /// Whether the PropNameID was created from ASCII, UTF8, jsi::String - /// (TRACEVALUE) or jsi::Symbol (TRACEVALUE). - enum ValueType { ASCII, UTF8, TRACEVALUE } valueType_; - - // General UTF-8. - CreatePropNameIDRecord( - TimeSinceStart time, - ObjectID propNameID, - const uint8_t *chars, - size_t length) - : Record(time), - propNameID_(propNameID), - chars_(reinterpret_cast(chars), length), - valueType_(UTF8) {} - // Ascii. - CreatePropNameIDRecord( - TimeSinceStart time, - ObjectID propNameID, - const char *chars, - size_t length) - : Record(time), - propNameID_(propNameID), - chars_(chars, length), - valueType_(ASCII) {} - // jsi::String or jsi::Symbol. - CreatePropNameIDRecord( - TimeSinceStart time, - ObjectID propNameID, - TraceValue traceValue) - : Record(time), - propNameID_(propNameID), - traceValue_(traceValue), - valueType_(TRACEVALUE) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {propNameID_}; - } - - std::vector uses() const override { - std::vector vec; - pushIfTrackedValue(traceValue_, vec); - return vec; - } - }; - - struct CreateHostObjectRecord final : public CreateObjectRecord { - static constexpr RecordType type{RecordType::CreateHostObject}; - using CreateObjectRecord::CreateObjectRecord; - RecordType getType() const override { - return type; - } - }; - - struct CreateHostFunctionRecord final : public CreateObjectRecord { - static constexpr RecordType type{RecordType::CreateHostFunction}; - /// The ObjectID of the PropNameID that was passed to - /// Runtime::createFromHostFunction(). - uint32_t propNameID_; -#ifdef HERMESVM_API_TRACE_DEBUG - const std::string functionName_; -#endif - /// The number of parameters that the created host function takes. - const unsigned paramCount_; - - CreateHostFunctionRecord( - TimeSinceStart time, - ObjectID objID, - ObjectID propNameID, -#ifdef HERMESVM_API_TRACE_DEBUG - std::string functionName, -#endif - unsigned paramCount) - : CreateObjectRecord(time, objID), - propNameID_(propNameID), -#ifdef HERMESVM_API_TRACE_DEBUG - functionName_(std::move(functionName)), -#endif - paramCount_(paramCount) { - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - return {propNameID_}; - } - }; - - struct QueueMicrotaskRecord : public Record { - static constexpr RecordType type{RecordType::QueueMicrotask}; - /// The ObjectID of the callback function that was queued. - const ObjectID callbackID_; - - QueueMicrotaskRecord(TimeSinceStart time, ObjectID callbackID) - : Record(time), callbackID_(callbackID) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - std::vector uses() const override { - return {callbackID_}; - } - }; - - struct DrainMicrotasksRecord : public Record { - static constexpr RecordType type{RecordType::DrainMicrotasks}; - /// maxMicrotasksHint value passed to Runtime::drainMicrotasks() call. - int maxMicrotasksHint_; - - DrainMicrotasksRecord(TimeSinceStart time, int tasksHint = -1) - : Record(time), maxMicrotasksHint_(tasksHint) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A GetPropertyRecord is an event where native code accesses the property - /// of a JS object. - struct GetPropertyRecord : public Record { - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; - /// String or PropNameID passed to getProperty. - const TraceValue propID_; -#ifdef HERMESVM_API_TRACE_DEBUG - std::string propNameDbg_; -#endif - - GetPropertyRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue propID -#ifdef HERMESVM_API_TRACE_DEBUG - , - const std::string &propNameDbg -#endif - ) - : Record(time), - objID_(objID), - propID_(propID) -#ifdef HERMESVM_API_TRACE_DEBUG - , - propNameDbg_(propNameDbg) -#endif - { - } - - static constexpr RecordType type{RecordType::GetProperty}; - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(propID_, uses); - return uses; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A SetPropertyRecord is an event where native code writes to the property - /// of a JS object. - struct SetPropertyRecord : public Record { - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; - /// String or PropNameID passed to setProperty. - const TraceValue propID_; -#ifdef HERMESVM_API_TRACE_DEBUG - std::string propNameDbg_; -#endif - /// The value being assigned. - const TraceValue value_; - - SetPropertyRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue propID, -#ifdef HERMESVM_API_TRACE_DEBUG - const std::string &propNameDbg, -#endif - TraceValue value) - : Record(time), - objID_(objID), - propID_(propID), -#ifdef HERMESVM_API_TRACE_DEBUG - propNameDbg_(propNameDbg), -#endif - value_(value) { - } - - static constexpr RecordType type{RecordType::SetProperty}; - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(propID_, uses); - pushIfTrackedValue(value_, uses); - return uses; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A HasPropertyRecord is an event where native code queries whether a - /// property exists on an object. (We don't care about the result because - /// it cannot influence the trace.) - struct HasPropertyRecord final : public Record { - static constexpr RecordType type{RecordType::HasProperty}; - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; -#ifdef HERMESVM_API_TRACE_DEBUG - std::string propNameDbg_; -#endif - /// The property name that was passed to hasProperty(). - const TraceValue propID_; - - HasPropertyRecord( - TimeSinceStart time, - ObjectID objID, - TraceValue propID -#ifdef HERMESVM_API_TRACE_DEBUG - , - const std::string &propNameDbg -#endif - ) - : Record(time), - objID_(objID), -#ifdef HERMESVM_API_TRACE_DEBUG - propNameDbg_(propNameDbg), -#endif - propID_(propID) { - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - std::vector vec{objID_}; - pushIfTrackedValue(propID_, vec); - return vec; - } - }; - - struct GetPropertyNamesRecord final : public Record { - static constexpr RecordType type{RecordType::GetPropertyNames}; - /// The ObjectID of the object that was accessed for its property. - const ObjectID objID_; - - explicit GetPropertyNamesRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - return {objID_}; - } - }; - - /// A CreateArrayRecord is an event where a new array is created of a specific - /// length. - struct CreateArrayRecord final : public Record { - static constexpr RecordType type{RecordType::CreateArray}; - /// The ObjectID of the array that was created by the createArray(). - const ObjectID objID_; - /// The length of the array that was passed to createArray(). - const size_t length_; - - explicit CreateArrayRecord( - TimeSinceStart time, - ObjectID objID, - size_t length) - : Record(time), objID_(objID), length_(length) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector defs() const override { - return {objID_}; - } - }; - - /// An ArrayReadRecord is an event where a value was read from an index - /// of an array. - /// It is modeled separately from GetProperty because it is more efficient to - /// read from a numeric index on an array than a string. - struct ArrayReadRecord final : public Record { - /// The ObjectID of the array that was accessed. - const ObjectID objID_; - /// The index of the element that was accessed in the array. - const size_t index_; - - explicit ArrayReadRecord(TimeSinceStart time, ObjectID objID, size_t index) - : Record(time), objID_(objID), index_(index) {} - - static constexpr RecordType type{RecordType::ArrayRead}; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - return {objID_}; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// An ArrayWriteRecord is an event where a value was written into an index - /// of an array. - struct ArrayWriteRecord final : public Record { - /// The ObjectID of the array that was accessed. - const ObjectID objID_; - /// The index of the element that was accessed in the array. - const size_t index_; - /// The value that was written to the array. - const TraceValue value_; - - explicit ArrayWriteRecord( - TimeSinceStart time, - ObjectID objID, - size_t index, - TraceValue value) - : Record(time), objID_(objID), index_(index), value_(value) {} - - static constexpr RecordType type{RecordType::ArrayWrite}; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - std::vector uses{objID_}; - pushIfTrackedValue(value_, uses); - return uses; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - struct CallRecord : public Record { - /// The ObjectID of the function JS object that was called from - /// JS or native. - const ObjectID functionID_; - /// The value of the this argument passed to the function call. - const TraceValue thisArg_; - /// The arguments given to a call (excluding the this parameter), - /// already JSON stringified. - const std::vector args_; - - explicit CallRecord( - TimeSinceStart time, - ObjectID functionID, - TraceValue thisArg, - const std::vector &args) - : Record(time), - functionID_(functionID), - thisArg_(thisArg), - args_(args) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - std::vector uses() const override { - // The function is used regardless of direction. - return {functionID_}; - } - - protected: - std::vector getArgTrackedIDs() const { - std::vector objs; - pushIfTrackedValue(thisArg_, objs); - for (const auto &arg : args_) { - pushIfTrackedValue(arg, objs); - } - return objs; - } - }; - - /// A CallFromNativeRecord is an event where native code calls into a JS - /// function. - struct CallFromNativeRecord : public CallRecord { - static constexpr RecordType type{RecordType::CallFromNative}; - using CallRecord::CallRecord; - RecordType getType() const override { - return type; - } - std::vector uses() const override { - auto uses = CallRecord::uses(); - auto objs = CallRecord::getArgTrackedIDs(); - uses.insert(uses.end(), objs.begin(), objs.end()); - return uses; - } - }; - - /// A ConstructFromNativeRecord is the same as \c CallFromNativeRecord, except - /// the function is called with the new operator. - struct ConstructFromNativeRecord final : public CallFromNativeRecord { - static constexpr RecordType type{RecordType::ConstructFromNative}; - using CallFromNativeRecord::CallFromNativeRecord; - RecordType getType() const override { - return type; - } - }; - - /// A ReturnFromNativeRecord is an event where a native function returns to a - /// JS caller. - /// It pairs with \c CallToNativeRecord. - struct ReturnFromNativeRecord final : public Record, public ReturnMixin { - static constexpr RecordType type{RecordType::ReturnFromNative}; - ReturnFromNativeRecord(TimeSinceStart time, TraceValue retVal) - : Record(time), ReturnMixin(retVal) {} - RecordType getType() const override { - return type; - } - std::vector uses() const override { - auto uses = Record::uses(); - pushIfTrackedValue(retVal_, uses); - return uses; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A ReturnToNativeRecord is an event where a JS function returns to a native - /// caller. - /// It pairs with \c CallFromNativeRecord. - struct ReturnToNativeRecord final : public Record, public ReturnMixin { - static constexpr RecordType type{RecordType::ReturnToNative}; - ReturnToNativeRecord(TimeSinceStart time, TraceValue retVal) - : Record(time), ReturnMixin(retVal) {} - RecordType getType() const override { - return type; - } - std::vector defs() const override { - auto defs = Record::defs(); - pushIfTrackedValue(retVal_, defs); - return defs; - } - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A CallToNativeRecord is an event where JS code calls into a natively - /// defined function. - struct CallToNativeRecord final : public CallRecord { - static constexpr RecordType type{RecordType::CallToNative}; - using CallRecord::CallRecord; - RecordType getType() const override { - return type; - } - std::vector defs() const override { - auto defs = CallRecord::defs(); - auto objs = CallRecord::getArgTrackedIDs(); - defs.insert(defs.end(), objs.begin(), objs.end()); - return defs; - } - }; - - struct GetOrSetPropertyNativeRecord : public Record { - /// The ObjectID of the host object that was being accessed for its - /// property. - const ObjectID hostObjectID_; - /// The ObjectID of the PropNameID that was passed to HostObject::get() - /// or HostObject::set(). - const ObjectID propNameID_; - /// The UTF-8 string of the PropNameID that was passed to HostObject::get() - /// or HostObject::set(). - const std::string propName_; - - GetOrSetPropertyNativeRecord( - TimeSinceStart time, - ObjectID hostObjectID, - ObjectID propNameID, - const std::string &propName) - : Record(time), - hostObjectID_(hostObjectID), - propNameID_(propNameID), - propName_(propName) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - std::vector defs() const override { - return {propNameID_}; - } - std::vector uses() const override { - return {hostObjectID_}; - } - - protected: - }; - - /// A GetPropertyNativeRecord is an event where JS tries to access a property - /// on a native object. - /// This needs to be modeled as a call with no arguments, since native code - /// can arbitrarily affect the JS heap during the accessor. - struct GetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord { - static constexpr RecordType type{RecordType::GetPropertyNative}; - using GetOrSetPropertyNativeRecord::GetOrSetPropertyNativeRecord; - RecordType getType() const override { - return type; - } - }; - - struct GetPropertyNativeReturnRecord final : public Record, - public ReturnMixin { - static constexpr RecordType type{RecordType::GetPropertyNativeReturn}; - GetPropertyNativeReturnRecord(TimeSinceStart time, TraceValue retVal) - : Record(time), ReturnMixin(retVal) {} - RecordType getType() const override { - return type; - } - std::vector uses() const override { - auto uses = Record::uses(); - pushIfTrackedValue(retVal_, uses); - return uses; - } - - protected: - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// A SetPropertyNativeRecord is an event where JS code writes to the property - /// of a Native object. - /// This needs to be modeled as a call with one argument, since native code - /// can arbitrarily affect the JS heap during the accessor. - struct SetPropertyNativeRecord final : public GetOrSetPropertyNativeRecord { - static constexpr RecordType type{RecordType::SetPropertyNative}; - /// The value that was passed to HostObject::set() call. - TraceValue value_; - - SetPropertyNativeRecord( - TimeSinceStart time, - ObjectID hostObjectID, - ObjectID propNameID, - const std::string &propName, - TraceValue value) - : GetOrSetPropertyNativeRecord( - time, - hostObjectID, - propNameID, - propName), - value_(value) {} - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - RecordType getType() const override { - return type; - } - std::vector defs() const override { - auto defs = GetOrSetPropertyNativeRecord::defs(); - pushIfTrackedValue(value_, defs); - return defs; - } - }; - - /// A SetPropertyNativeReturnRecord needs to record no extra information - struct SetPropertyNativeReturnRecord final : public Record { - static constexpr RecordType type{RecordType::SetPropertyNativeReturn}; - using Record::Record; - RecordType getType() const override { - return type; - } - }; - - /// A GetNativePropertyNamesRecord records an event where JS asked for a list - /// of property names available on a host object. It records the object, and - /// the returned list of property names. - struct GetNativePropertyNamesRecord : public Record { - static constexpr RecordType type{RecordType::GetNativePropertyNames}; - /// The ObjectID of the host object that was being accessed for - /// HostObjet::getPropertyNames() call. - const ObjectID hostObjectID_; - - explicit GetNativePropertyNamesRecord( - TimeSinceStart time, - ObjectID hostObjectID) - : Record(time), hostObjectID_(hostObjectID) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - std::vector uses() const override { - return {hostObjectID_}; - } - }; - - /// A GetNativePropertyNamesReturnRecord records what property names were - /// returned by the GetNativePropertyNames query. - struct GetNativePropertyNamesReturnRecord final : public Record { - static constexpr RecordType type{RecordType::GetNativePropertyNamesReturn}; - - /// Returned list of property names - const std::vector propNameIDs_; - - explicit GetNativePropertyNamesReturnRecord( - TimeSinceStart time, - const std::vector &propNameIDs) - : Record(time), propNameIDs_(propNameIDs) {} - - RecordType getType() const override { - return type; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - - std::vector uses() const override { - auto uses = Record::uses(); - for (const auto &val : propNameIDs_) { - pushIfTrackedValue(val, uses); - } - return uses; - } - }; - - struct SetExternalMemoryPressureRecord final : public Record { - static constexpr RecordType type{RecordType::SetExternalMemoryPressure}; - /// The ObjectID of the object that was passed to - /// Runtime::setExternalMemoryPressure() call. - const ObjectID objID_; - /// The value passed to Runtime::setExternalMemoryPressure() call. - const size_t amount_; - - explicit SetExternalMemoryPressureRecord( - TimeSinceStart time, - const ObjectID objID, - const size_t amount) - : Record(time), objID_(objID), amount_(amount) {} - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - return {objID_}; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// An Utf8Record is an event where a PropNameID or String or Symbol was - /// converted to utf8. - struct Utf8Record final : public Record { - static constexpr RecordType type{RecordType::Utf8}; - /// PropNameID, String or Symbol passed to utf8() or symbolToString() as an - /// argument - const TraceValue objID_; - /// Returned string from utf8() or symbolToString() - const std::string retVal_; - - explicit Utf8Record( - TimeSinceStart time, - const TraceValue objID, - std::string retval) - : Record(time), objID_(objID), retVal_(std::move(retval)) {} - - RecordType getType() const override { - return type; - } - - std::vector uses() const override { - std::vector vec; - pushIfTrackedValue(objID_, vec); - return vec; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - struct GlobalRecord final : public Record { - static constexpr RecordType type{RecordType::Global}; - const ObjectID objID_; // global's ObjectID returned from Runtime::global(). - - explicit GlobalRecord(TimeSinceStart time, ObjectID objID) - : Record(time), objID_(objID) {} - - RecordType getType() const override { - return type; - } - - std::vector defs() const override { - return {objID_}; - } - - void toJSONInternal(::hermes::JSONEmitter &json) const override; - }; - - /// Completes writing of the trace to the trace stream. If writing - /// to a file, disables further writing to the file, or accumulation - /// of data. - void flushAndDisable(const ::hermes::vm::GCExecTrace &gcTrace); -}; - -} // namespace tracing -} // namespace hermes -} // namespace facebook - -#endif // HERMES_SYNTHTRACE_H diff --git a/NativeScript/napi/hermes/include_old/hermes/SynthTraceParser.h b/NativeScript/napi/hermes/include_old/hermes/SynthTraceParser.h deleted file mode 100644 index 7844ee50e..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/SynthTraceParser.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_SYNTHTRACEPARSER_H -#define HERMES_SYNTHTRACEPARSER_H - -#include - -#include "hermes/Public/RuntimeConfig.h" -#include "hermes/SynthTrace.h" - -#include "llvh/Support/MemoryBuffer.h" - -namespace facebook { -namespace hermes { -namespace tracing { - -/// Parse a trace from a JSON string stored in a MemoryBuffer. -std::tuple< - SynthTrace, - ::hermes::vm::RuntimeConfig::Builder, - ::hermes::vm::GCConfig::Builder> -parseSynthTrace(std::unique_ptr trace); - -/// Parse a trace from a JSON string stored in the given file name. -std::tuple< - SynthTrace, - ::hermes::vm::RuntimeConfig::Builder, - ::hermes::vm::GCConfig::Builder> -parseSynthTrace(const std::string &tracefile); - -} // namespace tracing -} // namespace hermes -} // namespace facebook - -#endif // HERMES_SYNTHTRACEPARSER_H diff --git a/NativeScript/napi/hermes/include_old/hermes/ThreadSafetyAnalysis.h b/NativeScript/napi/hermes/include_old/hermes/ThreadSafetyAnalysis.h deleted file mode 100644 index 39e6cf661..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/ThreadSafetyAnalysis.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// Based on mutex.h from https://clang.llvm.org/docs/ThreadSafetyAnalysis.html - -#ifndef THREAD_SAFETY_ANALYSIS_MUTEX_H -#define THREAD_SAFETY_ANALYSIS_MUTEX_H - -// Enable thread safety attributes only with clang. -// The attributes can be safely erased when compiling with other compilers. -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#define TSA_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) -#else -#define TSA_THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op -#endif - -#define TSA_CAPABILITY(x) TSA_THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) - -#define TSA_SCOPED_CAPABILITY TSA_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) - -#define TSA_GUARDED_BY(x) TSA_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) - -#define TSA_PT_GUARDED_BY(x) TSA_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) - -#define TSA_ACQUIRED_BEFORE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) - -#define TSA_ACQUIRED_AFTER(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) - -#define TSA_REQUIRES(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) - -#define TSA_REQUIRES_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) - -#define TSA_ACQUIRE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) - -#define TSA_ACQUIRE_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) - -#define TSA_RELEASE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) - -#define TSA_RELEASE_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) - -#define TSA_RELEASE_GENERIC(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) - -#define TSA_TRY_ACQUIRE(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) - -#define TSA_TRY_ACQUIRE_SHARED(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) - -#define TSA_EXCLUDES(...) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) - -#define TSA_ASSERT_CAPABILITY(x) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) - -#define TSA_ASSERT_SHARED_CAPABILITY(x) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) - -#define TSA_RETURN_CAPABILITY(x) \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) - -#define TSA_NO_THREAD_SAFETY_ANALYSIS \ - TSA_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) - -#endif // THREAD_SAFETY_ANALYSIS_MUTEX_H diff --git a/NativeScript/napi/hermes/include_old/hermes/TimerStats.h b/NativeScript/napi/hermes/include_old/hermes/TimerStats.h deleted file mode 100644 index 6b3e84ec4..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/TimerStats.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include - -namespace facebook { -namespace hermes { - -/// Creates and returns a Runtime that computes the time spent in invocations to -/// the Hermes VM. -std::unique_ptr makeTimedRuntime( - std::unique_ptr hermesRuntime); - -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/TraceInterpreter.h b/NativeScript/napi/hermes/include_old/hermes/TraceInterpreter.h deleted file mode 100644 index 0a1240c1f..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/TraceInterpreter.h +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { - -namespace tracing { - -class TraceInterpreter final { - public: - /// Options for executing the trace. - struct ExecuteOptions { - /// Customizes the GCConfig of the Runtime. - ::hermes::vm::GCConfig::Builder gcConfigBuilder; - - /// If true, trace again while replaying. After normalization (see - /// hermes/tools/synth/trace_normalize.py) the output trace should be - /// identical to the input trace. If they're not, there was a bug in replay. - mutable bool traceEnabled{false}; - - /// If true, verify that the replay results such as returned values from JS - /// execution, inputs from JS to native function calls are matching with the - /// trace record. - bool verificationEnabled{false}; - - /// If true, command-line options override the config options recorded in - /// the trace. If false, start from the default config. - bool useTraceConfig{false}; - - /// Number of initial executions whose stats are discarded. - int warmupReps{0}; - - /// Number of repetitions of execution. Stats returned are those for the rep - /// with the median totalTime. - int reps{1}; - - /// If true, run a complete collection before printing stats. Useful for - /// guaranteeing there's no garbage in heap size numbers. - bool forceGCBeforeStats{false}; - - /// If true, remove the requirement that the input bytecode was compiled - /// from the same source used to record the trace. There must only be one - /// input bytecode file in this case. If its observable behavior deviates - /// from the trace, the results are undefined. - bool disableSourceHashCheck{false}; - - /// A trace contains many MarkerRecords which have a name used to identify - /// them. If the replay encounters this given marker, perform an action - /// described by MarkerAction. All actions will stop the trace early and - /// collect stats at the marker point, unless the marker is set to the - /// special marker "end". In that case the trace will run to completion. - std::string marker{"end"}; - - enum class MarkerAction { - NONE, - /// Take a snapshot at marker. - SNAPSHOT, - /// Take a heap timeline that ends at marker. - TIMELINE, - /// Take a sampling heap profile that ends at marker. - SAMPLE_MEMORY, - /// Take a sampling time profile that ends at marker. - SAMPLE_TIME, - }; - - /// Sets the action to take upon encountering the marker. The action will - /// write results into the \p profileFileName. - MarkerAction action{MarkerAction::NONE}; - - /// Output file name for any profiling information. - std::string profileFileName; - - // These are the config parameters. We wrap them in llvh::Optional - // to indicate whether the corresponding command line flag was set - // explicitly. We override the trace's config only when that is true. - - /// If true, track all disk I/O done by the runtime and print a report at - /// the end to stdout. - llvh::Optional shouldTrackIO; - - /// If present, do a bytecode warmup run that touches a percentage of the - /// bytecode. A value of 50 here means 50% of the bytecode should be warmed. - llvh::Optional bytecodeWarmupPercent; - }; - - private: - jsi::Runtime &rt_; - ExecuteOptions options_; - llvh::raw_ostream *traceStream_; - // Map from source hash to source file to run. - std::map<::hermes::SHA1, std::shared_ptr> bundles_; - const SynthTrace &trace_; - - /// The last use of each object. - std::unordered_map lastUsePerObj_; - - /// The list of pairs from record index to ObjectID. Each record index is the - /// lastly used position of each Object, at which we can remove the object - /// from gom_ and gpnm_. - std::vector> lastUses_; - /// Index of lastUses_ vector that the interpreter is currently processing. - uint64_t lastUsesIndex_{0}; - - // Invariant: the value is either jsi::Object, jsi::String, jsi::Symbol, - // jsi::BigInt. - std::unordered_map gom_; - // For the PropNameIDs, which are not representable as jsi::Value. - std::unordered_map gpnm_; - - std::string stats_; - /// Whether the marker was reached. - bool markerFound_{false}; - /// Depth in the execution stack. Zero is the outermost function. - uint64_t depth_{0}; - - /// The index of the record that the TraceInterpreter is executing. - uint64_t nextExecIndex_{0}; - - public: - /// Execute the trace given by \p traceFile, that was the trace of executing - /// the bundle given by \p bytecodeFile. - /// \return The stats collected by the runtime about times and memory usage. - static std::string execAndGetStats( - const std::string &traceFile, - const std::vector &bytecodeFiles, - const ExecuteOptions &options); - - /// Same as execAndGetStats, except it additionally accepts a function to - /// create the runtime instance for replaying. This can be used to pass, for - /// example, TracingRuntime to trace while replaying. - static std::string execWithRuntime( - const std::string &traceFile, - const std::vector &bytecodeFiles, - const ExecuteOptions &options, - const std::function( - const ::hermes::vm::RuntimeConfig &runtimeConfig)> &createRuntime); - - /// \param traceStream If non-null, write a trace of the execution into this - /// stream. - /// \return Tuple of GC stats and the runtime instance used for replaying. - static std::tuple> - execFromMemoryBuffer( - std::unique_ptr &&traceBuf, - std::vector> &&codeBufs, - const ExecuteOptions &options, - const std::function( - const ::hermes::vm::RuntimeConfig &runtimeConfig)> &createRuntime); - - private: - TraceInterpreter( - jsi::Runtime &rt, - const ExecuteOptions &options, - const SynthTrace &trace, - std::map<::hermes::SHA1, std::shared_ptr> bundles); - - static std::string exec( - jsi::Runtime &rt, - const ExecuteOptions &options, - const SynthTrace &trace, - std::map<::hermes::SHA1, std::shared_ptr> bundles); - - static ::hermes::vm::RuntimeConfig merge( - ::hermes::vm::RuntimeConfig::Builder &, - const ::hermes::vm::GCConfig::Builder &, - const ExecuteOptions &, - bool, - bool); - - /// Requires \p codeBufs to be the memory buffers containing the code - /// referenced (via source hash) by the given \p trace. Returns a map from - /// the source hash to the memory buffer. In addition, if \p codeIsMmapped is - /// non-null, sets \p *codeIsMmapped to indicate whether all the code is - /// mmapped, and, if \p isBytecode is non-null, sets \p *isBytecode - /// to indicate whether all the code is bytecode. - static std::map<::hermes::SHA1, std::shared_ptr> - getSourceHashToBundleMap( - std::vector> &&codeBufs, - const SynthTrace &trace, - const ExecuteOptions &options, - bool *codeIsMmapped = nullptr, - bool *isBytecode = nullptr); - - jsi::Function createHostFunction( - const SynthTrace::CreateHostFunctionRecord &rec, - const jsi::PropNameID &propNameID); - - jsi::Object createHostObject(SynthTrace::ObjectID objID); - - /// Execute the records with the given ExecuteOptions::MarkerOption - std::string executeRecordsWithMarkerOptions(); - - /// Execute the records. JS might call this recursively when HostFunction or - /// HostObject's functions are called. - void executeRecords(); - - /// Requires that \p valID is the proper id for \p val, and that a - /// defining occurrence of \p valID occurs at the current \p defIndex. Decides - /// whether the definition should be recorded, and, if so, adds the - /// association between \p valID and \p val \p gom_ as appropriate. - void addToObjectMap( - SynthTrace::ObjectID valID, - jsi::Value &&val, - uint64_t defIndex); - - /// Similar to addToObjectMap, but for PropNameIDs. - void addToPropNameIDMap( - SynthTrace::ObjectID id, - jsi::PropNameID &&val, - uint64_t defIndex); - - /// If \p traceValue specifies an Object, String, BigInt or Symbol, requires - /// \p val to be of the corresponding runtime type. Adds this \p val to gom_. - /// - /// \p isThis should be true if and only if the value is a 'this' in a call - /// (only used for validation). TODO(T84791675): Remove this parameter. - /// - /// N.B. This method should be called even if you happen to know that the - /// value cannot be an Object, String, Symbol or BigInt, since it performs - /// useful validation. - void ifObjectAddToObjectMap( - SynthTrace::TraceValue traceValue, - const jsi::Value &val, - uint64_t defIndex, - bool isThis = false); - - /// Same as above, except it avoids copies on temporary objects. - void ifObjectAddToObjectMap( - SynthTrace::TraceValue traceValue, - jsi::Value &&val, - uint64_t defIndex, - bool isThis = false); - - /// Check if the \p marker is the one that is being searched for. If this is - /// the first time encountering the matching marker, perform the actions set - /// up for that marker. - void checkMarker(const std::string &marker); - - /// Get a jsi::Value from gom_ for given ObjectID. - jsi::Value getJSIValueForUse(SynthTrace::ObjectID id); - - /// Get a jsi::PropNameID from gpnm_ for given ObjectID. - jsi::PropNameID getPropNameIDForUse(SynthTrace::ObjectID id); - - /// Convert a TraceValue to a jsi::Value. This calls \p getJSIValueForUse, - /// which will remove the entry from gom_ and globalDefsAndUses_. - jsi::Value traceValueToJSIValue(SynthTrace::TraceValue value); - - /// Erase all references to objects of which last use is before the given - /// record index. - void eraseRefsBefore(uint64_t index); - - std::string printStats(); - - LLVM_ATTRIBUTE_NORETURN void crashOnException( - const std::exception &e, - ::hermes::OptValue globalRecordNum); - - void assertMatch( - const SynthTrace::TraceValue &traceValue, - const jsi::Value &val) const; -}; - -} // namespace tracing -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/TracingRuntime.h b/NativeScript/napi/hermes/include_old/hermes/TracingRuntime.h deleted file mode 100644 index f3d082d52..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/TracingRuntime.h +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_TRACINGRUNTIME_H -#define HERMES_TRACINGRUNTIME_H - -#include "SynthTrace.h" - -#include -#include -#include "llvh/Support/raw_ostream.h" - -namespace facebook { -namespace hermes { -namespace tracing { - -class TracingRuntime : public jsi::RuntimeDecorator { - public: - using RD = RuntimeDecorator; - - TracingRuntime( - std::unique_ptr runtime, - const ::hermes::vm::RuntimeConfig &conf, - std::unique_ptr traceStream); - - /// Assign a new ObjectID for given jsi::Pointer. - SynthTrace::ObjectID defObjectID(const jsi::Pointer &p); - /// Get the ObjectID for given jsi::Pointer. - SynthTrace::ObjectID useObjectID(const jsi::Pointer &p) const; - - virtual void flushAndDisableTrace() = 0; - - /// @name jsi::Runtime methods. - /// @{ - - jsi::Value evaluateJavaScript( - const std::shared_ptr &buffer, - const std::string &sourceURL) override; - - void queueMicrotask(const jsi::Function &callback) override; - bool drainMicrotasks(int maxMicrotasksHint = -1) override; - - jsi::Object global() override; - - jsi::Object createObject() override; - jsi::Object createObject(std::shared_ptr ho) override; - - // Note that the NativeState methods do not need to be traced since they - // cannot be observed in JS. - - jsi::BigInt createBigIntFromInt64(int64_t value) override; - jsi::BigInt createBigIntFromUint64(uint64_t value) override; - jsi::String bigintToString(const jsi::BigInt &bigint, int radix) override; - - jsi::String createStringFromAscii(const char *str, size_t length) override; - jsi::String createStringFromUtf8(const uint8_t *utf8, size_t length) override; - std::string utf8(const jsi::PropNameID &) override; - - jsi::PropNameID createPropNameIDFromAscii(const char *str, size_t length) - override; - jsi::PropNameID createPropNameIDFromUtf8(const uint8_t *utf8, size_t length) - override; - std::string utf8(const jsi::String &) override; - - std::string symbolToString(const jsi::Symbol &) override; - - jsi::PropNameID createPropNameIDFromString(const jsi::String &str) override; - jsi::PropNameID createPropNameIDFromSymbol(const jsi::Symbol &sym) override; - - jsi::Value getProperty(const jsi::Object &obj, const jsi::String &name) - override; - jsi::Value getProperty(const jsi::Object &obj, const jsi::PropNameID &name) - override; - - bool hasProperty(const jsi::Object &obj, const jsi::String &name) override; - bool hasProperty(const jsi::Object &obj, const jsi::PropNameID &name) - override; - - void setPropertyValue( - const jsi::Object &obj, - const jsi::String &name, - const jsi::Value &value) override; - void setPropertyValue( - const jsi::Object &obj, - const jsi::PropNameID &name, - const jsi::Value &value) override; - - jsi::Array getPropertyNames(const jsi::Object &o) override; - - jsi::WeakObject createWeakObject(const jsi::Object &o) override; - - jsi::Value lockWeakObject(const jsi::WeakObject &wo) override; - - jsi::Array createArray(size_t length) override; - jsi::ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) override; - - size_t size(const jsi::Array &arr) override; - size_t size(const jsi::ArrayBuffer &buf) override; - - uint8_t *data(const jsi::ArrayBuffer &buf) override; - - jsi::Value getValueAtIndex(const jsi::Array &arr, size_t i) override; - - void setValueAtIndexImpl( - const jsi::Array &arr, - size_t i, - const jsi::Value &value) override; - - jsi::Function createFunctionFromHostFunction( - const jsi::PropNameID &name, - unsigned int paramCount, - jsi::HostFunctionType func) override; - - jsi::Value call( - const jsi::Function &func, - const jsi::Value &jsThis, - const jsi::Value *args, - size_t count) override; - - jsi::Value callAsConstructor( - const jsi::Function &func, - const jsi::Value *args, - size_t count) override; - - void setExternalMemoryPressure(const jsi::Object &obj, size_t amount) - override; - - /// @} - - void addMarker(const std::string &marker); - - SynthTrace &trace() { - return trace_; - } - - const SynthTrace &trace() const { - return trace_; - } - - void replaceNondeterministicFuncs(); - - // This is the number of records recorded as part of the 'preamble' of a synth - // trace. This means all the records after this amount are from the actual - // execution of the trace. - uint32_t getNumPreambleRecordsForTest() const { - assert( - numPreambleRecords_ > 0 && - "Only call this method if the preamble has been executed"); - return numPreambleRecords_; - } - - private: - SynthTrace::TraceValue defTraceValue(const jsi::Value &value) { - return toTraceValue(value, true); - } - SynthTrace::TraceValue useTraceValue(const jsi::Value &value) { - return toTraceValue(value, false); - } - SynthTrace::TraceValue toTraceValue( - const jsi::Value &value, - bool assignNewUID = false); - - std::vector argStringifyer( - const jsi::Value *args, - size_t count, - bool assignNewUID = false); - - SynthTrace::TimeSinceStart getTimeSinceStart() const; - - std::unique_ptr runtime_; - SynthTrace trace_; - std::deque savedFunctions; - const SynthTrace::TimePoint startTime_{std::chrono::steady_clock::now()}; - uint32_t numPreambleRecords_; - - SynthTrace::ObjectID currentUniqueID_{0}; - - /// Map from PointerValue* to ObjectID. Except WeakRef case (see below), we - /// assign a new ObjectID whenever we see a new def of jsi::Pointer Value. - std::unordered_map - uniqueIDs_; - - /// WeakObject's PointerValue* to ObjectID mapping. - /// The key is the PointerValue of the WeakObject at the time of - /// it is created. - /// The value is newly assign ObjectID for that PointerValue. - std::unordered_map - weakRefIDs_; -}; - -// TracingRuntime is *almost* vm independent. This provides the -// vm-specific bits. And, it's not a HermesRuntime, but it holds one. -class TracingHermesRuntime final : public TracingRuntime { - public: - /// This constructor is not intended to be invoked directly. - /// Use makeTracingHermesRuntime instead. - /// - /// \p traceStream the stream to write trace to. - /// \p commitAction is invoked on completion of tracing. - /// Completion can be triggered implicitly by crash (if crash manager is - /// provided) or explicitly by invocation of flush. If the committed trace - /// can be found in a file, the callback returns the file name. Otherwise, - /// the callback returns empty. - /// \p rollbackAction is invoked if the runtime is destructed prior to - /// completion of tracing. It may or may not invoked if completion failed. - TracingHermesRuntime( - std::unique_ptr runtime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - std::unique_ptr traceStream, - std::function commitAction, - std::function rollbackAction); - - ~TracingHermesRuntime() override; - - void flushAndDisableTrace() override; - - std::string flushAndDisableBridgeTrafficTrace() override; - - jsi::Value evaluateJavaScript( - const std::shared_ptr &buffer, - const std::string &sourceURL) override; - - HermesRuntime &hermesRuntime() { - return static_cast(plain()); - } - - const HermesRuntime &hermesRuntime() const { - return static_cast(plain()); - } - - private: - void crashCallback(int fd); - - const ::hermes::vm::RuntimeConfig conf_; - const std::function commitAction_; - const std::function rollbackAction_; - const llvh::Optional<::hermes::vm::CrashManager::CallbackKey> - crashCallbackKey_; - - bool flushedAndDisabled_{false}; - std::string committedTraceFilename_; -}; - -/// Creates and returns a HermesRuntime that traces JSI interactions. -/// The trace will be written to \p traceScratchPath incrementally. -/// On completion, the file will be renamed to \p traceResultPath, and -/// \p traceCompletionCallback (for post-processing) will be invoked. -/// Completion can be triggered implicitly by crash (if crash manager is -/// provided) or explicitly by invocation of flush. -/// If the runtime is destructed without triggering trace completion, -/// the file at \p traceScratchPath will be deleted. -/// The return value of \p traceCompletionCallback indicates whether the -/// invocation completed successfully. -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - const std::string &traceScratchPath, - const std::string &traceResultPath, - std::function traceCompletionCallback); - -/// Creates and returns a HermesRuntime that traces JSI interactions. -/// If \p traceStream is non-null, writes the trace to \p traceStream. -/// The \p forReplay parameter indicates whether the runtime is being used -/// in trace replay. (Its behavior can differ slightly in that case.) -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - std::unique_ptr traceStream, - bool forReplay = false); - -} // namespace tracing -} // namespace hermes -} // namespace facebook - -#endif // HERMES_TRACINGRUNTIME_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/CDPAgent.h b/NativeScript/napi/hermes/include_old/hermes/cdp/CDPAgent.h deleted file mode 100644 index e22432599..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/CDPAgent.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CDPAGENT_H -#define HERMES_CDP_CDPAGENT_H - -#include -#include - -#include -#include -#include -#include - -class CDPAgentTest; - -namespace facebook { -namespace hermes { -namespace cdp { - -using OutboundMessageFunc = std::function; - -class CDPAgentImpl; -class CDPDebugAPI; - -/// Public-facing wrapper for internal CDP state that can be preserved across -/// reloads. -struct HERMES_EXPORT State { - /// Incomplete type that stores the actual state. - struct Private; - - /// Create a new empty wrapper. - State(); - /// Create a new wrapper with the provided \p privateState. - explicit State(std::unique_ptr privateState); - - State(const State &other) = delete; - State &operator=(const State &other) = delete; - State(State &&other) noexcept; - State &operator=(State &&other) noexcept; - ~State(); - - inline operator bool() const { - return privateState_ != nullptr; - } - - /// Get the wrapped state. - inline Private &operator*() { - return *privateState_.get(); - } - - /// Get the wrapped state. - inline Private *operator->() { - return privateState_.get(); - } - - private: - /// Pointer to the actual stored state, hidden from users of this wrapper. - std::unique_ptr privateState_; -}; - -/// An agent for interacting with the provided \p runtime and -/// \p asyncDebuggerAPI via CDP messages in the Debugger, Runtime, Profiler, -/// HeapProfiler domains. -/// The integrator of the agent is expected to manage a queue of tasks to be -/// executed with exclusive access to the runtime (i.e. executed when -/// JavaScript is not running). Tasks to be run are delivered to the integrator -/// via the provided \p enqueueRuntimeTaskCallback, and should be executed in -/// order, at the first opportunity between evaluating JavaScript. -/// The integrator can deliver CDP commands to the agent via the -/// \p handleCommand method. When a CDP response or event is generated, it will -/// be delivered to the integrator via the provided \p messageCallback. -/// Both callbacks may be invoked from arbitrary threads. -class HERMES_EXPORT CDPAgent { - friend class ::CDPAgentTest; - - /// Hide the constructor so users can only construct via static create - /// methods. - CDPAgent( - int32_t executionContextID, - CDPDebugAPI &cdpDebugAPI, - debugger::EnqueueRuntimeTaskFunc enqueueRuntimeTaskCallback, - OutboundMessageFunc messageCallback, - State state, - std::shared_ptr destroyedDomainAgents); - - public: - /// Create a new CDP Agent. This can be done on an arbitrary thread; the - /// runtime will not be accessed during execution of this function. - static std::unique_ptr create( - int32_t executionContextID, - CDPDebugAPI &cdpDebugAPI, - debugger::EnqueueRuntimeTaskFunc enqueueRuntimeTaskCallback, - OutboundMessageFunc messageCallback, - State state = {}); - - /// Destroy the CDP Agent. This can be done on an arbitrary thread. - /// It's expected that the integrator will continue to process any runtime - /// tasks enqueued during destruction. - ~CDPAgent(); - - /// Process a CDP command encoded in \p json. This can be called from - /// arbitrary threads. - void handleCommand(std::string json); - - /// Enable the Runtime domain without processing a CDP command or sending a - /// CDP response. This can be called from arbitrary threads. - void enableRuntimeDomain(); - - /// Enable the Debugger domain without processing a CDP command or sending a - /// CDP response. This can be called from arbitrary threads. - void enableDebuggerDomain(); - - /// Extract state to be persisted across reloads. This can be called from - /// arbitrary threads. - State getState(); - - private: - /// This should be a unique_ptr to provide predictable destruction time lined - /// up with when CDPAgent is destroyed. Do not use shared_ptr. - std::unique_ptr impl_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CDPAGENT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/CDPDebugAPI.h b/NativeScript/napi/hermes/include_old/hermes/cdp/CDPDebugAPI.h deleted file mode 100644 index 9809ec9a4..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/CDPDebugAPI.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CDPDEBUGAPI_H -#define HERMES_CDP_CDPDEBUGAPI_H - -#include - -#include "ConsoleMessage.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -class CDPAgentImpl; - -/// Storage and interfaces for carrying out a CDP debug session. Contains -/// information and operations that correspond to a single runtime being -/// debugged, independent of any particular CDPAgent. -class HERMES_EXPORT CDPDebugAPI { - public: - /// Create a new CDPDebugAPI instance. The provided runtime must remain valid - /// until the returned CDPDebugAPI is destroyed. - static std::unique_ptr create( - HermesRuntime &runtime, - size_t maxCachedMessages = kMaxCachedConsoleMessages); - ~CDPDebugAPI(); - - /// Gets the runtime originally passed into this instance. - HermesRuntime &runtime() { - return runtime_; - } - - /// Gets the AsyncDebuggerAPI associated with this instance. - debugger::AsyncDebuggerAPI &asyncDebuggerAPI() { - return *asyncDebuggerAPI_; - } - - /// Adds a console message to the current CDPDebugAPI instance, - /// broadcasting it to all current agents, and storing it for - /// future agents (within buffer limitations). This function - /// must only be called from the runtime thread. - void addConsoleMessage(ConsoleMessage message); - - private: - /// Allow CDPAgentImpl (but not integrators) to access - /// consoleMessageStorage_. - friend class CDPAgentImpl; - - CDPDebugAPI(HermesRuntime &runtime, size_t maxCachedMessages); - - HermesRuntime &runtime_; - std::unique_ptr asyncDebuggerAPI_; - ConsoleMessageStorage consoleMessageStorage_; - ConsoleMessageDispatcher consoleMessageDispatcher_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CDPDEBUGAPI_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/CallbackOStream.h b/NativeScript/napi/hermes/include_old/hermes/cdp/CallbackOStream.h deleted file mode 100644 index b8a4eb3bb..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/CallbackOStream.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CALLBACKOSTREAM_H -#define HERMES_CDP_CALLBACKOSTREAM_H - -#include -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Subclass of \c std::ostream where flushing is implemented through a -/// callback. Writes are collected in a buffer. When filled, the buffer's -/// contents are emptied out and sent to a callback. -struct CallbackOStream : public std::ostream { - /// Signature of callback called to flush buffer contents. Accepts the buffer - /// as a string. Returns a boolean indicating whether flushing succeeded. - /// Callback failure will be translated to stream failure. If the callback - /// throws an exception it will be swallowed and translated into stream - /// failure. - using Fn = std::function; - - /// Construct a new stream. - /// - /// \p sz The size of the buffer -- how large it can get before it must be - /// flushed. Must be non-zero. - /// \p cb The callback function. - CallbackOStream(size_t sz, Fn cb); - - /// This class is neither movable nor copyable. - CallbackOStream(CallbackOStream &&that) = delete; - CallbackOStream &operator=(CallbackOStream &&that) = delete; - CallbackOStream(const CallbackOStream &that) = delete; - CallbackOStream &operator=(const CallbackOStream &that) = delete; - - private: - /// \c std::streambuf sub-class backed by a std::string buffer and - /// implementing overflow by calling a callback. - struct StreamBuf : public std::streambuf { - /// Construct a new streambuf. Parameters are the same as those of - /// \c CallbackOStream . - StreamBuf(size_t sz, Fn cb); - - /// Destruction will flush any remaining buffer contents. - ~StreamBuf() override; - - /// StreamBufs are not copyable, to avoid the flush callback receiving - /// the contents of multiple streams. - StreamBuf(const StreamBuf &) = delete; - StreamBuf &operator=(const StreamBuf &) = delete; - - protected: - /// std::streambuf overrides - int_type overflow(int_type ch) override; - int sync() override; - - private: - /// The size of the backing buffer. Fixed for an instance of the streambuf. - size_t sz_; - - /// The backing buffer that writes will go to until full. - std::unique_ptr buf_; - - /// The function called when buf_ has been filled. - Fn cb_; - - /// Clears the backing buffer. - void reset(); - - /// Clears the backing buffer and returns it contents in a string. - std::string take(); - }; - - StreamBuf sbuf_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CALLBACKOSTREAM_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/ConsoleMessage.h b/NativeScript/napi/hermes/include_old/hermes/cdp/ConsoleMessage.h deleted file mode 100644 index 906dbb9a8..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/ConsoleMessage.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H -#define HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H - -#include -#include -#include - -#include - -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Controls the max number of message to cached in \p consoleMessageCache_. The -/// value here is chosen to match what Chromium uses in their CDP -/// implementation. -static const int kMaxCachedConsoleMessages = 1000; - -enum class ConsoleAPIType { - kLog, - kDebug, - kInfo, - kError, - kWarning, - kDir, - kDirXML, - kTable, - kTrace, - kStartGroup, - kStartGroupCollapsed, - kEndGroup, - kClear, - kAssert, - kTimeEnd, - kCount -}; - -struct ConsoleMessage { - double timestamp; - ConsoleAPIType type; - std::vector args; - debugger::StackTrace stackTrace; - - ConsoleMessage( - double timestamp, - ConsoleAPIType type, - std::vector args, - debugger::StackTrace stackTrace = {}) - : timestamp(timestamp), - type(type), - args(std::move(args)), - stackTrace(stackTrace) {} -}; - -class ConsoleMessageStorage { - public: - ConsoleMessageStorage(size_t maxCachedMessages = kMaxCachedConsoleMessages); - - void addMessage(ConsoleMessage message); - void clear(); - - const std::deque &messages() const; - size_t discarded() const; - std::optional oldestTimestamp() const; - - private: - /// Maximum number of messages to cache. - size_t maxCachedMessages_; - /// Counts the number of console messages discarded when - /// \p consoleMessageCache_ is full. - size_t numConsoleMessagesDiscardedFromCache_ = 0; - /// Cache for storing console messages. Earlier messages are discarded when - /// the cache is full. The choice to use a std::deque is for fast operations - /// at the beginning and the end, so that adding to the cache and discarding - /// from the cache are fast. - std::deque consoleMessageCache_{}; -}; - -class CDPAgent; - -/// Token that identifies a specific subscription to console messages. -using ConsoleMessageRegistration = uint32_t; - -/// Dispatcher to deliver console messages to all registered subscribers. -/// Everything in this class must be used exclusively from the runtime thread. -class ConsoleMessageDispatcher { - public: - ConsoleMessageDispatcher() {} - ~ConsoleMessageDispatcher() {} - - /// Register a subscriber and return a token that can be used to - /// unregister in the future. Must only be called from the runtime thread. - ConsoleMessageRegistration subscribe( - std::function handler) { - auto token = ++tokenCounter_; - subscribers_[token] = handler; - return token; - } - - /// Unregister a subscriber using the token returned from registration. - /// Must only be called from the runtime thread. - void unsubscribe(ConsoleMessageRegistration token) { - subscribers_.erase(token); - } - - /// Deliver a new console message to each subscriber. Must only be called - /// from the runtime thread. - void deliverMessage(const ConsoleMessage &message) { - for (auto &pair : subscribers_) { - pair.second(message); - } - } - - private: - /// Collection of subscribers, identified by registration token. - std::unordered_map< - ConsoleMessageRegistration, - std::function> - subscribers_; - - /// Counter to generate unique registration tokens. - ConsoleMessageRegistration tokenCounter_ = 0; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_CDPCONSOLEMESSAGESTORAGE_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/DebuggerDomainAgent.h b/NativeScript/napi/hermes/include_old/hermes/cdp/DebuggerDomainAgent.h deleted file mode 100644 index b1336e6b7..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/DebuggerDomainAgent.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_DEBUGGERDOMAINAGENT_H -#define HERMES_CDP_DEBUGGERDOMAINAGENT_H - -#include -#include - -#include -#include -#include - -#include "DomainAgent.h" -#include "DomainState.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -enum class PausedNotificationReason; - -namespace m = ::facebook::hermes::cdp::message; - -/// Details about a single Hermes breakpoint, implied by a CDP breakpoint. -struct HermesBreakpoint { - debugger::BreakpointID breakpointID; - debugger::ScriptID scriptID; -}; - -/// Type used to store CDP breakpoint identifiers. These IDs are generated by -/// the CDP Handler, so we can constrain them to a specific range. -using CDPBreakpointID = uint32_t; - -/// Description of where breakpoints should be created. -struct CDPBreakpointDescription : public StateValue { - ~CDPBreakpointDescription() override = default; - std::unique_ptr copy() const override { - auto value = std::make_unique(); - value->line = line; - value->column = column; - value->condition = condition; - value->url = url; - return value; - } - - /// Determines whether this breakpoint can be persisted across sessions - bool persistable() const { - // Only persist breakpoints that can apply to future scripts (i.e. - // breakpoints set on a set of files specified by script URL, not - // breakpoints set on an exact, session-specific script ID). - return url.has_value(); - } - - std::optional url; - long long line; - std::optional column; - std::optional condition; -}; - -/// Details of each existing CDP breakpoint, which may correspond to multiple -/// Hermes breakpoints. -struct CDPBreakpoint { - explicit CDPBreakpoint(CDPBreakpointDescription description) - : description(description) {} - - // Description of where the breakpoint should be applied - CDPBreakpointDescription description; - - // Registered breakpoints in Hermes - std::vector hermesBreakpoints; -}; - -struct HermesBreakpointLocation { - debugger::BreakpointID id; - debugger::SourceLocation location; -}; - -/// Handler for the "Debugger" domain of CDP. Accepts events from the runtime, -/// and CDP requests from the debug client belonging to the "Debugger" domain. -/// Produces CDP responses and events belonging to the "Debugger" domain. All -/// methods expect to be invoked with exclusive access to the runtime. -class DebuggerDomainAgent : public DomainAgent { - public: - DebuggerDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - debugger::AsyncDebuggerAPI &asyncDebugger, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable_, - DomainState &state); - ~DebuggerDomainAgent(); - - /// Enables the Debugger domain without processing CDP message or sending a - /// CDP response. It will still send CDP notifications if needed. - void enable(); - /// Handles Debugger.enable request - /// @cdp Debugger.enable If domain is already enabled, will return success. - void enable(const m::debugger::EnableRequest &req); - /// Handles Debugger.disable request - /// @cdp Debugger.disable If domain is already disabled, will return success. - void disable(const m::debugger::DisableRequest &req); - - /// Handles Debugger.pause request - void pause(const m::debugger::PauseRequest &req); - /// Handles Debugger.resume request - void resume(const m::debugger::ResumeRequest &req); - - /// Handles Debugger.stepInto request - void stepInto(const m::debugger::StepIntoRequest &req); - /// Handles Debugger.stepOut request - void stepOut(const m::debugger::StepOutRequest &req); - /// Handles Debugger.stepOver request - void stepOver(const m::debugger::StepOverRequest &req); - - /// Handles Debugger.setBlackboxedRanges request - void setBlackboxedRanges(const m::debugger::SetBlackboxedRangesRequest &req); - - /// Handles Debugger.setPauseOnExceptions - void setPauseOnExceptions( - const m::debugger::SetPauseOnExceptionsRequest &req); - - /// Handles Debugger.evaluateOnCallFrame - void evaluateOnCallFrame(const m::debugger::EvaluateOnCallFrameRequest &req); - - /// Debugger.setBreakpoint creates a CDP breakpoint that applies to exactly - /// one script (identified by script ID) that does not survive reloads. - void setBreakpoint(const m::debugger::SetBreakpointRequest &req); - // Debugger.setBreakpointByUrl creates a CDP breakpoint that may apply to - // multiple scripts (identified by URL), and survives reloads. - void setBreakpointByUrl(const m::debugger::SetBreakpointByUrlRequest &req); - /// Handles Debugger.removeBreakpoint - void removeBreakpoint(const m::debugger::RemoveBreakpointRequest &req); - /// Handles Debugger.setBreakpointsActive - /// @cdp Debugger.setBreakpointsActive Allowed even if domain is not enabled. - void setBreakpointsActive( - const m::debugger::SetBreakpointsActiveRequest &req); - - private: - /// Handle an event originating from the runtime. - void handleDebuggerEvent( - HermesRuntime &runtime, - debugger::AsyncDebuggerAPI &asyncDebugger, - debugger::DebuggerEventType event); - - /// Send a Debugger.paused notification to the debug client - void sendPausedNotificationToClient(PausedNotificationReason reason); - /// Send a Debugger.scriptParsed notification to the debug client - void sendScriptParsedNotificationToClient( - const debugger::SourceLocation srcLoc); - - /// Obtain the newly loaded script and send a ScriptParsed notification to the - /// debug client - void processNewLoadedScript(); - - std::pair createCDPBreakpoint( - CDPBreakpointDescription &&description, - std::optional hermesBreakpoint = std::nullopt); - - std::optional createHermesBreakpont( - debugger::ScriptID scriptID, - const CDPBreakpointDescription &description); - - std::optional applyBreakpoint( - CDPBreakpoint &breakpoint, - debugger::ScriptID scriptID); - - bool checkDebuggerEnabled(const m::Request &req); - bool checkDebuggerPaused(const m::Request &req); - - /// Removes any modifications this agent made to Hermes in order to enable - /// debugging - void cleanUp(); - - HermesRuntime &runtime_; - debugger::AsyncDebuggerAPI &asyncDebugger_; - - /// ID for the registered DebuggerEventCallback - debugger::DebuggerEventCallbackID debuggerEventCallbackId_; - - /// Details of each CDP breakpoint that has been created, and not - /// yet destroyed. - std::unordered_map cdpBreakpoints_{}; - - /// CDP breakpoint IDs are assigned by the DebuggerDomainAgent. Keep track of - /// the next available ID. - CDPBreakpointID nextBreakpointID_ = 1; - - DomainState &state_; - - /// Whether the currently installed breakpoints actually take effect. If - /// they're supposed to be inactive, then debugger agent will automatically - /// resume execution when breakpoints are hit. - bool breakpointsActive_ = true; - - /// Whether Debugger.enable was received and wasn't disabled by receiving - /// Debugger.disable - bool enabled_; - - /// Whether to consider the debugger as currently paused. There are some - /// debugger events such as ScriptLoaded where we don't consider the debugger - /// to be paused. - bool paused_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_DEBUGGERDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/DomainAgent.h b/NativeScript/napi/hermes/include_old/hermes/cdp/DomainAgent.h deleted file mode 100644 index 6770e829f..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/DomainAgent.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_DOMAINAGENT_H -#define HERMES_CDP_DOMAINAGENT_H - -#include -#include - -#include -#include - -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#include -#else -#ifndef TSA_GUARDED_BY -#define TSA_GUARDED_BY(x) -#endif -#endif - -namespace facebook { -namespace hermes { -namespace cdp { - -namespace m = ::facebook::hermes::cdp::message; - -/// A wrapper around std::function to make it safe to use from -/// multiple threads. The wrapper implements an invalidate function so that one -/// thread can clean up the underlying std::function in a thread-safe way. -template -class SynchronizedCallback { - public: - SynchronizedCallback(std::function func) - : funcContainer_(std::make_shared(func)) {} - - /// Thread-safe version that calls the underlying std::function. If the - /// underlying std::function is empty, this function is a no-op. - void operator()(Args... args) const { - std::lock_guard lock(funcContainer_->mutex); - if (funcContainer_->func) { - funcContainer_->func(args...); - } - } - - /// Reset the underlying std::function so that future invocations of - /// operator() would just be a no-op. - void invalidate() { - std::lock_guard lock(funcContainer_->mutex); - funcContainer_->func = std::function(); - } - - private: - struct FunctionContainer { - FunctionContainer(std::function func) : func(func) {} - - std::mutex mutex{}; - - /// The actual std::function to be invoked by operator() - std::function func TSA_GUARDED_BY(mutex); - }; - std::shared_ptr funcContainer_; -}; - -using SynchronizedOutboundCallback = SynchronizedCallback; - -class DomainAgent { - protected: - DomainAgent( - int32_t executionContextID, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable) - : executionContextID_(executionContextID), - messageCallback_(messageCallback), - objTable_(objTable) {} - virtual ~DomainAgent() {} - - /// Sends the provided string back to the debug client - void sendToClient(const std::string &str) { - messageCallback_(str); - } - - /// Sends the provided \p Response back to the debug client - void sendResponseToClient(const m::Response &resp) { - sendToClient(resp.toJsonStr()); - } - - /// Sends the provided \p Notification back to the debug client - void sendNotificationToClient(const m::Notification ¬e) { - sendToClient(note.toJsonStr()); - } - - /// Execution context ID associated with the HermesRuntime - int32_t executionContextID_; - - /// Callback function to send CDP response back to the debug client - SynchronizedOutboundCallback messageCallback_; - - std::shared_ptr objTable_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_DOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/DomainState.h b/NativeScript/napi/hermes/include_old/hermes/cdp/DomainState.h deleted file mode 100644 index 4c21603cb..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/DomainState.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_DOMAINSTATE_H -#define HERMES_CDP_DOMAINSTATE_H - -#include -#include -#include -#include -#include - -#if defined(__clang__) && (!defined(SWIG)) && defined(_LIBCPP_VERSION) && \ - defined(_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS) -#include -#else -#ifndef TSA_GUARDED_BY -#define TSA_GUARDED_BY(x) -#endif -#ifndef TSA_REQUIRES -#define TSA_REQUIRES(x) -#endif -#endif - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Base class for data to be stored in DomainState. -struct StateValue { - public: - virtual ~StateValue() = default; - virtual std::unique_ptr copy() const = 0; -}; - -/// StateValue that can be used as a dictionary. Used as the main storage value -/// of DomainState so that modifications can be based on keys of the dictionary -/// hierarchy. -struct DictionaryStateValue : public StateValue { - ~DictionaryStateValue() override = default; - std::unique_ptr copy() const override; - - std::unordered_map> values; -}; - -using StateModification = - std::pair, std::unique_ptr>; - -/// This class acts as container for saving state that CDP agents need after a -/// reload. Its main purpose is to synchronize the manipulation of state on the -/// runtime thread and when CDPAgent::getState() gets called on arbitrary -/// thread. Functions in this class specifically do not contain callbacks to -/// ensure the mutex locking usage remain simple with no reentrancy to think -/// about. -class DomainState { - public: - DomainState(); - explicit DomainState(std::unique_ptr dict); - - /// TSA doesn't get applied to constructors, so delete the normal mechanism. - /// There is a separate copy() function instead. - DomainState(const DomainState &) = delete; - DomainState &operator=(const DomainState &) = delete; - - /// Deep copy of the data and make a new instance. Used by - /// CDPAgent::getState() to get the state in a thread-safe manner. - std::unique_ptr copy(); - - /// This function allows the caller to access values in the saved state. This - /// obtains a copy of the data so that no further synchronization is required - /// after calling this function. This function is expected to only be called a - /// few times after reload, so it isn't used frequently. All entries in the - /// \p paths vector are expected to be pointing to DictionaryStateValue(s) - /// except the last entry, which is a key to any StateValue. - /// \return a copy of the StateValue stored at \p paths, nullptr if no value - /// exists at paths - std::unique_ptr getCopy(std::vector paths); - - /// This class is the only way for callers to manipulate the DomainState. It - /// is a scope-based commit where the modifications get saved upon the class's - /// destruction. The class must not be saved elsewhere and outlive the - /// DomainState where it came from. The intent is to nudge the caller to batch - /// modifications and commit the changes in one go. Because we make a copy of - /// the state with copy(), we want state changes to be atomic. Caller can - /// still break things up into multiple transactions, but the hope is that - /// this nudges them to think about modifications as one atomic unit. - class Transaction { - public: - explicit Transaction(DomainState &state); - ~Transaction(); - - /// Adds a value to the container. All entries in the \p paths vector are - /// expected to be pointing to DictionaryStateValue(s) except the last - /// entry, which is a key to any StateValue. - void add(std::vector paths, const StateValue &value); - - /// Removes a value from the container. All entries in the \p paths vector - /// are expected to be pointing to DictionaryStateValue(s) except the last - /// entry, which is a key to any StateValue. - void remove(std::vector paths); - - private: - friend DomainState; - - DomainState &state_; - std::vector modifications_{}; - }; - - /// Gets a Transaction for modification. - Transaction transaction(); - - private: - /// Helper function for traversing the dictionary hierarchy. - DictionaryStateValue *getDict( - const std::vector &paths, - bool createMissingDict) TSA_REQUIRES(mutex_); - - /// Save modifications to \p dict_. - void commitTransaction(Transaction &transaction); - - std::mutex mutex_{}; - - /// The actual value container. TSA doesn't work if this is just a direct - /// value on the class, so using an unique_ptr. - std::unique_ptr dict_ TSA_GUARDED_BY(mutex_){}; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_DOMAINSTATE_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/HeapProfilerDomainAgent.h b/NativeScript/napi/hermes/include_old/hermes/cdp/HeapProfilerDomainAgent.h deleted file mode 100644 index 227214bcc..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/HeapProfilerDomainAgent.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_HEAPPROFILERDOMAINAGENT_H -#define HERMES_CDP_HEAPPROFILERDOMAINAGENT_H - -#include - -#include "DomainAgent.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Handler for the "HeapProfiler" domain of CDP. All methods expect to be -/// invoked with exclusive access to the runtime. -class HeapProfilerDomainAgent : public DomainAgent { - public: - HeapProfilerDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable); - ~HeapProfilerDomainAgent(); - - /// Handles HeapProfiler.takeHeapSnapshot request - void takeHeapSnapshot(const m::heapProfiler::TakeHeapSnapshotRequest &req); - - /// Handle HeapProfiler.getObjectByHeapObjectId - void getObjectByHeapObjectId( - const m::heapProfiler::GetObjectByHeapObjectIdRequest &req); - - /// Handle HeapProfiler.getObjectByHeapObjectId - void getHeapObjectId(const m::heapProfiler::GetHeapObjectIdRequest &req); - - /// Handle HeapProfiler.collectGarbage - void collectGarbage(const m::heapProfiler::CollectGarbageRequest &req); - - /// Handle HeapProfiler.startTrackingHeapObjects - void startTrackingHeapObjects( - const m::heapProfiler::StartTrackingHeapObjectsRequest &req); - - /// Handle HeapProfiler.stopTrackingHeapObjects - void stopTrackingHeapObjects( - const m::heapProfiler::StopTrackingHeapObjectsRequest &req); - - /// Handle HeapProfiler.startSampling - void startSampling(const m::heapProfiler::StartSamplingRequest &req); - - /// Handle HeapProfiler.stopSampling - void stopSampling(const m::heapProfiler::StopSamplingRequest &req); - - private: - void sendSnapshot(int reqId, bool reportProgress, bool captureNumericValue); - - HermesRuntime &runtime_; - - /// Flag indicating whether this agent is registered to receive heap object - /// tracking callbacks. - bool trackingHeapObjectStackTraces_ = false; - - /// Flag indicating whether this agent is currently running a heap sampling - /// session. - bool samplingHeap_ = false; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_HEAPPROFILERDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/JSONValueInterfaces.h b/NativeScript/napi/hermes/include_old/hermes/cdp/JSONValueInterfaces.h deleted file mode 100644 index 23a12ba8c..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/JSONValueInterfaces.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_JSONVALUEINTERFACES_H -#define HERMES_CDP_JSONVALUEINTERFACES_H - -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace cdp { -using namespace ::hermes::parser; - -/// Convert a string to a JSONValue. Will return nullopt if parsing is not -/// successful. -std::optional parseStr( - const std::string &str, - JSONFactory &factory); - -/// Convert a string to a JSON object. Will return nullopt if parsing is not -/// successful, or the resulting JSON value is not an object. -std::optional parseStrAsJsonObj( - const std::string &str, - JSONFactory &factory); - -/// Convert a JSONValue to a string. -std::string jsonValToStr(const JSONValue *v); - -/// Check if two JSONValues are equal. -bool jsonValsEQ(const JSONValue *A, const JSONValue *B); - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_JSONVALUEINTERFACES_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageConverters.h b/NativeScript/napi/hermes/include_old/hermes/cdp/MessageConverters.h deleted file mode 100644 index 7397bd1d0..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageConverters.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_MESSAGECONVERTERS_H -#define HERMES_CDP_MESSAGECONVERTERS_H - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { - -template -void setChromeLocation( - T &chromeLoc, - const facebook::hermes::debugger::SourceLocation &hermesLoc) { - if (hermesLoc.line != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.lineNumber = hermesLoc.line - 1; - } - - if (hermesLoc.column != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.columnNumber = hermesLoc.column - 1; - } -} - -/// ErrorCode magic numbers match JSC's (see InspectorBackendDispatcher.cpp) -enum class ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - ServerError = -32000 -}; - -ErrorResponse -makeErrorResponse(int id, ErrorCode code, const std::string &message); - -OkResponse makeOkResponse(int id); - -namespace debugger { - -Location makeLocation(const facebook::hermes::debugger::SourceLocation &loc); - -} // namespace debugger - -namespace runtime { - -CallFrame makeCallFrame(const facebook::hermes::debugger::CallFrameInfo &info); - -std::vector makeCallFrames( - const facebook::hermes::debugger::StackTrace &stackTrace); - -} // namespace runtime - -namespace heapProfiler { - -std::unique_ptr makeSamplingHeapProfile( - const std::string &value); - -} // namespace heapProfiler - -namespace profiler { - -std::unique_ptr makeProfile(const std::string &value); - -} // namespace profiler - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_MESSAGECONVERTERS_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageInterfaces.h b/NativeScript/napi/hermes/include_old/hermes/cdp/MessageInterfaces.h deleted file mode 100644 index f19418f57..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageInterfaces.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_MESSAGEINTERFACES_H -#define HERMES_CDP_MESSAGEINTERFACES_H - -#include -#include -#include -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { -using namespace ::hermes::parser; - -struct RequestHandler; - -/// Serializable is an interface for objects that can be serialized to and from -/// JSON. -struct Serializable { - virtual ~Serializable() = default; - virtual JSONValue *toJsonVal(JSONFactory &factory) const = 0; - - std::string toJsonStr() const; -}; - -/// Requests are sent from the debugger to the target. -struct Request : public Serializable { - using ParseResult = std::variant, std::string>; - static std::unique_ptr fromJson(const std::string &str); - - Request() = default; - explicit Request(std::string method) : method(method) {} - - // accept dispatches to the appropriate handler method in RequestHandler based - // on the type of the request. - virtual void accept(RequestHandler &handler) const = 0; - - long long id = 0; - std::string method; -}; - -/// Responses are sent from the target to the debugger in response to a Request. -struct Response : public Serializable { - Response() = default; - - std::optional id = std::nullopt; -}; - -/// Notifications are sent from the target to the debugger. This is used to -/// notify the debugger about events that occur in the target, e.g. stopping -/// at a breakpoint. -struct Notification : public Serializable { - Notification() = default; - explicit Notification(std::string method) : method(method) {} - - std::string method; -}; - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_MESSAGEINTERFACES_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageTypes.h b/NativeScript/napi/hermes/include_old/hermes/cdp/MessageTypes.h deleted file mode 100644 index fcc86c321..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageTypes.h +++ /dev/null @@ -1,1262 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved. -// @generated SignedSource<> - -#pragma once - -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { - -template -void deleter(T *p); -using JSONBlob = std::string; -struct UnknownRequest; - -namespace debugger { -using BreakpointId = std::string; -struct BreakpointResolvedNotification; -struct CallFrame; -using CallFrameId = std::string; -struct DisableRequest; -struct EnableRequest; -struct EvaluateOnCallFrameRequest; -struct EvaluateOnCallFrameResponse; -struct Location; -struct PauseRequest; -struct PausedNotification; -struct RemoveBreakpointRequest; -struct ResumeRequest; -struct ResumedNotification; -struct Scope; -struct ScriptParsedNotification; -struct ScriptPosition; -struct SetBlackboxedRangesRequest; -struct SetBreakpointByUrlRequest; -struct SetBreakpointByUrlResponse; -struct SetBreakpointRequest; -struct SetBreakpointResponse; -struct SetBreakpointsActiveRequest; -struct SetInstrumentationBreakpointRequest; -struct SetInstrumentationBreakpointResponse; -struct SetPauseOnExceptionsRequest; -struct StepIntoRequest; -struct StepOutRequest; -struct StepOverRequest; -} // namespace debugger - -namespace runtime { -struct CallArgument; -struct CallFrame; -struct CallFunctionOnRequest; -struct CallFunctionOnResponse; -struct CompileScriptRequest; -struct CompileScriptResponse; -struct ConsoleAPICalledNotification; -struct CustomPreview; -struct DisableRequest; -struct DiscardConsoleEntriesRequest; -struct EnableRequest; -struct EntryPreview; -struct EvaluateRequest; -struct EvaluateResponse; -struct ExceptionDetails; -struct ExecutionContextCreatedNotification; -struct ExecutionContextDescription; -using ExecutionContextId = long long; -struct GetHeapUsageRequest; -struct GetHeapUsageResponse; -struct GetPropertiesRequest; -struct GetPropertiesResponse; -struct GlobalLexicalScopeNamesRequest; -struct GlobalLexicalScopeNamesResponse; -struct InspectRequestedNotification; -struct InternalPropertyDescriptor; -struct ObjectPreview; -struct PropertyDescriptor; -struct PropertyPreview; -struct ReleaseObjectGroupRequest; -struct ReleaseObjectRequest; -struct RemoteObject; -using RemoteObjectId = std::string; -struct RunIfWaitingForDebuggerRequest; -using ScriptId = std::string; -struct StackTrace; -using Timestamp = double; -using UnserializableValue = std::string; -} // namespace runtime - -namespace heapProfiler { -struct AddHeapSnapshotChunkNotification; -struct CollectGarbageRequest; -struct GetHeapObjectIdRequest; -struct GetHeapObjectIdResponse; -struct GetObjectByHeapObjectIdRequest; -struct GetObjectByHeapObjectIdResponse; -using HeapSnapshotObjectId = std::string; -struct HeapStatsUpdateNotification; -struct LastSeenObjectIdNotification; -struct ReportHeapSnapshotProgressNotification; -struct SamplingHeapProfile; -struct SamplingHeapProfileNode; -struct SamplingHeapProfileSample; -struct StartSamplingRequest; -struct StartTrackingHeapObjectsRequest; -struct StopSamplingRequest; -struct StopSamplingResponse; -struct StopTrackingHeapObjectsRequest; -struct TakeHeapSnapshotRequest; -} // namespace heapProfiler - -namespace profiler { -struct PositionTickInfo; -struct Profile; -struct ProfileNode; -struct StartRequest; -struct StopRequest; -struct StopResponse; -} // namespace profiler - -/// RequestHandler handles requests via the visitor pattern. -struct RequestHandler { - virtual ~RequestHandler() = default; - - virtual void handle(const UnknownRequest &req) = 0; - virtual void handle(const debugger::DisableRequest &req) = 0; - virtual void handle(const debugger::EnableRequest &req) = 0; - virtual void handle(const debugger::EvaluateOnCallFrameRequest &req) = 0; - virtual void handle(const debugger::PauseRequest &req) = 0; - virtual void handle(const debugger::RemoveBreakpointRequest &req) = 0; - virtual void handle(const debugger::ResumeRequest &req) = 0; - virtual void handle(const debugger::SetBlackboxedRangesRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointByUrlRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointsActiveRequest &req) = 0; - virtual void handle( - const debugger::SetInstrumentationBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetPauseOnExceptionsRequest &req) = 0; - virtual void handle(const debugger::StepIntoRequest &req) = 0; - virtual void handle(const debugger::StepOutRequest &req) = 0; - virtual void handle(const debugger::StepOverRequest &req) = 0; - virtual void handle(const heapProfiler::CollectGarbageRequest &req) = 0; - virtual void handle(const heapProfiler::GetHeapObjectIdRequest &req) = 0; - virtual void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) = 0; - virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::StopSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0; - virtual void handle(const profiler::StartRequest &req) = 0; - virtual void handle(const profiler::StopRequest &req) = 0; - virtual void handle(const runtime::CallFunctionOnRequest &req) = 0; - virtual void handle(const runtime::CompileScriptRequest &req) = 0; - virtual void handle(const runtime::DisableRequest &req) = 0; - virtual void handle(const runtime::DiscardConsoleEntriesRequest &req) = 0; - virtual void handle(const runtime::EnableRequest &req) = 0; - virtual void handle(const runtime::EvaluateRequest &req) = 0; - virtual void handle(const runtime::GetHeapUsageRequest &req) = 0; - virtual void handle(const runtime::GetPropertiesRequest &req) = 0; - virtual void handle(const runtime::GlobalLexicalScopeNamesRequest &req) = 0; - virtual void handle(const runtime::ReleaseObjectRequest &req) = 0; - virtual void handle(const runtime::ReleaseObjectGroupRequest &req) = 0; - virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0; -}; - -/// NoopRequestHandler can be subclassed to only handle some requests. -struct NoopRequestHandler : public RequestHandler { - void handle(const UnknownRequest &req) override {} - void handle(const debugger::DisableRequest &req) override {} - void handle(const debugger::EnableRequest &req) override {} - void handle(const debugger::EvaluateOnCallFrameRequest &req) override {} - void handle(const debugger::PauseRequest &req) override {} - void handle(const debugger::RemoveBreakpointRequest &req) override {} - void handle(const debugger::ResumeRequest &req) override {} - void handle(const debugger::SetBlackboxedRangesRequest &req) override {} - void handle(const debugger::SetBreakpointRequest &req) override {} - void handle(const debugger::SetBreakpointByUrlRequest &req) override {} - void handle(const debugger::SetBreakpointsActiveRequest &req) override {} - void handle( - const debugger::SetInstrumentationBreakpointRequest &req) override {} - void handle(const debugger::SetPauseOnExceptionsRequest &req) override {} - void handle(const debugger::StepIntoRequest &req) override {} - void handle(const debugger::StepOutRequest &req) override {} - void handle(const debugger::StepOverRequest &req) override {} - void handle(const heapProfiler::CollectGarbageRequest &req) override {} - void handle(const heapProfiler::GetHeapObjectIdRequest &req) override {} - void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) override {} - void handle(const heapProfiler::StartSamplingRequest &req) override {} - void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::StopSamplingRequest &req) override {} - void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {} - void handle(const profiler::StartRequest &req) override {} - void handle(const profiler::StopRequest &req) override {} - void handle(const runtime::CallFunctionOnRequest &req) override {} - void handle(const runtime::CompileScriptRequest &req) override {} - void handle(const runtime::DisableRequest &req) override {} - void handle(const runtime::DiscardConsoleEntriesRequest &req) override {} - void handle(const runtime::EnableRequest &req) override {} - void handle(const runtime::EvaluateRequest &req) override {} - void handle(const runtime::GetHeapUsageRequest &req) override {} - void handle(const runtime::GetPropertiesRequest &req) override {} - void handle(const runtime::GlobalLexicalScopeNamesRequest &req) override {} - void handle(const runtime::ReleaseObjectRequest &req) override {} - void handle(const runtime::ReleaseObjectGroupRequest &req) override {} - void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {} -}; - -/// Types -struct debugger::Location : public Serializable { - Location() = default; - Location(Location &&) = default; - Location(const Location &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Location &operator=(const Location &) = delete; - Location &operator=(Location &&) = default; - - runtime::ScriptId scriptId{}; - long long lineNumber{}; - std::optional columnNumber; -}; - -struct runtime::PropertyPreview : public Serializable { - PropertyPreview() = default; - PropertyPreview(PropertyPreview &&) = default; - PropertyPreview(const PropertyPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyPreview &operator=(const PropertyPreview &) = delete; - PropertyPreview &operator=(PropertyPreview &&) = default; - - std::string name; - std::string type; - std::optional value; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - valuePreview{nullptr, deleter}; - std::optional subtype; -}; - -struct runtime::EntryPreview : public Serializable { - EntryPreview() = default; - EntryPreview(EntryPreview &&) = default; - EntryPreview(const EntryPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - EntryPreview &operator=(const EntryPreview &) = delete; - EntryPreview &operator=(EntryPreview &&) = default; - - std::unique_ptr< - runtime::ObjectPreview, - std::function> - key{nullptr, deleter}; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - value{nullptr, deleter}; -}; - -struct runtime::ObjectPreview : public Serializable { - ObjectPreview() = default; - ObjectPreview(ObjectPreview &&) = default; - ObjectPreview(const ObjectPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ObjectPreview &operator=(const ObjectPreview &) = delete; - ObjectPreview &operator=(ObjectPreview &&) = default; - - std::string type; - std::optional subtype; - std::optional description; - bool overflow{}; - std::vector properties; - std::optional> entries; -}; - -struct runtime::CustomPreview : public Serializable { - CustomPreview() = default; - CustomPreview(CustomPreview &&) = default; - CustomPreview(const CustomPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CustomPreview &operator=(const CustomPreview &) = delete; - CustomPreview &operator=(CustomPreview &&) = default; - - std::string header; - std::optional bodyGetterId; -}; - -struct runtime::RemoteObject : public Serializable { - RemoteObject() = default; - RemoteObject(RemoteObject &&) = default; - RemoteObject(const RemoteObject &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - RemoteObject &operator=(const RemoteObject &) = delete; - RemoteObject &operator=(RemoteObject &&) = default; - - std::string type; - std::optional subtype; - std::optional className; - std::optional value; - std::optional unserializableValue; - std::optional description; - std::optional objectId; - std::optional preview; - std::optional customPreview; -}; - -struct runtime::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - std::string functionName; - runtime::ScriptId scriptId{}; - std::string url; - long long lineNumber{}; - long long columnNumber{}; -}; - -struct runtime::StackTrace : public Serializable { - StackTrace() = default; - StackTrace(StackTrace &&) = default; - StackTrace(const StackTrace &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - StackTrace &operator=(const StackTrace &) = delete; - StackTrace &operator=(StackTrace &&) = default; - - std::optional description; - std::vector callFrames; - std::unique_ptr parent; -}; - -struct runtime::ExceptionDetails : public Serializable { - ExceptionDetails() = default; - ExceptionDetails(ExceptionDetails &&) = default; - ExceptionDetails(const ExceptionDetails &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExceptionDetails &operator=(const ExceptionDetails &) = delete; - ExceptionDetails &operator=(ExceptionDetails &&) = default; - - long long exceptionId{}; - std::string text; - long long lineNumber{}; - long long columnNumber{}; - std::optional scriptId; - std::optional url; - std::optional stackTrace; - std::optional exception; - std::optional executionContextId; -}; - -struct debugger::Scope : public Serializable { - Scope() = default; - Scope(Scope &&) = default; - Scope(const Scope &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Scope &operator=(const Scope &) = delete; - Scope &operator=(Scope &&) = default; - - std::string type; - runtime::RemoteObject object{}; - std::optional name; - std::optional startLocation; - std::optional endLocation; -}; - -struct debugger::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - debugger::CallFrameId callFrameId{}; - std::string functionName; - std::optional functionLocation; - debugger::Location location{}; - std::string url; - std::vector scopeChain; - runtime::RemoteObject thisObj{}; - std::optional returnValue; -}; - -struct debugger::ScriptPosition : public Serializable { - ScriptPosition() = default; - ScriptPosition(ScriptPosition &&) = default; - ScriptPosition(const ScriptPosition &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ScriptPosition &operator=(const ScriptPosition &) = delete; - ScriptPosition &operator=(ScriptPosition &&) = default; - - long long lineNumber{}; - long long columnNumber{}; -}; - -struct heapProfiler::SamplingHeapProfileNode : public Serializable { - SamplingHeapProfileNode() = default; - SamplingHeapProfileNode(SamplingHeapProfileNode &&) = default; - SamplingHeapProfileNode(const SamplingHeapProfileNode &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileNode &operator=(const SamplingHeapProfileNode &) = delete; - SamplingHeapProfileNode &operator=(SamplingHeapProfileNode &&) = default; - - runtime::CallFrame callFrame{}; - double selfSize{}; - long long id{}; - std::vector children; -}; - -struct heapProfiler::SamplingHeapProfileSample : public Serializable { - SamplingHeapProfileSample() = default; - SamplingHeapProfileSample(SamplingHeapProfileSample &&) = default; - SamplingHeapProfileSample(const SamplingHeapProfileSample &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileSample &operator=(const SamplingHeapProfileSample &) = - delete; - SamplingHeapProfileSample &operator=(SamplingHeapProfileSample &&) = default; - - double size{}; - long long nodeId{}; - double ordinal{}; -}; - -struct heapProfiler::SamplingHeapProfile : public Serializable { - SamplingHeapProfile() = default; - SamplingHeapProfile(SamplingHeapProfile &&) = default; - SamplingHeapProfile(const SamplingHeapProfile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfile &operator=(const SamplingHeapProfile &) = delete; - SamplingHeapProfile &operator=(SamplingHeapProfile &&) = default; - - heapProfiler::SamplingHeapProfileNode head{}; - std::vector samples; -}; - -struct profiler::PositionTickInfo : public Serializable { - PositionTickInfo() = default; - PositionTickInfo(PositionTickInfo &&) = default; - PositionTickInfo(const PositionTickInfo &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PositionTickInfo &operator=(const PositionTickInfo &) = delete; - PositionTickInfo &operator=(PositionTickInfo &&) = default; - - long long line{}; - long long ticks{}; -}; - -struct profiler::ProfileNode : public Serializable { - ProfileNode() = default; - ProfileNode(ProfileNode &&) = default; - ProfileNode(const ProfileNode &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ProfileNode &operator=(const ProfileNode &) = delete; - ProfileNode &operator=(ProfileNode &&) = default; - - long long id{}; - runtime::CallFrame callFrame{}; - std::optional hitCount; - std::optional> children; - std::optional deoptReason; - std::optional> positionTicks; -}; - -struct profiler::Profile : public Serializable { - Profile() = default; - Profile(Profile &&) = default; - Profile(const Profile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Profile &operator=(const Profile &) = delete; - Profile &operator=(Profile &&) = default; - - std::vector nodes; - double startTime{}; - double endTime{}; - std::optional> samples; - std::optional> timeDeltas; -}; - -struct runtime::CallArgument : public Serializable { - CallArgument() = default; - CallArgument(CallArgument &&) = default; - CallArgument(const CallArgument &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallArgument &operator=(const CallArgument &) = delete; - CallArgument &operator=(CallArgument &&) = default; - - std::optional value; - std::optional unserializableValue; - std::optional objectId; -}; - -struct runtime::ExecutionContextDescription : public Serializable { - ExecutionContextDescription() = default; - ExecutionContextDescription(ExecutionContextDescription &&) = default; - ExecutionContextDescription(const ExecutionContextDescription &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExecutionContextDescription &operator=(const ExecutionContextDescription &) = - delete; - ExecutionContextDescription &operator=(ExecutionContextDescription &&) = - default; - - runtime::ExecutionContextId id{}; - std::string origin; - std::string name; - std::optional auxData; -}; - -struct runtime::PropertyDescriptor : public Serializable { - PropertyDescriptor() = default; - PropertyDescriptor(PropertyDescriptor &&) = default; - PropertyDescriptor(const PropertyDescriptor &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyDescriptor &operator=(const PropertyDescriptor &) = delete; - PropertyDescriptor &operator=(PropertyDescriptor &&) = default; - - std::string name; - std::optional value; - std::optional writable; - std::optional get; - std::optional set; - bool configurable{}; - bool enumerable{}; - std::optional wasThrown; - std::optional isOwn; - std::optional symbol; -}; - -struct runtime::InternalPropertyDescriptor : public Serializable { - InternalPropertyDescriptor() = default; - InternalPropertyDescriptor(InternalPropertyDescriptor &&) = default; - InternalPropertyDescriptor(const InternalPropertyDescriptor &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - InternalPropertyDescriptor &operator=(const InternalPropertyDescriptor &) = - delete; - InternalPropertyDescriptor &operator=(InternalPropertyDescriptor &&) = - default; - - std::string name; - std::optional value; -}; - -/// Requests -struct UnknownRequest : public Request { - UnknownRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional params; -}; - -struct debugger::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EvaluateOnCallFrameRequest : public Request { - EvaluateOnCallFrameRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::CallFrameId callFrameId{}; - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional throwOnSideEffect; -}; - -struct debugger::PauseRequest : public Request { - PauseRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::RemoveBreakpointRequest : public Request { - RemoveBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct debugger::ResumeRequest : public Request { - ResumeRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional terminateOnResume; -}; - -struct debugger::SetBlackboxedRangesRequest : public Request { - SetBlackboxedRangesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::ScriptId scriptId{}; - std::vector positions; -}; - -struct debugger::SetBreakpointRequest : public Request { - SetBreakpointRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::Location location{}; - std::optional condition; -}; - -struct debugger::SetBreakpointByUrlRequest : public Request { - SetBreakpointByUrlRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - long long lineNumber{}; - std::optional url; - std::optional urlRegex; - std::optional scriptHash; - std::optional columnNumber; - std::optional condition; -}; - -struct debugger::SetBreakpointsActiveRequest : public Request { - SetBreakpointsActiveRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - bool active{}; -}; - -struct debugger::SetInstrumentationBreakpointRequest : public Request { - SetInstrumentationBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string instrumentation; -}; - -struct debugger::SetPauseOnExceptionsRequest : public Request { - SetPauseOnExceptionsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string state; -}; - -struct debugger::StepIntoRequest : public Request { - StepIntoRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOutRequest : public Request { - StepOutRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOverRequest : public Request { - StepOverRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::CollectGarbageRequest : public Request { - CollectGarbageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::GetHeapObjectIdRequest : public Request { - GetHeapObjectIdRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdRequest : public Request { - GetObjectByHeapObjectIdRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - heapProfiler::HeapSnapshotObjectId objectId{}; - std::optional objectGroup; -}; - -struct heapProfiler::StartSamplingRequest : public Request { - StartSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional samplingInterval; - std::optional includeObjectsCollectedByMajorGC; - std::optional includeObjectsCollectedByMinorGC; -}; - -struct heapProfiler::StartTrackingHeapObjectsRequest : public Request { - StartTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional trackAllocations; -}; - -struct heapProfiler::StopSamplingRequest : public Request { - StopSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::StopTrackingHeapObjectsRequest : public Request { - StopTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct heapProfiler::TakeHeapSnapshotRequest : public Request { - TakeHeapSnapshotRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct profiler::StartRequest : public Request { - StartRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct profiler::StopRequest : public Request { - StopRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::CallFunctionOnRequest : public Request { - CallFunctionOnRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string functionDeclaration; - std::optional objectId; - std::optional> arguments; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; - std::optional executionContextId; - std::optional objectGroup; -}; - -struct runtime::CompileScriptRequest : public Request { - CompileScriptRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::string sourceURL; - bool persistScript{}; - std::optional executionContextId; -}; - -struct runtime::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::DiscardConsoleEntriesRequest : public Request { - DiscardConsoleEntriesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EvaluateRequest : public Request { - EvaluateRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional contextId; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; -}; - -struct runtime::GetHeapUsageRequest : public Request { - GetHeapUsageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::GetPropertiesRequest : public Request { - GetPropertiesRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; - std::optional ownProperties; - std::optional accessorPropertiesOnly; - std::optional generatePreview; -}; - -struct runtime::GlobalLexicalScopeNamesRequest : public Request { - GlobalLexicalScopeNamesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional executionContextId; -}; - -struct runtime::ReleaseObjectRequest : public Request { - ReleaseObjectRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; -}; - -struct runtime::ReleaseObjectGroupRequest : public Request { - ReleaseObjectGroupRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string objectGroup; -}; - -struct runtime::RunIfWaitingForDebuggerRequest : public Request { - RunIfWaitingForDebuggerRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -/// Responses -struct ErrorResponse : public Response { - ErrorResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long code; - std::string message; - std::optional data; -}; - -struct OkResponse : public Response { - OkResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::EvaluateOnCallFrameResponse : public Response { - EvaluateOnCallFrameResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct debugger::SetBreakpointResponse : public Response { - SetBreakpointResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location actualLocation{}; -}; - -struct debugger::SetBreakpointByUrlResponse : public Response { - SetBreakpointByUrlResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - std::vector locations; -}; - -struct debugger::SetInstrumentationBreakpointResponse : public Response { - SetInstrumentationBreakpointResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct heapProfiler::GetHeapObjectIdResponse : public Response { - GetHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::HeapSnapshotObjectId heapSnapshotObjectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdResponse : public Response { - GetObjectByHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; -}; - -struct heapProfiler::StopSamplingResponse : public Response { - StopSamplingResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::SamplingHeapProfile profile{}; -}; - -struct profiler::StopResponse : public Response { - StopResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - profiler::Profile profile{}; -}; - -struct runtime::CallFunctionOnResponse : public Response { - CallFunctionOnResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::CompileScriptResponse : public Response { - CompileScriptResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::optional scriptId; - std::optional exceptionDetails; -}; - -struct runtime::EvaluateResponse : public Response { - EvaluateResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::GetHeapUsageResponse : public Response { - GetHeapUsageResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - double usedSize{}; - double totalSize{}; -}; - -struct runtime::GetPropertiesResponse : public Response { - GetPropertiesResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector result; - std::optional> - internalProperties; - std::optional exceptionDetails; -}; - -struct runtime::GlobalLexicalScopeNamesResponse : public Response { - GlobalLexicalScopeNamesResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector names; -}; - -/// Notifications -struct debugger::BreakpointResolvedNotification : public Notification { - BreakpointResolvedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location location{}; -}; - -struct debugger::PausedNotification : public Notification { - PausedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector callFrames; - std::string reason; - std::optional data; - std::optional> hitBreakpoints; - std::optional asyncStackTrace; -}; - -struct debugger::ResumedNotification : public Notification { - ResumedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::ScriptParsedNotification : public Notification { - ScriptParsedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ScriptId scriptId{}; - std::string url; - long long startLine{}; - long long startColumn{}; - long long endLine{}; - long long endColumn{}; - runtime::ExecutionContextId executionContextId{}; - std::string hash; - std::optional executionContextAuxData; - std::optional sourceMapURL; - std::optional hasSourceURL; - std::optional isModule; - std::optional length; -}; - -struct heapProfiler::AddHeapSnapshotChunkNotification : public Notification { - AddHeapSnapshotChunkNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string chunk; -}; - -struct heapProfiler::HeapStatsUpdateNotification : public Notification { - HeapStatsUpdateNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector statsUpdate; -}; - -struct heapProfiler::LastSeenObjectIdNotification : public Notification { - LastSeenObjectIdNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long lastSeenObjectId{}; - double timestamp{}; -}; - -struct heapProfiler::ReportHeapSnapshotProgressNotification - : public Notification { - ReportHeapSnapshotProgressNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long done{}; - long long total{}; - std::optional finished; -}; - -struct runtime::ConsoleAPICalledNotification : public Notification { - ConsoleAPICalledNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string type; - std::vector args; - runtime::ExecutionContextId executionContextId{}; - runtime::Timestamp timestamp{}; - std::optional stackTrace; -}; - -struct runtime::ExecutionContextCreatedNotification : public Notification { - ExecutionContextCreatedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ExecutionContextDescription context{}; -}; - -struct runtime::InspectRequestedNotification : public Notification { - InspectRequestedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject object{}; - JSONBlob hints; - std::optional executionContextId; -}; - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageTypesInlines.h b/NativeScript/napi/hermes/include_old/hermes/cdp/MessageTypesInlines.h deleted file mode 100644 index fe765f935..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/MessageTypesInlines.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_MESSAGETYPESINLINES_H -#define HERMES_CDP_MESSAGETYPESINLINES_H - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { -namespace message { - -template -using optional = std::optional; - -template -struct is_vector : std::false_type {}; - -template -struct is_vector> : std::true_type {}; - -/// valueFromJson - -/// Convert JSONValue to a Serializable type. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return T::tryMake(res); -} - -/// Convert JSONValue to a bool. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a long long. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a double. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a string. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->c_str()); -} - -/// Convert JSONValue to a vector. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *items) { - auto *arr = llvh::dyn_cast(items); - std::unique_ptr result = std::make_unique(); - result->reserve(arr->size()); - for (const auto &item : *arr) { - auto itemResult = valueFromJson(item); - if (!itemResult) { - return nullptr; - } - result->push_back(std::move(*itemResult)); - } - return result; -} - -/// Convert JSONValue to a JSONObject. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - auto *res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res); -} - -/// Pass through JSONValues. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - return std::make_unique(v); -} - -/// assign(lhs, obj, key) is a wrapper for: -/// -/// lhs = obj[key] -/// -/// It mainly exists so that we can choose the right version of valueFromJson -/// based on the type of lhs. - -template -bool assign(T &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v == nullptr) { - return false; - } - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; -} - -template -bool assign(optional &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign(std::unique_ptr &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign( - std::unique_ptr> &lhs, - const JSONObject *obj, - const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -/// valueToJson - -inline JSONValue *valueToJson(const Serializable &value, JSONFactory &factory) { - return value.toJsonVal(factory); -} - -// Convert a bool to JSONValue. -inline JSONValue *valueToJson(bool b, JSONFactory &factory) { - return factory.getBoolean(b); -} - -// Convert a long long to JSONValue. -inline JSONValue *valueToJson(long long num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a double to JSONValue. -inline JSONValue *valueToJson(double num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a string to JSONValue. -inline JSONValue *valueToJson(const std::string &str, JSONFactory &factory) { - return factory.getString(str); -} - -// Convert a vector to JSONValue. -template -JSONValue *valueToJson(const std::vector &items, JSONFactory &factory) { - llvh::SmallVector storage; - for (const auto &item : items) { - storage.push_back(valueToJson(item, factory)); - } - return factory.newArray(storage.size(), storage.begin(), storage.end()); -} - -// Cast a JSONObject to JSONValue. -inline JSONValue *valueToJson(JSONObject *obj, JSONFactory &factory) { - return llvh::cast(obj); -} - -// Pass through JSONValues. -inline JSONValue *valueToJson(JSONValue *v, JSONFactory &factory) { - return v; -} - -/// put(obj, key, value) is meant to be a wrapper for: -/// obj[key] = valueToJson(value); -/// However, JSONObjects are immutable, so we represent a 'put' operation as -/// pushing a new element onto a vector of JSONFactory::Props. - -using Properties = llvh::SmallVectorImpl; - -template -void put( - Properties &props, - const std::string &key, - const V &value, - JSONFactory &factory) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(value, factory); - props.push_back({jsStr, jsVal}); -} - -template -void put( - Properties &props, - const std::string &key, - const optional &optValue, - JSONFactory &factory) { - if (optValue.has_value()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(optValue.value(), factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr> &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void deleter(T *p) { - delete p; -} - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_MESSAGETYPESINLINES_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/ProfilerDomainAgent.h b/NativeScript/napi/hermes/include_old/hermes/cdp/ProfilerDomainAgent.h deleted file mode 100644 index 6c62b9c8a..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/ProfilerDomainAgent.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_PROFILERDOMAINAGENT_H -#define HERMES_CDP_PROFILERDOMAINAGENT_H - -#include -#include - -#include "DomainAgent.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Handler for the "Profiler" domain of CDP. All methods expect to be invoked -/// with exclusive access to the runtime. -class ProfilerDomainAgent : public DomainAgent { - public: - ProfilerDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable); - ~ProfilerDomainAgent() = default; - - void start(const m::profiler::StartRequest &req); - void stop(const m::profiler::StopRequest &req); - - private: - HermesRuntime &runtime_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_PROFILERDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/RemoteObjectConverters.h b/NativeScript/napi/hermes/include_old/hermes/cdp/RemoteObjectConverters.h deleted file mode 100644 index ae688884e..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/RemoteObjectConverters.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_REMOTEOBJECTCONVERTERS_H -#define HERMES_CDP_REMOTEOBJECTCONVERTERS_H - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -struct ObjectSerializationOptions { - bool returnByValue = false; - bool generatePreview = false; -}; - -namespace message { - -namespace debugger { - -CallFrame makeCallFrame( - uint32_t callFrameIndex, - const facebook::hermes::debugger::CallFrameInfo &callFrameInfo, - const facebook::hermes::debugger::LexicalInfo &lexicalInfo, - cdp::RemoteObjectsTable &objTable, - jsi::Runtime &runtime, - const facebook::hermes::debugger::ProgramState &state); - -std::vector makeCallFrames( - const facebook::hermes::debugger::ProgramState &state, - cdp::RemoteObjectsTable &objTable, - jsi::Runtime &runtime); - -} // namespace debugger - -namespace runtime { - -RemoteObject makeRemoteObject( - facebook::jsi::Runtime &runtime, - const facebook::jsi::Value &value, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup, - const cdp::ObjectSerializationOptions &serializationOptions); - -RemoteObject makeRemoteObjectForError( - facebook::jsi::Runtime &runtime, - const facebook::jsi::Value &value, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup); - -ExceptionDetails makeExceptionDetails( - jsi::Runtime &runtime, - const jsi::JSError &error, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup); - -ExceptionDetails makeExceptionDetails(const jsi::JSIException &err); - -ExceptionDetails makeExceptionDetails( - facebook::jsi::Runtime &runtime, - const facebook::hermes::debugger::EvalResult &result, - cdp::RemoteObjectsTable &objTable, - const std::string &objectGroup); - -} // namespace runtime - -} // namespace message -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_REMOTEOBJECTCONVERTERS_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/RemoteObjectsTable.h b/NativeScript/napi/hermes/include_old/hermes/cdp/RemoteObjectsTable.h deleted file mode 100644 index 1b8fff5a2..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/RemoteObjectsTable.h +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_REMOTEOBJECTSTABLE_H -#define HERMES_CDP_REMOTEOBJECTSTABLE_H - -#include -#include -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace cdp { - -/// Well-known object group names - -/** - * Objects created as a result of the Debugger.paused notification (e.g. scope - * objects) are placed in the "backtrace" object group. This object group is - * cleared when the VM resumes. - */ -extern const char *BacktraceObjectGroup; - -/** - * Objects that are created as a result of a console evaluation are placed in - * the "console" object group. This object group is cleared when the client - * clears the console. - */ -extern const char *ConsoleObjectGroup; - -/** - * RemoteObjectsTable manages the mapping of string object ids to scope metadata - * or actual JSI objects. The debugger vends these ids to the client so that the - * client can perform operations on the ids (e.g. enumerate properties on the - * object backed by the id). See Runtime.RemoteObjectId in the CDT docs for - * more details. - * - * Note that object handles are not ref-counted. Suppose an object foo is mapped - * to object id "objId" and is also in object group "objGroup". Then *either* of - * `releaseObject("objId")` or `releaseObjectGroup("objGroup")` will remove foo - * from the table. This matches the behavior of object groups in CDT. - */ -class RemoteObjectsTable { - public: - RemoteObjectsTable(); - ~RemoteObjectsTable(); - - RemoteObjectsTable(const RemoteObjectsTable &) = delete; - RemoteObjectsTable &operator=(const RemoteObjectsTable &) = delete; - - /** - * addScope adds the provided (frameIndex, scopeIndex) mapping to the table. - * If objectGroup is non-empty, then the scope object is also added to that - * object group for releasing via releaseObjectGroup. Returns an object id. - */ - std::string addScope( - std::pair frameAndScopeIndex, - const std::string &objectGroup); - - /** - * addValue adds the JSI value to the table. If objectGroup is non-empty, then - * the scope object is also added to that object group for releasing via - * releaseObjectGroup. Returns an object id. - */ - std::string addValue( - ::facebook::jsi::Value value, - const std::string &objectGroup); - - /// /param objId The object ID. - /// /return true if object ID represents a scope in the scope chain of a call - /// frame. - bool isScopeId(const std::string &objId) const; - - /** - * Retrieves the (frameIndex, scopeIndex) associated with this object id, or - * nullptr if no mapping exists. The pointer stays valid as long as you only - * call const methods on this class. - */ - const std::pair *getScope(const std::string &objId) const; - - /** - * Retrieves the JSI value associated with this object id, or nullptr if no - * mapping exists. The pointer stays valid as long as you only call const - * methods on this class. - */ - const ::facebook::jsi::Value *getValue(const std::string &objId) const; - - /** - * Retrieves the object group that this object id is in, or empty string if it - * isn't in an object group. The returned pointer is only guaranteed to be - * valid until the next call to this class. - */ - std::string getObjectGroup(const std::string &objId) const; - - /** - * Removes the scope or JSI value backed by the provided object ID from the - * table. \return true if the object was removed, false if it was not found. - */ - bool releaseObject(const std::string &objId); - - /** - * Removes all objects that are part of the provided object group from the - * table. - */ - void releaseObjectGroup(const std::string &objectGroup); - - private: - bool releaseObject(int64_t id); - - int64_t scopeId_ = -1; - int64_t valueId_ = 1; - - std::unordered_map> scopes_; - std::unordered_map values_; - std::unordered_map idToGroup_; - std::unordered_map> groupToIds_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_REMOTEOBJECTSTABLE_H diff --git a/NativeScript/napi/hermes/include_old/hermes/cdp/RuntimeDomainAgent.h b/NativeScript/napi/hermes/include_old/hermes/cdp/RuntimeDomainAgent.h deleted file mode 100644 index 9c8142aab..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/cdp/RuntimeDomainAgent.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_CDP_RUNTIMEDOMAINAGENT_H -#define HERMES_CDP_RUNTIMEDOMAINAGENT_H - -#include - -#include "CDPDebugAPI.h" -#include "DomainAgent.h" -#include "RemoteObjectConverters.h" - -namespace facebook { -namespace hermes { -namespace cdp { - -namespace m = ::facebook::hermes::cdp::message; - -/// Handler for the "Runtime" domain of CDP. Accepts CDP requests belonging to -/// the "Runtime" domain from the debug client. Produces CDP responses and -/// events belonging to the "Runtime" domain. All methods expect to be invoked -/// with exclusive access to the runtime. -class RuntimeDomainAgent : public DomainAgent { - public: - RuntimeDomainAgent( - int32_t executionContextID, - HermesRuntime &runtime, - debugger::AsyncDebuggerAPI &asyncDebuggerAPI, - SynchronizedOutboundCallback messageCallback, - std::shared_ptr objTable, - ConsoleMessageStorage &consoleMessageStorage, - ConsoleMessageDispatcher &consoleMessageDispatcher); - ~RuntimeDomainAgent(); - - /// Enables the Runtime domain without processing CDP message or sending a CDP - /// response. It will still send CDP notifications if needed. - void enable(); - /// Handles Runtime.enable request - /// @cdp Runtime.enable If domain is already enabled, will return success. - void enable(const m::runtime::EnableRequest &req); - /// @cdp Runtime.discardConsoleEntries - void discardConsoleEntries( - const m::runtime::DiscardConsoleEntriesRequest &req); - /// Handles Runtime.disable request - /// @cdp Runtime.disable If domain is already disabled, will return success. - void disable(const m::runtime::DisableRequest &req); - /// Handles Runtime.getHeapUsage request - /// @cdp Runtime.getHeapUsage Allowed even if domain is not enabled. - void getHeapUsage(const m::runtime::GetHeapUsageRequest &req); - /// Handles Runtime.globalLexicalScopeNames request - /// @cdp Runtime.globalLexicalScopeNames Allowed even if domain is not - /// enabled. - void globalLexicalScopeNames( - const m::runtime::GlobalLexicalScopeNamesRequest &req); - /// Handles Runtime.compileScript request - /// @cdp Runtime.compileScript Not allowed if domain is not enabled. - void compileScript(const m::runtime::CompileScriptRequest &req); - /// Handles Runtime.getProperties request - /// @cdp Runtime.getProperties Allowed even if domain is not enabled. - void getProperties(const m::runtime::GetPropertiesRequest &req); - /// Handles Runtime.evaluate request - /// @cdp Runtime.evaluate Allowed even if domain is not enabled. - void evaluate(const m::runtime::EvaluateRequest &req); - /// Handles Runtime.callFunctionOn request - /// @cdp Runtime.callFunctionOn Allowed even if domain is not enabled. - void callFunctionOn(const m::runtime::CallFunctionOnRequest &req); - /// Dispatches a Runtime.consoleAPICalled notification - void consoleAPICalled(const ConsoleMessage &message, bool isBuffered); - /// Handles Runtime.releaseObject request - /// @cdp Runtime.releaseObject Allowed even if domain is not enabled. - void releaseObject(const m::runtime::ReleaseObjectRequest &req); - /// Handles Runtime.releaseObjectGroup request - /// @cdp Runtime.releaseObjectGroup Allowed even if domain is not enabled. - void releaseObjectGroup(const m::runtime::ReleaseObjectGroupRequest &req); - - private: - struct Helpers { - jsi::Function objectGetOwnPropertySymbols; - jsi::Function objectGetOwnPropertyNames; - jsi::Function objectGetOwnPropertyDescriptor; - jsi::Function objectGetPrototypeOf; - - explicit Helpers(jsi::Runtime &runtime); - }; - - bool checkRuntimeEnabled(const m::Request &req); - - /// Ensure the provided \p executionContextId matches the one - /// indicated via the constructor. Returns true if they match. - /// Sends an error message with the specified \p commandId - /// and returns false otherwise. - bool validateExecutionContextId( - m::runtime::ExecutionContextId executionContextId, - long long commandId); - - std::optional> makePropsFromScope( - std::pair frameAndScopeIndex, - const std::string &objectGroup, - const debugger::ProgramState &state, - const ObjectSerializationOptions &serializationOptions); - std::vector makePropsFromValue( - const jsi::Value &value, - const std::string &objectGroup, - bool onlyOwnProperties, - bool accessorPropertiesOnly, - const ObjectSerializationOptions &serializationOptions); - std::vector - makeInternalPropsFromValue( - const jsi::Value &value, - const std::string &objectGroup, - const ObjectSerializationOptions &serializationOptions); - - HermesRuntime &runtime_; - debugger::AsyncDebuggerAPI &asyncDebuggerAPI_; - ConsoleMessageStorage &consoleMessageStorage_; - ConsoleMessageDispatcher &consoleMessageDispatcher_; - - /// Whether Runtime.enable was received and wasn't disabled by receiving - /// Runtime.disable - bool enabled_; - - // preparedScripts_ stores user-entered scripts that have been prepared for - // execution, and may be invoked by a later command. - std::vector> preparedScripts_; - - /// Console message subscription token, used to unsubscribe during shutdown. - ConsoleMessageRegistration consoleMessageRegistration_; - - /// Cached helper JS functions used by agent methods. - const Helpers helpers_; -}; - -} // namespace cdp -} // namespace hermes -} // namespace facebook - -#endif // HERMES_CDP_RUNTIMEDOMAINAGENT_H diff --git a/NativeScript/napi/hermes/include_old/hermes/hermes.h b/NativeScript/napi/hermes/include_old/hermes/hermes.h deleted file mode 100644 index 0d6d70fc8..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/hermes.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_HERMES_H -#define HERMES_HERMES_H - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "js_native_api.h" - -struct HermesTestHelper; - -namespace hermes { - namespace vm { - class GCExecTrace; - class Runtime; - } // namespace vm -} // namespace hermes - -namespace facebook { - namespace jsi { - - class ThreadSafeRuntime; - - } - - namespace hermes { - - namespace debugger { - class Debugger; - } - - class HermesRuntimeImpl; - -/// Represents a Hermes JS runtime. - class HERMES_EXPORT HermesRuntime : public jsi::Runtime { - public: - - napi_status createNapiEnv(napi_env *env); - - static bool isHermesBytecode(const uint8_t *data, size_t len); - // Returns the supported bytecode version. - static uint32_t getBytecodeVersion(); - // (EXPERIMENTAL) Issues madvise calls for portions of the given - // bytecode file that will likely be used when loading the bytecode - // file and running its global function. - static void prefetchHermesBytecode(const uint8_t *data, size_t len); - // Returns whether the data is valid HBC with more extensive checks than - // isHermesBytecode and returns why it isn't in errorMessage (if nonnull) - // if not. - static bool hermesBytecodeSanityCheck( - const uint8_t *data, - size_t len, - std::string *errorMessage = nullptr); - static void setFatalHandler(void (*handler)(const std::string &)); - - // Assuming that \p data is valid HBC bytecode data, returns a pointer to the - // first element of the epilogue, data append to the end of the bytecode - // stream. Return pair contain ptr to data and header. - static std::pair getBytecodeEpilogue( - const uint8_t *data, - size_t len); - - /// Enable sampling profiler. - /// Starts a separate thread that polls VM state with \p meanHzFreq frequency. - /// Any subsequent call to \c enableSamplingProfiler() is ignored until - /// next call to \c disableSamplingProfiler() - static void enableSamplingProfiler(double meanHzFreq = 100); - - /// Disable the sampling profiler - static void disableSamplingProfiler(); - - /// Dump sampled stack trace to the given file name. - static void dumpSampledTraceToFile(const std::string &fileName); - - /// Dump sampled stack trace to the given stream. - static void dumpSampledTraceToStream(std::ostream &stream); - - /// Serialize the sampled stack to the format expected by DevTools' - /// Profiler.stop return type. - void sampledTraceToStreamInDevToolsFormat(std::ostream &stream); - - /// Return the executed JavaScript function info. - /// This information holds the segmentID, Virtualoffset and sourceURL. - /// This information is needed specifically to be able to symbolicate non-CJS - /// bundles correctly. This API will be simplified later to simply return a - /// segmentID and virtualOffset, when we are able to only support CJS bundles. - static std::unordered_map> - getExecutedFunctions(); - - /// \return whether code coverage profiler is enabled or not. - static bool isCodeCoverageProfilerEnabled(); - - /// Enable code coverage profiler. - static void enableCodeCoverageProfiler(); - - /// Disable code coverage profiler. - static void disableCodeCoverageProfiler(); - - // The base class declares most of the interesting methods. This - // just declares new methods which are specific to HermesRuntime. - // The actual implementations of the pure virtual methods are - // provided by a class internal to the .cpp file, which is created - // by the factory. - - /// Load a new segment into the Runtime. - /// The \param context must be a valid RequireContext retrieved from JS - /// using `require.context`. - void loadSegment( - std::unique_ptr buffer, - const jsi::Value &context); - - /// Gets a guaranteed unique id for an Object (or, respectively, String - /// or PropNameId), which is assigned at allocation time and is - /// static throughout that object's (or string's, or PropNameID's) - /// lifetime. - uint64_t getUniqueID(const jsi::Object &o) const; - uint64_t getUniqueID(const jsi::BigInt &s) const; - uint64_t getUniqueID(const jsi::String &s) const; - uint64_t getUniqueID(const jsi::PropNameID &pni) const; - uint64_t getUniqueID(const jsi::Symbol &sym) const; - - /// Same as the other \c getUniqueID, except it can return 0 for some values. - /// 0 means there is no ID associated with the value. - uint64_t getUniqueID(const jsi::Value &val) const; - - /// From an ID retrieved from \p getUniqueID, go back to the object. - /// NOTE: This is much slower in general than the reverse operation, and takes - /// up more memory. Don't use this unless it's absolutely necessary. - /// \return a jsi::Object if a matching object is found, else returns null. - jsi::Value getObjectForID(uint64_t id); - - /// Get a structure representing the execution history (currently just of - /// GC, but will be generalized as necessary), to aid in debugging - /// non-deterministic execution. - const ::hermes::vm::GCExecTrace &getGCExecTrace() const; - - /// Get IO tracking (aka HBC page access) info as a JSON string. - /// See hermes::vm::Runtime::getIOTrackingInfoJSON() for conditions - /// needed for there to be useful output. - std::string getIOTrackingInfoJSON(); - -#ifdef HERMESVM_PROFILER_BB - /// Write the trace to the given stream. - void dumpBasicBlockProfileTrace(std::ostream &os) const; -#endif - -#ifdef HERMESVM_PROFILER_OPCODE - /// Write the opcode stats to the given stream. - void dumpOpcodeStats(std::ostream &os) const; -#endif - - /// \return a reference to the Debugger for this Runtime. - debugger::Debugger &getDebugger(); - -#ifdef HERMES_ENABLE_DEBUGGER - - struct DebugFlags { - // Looking for the .lazy flag? It's no longer necessary. - // Source is evaluated lazily by default. See - // RuntimeConfig::CompilationMode. - }; - - /// Evaluate the given code in an unoptimized form, - /// used for debugging. - void debugJavaScript( - const std::string &src, - const std::string &sourceURL, - const DebugFlags &debugFlags); -#endif - - /// Register this runtime and thread for sampling profiler. Before using the - /// runtime on another thread, invoke this function again from the new thread - /// to make the sampling profiler target the new thread (and forget the old - /// thread). - void registerForProfiling(); - /// Unregister this runtime for sampling profiler. - void unregisterForProfiling(); - - /// Define methods to interrupt JS execution and set time limits. - /// All JS compiled to bytecode via prepareJS, or evaluateJS, will support - /// interruption and time limit monitoring if the runtime is configured with - /// AsyncBreakCheckInEval. If JS prepared in other ways is executed, care must - /// be taken to ensure that it is compiled in a mode that supports it (i.e., - /// the emitted code contains async break checks). - - /// Asynchronously terminates the current execution. This can be called on - /// any thread. - void asyncTriggerTimeout(); - - /// Register this runtime for execution time limit monitoring, with a time - /// limit of \p timeoutInMs milliseconds. - /// See compilation notes above. - void watchTimeLimit(uint32_t timeoutInMs); - /// Unregister this runtime for execution time limit monitoring. - void unwatchTimeLimit(); - - /// Same as \c evaluate JavaScript but with a source map, which will be - /// applied to exception traces and debug information. - /// - /// This is an experimental Hermes-specific API. In the future it may be - /// renamed, moved or combined with another API, but the provided - /// functionality will continue to be available in some form. - jsi::Value evaluateJavaScriptWithSourceMap( - const std::shared_ptr &buffer, - const std::shared_ptr &sourceMapBuf, - const std::string &sourceURL); - - /// Returns the underlying low level Hermes VM runtime instance. - /// This function is considered unsafe and unstable. - /// Direct use of a vm::Runtime should be avoided as the lower level APIs are - /// unsafe and they can change without notice. - ::hermes::vm::Runtime *getVMRuntimeUnsafe() const; - - private: - // Only HermesRuntimeImpl can subclass this. - HermesRuntime() = default; - friend class HermesRuntimeImpl; - - friend struct ::HermesTestHelper; - size_t rootsListLengthForTests() const; - - // Do not add any members here. This ensures that there are no - // object size inconsistencies. All data should be in the impl - // class in the .cpp file. - }; - -/// Return a RuntimeConfig that is more suited for running untrusted JS than -/// the default config. Disables some language features and may trade off some -/// performance for security. -/// -/// Can serve as a starting point with tweaks to re-enable needed features: -/// auto conf = hardenedHermesRuntimeConfig().rebuild(); -/// conf.withArrayBuffer(true); -/// ... -/// auto runtime = makeHermesRuntime(conf.build()); - HERMES_EXPORT ::hermes::vm::RuntimeConfig hardenedHermesRuntimeConfig(); - - HERMES_EXPORT std::unique_ptr makeHermesRuntime( - const ::hermes::vm::RuntimeConfig &runtimeConfig = - ::hermes::vm::RuntimeConfig()); - HERMES_EXPORT std::unique_ptr - makeThreadSafeHermesRuntime( - const ::hermes::vm::RuntimeConfig &runtimeConfig = - ::hermes::vm::RuntimeConfig()); - } // namespace hermes -} // namespace facebook - -#endif \ No newline at end of file diff --git a/NativeScript/napi/hermes/include_old/hermes/hermes_api.h b/NativeScript/napi/hermes/include_old/hermes/hermes_api.h deleted file mode 100644 index f0e616f8b..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/hermes_api.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_HERMES_API_H -#define HERMES_HERMES_API_H - -#include "js_runtime.h" - -EXTERN_C_START - -typedef struct hermes_local_connection_s *hermes_local_connection; -typedef struct hermes_remote_connection_s *hermes_remote_connection; - -//============================================================================= -// jsr_runtime -//============================================================================= - -JSR_API hermes_dump_crash_data(jsr_runtime runtime, int32_t fd); -JSR_API hermes_sampling_profiler_enable(); -JSR_API hermes_sampling_profiler_disable(); -JSR_API hermes_sampling_profiler_add(jsr_runtime runtime); -JSR_API hermes_sampling_profiler_remove(jsr_runtime runtime); -JSR_API hermes_sampling_profiler_dump_to_file(const char *filename); - -//============================================================================= -// jsr_config -//============================================================================= - -JSR_API hermes_config_enable_default_crash_handler( - jsr_config config, - bool value); - -//============================================================================= -// Setting inspector singleton -//============================================================================= - -typedef int32_t(NAPI_CDECL *hermes_inspector_add_page_cb)( - const char *title, - const char *vm, - void *connectFunc); - -typedef void(NAPI_CDECL *hermes_inspector_remove_page_cb)(int32_t page_id); - -JSR_API hermes_set_inspector( - hermes_inspector_add_page_cb add_page_cb, - hermes_inspector_remove_page_cb remove_page_cb); - -//============================================================================= -// Local and remote inspector connections. -// Local is defined in Hermes VM, Remote is defined by inspector outside of VM. -//============================================================================= - -typedef void(NAPI_CDECL *hermes_remote_connection_send_message_cb)( - hermes_remote_connection remote_connection, - const char *message); - -typedef void(NAPI_CDECL *hermes_remote_connection_disconnect_cb)( - hermes_remote_connection remote_connection); - -JSR_API hermes_create_local_connection( - void *connect_func, - hermes_remote_connection remote_connection, - hermes_remote_connection_send_message_cb on_send_message_cb, - hermes_remote_connection_disconnect_cb on_disconnect_cb, - jsr_data_delete_cb on_delete_cb, - void *deleter_data, - hermes_local_connection *local_connection); - -JSR_API hermes_delete_local_connection( - hermes_local_connection local_connection); - -JSR_API hermes_local_connection_send_message( - hermes_local_connection local_connection, - const char *message); - -JSR_API hermes_local_connection_disconnect( - hermes_local_connection local_connection); - -EXTERN_C_END - -#endif // !HERMES_HERMES_API_H \ No newline at end of file diff --git a/NativeScript/napi/hermes/include_old/hermes/hermes_tracing.h b/NativeScript/napi/hermes/include_old/hermes/hermes_tracing.h deleted file mode 100644 index 470e82d9c..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/hermes_tracing.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_HERMES_TRACING_H -#define HERMES_HERMES_TRACING_H - -#include - -namespace llvh { -class raw_ostream; -} // namespace llvh - -namespace facebook { -namespace hermes { - -/// Creates and returns a tracing runtime if \p runtimeConfig.SynthTraceMode is -/// either SynthTraceMode::Tracing or SynthTraceMode::TracingAndReplaying. -/// Otherwise, returns the passed \n hermesRuntime as is. -/// The trace will be written to \p traceScratchPath incrementally. -/// On completion, the file will be renamed to \p traceResultPath, and -/// \p traceCompletionCallback (for post-processing) will be invoked. -/// Completion can be triggered implicitly by crash (if crash manager is -/// provided) or explicitly by invocation of flush. -/// If the runtime is destructed without triggering trace completion, -/// the file at \p traceScratchPath will be deleted. -/// The return value of \p traceCompletionCallback indicates whether the -/// invocation completed successfully. If \p traceCompletionCallback is null, it -/// also assumes as if the callback is successful. -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - const std::string &traceScratchPath, - const std::string &traceResultPath, - std::function traceCompletionCallback); - -/// Creates and returns a tracing runtime that wrapps the passed -/// \p hermesRuntime. This API is mainly for Synth Trace replay (and tracing), -/// and for testing. -/// \p traceStream the stream to write trace to. -/// \p forReplay indicates whether the runtime is being used in trace replay and -/// tracing. -std::unique_ptr makeTracingHermesRuntime( - std::unique_ptr hermesRuntime, - const ::hermes::vm::RuntimeConfig &runtimeConfig, - std::unique_ptr traceStream, - bool forReplay = false); - -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/RuntimeAdapter.h b/NativeScript/napi/hermes/include_old/hermes/inspector/RuntimeAdapter.h deleted file mode 100644 index 64396f2cc..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/RuntimeAdapter.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include - -#ifndef INSPECTOR_EXPORT -#ifdef _MSC_VER -#ifdef CREATE_SHARED_LIBRARY -#define INSPECTOR_EXPORT __declspec(dllexport) -#else -#define INSPECTOR_EXPORT -#endif // CREATE_SHARED_LIBRARY -#else // _MSC_VER -#define INSPECTOR_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(INSPECTOR_EXPORT) - -namespace facebook { -namespace hermes { -namespace inspector_modern { - -/** - * RuntimeAdapter encapsulates a HermesRuntime object. The underlying Hermes - * runtime object should stay alive for at least as long as the RuntimeAdapter - * is alive. - */ -class INSPECTOR_EXPORT RuntimeAdapter { - public: - virtual ~RuntimeAdapter() = 0; - - /// getRuntime should return the runtime encapsulated by this adapter. The - /// CDP Handler will only invoke this function from the runtime thread. - virtual HermesRuntime &getRuntime() = 0; - - /// \p tickleJs is a method that subclasses can choose to override to make - /// the inspector more responsive. If overridden, it should call the - /// \p __tickleJs JavaScript function. Calling JavaScript functions must be - /// done on the runtime thread, and \p tickleJs() may be invoked from an - /// arbitrary thread. Thus, the call to \p __tickleJs should occur with - /// appropriate locking (e.g. via a thread-safe runtime instance, or by - /// enqueuing the call on to a dedicated JS thread). - /// - /// This makes the inspector more responsive because it gives the inspector - /// the ability to force the process to enter the Hermes interpreter loop - /// soon. This is important because the inspector can only do a number of - /// important operations (like manipulating breakpoints) within the context of - /// a Hermes interperter loop. - /// - /// The default implementation does nothing. - virtual void tickleJs(); -}; - -/** - * SharedRuntimeAdapter is a simple implementation of RuntimeAdapter that - * uses shared_ptr to hold on to the runtime. It's generally only used in tests, - * since it does not implement tickleJs. - */ -class INSPECTOR_EXPORT SharedRuntimeAdapter : public RuntimeAdapter { - public: - SharedRuntimeAdapter(std::shared_ptr runtime); - ~SharedRuntimeAdapter() override; - - HermesRuntime &getRuntime() override; - - private: - std::shared_ptr runtime_; -}; - -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/CDPHandler.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/CDPHandler.h deleted file mode 100644 index 01fe26eb4..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/CDPHandler.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// using include guards instead of #pragma once due to compile issues -// with MSVC and BUCK -#ifndef HERMES_INSPECTOR_CDPHANDLER_H -#define HERMES_INSPECTOR_CDPHANDLER_H - -#include -#include -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -using CDPMessageCallbackFunction = std::function; -using OnUnregisterFunction = std::function; - -class CDPHandlerImpl; - -struct State; - -/// Utility struct to configure the initial state of the CDP session. -struct INSPECTOR_EXPORT CDPHandlerSessionConfig { - bool isRuntimeDomainEnabled{false}; -}; - -/// Configuration for the execution context managed by the CDPHandler. -struct INSPECTOR_EXPORT CDPHandlerExecutionContextDescription { - int32_t id{}; - std::string origin; - std::string name; - std::optional auxData; - bool shouldSendNotifications{}; -}; - -/// CDPHandler processes CDP messages between the client and the debugger. -/// It performs no networking or connection logic itself. -/// The CDP Handler is invoked from multiple threads. The locking strategy is -/// to acquire the lock at each entry point into the class, and hold it until -/// the entry function has returned. In practice, these functions fall into 2 -/// categories: public functions invoked by the creator of this instance, and -/// callbacks invoked by the runtime to report events. -/// Once the lock is held, most members are safe to use from any thread, with -/// the notable exception of the runtime (and debugger retrieved from the -/// runtime). Most runtime methods must only be invoked when running on the -/// runtime thread, which occurs in the CDP Handler constructor/destructor, and -/// callbacks from the runtime thread (e.g. host functions, instrumentation -/// callbacks, and pause callback). -class INSPECTOR_EXPORT CDPHandler { - /// Hide the constructor so users can only construct via static create - /// methods. - CDPHandler( - std::unique_ptr adapter, - const std::string &title, - bool waitForDebugger, - bool processConsoleAPI, - std::shared_ptr state, - const CDPHandlerSessionConfig &sessionConfig, - std::optional - executionContextDescription); - - public: - /// Creating a CDPHandler enables the debugger on the provided runtime. This - /// should generally called before you start running any JS in the runtime. - /// This should also be called on the runtime thread, as methods are invoked - /// on the given \p adapter. - static std::shared_ptr create( - std::unique_ptr adapter, - bool waitForDebugger = false, - bool processConsoleAPI = true, - std::shared_ptr state = nullptr, - const CDPHandlerSessionConfig &sessionConfig = {}, - std::optional - executionContextDescription = std::nullopt); - /// Temporarily kept to allow React Native build to still work - static std::shared_ptr create( - std::unique_ptr adapter, - const std::string &title, - bool waitForDebugger = false, - bool processConsoleAPI = true, - std::shared_ptr state = nullptr, - const CDPHandlerSessionConfig &sessionConfig = {}, - std::optional - executionContextDescription = std::nullopt); - ~CDPHandler(); - - /// getTitle returns the name of the friendly name of the runtime that's shown - /// to users in the CDP frontend (e.g. Chrome DevTools). - std::string getTitle() const; - - /// Provide a callback to receive replies and notifications from the debugger, - /// and optionally provide a function to be called during - /// unregisterCallbacks(). - /// \param msgCallback Function to receive replies and notifications from the - /// debugger - /// \param onDisconnect Function that will be invoked upon calling - /// unregisterCallbacks - /// \return true if there wasn't a previously registered callback - bool registerCallbacks( - CDPMessageCallbackFunction msgCallback, - OnUnregisterFunction onUnregister); - - /// Unregister any previously registered callbacks. - /// \return true if there were previously registered callbacks - bool unregisterCallbacks(); - - /// Process a JSON-encoded Chrome DevTools Protocol request. - void handle(std::string str); - - /// Extract state to be persisted across reloads. - std::unique_ptr getState(); - - private: - std::shared_ptr impl_; - const std::string title_; -}; - -/// Public-facing wrapper for internal CDP state that can be preserved across -/// reloads. -struct INSPECTOR_EXPORT State { - /// Incomplete type that stores the actual state. - struct Private; - - /// Create a new wrapper with the provided \p privateState. - explicit State(std::unique_ptr privateState); - ~State(); - - /// Get the wrapped state. - Private &get() { - return *privateState_.get(); - } - - private: - /// Pointer to the actual stored state, hidden from users of this wrapper. - std::unique_ptr privateState_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook - -#endif // HERMES_INSPECTOR_CDPHandler_H diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/CallbackOStream.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/CallbackOStream.h deleted file mode 100644 index a9831555a..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/CallbackOStream.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -/// Subclass of \c std::ostream where flushing is implemented through a -/// callback. Writes are collected in a buffer. When filled, the buffer's -/// contents are emptied out and sent to a callback. -struct CallbackOStream : public std::ostream { - /// Signature of callback called to flush buffer contents. Accepts the buffer - /// as a string. Returns a boolean indicating whether flushing succeeded. - /// Callback failure will be translated to stream failure. If the callback - /// throws an exception it will be swallowed and translated into stream - /// failure. - using Fn = std::function; - - /// Construct a new stream. - /// - /// \p sz The size of the buffer -- how large it can get before it must be - /// flushed. Must be non-zero. - /// \p cb The callback function. - CallbackOStream(size_t sz, Fn cb); - - /// This class is neither movable nor copyable. - CallbackOStream(CallbackOStream &&that) = delete; - CallbackOStream &operator=(CallbackOStream &&that) = delete; - CallbackOStream(const CallbackOStream &that) = delete; - CallbackOStream &operator=(const CallbackOStream &that) = delete; - - private: - /// \c std::streambuf sub-class backed by a std::string buffer and - /// implementing overflow by calling a callback. - struct StreamBuf : public std::streambuf { - /// Construct a new streambuf. Parameters are the same as those of - /// \c CallbackOStream . - StreamBuf(size_t sz, Fn cb); - - /// Destruction will flush any remaining buffer contents. - ~StreamBuf() override; - - /// StreamBufs are not copyable, to avoid the flush callback receiving - /// the contents of multiple streams. - StreamBuf(const StreamBuf &) = delete; - StreamBuf &operator=(const StreamBuf &) = delete; - - protected: - /// std::streambuf overrides - int_type overflow(int_type ch) override; - int sync() override; - - private: - /// The size of the backing buffer. Fixed for an instance of the streambuf. - size_t sz_; - - /// The backing buffer that writes will go to until full. - std::unique_ptr buf_; - - /// The function called when buf_ has been filled. - Fn cb_; - - /// Clears the backing buffer. - void reset(); - - /// Clears the backing buffer and returns it contents in a string. - std::string take(); - }; - - StreamBuf sbuf_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/JSONValueInterfaces.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/JSONValueInterfaces.h deleted file mode 100644 index 263313810..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/JSONValueInterfaces.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -using namespace ::hermes::parser; - -/// Convert a string to a JSONValue. Will return nullopt if parsing is not -/// successful. -std::optional parseStr( - const std::string &str, - JSONFactory &factory); - -/// Convert a string to a JSON object. Will return nullopt if parsing is not -/// successful, or the resulting JSON value is not an object. -std::optional parseStrAsJsonObj( - const std::string &str, - JSONFactory &factory); - -/// Convert a JSONValue to a string. -std::string jsonValToStr(const JSONValue *v); - -/// Check if two JSONValues are equal. -bool jsonValsEQ(const JSONValue *A, const JSONValue *B); - -}; // namespace chrome -}; // namespace inspector_modern -}; // namespace hermes -}; // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageConverters.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageConverters.h deleted file mode 100644 index fd26c9ed5..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageConverters.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -template -void setChromeLocation( - T &chromeLoc, - const facebook::hermes::debugger::SourceLocation &hermesLoc) { - if (hermesLoc.line != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.lineNumber = hermesLoc.line - 1; - } - - if (hermesLoc.column != facebook::hermes::debugger::kInvalidLocation) { - chromeLoc.columnNumber = hermesLoc.column - 1; - } -} - -/// ErrorCode magic numbers match JSC's (see InspectorBackendDispatcher.cpp) -enum class ErrorCode { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - ServerError = -32000 -}; - -ErrorResponse -makeErrorResponse(int id, ErrorCode code, const std::string &message); - -OkResponse makeOkResponse(int id); - -namespace debugger { - -Location makeLocation(const facebook::hermes::debugger::SourceLocation &loc); - -} // namespace debugger - -namespace runtime { - -CallFrame makeCallFrame(const facebook::hermes::debugger::CallFrameInfo &info); - -std::vector makeCallFrames( - const facebook::hermes::debugger::StackTrace &stackTrace); - -ExceptionDetails makeExceptionDetails( - const facebook::hermes::debugger::ExceptionDetails &details); - -} // namespace runtime - -namespace heapProfiler { - -std::unique_ptr makeSamplingHeapProfile( - const std::string &value); - -} // namespace heapProfiler - -namespace profiler { - -std::unique_ptr makeProfile(const std::string &value); - -} // namespace profiler - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageInterfaces.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageInterfaces.h deleted file mode 100644 index 01e369e22..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageInterfaces.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { -using namespace ::hermes::parser; - -struct RequestHandler; - -/// Serializable is an interface for objects that can be serialized to and from -/// JSON. -struct Serializable { - virtual ~Serializable() = default; - virtual JSONValue *toJsonVal(JSONFactory &factory) const = 0; - - std::string toJsonStr() const; -}; - -/// Requests are sent from the debugger to the target. -struct Request : public Serializable { - using ParseResult = std::variant, std::string>; - static std::unique_ptr fromJson(const std::string &str); - - Request() = default; - explicit Request(std::string method) : method(method) {} - - // accept dispatches to the appropriate handler method in RequestHandler based - // on the type of the request. - virtual void accept(RequestHandler &handler) const = 0; - - long long id = 0; - std::string method; -}; - -/// Responses are sent from the target to the debugger in response to a Request. -struct Response : public Serializable { - Response() = default; - - long long id = 0; -}; - -/// Notifications are sent from the target to the debugger. This is used to -/// notify the debugger about events that occur in the target, e.g. stopping -/// at a breakpoint. -struct Notification : public Serializable { - Notification() = default; - explicit Notification(std::string method) : method(method) {} - - std::string method; -}; - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageTypes.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageTypes.h deleted file mode 100644 index e039758f6..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageTypes.h +++ /dev/null @@ -1,1183 +0,0 @@ -// Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved. -// @generated SignedSource<<3ebea508f76e06269045891097f89eb5>> - -#pragma once - -#include -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -template -void deleter(T *p); -using JSONBlob = std::string; -struct UnknownRequest; - -namespace debugger { -using BreakpointId = std::string; -struct BreakpointResolvedNotification; -struct CallFrame; -using CallFrameId = std::string; -struct DisableRequest; -struct EnableRequest; -struct EvaluateOnCallFrameRequest; -struct EvaluateOnCallFrameResponse; -struct Location; -struct PauseRequest; -struct PausedNotification; -struct RemoveBreakpointRequest; -struct ResumeRequest; -struct ResumedNotification; -struct Scope; -struct ScriptParsedNotification; -struct SetBreakpointByUrlRequest; -struct SetBreakpointByUrlResponse; -struct SetBreakpointRequest; -struct SetBreakpointResponse; -struct SetBreakpointsActiveRequest; -struct SetInstrumentationBreakpointRequest; -struct SetInstrumentationBreakpointResponse; -struct SetPauseOnExceptionsRequest; -struct StepIntoRequest; -struct StepOutRequest; -struct StepOverRequest; -} // namespace debugger - -namespace runtime { -struct CallArgument; -struct CallFrame; -struct CallFunctionOnRequest; -struct CallFunctionOnResponse; -struct CompileScriptRequest; -struct CompileScriptResponse; -struct ConsoleAPICalledNotification; -struct CustomPreview; -struct DisableRequest; -struct EnableRequest; -struct EntryPreview; -struct EvaluateRequest; -struct EvaluateResponse; -struct ExceptionDetails; -struct ExecutionContextCreatedNotification; -struct ExecutionContextDescription; -using ExecutionContextId = long long; -struct GetHeapUsageRequest; -struct GetHeapUsageResponse; -struct GetPropertiesRequest; -struct GetPropertiesResponse; -struct GlobalLexicalScopeNamesRequest; -struct GlobalLexicalScopeNamesResponse; -struct InternalPropertyDescriptor; -struct ObjectPreview; -struct PropertyDescriptor; -struct PropertyPreview; -struct RemoteObject; -using RemoteObjectId = std::string; -struct RunIfWaitingForDebuggerRequest; -using ScriptId = std::string; -struct StackTrace; -using Timestamp = double; -using UnserializableValue = std::string; -} // namespace runtime - -namespace heapProfiler { -struct AddHeapSnapshotChunkNotification; -struct CollectGarbageRequest; -struct GetHeapObjectIdRequest; -struct GetHeapObjectIdResponse; -struct GetObjectByHeapObjectIdRequest; -struct GetObjectByHeapObjectIdResponse; -using HeapSnapshotObjectId = std::string; -struct HeapStatsUpdateNotification; -struct LastSeenObjectIdNotification; -struct ReportHeapSnapshotProgressNotification; -struct SamplingHeapProfile; -struct SamplingHeapProfileNode; -struct SamplingHeapProfileSample; -struct StartSamplingRequest; -struct StartTrackingHeapObjectsRequest; -struct StopSamplingRequest; -struct StopSamplingResponse; -struct StopTrackingHeapObjectsRequest; -struct TakeHeapSnapshotRequest; -} // namespace heapProfiler - -namespace profiler { -struct PositionTickInfo; -struct Profile; -struct ProfileNode; -struct StartRequest; -struct StopRequest; -struct StopResponse; -} // namespace profiler - -/// RequestHandler handles requests via the visitor pattern. -struct RequestHandler { - virtual ~RequestHandler() = default; - - virtual void handle(const UnknownRequest &req) = 0; - virtual void handle(const debugger::DisableRequest &req) = 0; - virtual void handle(const debugger::EnableRequest &req) = 0; - virtual void handle(const debugger::EvaluateOnCallFrameRequest &req) = 0; - virtual void handle(const debugger::PauseRequest &req) = 0; - virtual void handle(const debugger::RemoveBreakpointRequest &req) = 0; - virtual void handle(const debugger::ResumeRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointByUrlRequest &req) = 0; - virtual void handle(const debugger::SetBreakpointsActiveRequest &req) = 0; - virtual void handle( - const debugger::SetInstrumentationBreakpointRequest &req) = 0; - virtual void handle(const debugger::SetPauseOnExceptionsRequest &req) = 0; - virtual void handle(const debugger::StepIntoRequest &req) = 0; - virtual void handle(const debugger::StepOutRequest &req) = 0; - virtual void handle(const debugger::StepOverRequest &req) = 0; - virtual void handle(const heapProfiler::CollectGarbageRequest &req) = 0; - virtual void handle(const heapProfiler::GetHeapObjectIdRequest &req) = 0; - virtual void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) = 0; - virtual void handle(const heapProfiler::StartSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::StopSamplingRequest &req) = 0; - virtual void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) = 0; - virtual void handle(const heapProfiler::TakeHeapSnapshotRequest &req) = 0; - virtual void handle(const profiler::StartRequest &req) = 0; - virtual void handle(const profiler::StopRequest &req) = 0; - virtual void handle(const runtime::CallFunctionOnRequest &req) = 0; - virtual void handle(const runtime::CompileScriptRequest &req) = 0; - virtual void handle(const runtime::DisableRequest &req) = 0; - virtual void handle(const runtime::EnableRequest &req) = 0; - virtual void handle(const runtime::EvaluateRequest &req) = 0; - virtual void handle(const runtime::GetHeapUsageRequest &req) = 0; - virtual void handle(const runtime::GetPropertiesRequest &req) = 0; - virtual void handle(const runtime::GlobalLexicalScopeNamesRequest &req) = 0; - virtual void handle(const runtime::RunIfWaitingForDebuggerRequest &req) = 0; -}; - -/// NoopRequestHandler can be subclassed to only handle some requests. -struct NoopRequestHandler : public RequestHandler { - void handle(const UnknownRequest &req) override {} - void handle(const debugger::DisableRequest &req) override {} - void handle(const debugger::EnableRequest &req) override {} - void handle(const debugger::EvaluateOnCallFrameRequest &req) override {} - void handle(const debugger::PauseRequest &req) override {} - void handle(const debugger::RemoveBreakpointRequest &req) override {} - void handle(const debugger::ResumeRequest &req) override {} - void handle(const debugger::SetBreakpointRequest &req) override {} - void handle(const debugger::SetBreakpointByUrlRequest &req) override {} - void handle(const debugger::SetBreakpointsActiveRequest &req) override {} - void handle( - const debugger::SetInstrumentationBreakpointRequest &req) override {} - void handle(const debugger::SetPauseOnExceptionsRequest &req) override {} - void handle(const debugger::StepIntoRequest &req) override {} - void handle(const debugger::StepOutRequest &req) override {} - void handle(const debugger::StepOverRequest &req) override {} - void handle(const heapProfiler::CollectGarbageRequest &req) override {} - void handle(const heapProfiler::GetHeapObjectIdRequest &req) override {} - void handle( - const heapProfiler::GetObjectByHeapObjectIdRequest &req) override {} - void handle(const heapProfiler::StartSamplingRequest &req) override {} - void handle( - const heapProfiler::StartTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::StopSamplingRequest &req) override {} - void handle( - const heapProfiler::StopTrackingHeapObjectsRequest &req) override {} - void handle(const heapProfiler::TakeHeapSnapshotRequest &req) override {} - void handle(const profiler::StartRequest &req) override {} - void handle(const profiler::StopRequest &req) override {} - void handle(const runtime::CallFunctionOnRequest &req) override {} - void handle(const runtime::CompileScriptRequest &req) override {} - void handle(const runtime::DisableRequest &req) override {} - void handle(const runtime::EnableRequest &req) override {} - void handle(const runtime::EvaluateRequest &req) override {} - void handle(const runtime::GetHeapUsageRequest &req) override {} - void handle(const runtime::GetPropertiesRequest &req) override {} - void handle(const runtime::GlobalLexicalScopeNamesRequest &req) override {} - void handle(const runtime::RunIfWaitingForDebuggerRequest &req) override {} -}; - -/// Types -struct debugger::Location : public Serializable { - Location() = default; - Location(Location &&) = default; - Location(const Location &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Location &operator=(const Location &) = delete; - Location &operator=(Location &&) = default; - - runtime::ScriptId scriptId{}; - long long lineNumber{}; - std::optional columnNumber; -}; - -struct runtime::PropertyPreview : public Serializable { - PropertyPreview() = default; - PropertyPreview(PropertyPreview &&) = default; - PropertyPreview(const PropertyPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyPreview &operator=(const PropertyPreview &) = delete; - PropertyPreview &operator=(PropertyPreview &&) = default; - - std::string name; - std::string type; - std::optional value; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - valuePreview{nullptr, deleter}; - std::optional subtype; -}; - -struct runtime::EntryPreview : public Serializable { - EntryPreview() = default; - EntryPreview(EntryPreview &&) = default; - EntryPreview(const EntryPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - EntryPreview &operator=(const EntryPreview &) = delete; - EntryPreview &operator=(EntryPreview &&) = default; - - std::unique_ptr< - runtime::ObjectPreview, - std::function> - key{nullptr, deleter}; - std::unique_ptr< - runtime::ObjectPreview, - std::function> - value{nullptr, deleter}; -}; - -struct runtime::ObjectPreview : public Serializable { - ObjectPreview() = default; - ObjectPreview(ObjectPreview &&) = default; - ObjectPreview(const ObjectPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ObjectPreview &operator=(const ObjectPreview &) = delete; - ObjectPreview &operator=(ObjectPreview &&) = default; - - std::string type; - std::optional subtype; - std::optional description; - bool overflow{}; - std::vector properties; - std::optional> entries; -}; - -struct runtime::CustomPreview : public Serializable { - CustomPreview() = default; - CustomPreview(CustomPreview &&) = default; - CustomPreview(const CustomPreview &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CustomPreview &operator=(const CustomPreview &) = delete; - CustomPreview &operator=(CustomPreview &&) = default; - - std::string header; - std::optional bodyGetterId; -}; - -struct runtime::RemoteObject : public Serializable { - RemoteObject() = default; - RemoteObject(RemoteObject &&) = default; - RemoteObject(const RemoteObject &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - RemoteObject &operator=(const RemoteObject &) = delete; - RemoteObject &operator=(RemoteObject &&) = default; - - std::string type; - std::optional subtype; - std::optional className; - std::optional value; - std::optional unserializableValue; - std::optional description; - std::optional objectId; - std::optional preview; - std::optional customPreview; -}; - -struct runtime::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - std::string functionName; - runtime::ScriptId scriptId{}; - std::string url; - long long lineNumber{}; - long long columnNumber{}; -}; - -struct runtime::StackTrace : public Serializable { - StackTrace() = default; - StackTrace(StackTrace &&) = default; - StackTrace(const StackTrace &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - StackTrace &operator=(const StackTrace &) = delete; - StackTrace &operator=(StackTrace &&) = default; - - std::optional description; - std::vector callFrames; - std::unique_ptr parent; -}; - -struct runtime::ExceptionDetails : public Serializable { - ExceptionDetails() = default; - ExceptionDetails(ExceptionDetails &&) = default; - ExceptionDetails(const ExceptionDetails &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExceptionDetails &operator=(const ExceptionDetails &) = delete; - ExceptionDetails &operator=(ExceptionDetails &&) = default; - - long long exceptionId{}; - std::string text; - long long lineNumber{}; - long long columnNumber{}; - std::optional scriptId; - std::optional url; - std::optional stackTrace; - std::optional exception; - std::optional executionContextId; -}; - -struct debugger::Scope : public Serializable { - Scope() = default; - Scope(Scope &&) = default; - Scope(const Scope &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Scope &operator=(const Scope &) = delete; - Scope &operator=(Scope &&) = default; - - std::string type; - runtime::RemoteObject object{}; - std::optional name; - std::optional startLocation; - std::optional endLocation; -}; - -struct debugger::CallFrame : public Serializable { - CallFrame() = default; - CallFrame(CallFrame &&) = default; - CallFrame(const CallFrame &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallFrame &operator=(const CallFrame &) = delete; - CallFrame &operator=(CallFrame &&) = default; - - debugger::CallFrameId callFrameId{}; - std::string functionName; - std::optional functionLocation; - debugger::Location location{}; - std::string url; - std::vector scopeChain; - runtime::RemoteObject thisObj{}; - std::optional returnValue; -}; - -struct heapProfiler::SamplingHeapProfileNode : public Serializable { - SamplingHeapProfileNode() = default; - SamplingHeapProfileNode(SamplingHeapProfileNode &&) = default; - SamplingHeapProfileNode(const SamplingHeapProfileNode &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileNode &operator=(const SamplingHeapProfileNode &) = delete; - SamplingHeapProfileNode &operator=(SamplingHeapProfileNode &&) = default; - - runtime::CallFrame callFrame{}; - double selfSize{}; - long long id{}; - std::vector children; -}; - -struct heapProfiler::SamplingHeapProfileSample : public Serializable { - SamplingHeapProfileSample() = default; - SamplingHeapProfileSample(SamplingHeapProfileSample &&) = default; - SamplingHeapProfileSample(const SamplingHeapProfileSample &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfileSample &operator=(const SamplingHeapProfileSample &) = - delete; - SamplingHeapProfileSample &operator=(SamplingHeapProfileSample &&) = default; - - double size{}; - long long nodeId{}; - double ordinal{}; -}; - -struct heapProfiler::SamplingHeapProfile : public Serializable { - SamplingHeapProfile() = default; - SamplingHeapProfile(SamplingHeapProfile &&) = default; - SamplingHeapProfile(const SamplingHeapProfile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - SamplingHeapProfile &operator=(const SamplingHeapProfile &) = delete; - SamplingHeapProfile &operator=(SamplingHeapProfile &&) = default; - - heapProfiler::SamplingHeapProfileNode head{}; - std::vector samples; -}; - -struct profiler::PositionTickInfo : public Serializable { - PositionTickInfo() = default; - PositionTickInfo(PositionTickInfo &&) = default; - PositionTickInfo(const PositionTickInfo &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PositionTickInfo &operator=(const PositionTickInfo &) = delete; - PositionTickInfo &operator=(PositionTickInfo &&) = default; - - long long line{}; - long long ticks{}; -}; - -struct profiler::ProfileNode : public Serializable { - ProfileNode() = default; - ProfileNode(ProfileNode &&) = default; - ProfileNode(const ProfileNode &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ProfileNode &operator=(const ProfileNode &) = delete; - ProfileNode &operator=(ProfileNode &&) = default; - - long long id{}; - runtime::CallFrame callFrame{}; - std::optional hitCount; - std::optional> children; - std::optional deoptReason; - std::optional> positionTicks; -}; - -struct profiler::Profile : public Serializable { - Profile() = default; - Profile(Profile &&) = default; - Profile(const Profile &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - Profile &operator=(const Profile &) = delete; - Profile &operator=(Profile &&) = default; - - std::vector nodes; - double startTime{}; - double endTime{}; - std::optional> samples; - std::optional> timeDeltas; -}; - -struct runtime::CallArgument : public Serializable { - CallArgument() = default; - CallArgument(CallArgument &&) = default; - CallArgument(const CallArgument &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - CallArgument &operator=(const CallArgument &) = delete; - CallArgument &operator=(CallArgument &&) = default; - - std::optional value; - std::optional unserializableValue; - std::optional objectId; -}; - -struct runtime::ExecutionContextDescription : public Serializable { - ExecutionContextDescription() = default; - ExecutionContextDescription(ExecutionContextDescription &&) = default; - ExecutionContextDescription(const ExecutionContextDescription &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - ExecutionContextDescription &operator=(const ExecutionContextDescription &) = - delete; - ExecutionContextDescription &operator=(ExecutionContextDescription &&) = - default; - - runtime::ExecutionContextId id{}; - std::string origin; - std::string name; - std::optional auxData; -}; - -struct runtime::PropertyDescriptor : public Serializable { - PropertyDescriptor() = default; - PropertyDescriptor(PropertyDescriptor &&) = default; - PropertyDescriptor(const PropertyDescriptor &) = delete; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - PropertyDescriptor &operator=(const PropertyDescriptor &) = delete; - PropertyDescriptor &operator=(PropertyDescriptor &&) = default; - - std::string name; - std::optional value; - std::optional writable; - std::optional get; - std::optional set; - bool configurable{}; - bool enumerable{}; - std::optional wasThrown; - std::optional isOwn; - std::optional symbol; -}; - -struct runtime::InternalPropertyDescriptor : public Serializable { - InternalPropertyDescriptor() = default; - InternalPropertyDescriptor(InternalPropertyDescriptor &&) = default; - InternalPropertyDescriptor(const InternalPropertyDescriptor &) = delete; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - InternalPropertyDescriptor &operator=(const InternalPropertyDescriptor &) = - delete; - InternalPropertyDescriptor &operator=(InternalPropertyDescriptor &&) = - default; - - std::string name; - std::optional value; -}; - -/// Requests -struct UnknownRequest : public Request { - UnknownRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional params; -}; - -struct debugger::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::EvaluateOnCallFrameRequest : public Request { - EvaluateOnCallFrameRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::CallFrameId callFrameId{}; - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional throwOnSideEffect; -}; - -struct debugger::PauseRequest : public Request { - PauseRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::RemoveBreakpointRequest : public Request { - RemoveBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct debugger::ResumeRequest : public Request { - ResumeRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional terminateOnResume; -}; - -struct debugger::SetBreakpointRequest : public Request { - SetBreakpointRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - debugger::Location location{}; - std::optional condition; -}; - -struct debugger::SetBreakpointByUrlRequest : public Request { - SetBreakpointByUrlRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - long long lineNumber{}; - std::optional url; - std::optional urlRegex; - std::optional scriptHash; - std::optional columnNumber; - std::optional condition; -}; - -struct debugger::SetBreakpointsActiveRequest : public Request { - SetBreakpointsActiveRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - bool active{}; -}; - -struct debugger::SetInstrumentationBreakpointRequest : public Request { - SetInstrumentationBreakpointRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string instrumentation; -}; - -struct debugger::SetPauseOnExceptionsRequest : public Request { - SetPauseOnExceptionsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string state; -}; - -struct debugger::StepIntoRequest : public Request { - StepIntoRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOutRequest : public Request { - StepOutRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct debugger::StepOverRequest : public Request { - StepOverRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::CollectGarbageRequest : public Request { - CollectGarbageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::GetHeapObjectIdRequest : public Request { - GetHeapObjectIdRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdRequest : public Request { - GetObjectByHeapObjectIdRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - heapProfiler::HeapSnapshotObjectId objectId{}; - std::optional objectGroup; -}; - -struct heapProfiler::StartSamplingRequest : public Request { - StartSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional samplingInterval; - std::optional includeObjectsCollectedByMajorGC; - std::optional includeObjectsCollectedByMinorGC; -}; - -struct heapProfiler::StartTrackingHeapObjectsRequest : public Request { - StartTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional trackAllocations; -}; - -struct heapProfiler::StopSamplingRequest : public Request { - StopSamplingRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct heapProfiler::StopTrackingHeapObjectsRequest : public Request { - StopTrackingHeapObjectsRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct heapProfiler::TakeHeapSnapshotRequest : public Request { - TakeHeapSnapshotRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional reportProgress; - std::optional treatGlobalObjectsAsRoots; - std::optional captureNumericValue; -}; - -struct profiler::StartRequest : public Request { - StartRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct profiler::StopRequest : public Request { - StopRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::CallFunctionOnRequest : public Request { - CallFunctionOnRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string functionDeclaration; - std::optional objectId; - std::optional> arguments; - std::optional silent; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; - std::optional executionContextId; - std::optional objectGroup; -}; - -struct runtime::CompileScriptRequest : public Request { - CompileScriptRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::string sourceURL; - bool persistScript{}; - std::optional executionContextId; -}; - -struct runtime::DisableRequest : public Request { - DisableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EnableRequest : public Request { - EnableRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::EvaluateRequest : public Request { - EvaluateRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::string expression; - std::optional objectGroup; - std::optional includeCommandLineAPI; - std::optional silent; - std::optional contextId; - std::optional returnByValue; - std::optional generatePreview; - std::optional userGesture; - std::optional awaitPromise; -}; - -struct runtime::GetHeapUsageRequest : public Request { - GetHeapUsageRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -struct runtime::GetPropertiesRequest : public Request { - GetPropertiesRequest(); - static std::unique_ptr tryMake(const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - runtime::RemoteObjectId objectId{}; - std::optional ownProperties; - std::optional generatePreview; -}; - -struct runtime::GlobalLexicalScopeNamesRequest : public Request { - GlobalLexicalScopeNamesRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; - - std::optional executionContextId; -}; - -struct runtime::RunIfWaitingForDebuggerRequest : public Request { - RunIfWaitingForDebuggerRequest(); - static std::unique_ptr tryMake( - const JSONObject *obj); - - JSONValue *toJsonVal(JSONFactory &factory) const override; - void accept(RequestHandler &handler) const override; -}; - -/// Responses -struct ErrorResponse : public Response { - ErrorResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long code; - std::string message; - std::optional data; -}; - -struct OkResponse : public Response { - OkResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::EvaluateOnCallFrameResponse : public Response { - EvaluateOnCallFrameResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct debugger::SetBreakpointResponse : public Response { - SetBreakpointResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location actualLocation{}; -}; - -struct debugger::SetBreakpointByUrlResponse : public Response { - SetBreakpointByUrlResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - std::vector locations; -}; - -struct debugger::SetInstrumentationBreakpointResponse : public Response { - SetInstrumentationBreakpointResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; -}; - -struct heapProfiler::GetHeapObjectIdResponse : public Response { - GetHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::HeapSnapshotObjectId heapSnapshotObjectId{}; -}; - -struct heapProfiler::GetObjectByHeapObjectIdResponse : public Response { - GetObjectByHeapObjectIdResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; -}; - -struct heapProfiler::StopSamplingResponse : public Response { - StopSamplingResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - heapProfiler::SamplingHeapProfile profile{}; -}; - -struct profiler::StopResponse : public Response { - StopResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - profiler::Profile profile{}; -}; - -struct runtime::CallFunctionOnResponse : public Response { - CallFunctionOnResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::CompileScriptResponse : public Response { - CompileScriptResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::optional scriptId; - std::optional exceptionDetails; -}; - -struct runtime::EvaluateResponse : public Response { - EvaluateResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::RemoteObject result{}; - std::optional exceptionDetails; -}; - -struct runtime::GetHeapUsageResponse : public Response { - GetHeapUsageResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - double usedSize{}; - double totalSize{}; -}; - -struct runtime::GetPropertiesResponse : public Response { - GetPropertiesResponse() = default; - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector result; - std::optional> - internalProperties; - std::optional exceptionDetails; -}; - -struct runtime::GlobalLexicalScopeNamesResponse : public Response { - GlobalLexicalScopeNamesResponse() = default; - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector names; -}; - -/// Notifications -struct debugger::BreakpointResolvedNotification : public Notification { - BreakpointResolvedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - debugger::BreakpointId breakpointId{}; - debugger::Location location{}; -}; - -struct debugger::PausedNotification : public Notification { - PausedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector callFrames; - std::string reason; - std::optional data; - std::optional> hitBreakpoints; - std::optional asyncStackTrace; -}; - -struct debugger::ResumedNotification : public Notification { - ResumedNotification(); - static std::unique_ptr tryMake(const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; -}; - -struct debugger::ScriptParsedNotification : public Notification { - ScriptParsedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ScriptId scriptId{}; - std::string url; - long long startLine{}; - long long startColumn{}; - long long endLine{}; - long long endColumn{}; - runtime::ExecutionContextId executionContextId{}; - std::string hash; - std::optional executionContextAuxData; - std::optional sourceMapURL; - std::optional hasSourceURL; - std::optional isModule; - std::optional length; -}; - -struct heapProfiler::AddHeapSnapshotChunkNotification : public Notification { - AddHeapSnapshotChunkNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string chunk; -}; - -struct heapProfiler::HeapStatsUpdateNotification : public Notification { - HeapStatsUpdateNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::vector statsUpdate; -}; - -struct heapProfiler::LastSeenObjectIdNotification : public Notification { - LastSeenObjectIdNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long lastSeenObjectId{}; - double timestamp{}; -}; - -struct heapProfiler::ReportHeapSnapshotProgressNotification - : public Notification { - ReportHeapSnapshotProgressNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - long long done{}; - long long total{}; - std::optional finished; -}; - -struct runtime::ConsoleAPICalledNotification : public Notification { - ConsoleAPICalledNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - std::string type; - std::vector args; - runtime::ExecutionContextId executionContextId{}; - runtime::Timestamp timestamp{}; - std::optional stackTrace; -}; - -struct runtime::ExecutionContextCreatedNotification : public Notification { - ExecutionContextCreatedNotification(); - static std::unique_ptr tryMake( - const JSONObject *obj); - JSONValue *toJsonVal(JSONFactory &factory) const override; - - runtime::ExecutionContextDescription context{}; -}; - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageTypesInlines.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageTypesInlines.h deleted file mode 100644 index 49a4995dd..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/MessageTypesInlines.h +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -template -using optional = std::optional; - -template -struct is_vector : std::false_type {}; - -template -struct is_vector> : std::true_type {}; - -/// valueFromJson - -/// Convert JSONValue to a Serializable type. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return T::tryMake(res); -} - -/// Convert JSONValue to a bool. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a long long. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a double. -template -typename std::enable_if::value, std::unique_ptr>:: - type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->getValue()); -} - -/// Convert JSONValue to a string. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(const JSONValue *v) { - auto res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res->c_str()); -} - -/// Convert JSONValue to a vector. -template -typename std::enable_if::value, std::unique_ptr>::type -valueFromJson(const JSONValue *items) { - auto *arr = llvh::dyn_cast(items); - std::unique_ptr result = std::make_unique(); - result->reserve(arr->size()); - for (const auto &item : *arr) { - auto itemResult = valueFromJson(item); - if (!itemResult) { - return nullptr; - } - result->push_back(std::move(*itemResult)); - } - return result; -} - -/// Convert JSONValue to a JSONObject. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - auto *res = llvh::dyn_cast_or_null(v); - if (!res) { - return nullptr; - } - return std::make_unique(res); -} - -/// Pass through JSONValues. -template -typename std:: - enable_if::value, std::unique_ptr>::type - valueFromJson(JSONValue *v) { - return std::make_unique(v); -} - -/// assign(lhs, obj, key) is a wrapper for: -/// -/// lhs = obj[key] -/// -/// It mainly exists so that we can choose the right version of valueFromJson -/// based on the type of lhs. - -template -bool assign(T &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v == nullptr) { - return false; - } - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; -} - -template -bool assign(optional &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(*convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign(std::unique_ptr &lhs, const JSONObject *obj, const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -template -bool assign( - std::unique_ptr> &lhs, - const JSONObject *obj, - const U &key) { - JSONValue *v = obj->get(key); - if (v != nullptr) { - auto convertResult = valueFromJson(v); - if (convertResult) { - lhs = std::move(convertResult); - return true; - } - return false; - } else { - lhs.reset(); - return true; - } -} - -/// valueToJson - -inline JSONValue *valueToJson(const Serializable &value, JSONFactory &factory) { - return value.toJsonVal(factory); -} - -// Convert a bool to JSONValue. -inline JSONValue *valueToJson(bool b, JSONFactory &factory) { - return factory.getBoolean(b); -} - -// Convert a long long to JSONValue. -inline JSONValue *valueToJson(long long num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a double to JSONValue. -inline JSONValue *valueToJson(double num, JSONFactory &factory) { - return factory.getNumber(num); -} - -// Convert a string to JSONValue. -inline JSONValue *valueToJson(const std::string &str, JSONFactory &factory) { - return factory.getString(str); -} - -// Convert a vector to JSONValue. -template -JSONValue *valueToJson(const std::vector &items, JSONFactory &factory) { - llvh::SmallVector storage; - for (const auto &item : items) { - storage.push_back(valueToJson(item, factory)); - } - return factory.newArray(storage.size(), storage.begin(), storage.end()); -} - -// Cast a JSONObject to JSONValue. -inline JSONValue *valueToJson(JSONObject *obj, JSONFactory &factory) { - return llvh::cast(obj); -} - -// Pass through JSONValues. -inline JSONValue *valueToJson(JSONValue *v, JSONFactory &factory) { - return v; -} - -/// put(obj, key, value) is meant to be a wrapper for: -/// obj[key] = valueToJson(value); -/// However, JSONObjects are immutable, so we represent a 'put' operation as -/// pushing a new element onto a vector of JSONFactory::Props. - -using Properties = llvh::SmallVectorImpl; - -template -void put( - Properties &props, - const std::string &key, - const V &value, - JSONFactory &factory) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(value, factory); - props.push_back({jsStr, jsVal}); -} - -template -void put( - Properties &props, - const std::string &key, - const optional &optValue, - JSONFactory &factory) { - if (optValue.has_value()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(optValue.value(), factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void put( - Properties &props, - const std::string &key, - const std::unique_ptr> &ptr, - JSONFactory &factory) { - if (ptr.get()) { - JSONString *jsStr = factory.getString(key); - JSONValue *jsVal = valueToJson(*ptr, factory); - props.push_back({jsStr, jsVal}); - } -} - -template -void deleter(T *p) { - delete p; -} - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/RemoteObjectConverters.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/RemoteObjectConverters.h deleted file mode 100644 index 89355dc3e..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/RemoteObjectConverters.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { -namespace message { - -namespace debugger { - -CallFrame makeCallFrame( - uint32_t callFrameIndex, - const facebook::hermes::debugger::CallFrameInfo &callFrameInfo, - const facebook::hermes::debugger::LexicalInfo &lexicalInfo, - facebook::hermes::inspector_modern::chrome::RemoteObjectsTable &objTable, - jsi::Runtime &runtime, - const facebook::hermes::debugger::ProgramState &state); - -std::vector makeCallFrames( - const facebook::hermes::debugger::ProgramState &state, - facebook::hermes::inspector_modern::chrome::RemoteObjectsTable &objTable, - jsi::Runtime &runtime); - -} // namespace debugger - -namespace runtime { - -RemoteObject makeRemoteObject( - facebook::jsi::Runtime &runtime, - const facebook::jsi::Value &value, - facebook::hermes::inspector_modern::chrome::RemoteObjectsTable &objTable, - const std::string &objectGroup, - bool byValue = false, - bool generatePreview = false); - -} // namespace runtime - -} // namespace message -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/RemoteObjectsTable.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/RemoteObjectsTable.h deleted file mode 100644 index d7a3370f6..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/RemoteObjectsTable.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include - -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -/// Well-known object group names - -/** - * Objects created as a result of the Debugger.paused notification (e.g. scope - * objects) are placed in the "backtrace" object group. This object group is - * cleared when the VM resumes. - */ -extern const char *BacktraceObjectGroup; - -/** - * Objects that are created as a result of a console evaluation are placed in - * the "console" object group. This object group is cleared when the client - * clears the console. - */ -extern const char *ConsoleObjectGroup; - -/** - * RemoteObjectsTable manages the mapping of string object ids to scope metadata - * or actual JSI objects. The debugger vends these ids to the client so that the - * client can perform operations on the ids (e.g. enumerate properties on the - * object backed by the id). See Runtime.RemoteObjectId in the CDT docs for - * more details. - * - * Note that object handles are not ref-counted. Suppose an object foo is mapped - * to object id "objId" and is also in object group "objGroup". Then *either* of - * `releaseObject("objId")` or `releaseObjectGroup("objGroup")` will remove foo - * from the table. This matches the behavior of object groups in CDT. - */ -class RemoteObjectsTable { - public: - RemoteObjectsTable(); - ~RemoteObjectsTable(); - - RemoteObjectsTable(const RemoteObjectsTable &) = delete; - RemoteObjectsTable &operator=(const RemoteObjectsTable &) = delete; - - /** - * addScope adds the provided (frameIndex, scopeIndex) mapping to the table. - * If objectGroup is non-empty, then the scope object is also added to that - * object group for releasing via releaseObjectGroup. Returns an object id. - */ - std::string addScope( - std::pair frameAndScopeIndex, - const std::string &objectGroup); - - /** - * addValue adds the JSI value to the table. If objectGroup is non-empty, then - * the scope object is also added to that object group for releasing via - * releaseObjectGroup. Returns an object id. - */ - std::string addValue( - ::facebook::jsi::Value value, - const std::string &objectGroup); - - /** - * Retrieves the (frameIndex, scopeIndex) associated with this object id, or - * nullptr if no mapping exists. The pointer stays valid as long as you only - * call const methods on this class. - */ - const std::pair *getScope(const std::string &objId) const; - - /** - * Retrieves the JSI value associated with this object id, or nullptr if no - * mapping exists. The pointer stays valid as long as you only call const - * methods on this class. - */ - const ::facebook::jsi::Value *getValue(const std::string &objId) const; - - /** - * Retrieves the object group that this object id is in, or empty string if it - * isn't in an object group. The returned pointer is only guaranteed to be - * valid until the next call to this class. - */ - std::string getObjectGroup(const std::string &objId) const; - - /** - * Removes the scope or JSI value backed by the provided object ID from the - * table. - */ - void releaseObject(const std::string &objId); - - /** - * Removes all objects that are part of the provided object group from the - * table. - */ - void releaseObjectGroup(const std::string &objectGroup); - - private: - void releaseObject(int64_t id); - - int64_t scopeId_ = -1; - int64_t valueId_ = 1; - - std::unordered_map> scopes_; - std::unordered_map values_; - std::unordered_map idToGroup_; - std::unordered_map> groupToIds_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/AsyncHermesRuntime.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/AsyncHermesRuntime.h deleted file mode 100644 index aaaf9cd04..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/AsyncHermesRuntime.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -/// URL assigned to scripts being executed in the absense of a caller-specified -/// URL. -constexpr auto kDefaultUrl = "url"; - -/** - * AsyncHermesRuntime is a helper class that runs JS scripts in a Hermes VM on - * a separate thread. This is useful for tests that want to test running JS - * in a multithreaded environment. - */ -class AsyncHermesRuntime { - public: - // Create a runtime. If veryLazy, configure the runtime to use completely - // lazy compilation. - AsyncHermesRuntime(bool veryLazy = false); - ~AsyncHermesRuntime(); - - std::shared_ptr runtime() { - return runtime_; - } - - /** - * stop sets the stop flag on this instance. JS scripts can get the current - * value of the stop flag by calling the global shouldStop() function. - */ - void stop(); - - /** - * start unsets the stop flag on this instance. JS scripts can get the current - * value of the stop flag by calling the global shouldStop() function. - */ - void start(); - - /** - * hasStoredValue returns whether or not a value has been stored yet - */ - bool hasStoredValue(); - - /** - * awaitStoredValue is a helper for getStoredValue that returns the value - * synchronously rather than in a future. - */ - jsi::Value awaitStoredValue( - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - /** - * tickleJsAsync evaluates '__tickleJs()' in the underlying Hermes runtime on - * a separate thread. - */ - void tickleJsAsync(); - - /** - * executeScriptAsync evaluates JS in the underlying Hermes runtime on a - * separate thread. - * - * This method should be called at most once during the lifetime of an - * AsyncHermesRuntime instance. - */ - void executeScriptAsync( - const std::string &str, - const std::string &url = kDefaultUrl, - facebook::hermes::HermesRuntime::DebugFlags flags = - facebook::hermes::HermesRuntime::DebugFlags{}); - - /** - * executeScriptSync evaluates JS in the underlying Hermes runtime on a - * separate thread. It will block the caller until execution completes. If - * this takes longer than \p timeout, an exception will be thrown. - */ - void executeScriptSync( - const std::string &script, - const std::string &url = kDefaultUrl, - facebook::hermes::HermesRuntime::DebugFlags flags = - facebook::hermes::HermesRuntime::DebugFlags{}, - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - /// Evaluates the given bytecode in the underlying Hermes runtime on a - /// separate thread. - /// \param bytecode Bytecode compiled with compileJS() API - /// \param url Corresponding source URL - void evaluateBytecodeAsync( - const std::string &bytecode, - const std::string &url = "url"); - - /** - * wait blocks until all previous executeScriptAsync calls finish. - */ - void wait( - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - /** - * returns the number of thrown exceptions. - */ - size_t getNumberOfExceptions(); - - /** - * returns the message of the last thrown exception. - */ - std::string getLastThrownExceptionMessage(); - - /** - * registers the runtime for profiling in the executor thread. - */ - void registerForProfilingInExecutor(); - - /** - * unregisters the runtime for profiling in the executor thread. - */ - void unregisterForProfilingInExecutor(); - - private: - jsi::Value shouldStop( - jsi::Runtime &runtime, - const jsi::Value &thisVal, - const jsi::Value *args, - size_t count); - - jsi::Value storeValue( - jsi::Runtime &runtime, - const jsi::Value &thisVal, - const jsi::Value *args, - size_t count); - - std::shared_ptr runtime_; - std::unique_ptr<::hermes::SerialExecutor> executor_; - std::atomic stopFlag_{}; - std::promise storedValue_; - bool hasStoredValue_{false}; - std::vector thrownExceptions_; -}; - -/// RAII-style class dealing with sampling profiler registration in tests. This -/// is especially important in tests -- if any test failure is caused by an -/// uncaught exception, stack unwinding will destroy a VM registered for -/// profiling in a thread that's not the one where registration happened, which -/// will lead to a hermes fatal error. Using this RAII class ensure that the -/// proper test failure cause is reported. -struct SamplingProfilerRAII { - explicit SamplingProfilerRAII(AsyncHermesRuntime &rt) : runtime_(rt) { - runtime_.registerForProfilingInExecutor(); - } - - ~SamplingProfilerRAII() { - runtime_.unregisterForProfilingInExecutor(); - } - - AsyncHermesRuntime &runtime_; -}; -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/SyncConnection.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/SyncConnection.h deleted file mode 100644 index d9ecc509f..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/SyncConnection.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -#include "AsyncHermesRuntime.h" - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -class ExecutorRuntimeAdapter - : public facebook::hermes::inspector_modern::RuntimeAdapter { - public: - explicit ExecutorRuntimeAdapter(AsyncHermesRuntime &runtime) - : runtime_(runtime) {} - - virtual ~ExecutorRuntimeAdapter() override = default; - - HermesRuntime &getRuntime() override { - return *runtime_.runtime(); - } - - void tickleJs() override; - - private: - AsyncHermesRuntime &runtime_; -}; - -/** - * SyncConnection provides a synchronous interface over Connection that is - * useful in tests. - */ -class SyncConnection { - public: - explicit SyncConnection( - AsyncHermesRuntime &runtime, - bool waitForDebugger = false); - ~SyncConnection(); - - /// sends a message to the debugger - void send(const std::string &str); - - /// waits for the next message of either kind (response or notification) - /// from the debugger. returns the message. throws on timeout. - std::string waitForMessage( - std::chrono::milliseconds timeout = std::chrono::milliseconds(2500)); - - bool registerCallbacks(); - bool unregisterCallbacks(); - - /// \return True if onUnregister was called in a previous unregisterCallbacks - /// call. A registerCallbacks call will reset the status. - bool onUnregisterWasCalled(); - - private: - /// This function is given to the CDPHandler to receive replies in the form of - /// CDP messages - void onReply(const std::string &message); - - /// This function is given to the CDPHandler to be invoked upon - /// unregisterCallbacks call - void onUnregister(); - - std::shared_ptr cdpHandler_; - - bool onUnregisterCalled_ = false; - - std::mutex mutex_; - std::condition_variable hasMessage_; - std::queue messages_; -}; - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/TestHelpers.h b/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/TestHelpers.h deleted file mode 100644 index 2f0e03992..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/inspector/chrome/tests/TestHelpers.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include -#include -#include - -namespace facebook { -namespace hermes { -namespace inspector_modern { -namespace chrome { - -using namespace ::hermes::parser; - -inline JSONValue *mustParseStr(const std::string &str, JSONFactory &factory) { - std::optional v = parseStr(str, factory); - EXPECT_TRUE(v.has_value()); - return v.value(); -} - -inline JSONObject *mustParseStrAsJsonObj( - const std::string &str, - JSONFactory &factory) { - std::optional obj = parseStrAsJsonObj(str, factory); - EXPECT_TRUE(obj.has_value()); - return obj.value(); -} - -template -T mustMake(const JSONObject *obj) { - std::unique_ptr instance = T::tryMake(obj); - EXPECT_TRUE(instance != nullptr); - return std::move(*instance); -} - -namespace message { - -inline std::unique_ptr mustGetRequestFromJson(const std::string &str) { - std::unique_ptr req = Request::fromJson(str); - EXPECT_TRUE(req != nullptr); - return req; -} - -} // namespace message - -} // namespace chrome -} // namespace inspector_modern -} // namespace hermes -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/hermes/synthtest/tests/TestFunctions.h b/NativeScript/napi/hermes/include_old/hermes/synthtest/tests/TestFunctions.h deleted file mode 100644 index 480994731..000000000 --- a/NativeScript/napi/hermes/include_old/hermes/synthtest/tests/TestFunctions.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#ifndef HERMES_API_SYNTHTEST_TESTS_TESTFUNCTIONS -#define HERMES_API_SYNTHTEST_TESTS_TESTFUNCTIONS - -#define FOREACH_TEST(F) \ - F(callbacksCallJSFunction) \ - F(globalReturnObject) \ - F(getPropertyNames) \ - F(hostCallsJS) \ - F(hostCallsJSCallsHost) \ - F(hostCallsJSWithThis) \ - F(hostFunctionCachesObject) \ - F(hostFunctionCreatesObjects) \ - F(hostFunctionMutatesGlobalObject) \ - F(hostFunctionMutatesObject) \ - F(hostFunctionNameAndParams) \ - F(hostFunctionReturn) \ - F(hostFunctionReturnArgument) \ - F(hostFunctionReturnThis) \ - F(hostGlobalObject) \ - F(nativePropertyNames) \ - F(nativeSetsConstant) \ - F(parseGCConfig) \ - F(partialTraceHostFunction) \ - F(partialTraceHostObjectGet) \ - F(partialTraceHostObjectSet) \ - F(surrogatePairString) - -#define TEST_FUNC_FORWARD_DECL(name) \ - const char *name##Trace(); \ - const char *name##Source(); - -namespace facebook { -namespace hermes { -namespace synthtest { - -// Forward decls for all of the functions used. -FOREACH_TEST(TEST_FUNC_FORWARD_DECL) - -} // namespace synthtest -} // namespace hermes -} // namespace facebook - -#endif diff --git a/NativeScript/napi/hermes/include_old/jsi/JSIDynamic.h b/NativeScript/napi/hermes/include_old/jsi/JSIDynamic.h deleted file mode 100644 index a96cc281b..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/JSIDynamic.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include - -namespace facebook { -namespace jsi { - -facebook::jsi::Value valueFromDynamic( - facebook::jsi::Runtime& runtime, - const folly::dynamic& dyn); - -folly::dynamic dynamicFromValue( - facebook::jsi::Runtime& runtime, - const facebook::jsi::Value& value, - std::function filterObjectKeys = nullptr); - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/jsi/decorator.h b/NativeScript/napi/hermes/include_old/jsi/decorator.h deleted file mode 100644 index c0d3cc6d4..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/decorator.h +++ /dev/null @@ -1,901 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include - -// This file contains objects to help API users create their own -// runtime adapters, i.e. if you want to compose runtimes to add your -// own behavior. - -namespace facebook { -namespace jsi { - -// Use this to wrap host functions. It will pass the member runtime as -// the first arg to the callback. The first argument to the ctor -// should be the decorated runtime, not the plain one. -class DecoratedHostFunction { - public: - DecoratedHostFunction(Runtime& drt, HostFunctionType plainHF) - : drt_(drt), plainHF_(std::move(plainHF)) {} - - Runtime& decoratedRuntime() { - return drt_; - } - - Value - operator()(Runtime&, const Value& thisVal, const Value* args, size_t count) { - return plainHF_(decoratedRuntime(), thisVal, args, count); - } - - private: - template - friend class RuntimeDecorator; - - Runtime& drt_; - HostFunctionType plainHF_; -}; - -// From the perspective of the caller, a plain HostObject is passed to -// the decorated Runtime, and the HostObject methods expect to get -// passed that Runtime. But the plain Runtime will pass itself to its -// callback, so we need a helper here which curries the decorated -// Runtime, and calls the plain HostObject with it. -// -// If the concrete RuntimeDecorator derives DecoratedHostObject, it -// should call the base class get() and set() to invoke the plain -// HostObject functionality. The Runtime& it passes does not matter, -// as it is not used. -class DecoratedHostObject : public HostObject { - public: - DecoratedHostObject(Runtime& drt, std::shared_ptr plainHO) - : drt_(drt), plainHO_(plainHO) {} - - // The derived class methods can call this to get a reference to the - // decorated runtime, since the rt passed to the callback will be - // the plain runtime. - Runtime& decoratedRuntime() { - return drt_; - } - - Value get(Runtime&, const PropNameID& name) override { - return plainHO_->get(decoratedRuntime(), name); - } - - void set(Runtime&, const PropNameID& name, const Value& value) override { - plainHO_->set(decoratedRuntime(), name, value); - } - - std::vector getPropertyNames(Runtime&) override { - return plainHO_->getPropertyNames(decoratedRuntime()); - } - - private: - template - friend class RuntimeDecorator; - - Runtime& drt_; - std::shared_ptr plainHO_; -}; - -/// C++ variant on a standard Decorator pattern, using template -/// parameters. The \c Plain template parameter type is the -/// undecorated Runtime type. You can usually use \c Runtime here, -/// but if you know the concrete type ahead of time and it's final, -/// the compiler can devirtualize calls to the decorated -/// implementation. The \c Base template parameter type will be used -/// as the base class of the decorated type. Here, too, you can -/// usually use \c Runtime, but if you want the decorated type to -/// implement a derived class of Runtime, you can specify that here. -/// For an example, see threadsafe.h. -template -class RuntimeDecorator : public Base, private jsi::Instrumentation { - public: - Plain& plain() { - static_assert( - std::is_base_of::value, - "RuntimeDecorator's Plain type must derive from jsi::Runtime"); - static_assert( - std::is_base_of::value, - "RuntimeDecorator's Base type must derive from jsi::Runtime"); - return plain_; - } - const Plain& plain() const { - return plain_; - } - - Value evaluateJavaScript( - const std::shared_ptr& buffer, - const std::string& sourceURL) override { - return plain().evaluateJavaScript(buffer, sourceURL); - } - std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, - std::string sourceURL) override { - return plain().prepareJavaScript(buffer, std::move(sourceURL)); - } - Value evaluatePreparedJavaScript( - const std::shared_ptr& js) override { - return plain().evaluatePreparedJavaScript(js); - } - void queueMicrotask(const jsi::Function& callback) override { - return plain().queueMicrotask(callback); - } - bool drainMicrotasks(int maxMicrotasksHint) override { - return plain().drainMicrotasks(maxMicrotasksHint); - } - Object global() override { - return plain().global(); - } - std::string description() override { - return plain().description(); - }; - bool isInspectable() override { - return plain().isInspectable(); - }; - Instrumentation& instrumentation() override { - return *this; - } - - protected: - // plain is generally going to be a reference to an object managed - // by a derived class. We cache it here so this class can be - // concrete, and avoid making virtual calls to find the plain - // Runtime. Note that the ctor and dtor do not access through the - // reference, so passing a reference to an object before its - // lifetime has started is ok. - RuntimeDecorator(Plain& plain) : plain_(plain) {} - - Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { - return plain_.cloneSymbol(pv); - }; - Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { - return plain_.cloneBigInt(pv); - }; - Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { - return plain_.cloneString(pv); - }; - Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { - return plain_.cloneObject(pv); - }; - Runtime::PointerValue* clonePropNameID( - const Runtime::PointerValue* pv) override { - return plain_.clonePropNameID(pv); - }; - - PropNameID createPropNameIDFromAscii(const char* str, size_t length) - override { - return plain_.createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) - override { - return plain_.createPropNameIDFromUtf8(utf8, length); - }; - PropNameID createPropNameIDFromString(const String& str) override { - return plain_.createPropNameIDFromString(str); - }; - PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { - return plain_.createPropNameIDFromSymbol(sym); - }; - std::string utf8(const PropNameID& id) override { - return plain_.utf8(id); - }; - bool compare(const PropNameID& a, const PropNameID& b) override { - return plain_.compare(a, b); - }; - - std::string symbolToString(const Symbol& sym) override { - return plain_.symbolToString(sym); - } - - BigInt createBigIntFromInt64(int64_t value) override { - return plain_.createBigIntFromInt64(value); - } - BigInt createBigIntFromUint64(uint64_t value) override { - return plain_.createBigIntFromUint64(value); - } - bool bigintIsInt64(const BigInt& b) override { - return plain_.bigintIsInt64(b); - } - bool bigintIsUint64(const BigInt& b) override { - return plain_.bigintIsUint64(b); - } - uint64_t truncate(const BigInt& b) override { - return plain_.truncate(b); - } - String bigintToString(const BigInt& bigint, int radix) override { - return plain_.bigintToString(bigint, radix); - } - - String createStringFromAscii(const char* str, size_t length) override { - return plain_.createStringFromAscii(str, length); - }; - String createStringFromUtf8(const uint8_t* utf8, size_t length) override { - return plain_.createStringFromUtf8(utf8, length); - }; - std::string utf8(const String& s) override { - return plain_.utf8(s); - } - - std::u16string utf16(const String& str) override { - return plain_.utf16(str); - } - std::u16string utf16(const PropNameID& sym) override { - return plain_.utf16(sym); - } - - Object createObject() override { - return plain_.createObject(); - }; - - Object createObject(std::shared_ptr ho) override { - return plain_.createObject( - std::make_shared(*this, std::move(ho))); - }; - std::shared_ptr getHostObject(const jsi::Object& o) override { - std::shared_ptr dho = plain_.getHostObject(o); - return static_cast(*dho).plainHO_; - }; - -// HostFunctionType& getHostFunction(const jsi::Function& f) override { -// HostFunctionType& dhf = plain_.getHostFunction(f); -// // This will fail if a cpp file including this header is not compiled -// // with RTTI. -// return dhf.target()->plainHF_; -// }; - - bool hasNativeState(const Object& o) override { - return plain_.hasNativeState(o); - } - std::shared_ptr getNativeState(const Object& o) override { - return plain_.getNativeState(o); - } - void setNativeState(const Object& o, std::shared_ptr state) - override { - plain_.setNativeState(o, state); - } - - void setExternalMemoryPressure(const Object& obj, size_t amt) override { - plain_.setExternalMemoryPressure(obj, amt); - } - - Value getProperty(const Object& o, const PropNameID& name) override { - return plain_.getProperty(o, name); - }; - Value getProperty(const Object& o, const String& name) override { - return plain_.getProperty(o, name); - }; - bool hasProperty(const Object& o, const PropNameID& name) override { - return plain_.hasProperty(o, name); - }; - bool hasProperty(const Object& o, const String& name) override { - return plain_.hasProperty(o, name); - }; - void setPropertyValue( - const Object& o, - const PropNameID& name, - const Value& value) override { - plain_.setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, const Value& value) - override { - plain_.setPropertyValue(o, name, value); - }; - - bool isArray(const Object& o) const override { - return plain_.isArray(o); - }; - bool isArrayBuffer(const Object& o) const override { - return plain_.isArrayBuffer(o); - }; - bool isFunction(const Object& o) const override { - return plain_.isFunction(o); - }; - bool isHostObject(const jsi::Object& o) const override { - return plain_.isHostObject(o); - }; - bool isHostFunction(const jsi::Function& f) const override { - return plain_.isHostFunction(f); - }; - Array getPropertyNames(const Object& o) override { - return plain_.getPropertyNames(o); - }; - - WeakObject createWeakObject(const Object& o) override { - return plain_.createWeakObject(o); - }; - Value lockWeakObject(const WeakObject& wo) override { - return plain_.lockWeakObject(wo); - }; - - Array createArray(size_t length) override { - return plain_.createArray(length); - }; - ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) override { - return plain_.createArrayBuffer(std::move(buffer)); - }; - size_t size(const Array& a) override { - return plain_.size(a); - }; - size_t size(const ArrayBuffer& ab) override { - return plain_.size(ab); - }; - uint8_t* data(const ArrayBuffer& ab) override { - return plain_.data(ab); - }; - Value getValueAtIndex(const Array& a, size_t i) override { - return plain_.getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) - override { - plain_.setValueAtIndexImpl(a, i, value); - }; - - Function createFunctionFromHostFunction( - const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { - return plain_.createFunctionFromHostFunction( - name, paramCount, DecoratedHostFunction(*this, std::move(func))); - }; - Value call( - const Function& f, - const Value& jsThis, - const Value* args, - size_t count) override { - return plain_.call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, size_t count) - override { - return plain_.callAsConstructor(f, args, count); - }; - - // Private data for managing scopes. - Runtime::ScopeState* pushScope() override { - return plain_.pushScope(); - } - void popScope(Runtime::ScopeState* ss) override { - plain_.popScope(ss); - } - - bool strictEquals(const Symbol& a, const Symbol& b) const override { - return plain_.strictEquals(a, b); - }; - bool strictEquals(const BigInt& a, const BigInt& b) const override { - return plain_.strictEquals(a, b); - }; - bool strictEquals(const String& a, const String& b) const override { - return plain_.strictEquals(a, b); - }; - bool strictEquals(const Object& a, const Object& b) const override { - return plain_.strictEquals(a, b); - }; - - bool instanceOf(const Object& o, const Function& f) override { - return plain_.instanceOf(o, f); - }; - - // jsi::Instrumentation methods - - std::string getRecordedGCStats() override { - return plain().instrumentation().getRecordedGCStats(); - } - - std::unordered_map getHeapInfo( - bool includeExpensive) override { - return plain().instrumentation().getHeapInfo(includeExpensive); - } - - void collectGarbage(std::string cause) override { - plain().instrumentation().collectGarbage(std::move(cause)); - } - - void startTrackingHeapObjectStackTraces( - std::function)> callback) override { - plain().instrumentation().startTrackingHeapObjectStackTraces( - std::move(callback)); - } - - void stopTrackingHeapObjectStackTraces() override { - plain().instrumentation().stopTrackingHeapObjectStackTraces(); - } - - void startHeapSampling(size_t samplingInterval) override { - plain().instrumentation().startHeapSampling(samplingInterval); - } - - void stopHeapSampling(std::ostream& os) override { - plain().instrumentation().stopHeapSampling(os); - } - - void createSnapshotToFile( - const std::string& path, - const HeapSnapshotOptions& options) override { - plain().instrumentation().createSnapshotToFile(path, options); - } - - void createSnapshotToStream( - std::ostream& os, - const HeapSnapshotOptions& options) override { - plain().instrumentation().createSnapshotToStream(os, options); - } - - std::string flushAndDisableBridgeTrafficTrace() override { - return const_cast(plain()) - .instrumentation() - .flushAndDisableBridgeTrafficTrace(); - } - - void writeBasicBlockProfileTraceToFile( - const std::string& fileName) const override { - const_cast(plain()) - .instrumentation() - .writeBasicBlockProfileTraceToFile(fileName); - } - - /// Dump external profiler symbols to the given file name. - void dumpProfilerSymbolsToFile(const std::string& fileName) const override { - const_cast(plain()).instrumentation().dumpProfilerSymbolsToFile( - fileName); - } - - private: - Plain& plain_; -}; - -namespace detail { - -// This metaprogramming allows the With type's methods to be -// optional. - -template -struct BeforeCaller { - static void before(T&) {} -}; - -template -struct AfterCaller { - static void after(T&) {} -}; - -// decltype((void)&...) is either SFINAE, or void. -// So, if SFINAE does not happen for T, then this specialization exists -// for BeforeCaller, and always applies. If not, only the -// default above exists, and that is used instead. -template -struct BeforeCaller { - static void before(T& t) { - t.before(); - } -}; - -template -struct AfterCaller { - static void after(T& t) { - t.after(); - } -}; - -// It's possible to use multiple decorators by nesting -// WithRuntimeDecorator<...>, but this specialization allows use of -// std::tuple of decorator classes instead. See testlib.cpp for an -// example. -template -struct BeforeCaller> { - static void before(std::tuple& tuple) { - all_before<0, T...>(tuple); - } - - private: - template - static void all_before(std::tuple& tuple) { - detail::BeforeCaller::before(std::get(tuple)); - all_before(tuple); - } - - template - static void all_before(std::tuple&) {} -}; - -template -struct AfterCaller> { - static void after(std::tuple& tuple) { - all_after<0, T...>(tuple); - } - - private: - template - static void all_after(std::tuple& tuple) { - all_after(tuple); - detail::AfterCaller::after(std::get(tuple)); - } - - template - static void all_after(std::tuple&) {} -}; - -} // namespace detail - -// A decorator which implements an around idiom. A With instance is -// RAII constructed before each call to the undecorated class; the -// ctor is passed a single argument of type WithArg&. Plain and Base -// are used as in the base class. -template -class WithRuntimeDecorator : public RuntimeDecorator { - public: - using RD = RuntimeDecorator; - - // The reference arguments to the ctor are stored, but not used by - // the ctor, and there is no ctor, so they can be passed members of - // the derived class. - WithRuntimeDecorator(Plain& plain, With& with) : RD(plain), with_(with) {} - - Value evaluateJavaScript( - const std::shared_ptr& buffer, - const std::string& sourceURL) override { - Around around{with_}; - return RD::evaluateJavaScript(buffer, sourceURL); - } - std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, - std::string sourceURL) override { - Around around{with_}; - return RD::prepareJavaScript(buffer, std::move(sourceURL)); - } - Value evaluatePreparedJavaScript( - const std::shared_ptr& js) override { - Around around{with_}; - return RD::evaluatePreparedJavaScript(js); - } - void queueMicrotask(const Function& callback) override { - Around around{with_}; - RD::queueMicrotask(callback); - } - bool drainMicrotasks(int maxMicrotasksHint) override { - Around around{with_}; - return RD::drainMicrotasks(maxMicrotasksHint); - } - Object global() override { - Around around{with_}; - return RD::global(); - } - std::string description() override { - Around around{with_}; - return RD::description(); - }; - bool isInspectable() override { - Around around{with_}; - return RD::isInspectable(); - }; - - // The jsi:: prefix is necessary because MSVC compiler complains C2247: - // Instrumentation is not accessible because RuntimeDecorator uses private - // to inherit from Instrumentation. - // TODO(T40821815) Consider removing this workaround when updating MSVC - jsi::Instrumentation& instrumentation() override { - Around around{with_}; - return RD::instrumentation(); - } - - protected: - Runtime::PointerValue* cloneSymbol(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneSymbol(pv); - }; - Runtime::PointerValue* cloneBigInt(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneBigInt(pv); - }; - Runtime::PointerValue* cloneString(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneString(pv); - }; - Runtime::PointerValue* cloneObject(const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::cloneObject(pv); - }; - Runtime::PointerValue* clonePropNameID( - const Runtime::PointerValue* pv) override { - Around around{with_}; - return RD::clonePropNameID(pv); - }; - - PropNameID createPropNameIDFromAscii(const char* str, size_t length) - override { - Around around{with_}; - return RD::createPropNameIDFromAscii(str, length); - }; - PropNameID createPropNameIDFromUtf8(const uint8_t* utf8, size_t length) - override { - Around around{with_}; - return RD::createPropNameIDFromUtf8(utf8, length); - }; - PropNameID createPropNameIDFromString(const String& str) override { - Around around{with_}; - return RD::createPropNameIDFromString(str); - }; - PropNameID createPropNameIDFromSymbol(const Symbol& sym) override { - Around around{with_}; - return RD::createPropNameIDFromSymbol(sym); - }; - std::string utf8(const PropNameID& id) override { - Around around{with_}; - return RD::utf8(id); - }; - bool compare(const PropNameID& a, const PropNameID& b) override { - Around around{with_}; - return RD::compare(a, b); - }; - - std::string symbolToString(const Symbol& sym) override { - Around around{with_}; - return RD::symbolToString(sym); - }; - - BigInt createBigIntFromInt64(int64_t i) override { - Around around{with_}; - return RD::createBigIntFromInt64(i); - }; - BigInt createBigIntFromUint64(uint64_t i) override { - Around around{with_}; - return RD::createBigIntFromUint64(i); - }; - bool bigintIsInt64(const BigInt& bi) override { - Around around{with_}; - return RD::bigintIsInt64(bi); - }; - bool bigintIsUint64(const BigInt& bi) override { - Around around{with_}; - return RD::bigintIsUint64(bi); - }; - uint64_t truncate(const BigInt& bi) override { - Around around{with_}; - return RD::truncate(bi); - }; - String bigintToString(const BigInt& bi, int i) override { - Around around{with_}; - return RD::bigintToString(bi, i); - }; - - String createStringFromAscii(const char* str, size_t length) override { - Around around{with_}; - return RD::createStringFromAscii(str, length); - }; - String createStringFromUtf8(const uint8_t* utf8, size_t length) override { - Around around{with_}; - return RD::createStringFromUtf8(utf8, length); - }; - std::string utf8(const String& s) override { - Around around{with_}; - return RD::utf8(s); - } - - std::u16string utf16(const String& str) override { - Around around{with_}; - return RD::utf16(str); - } - std::u16string utf16(const PropNameID& sym) override { - Around around{with_}; - return RD::utf16(sym); - } - - Value createValueFromJsonUtf8(const uint8_t* json, size_t length) override { - Around around{with_}; - return RD::createValueFromJsonUtf8(json, length); - }; - - Object createObject() override { - Around around{with_}; - return RD::createObject(); - }; - Object createObject(std::shared_ptr ho) override { - Around around{with_}; - return RD::createObject(std::move(ho)); - }; - std::shared_ptr getHostObject(const jsi::Object& o) override { - Around around{with_}; - return RD::getHostObject(o); - }; - HostFunctionType& getHostFunction(const jsi::Function& f) override { - Around around{with_}; - return RD::getHostFunction(f); - }; - - bool hasNativeState(const Object& o) override { - Around around{with_}; - return RD::hasNativeState(o); - }; - std::shared_ptr getNativeState(const Object& o) override { - Around around{with_}; - return RD::getNativeState(o); - }; - void setNativeState(const Object& o, std::shared_ptr state) - override { - Around around{with_}; - RD::setNativeState(o, state); - }; - - Value getProperty(const Object& o, const PropNameID& name) override { - Around around{with_}; - return RD::getProperty(o, name); - }; - Value getProperty(const Object& o, const String& name) override { - Around around{with_}; - return RD::getProperty(o, name); - }; - bool hasProperty(const Object& o, const PropNameID& name) override { - Around around{with_}; - return RD::hasProperty(o, name); - }; - bool hasProperty(const Object& o, const String& name) override { - Around around{with_}; - return RD::hasProperty(o, name); - }; - void setPropertyValue( - const Object& o, - const PropNameID& name, - const Value& value) override { - Around around{with_}; - RD::setPropertyValue(o, name, value); - }; - void setPropertyValue(const Object& o, const String& name, const Value& value) - override { - Around around{with_}; - RD::setPropertyValue(o, name, value); - }; - - bool isArray(const Object& o) const override { - Around around{with_}; - return RD::isArray(o); - }; - bool isArrayBuffer(const Object& o) const override { - Around around{with_}; - return RD::isArrayBuffer(o); - }; - bool isFunction(const Object& o) const override { - Around around{with_}; - return RD::isFunction(o); - }; - bool isHostObject(const jsi::Object& o) const override { - Around around{with_}; - return RD::isHostObject(o); - }; - bool isHostFunction(const jsi::Function& f) const override { - Around around{with_}; - return RD::isHostFunction(f); - }; - Array getPropertyNames(const Object& o) override { - Around around{with_}; - return RD::getPropertyNames(o); - }; - - WeakObject createWeakObject(const Object& o) override { - Around around{with_}; - return RD::createWeakObject(o); - }; - Value lockWeakObject(const WeakObject& wo) override { - Around around{with_}; - return RD::lockWeakObject(wo); - }; - - Array createArray(size_t length) override { - Around around{with_}; - return RD::createArray(length); - }; - ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) override { - return RD::createArrayBuffer(std::move(buffer)); - }; - size_t size(const Array& a) override { - Around around{with_}; - return RD::size(a); - }; - size_t size(const ArrayBuffer& ab) override { - Around around{with_}; - return RD::size(ab); - }; - uint8_t* data(const ArrayBuffer& ab) override { - Around around{with_}; - return RD::data(ab); - }; - Value getValueAtIndex(const Array& a, size_t i) override { - Around around{with_}; - return RD::getValueAtIndex(a, i); - }; - void setValueAtIndexImpl(const Array& a, size_t i, const Value& value) - override { - Around around{with_}; - RD::setValueAtIndexImpl(a, i, value); - }; - - Function createFunctionFromHostFunction( - const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) override { - Around around{with_}; - return RD::createFunctionFromHostFunction( - name, paramCount, std::move(func)); - }; - Value call( - const Function& f, - const Value& jsThis, - const Value* args, - size_t count) override { - Around around{with_}; - return RD::call(f, jsThis, args, count); - }; - Value callAsConstructor(const Function& f, const Value* args, size_t count) - override { - Around around{with_}; - return RD::callAsConstructor(f, args, count); - }; - - // Private data for managing scopes. - Runtime::ScopeState* pushScope() override { - Around around{with_}; - return RD::pushScope(); - } - void popScope(Runtime::ScopeState* ss) override { - Around around{with_}; - RD::popScope(ss); - } - - bool strictEquals(const Symbol& a, const Symbol& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - bool strictEquals(const BigInt& a, const BigInt& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - - bool strictEquals(const String& a, const String& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - bool strictEquals(const Object& a, const Object& b) const override { - Around around{with_}; - return RD::strictEquals(a, b); - }; - - bool instanceOf(const Object& o, const Function& f) override { - Around around{with_}; - return RD::instanceOf(o, f); - }; - - void setExternalMemoryPressure(const jsi::Object& obj, size_t amount) - override { - Around around{with_}; - RD::setExternalMemoryPressure(obj, amount); - }; - - private: - // Wrap an RAII type around With& to guarantee after always happens. - struct Around { - Around(With& with) : with_(with) { - detail::BeforeCaller::before(with_); - } - ~Around() { - detail::AfterCaller::after(with_); - } - - With& with_; - }; - - With& with_; -}; - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/jsi/instrumentation.h b/NativeScript/napi/hermes/include_old/jsi/instrumentation.h deleted file mode 100644 index 726858ccd..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/instrumentation.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -namespace facebook { -namespace jsi { - -/// Methods for starting and collecting instrumentation, an \c Instrumentation -/// instance is associated with a particular \c Runtime instance, which it -/// controls the instrumentation of. -/// None of these functions should return newly created jsi values, nor should -/// it modify the values of any jsi values in the heap (although GCs are fine). -class JSI_EXPORT Instrumentation { - public: - /// Additional options controlling what to include when capturing a heap - /// snapshot. - struct HeapSnapshotOptions { - bool captureNumericValue{false}; - }; - - virtual ~Instrumentation() = default; - - /// Returns GC statistics as a JSON-encoded string, with an object containing - /// "type" and "version" fields outermost. "type" is a string, unique to a - /// particular implementation of \c jsi::Instrumentation, and "version" is a - /// number to indicate any revision to that implementation and its output - /// format. - /// - /// \pre This call can only be made on the instrumentation instance of a - /// runtime initialised to collect GC statistics. - /// - /// \post All cumulative measurements mentioned in the output are accumulated - /// across the entire lifetime of the Runtime. - /// - /// \return the GC statistics collected so far, as a JSON-encoded string. - virtual std::string getRecordedGCStats() = 0; - - /// Request statistics about the current state of the runtime's heap. This - /// function can be called at any time, and should produce information that is - /// correct at the instant it is called (i.e, not stale). - /// - /// \return a map from a string key to a number associated with that - /// statistic. - virtual std::unordered_map getHeapInfo( - bool includeExpensive) = 0; - - /// Perform a full garbage collection. - /// \param cause The cause of this collection, as it should be reported in - /// logs. - virtual void collectGarbage(std::string cause) = 0; - - /// A HeapStatsUpdate is a tuple of the fragment index, the number of objects - /// in that fragment, and the number of bytes used by those objects. - /// A "fragment" is a view of all objects allocated within a time slice. - using HeapStatsUpdate = std::tuple; - - /// Start capturing JS stack-traces for all JS heap allocated objects. These - /// can be accessed via \c ::createSnapshotToFile(). - /// \param fragmentCallback If present, invoke this callback every so often - /// with the most recently seen object ID, and a list of fragments that have - /// been updated. This callback will be invoked on the same thread that the - /// runtime is using. - virtual void startTrackingHeapObjectStackTraces( - std::function stats)> fragmentCallback) = 0; - - /// Stop capture JS stack-traces for JS heap allocated objects. - virtual void stopTrackingHeapObjectStackTraces() = 0; - - /// Start a heap sampling profiler that will sample heap allocations, and the - /// stack trace they were allocated at. Reports a summary of which functions - /// allocated the most. - /// \param samplingInterval The number of bytes allocated to wait between - /// samples. This will be used as the expected value of a poisson - /// distribution. - virtual void startHeapSampling(size_t samplingInterval) = 0; - - /// Turns off the heap sampling profiler previously enabled via - /// \c startHeapSampling. Writes the output of the sampling heap profiler to - /// \p os. The output is a JSON formatted string. - virtual void stopHeapSampling(std::ostream& os) = 0; - - /// Captures the heap to a file - /// - /// \param path to save the heap capture. - /// \param options additional options for what to capture. - virtual void createSnapshotToFile( - const std::string& path, - const HeapSnapshotOptions& options = {false}) = 0; - - /// Captures the heap to an output stream - /// - /// \param os output stream to write to. - /// \param options additional options for what to capture. - virtual void createSnapshotToStream( - std::ostream& os, - const HeapSnapshotOptions& options = {false}) = 0; - - /// If the runtime has been created to trace to a temp file, flush - /// any unwritten parts of the trace of bridge traffic to the file, - /// and return the name of the file. Otherwise, return the empty string. - /// Tracing is disabled after this call. - virtual std::string flushAndDisableBridgeTrafficTrace() = 0; - - /// Write basic block profile trace to the given file name. - virtual void writeBasicBlockProfileTraceToFile( - const std::string& fileName) const = 0; - - /// Dump external profiler symbols to the given file name. - virtual void dumpProfilerSymbolsToFile(const std::string& fileName) const = 0; -}; - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/jsi/jsi-inl.h b/NativeScript/napi/hermes/include_old/jsi/jsi-inl.h deleted file mode 100644 index 111a47028..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/jsi-inl.h +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -namespace facebook { -namespace jsi { -namespace detail { - -inline Value toValue(Runtime&, std::nullptr_t) { - return Value::null(); -} -inline Value toValue(Runtime&, bool b) { - return Value(b); -} -inline Value toValue(Runtime&, double d) { - return Value(d); -} -inline Value toValue(Runtime&, float f) { - return Value(static_cast(f)); -} -inline Value toValue(Runtime&, int i) { - return Value(i); -} -inline Value toValue(Runtime& runtime, const char* str) { - return String::createFromAscii(runtime, str); -} -inline Value toValue(Runtime& runtime, const std::string& str) { - return String::createFromUtf8(runtime, str); -} -template -inline Value toValue(Runtime& runtime, const T& other) { - static_assert( - std::is_base_of::value, - "This type cannot be converted to Value"); - return Value(runtime, other); -} -inline Value toValue(Runtime& runtime, const Value& value) { - return Value(runtime, value); -} -inline Value&& toValue(Runtime&, Value&& value) { - return std::move(value); -} - -inline PropNameID toPropNameID(Runtime& runtime, const char* name) { - return PropNameID::forAscii(runtime, name); -} -inline PropNameID toPropNameID(Runtime& runtime, const std::string& name) { - return PropNameID::forUtf8(runtime, name); -} -inline PropNameID&& toPropNameID(Runtime&, PropNameID&& name) { - return std::move(name); -} - -/// Helper to throw while still compiling with exceptions turned off. -template -[[noreturn]] inline void throwOrDie(Args&&... args) { - std::rethrow_exception( - std::make_exception_ptr(E{std::forward(args)...})); -} - -} // namespace detail - -template -inline T Runtime::make(Runtime::PointerValue* pv) { - return T(pv); -} - -inline Runtime::PointerValue* Runtime::getPointerValue(jsi::Pointer& pointer) { - return pointer.ptr_; -} - -inline const Runtime::PointerValue* Runtime::getPointerValue( - const jsi::Pointer& pointer) { - return pointer.ptr_; -} - -inline const Runtime::PointerValue* Runtime::getPointerValue( - const jsi::Value& value) { - return value.data_.pointer.ptr_; -} - -inline Value Object::getProperty(Runtime& runtime, const char* name) const { - return getProperty(runtime, String::createFromAscii(runtime, name)); -} - -inline Value Object::getProperty(Runtime& runtime, const String& name) const { - return runtime.getProperty(*this, name); -} - -inline Value Object::getProperty(Runtime& runtime, const PropNameID& name) - const { - return runtime.getProperty(*this, name); -} - -inline bool Object::hasProperty(Runtime& runtime, const char* name) const { - return hasProperty(runtime, String::createFromAscii(runtime, name)); -} - -inline bool Object::hasProperty(Runtime& runtime, const String& name) const { - return runtime.hasProperty(*this, name); -} - -inline bool Object::hasProperty(Runtime& runtime, const PropNameID& name) - const { - return runtime.hasProperty(*this, name); -} - -template -void Object::setProperty(Runtime& runtime, const char* name, T&& value) const { - setProperty( - runtime, String::createFromAscii(runtime, name), std::forward(value)); -} - -template -void Object::setProperty(Runtime& runtime, const String& name, T&& value) - const { - setPropertyValue( - runtime, name, detail::toValue(runtime, std::forward(value))); -} - -template -void Object::setProperty(Runtime& runtime, const PropNameID& name, T&& value) - const { - setPropertyValue( - runtime, name, detail::toValue(runtime, std::forward(value))); -} - -inline Array Object::getArray(Runtime& runtime) const& { - assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - return Array(runtime.cloneObject(ptr_)); -} - -inline Array Object::getArray(Runtime& runtime) && { - assert(runtime.isArray(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - Runtime::PointerValue* value = ptr_; - ptr_ = nullptr; - return Array(value); -} - -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) const& { - assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - return ArrayBuffer(runtime.cloneObject(ptr_)); -} - -inline ArrayBuffer Object::getArrayBuffer(Runtime& runtime) && { - assert(runtime.isArrayBuffer(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - Runtime::PointerValue* value = ptr_; - ptr_ = nullptr; - return ArrayBuffer(value); -} - -inline Function Object::getFunction(Runtime& runtime) const& { - assert(runtime.isFunction(*this)); - return Function(runtime.cloneObject(ptr_)); -} - -inline Function Object::getFunction(Runtime& runtime) && { - assert(runtime.isFunction(*this)); - (void)runtime; // when assert is disabled we need to mark this as used - Runtime::PointerValue* value = ptr_; - ptr_ = nullptr; - return Function(value); -} - -template -inline bool Object::isHostObject(Runtime& runtime) const { - return runtime.isHostObject(*this) && - std::dynamic_pointer_cast(runtime.getHostObject(*this)); -} - -template <> -inline bool Object::isHostObject(Runtime& runtime) const { - return runtime.isHostObject(*this); -} - -template -inline std::shared_ptr Object::getHostObject(Runtime& runtime) const { - assert(isHostObject(runtime)); - return std::static_pointer_cast(runtime.getHostObject(*this)); -} - -template -inline std::shared_ptr Object::asHostObject(Runtime& runtime) const { - if (!isHostObject(runtime)) { - detail::throwOrDie( - "Object is not a HostObject of desired type"); - } - return std::static_pointer_cast(runtime.getHostObject(*this)); -} - -template <> -inline std::shared_ptr Object::getHostObject( - Runtime& runtime) const { - assert(runtime.isHostObject(*this)); - return runtime.getHostObject(*this); -} - -template -inline bool Object::hasNativeState(Runtime& runtime) const { - return runtime.hasNativeState(*this) && - std::dynamic_pointer_cast(runtime.getNativeState(*this)); -} - -template <> -inline bool Object::hasNativeState(Runtime& runtime) const { - return runtime.hasNativeState(*this); -} - -template -inline std::shared_ptr Object::getNativeState(Runtime& runtime) const { - assert(hasNativeState(runtime)); - return std::static_pointer_cast(runtime.getNativeState(*this)); -} - -inline void Object::setNativeState( - Runtime& runtime, - std::shared_ptr state) const { - runtime.setNativeState(*this, state); -} - -inline void Object::setExternalMemoryPressure(Runtime& runtime, size_t amt) - const { - runtime.setExternalMemoryPressure(*this, amt); -} - -inline Array Object::getPropertyNames(Runtime& runtime) const { - return runtime.getPropertyNames(*this); -} - -inline Value WeakObject::lock(Runtime& runtime) const { - return runtime.lockWeakObject(*this); -} - -template -void Array::setValueAtIndex(Runtime& runtime, size_t i, T&& value) const { - setValueAtIndexImpl( - runtime, i, detail::toValue(runtime, std::forward(value))); -} - -inline Value Array::getValueAtIndex(Runtime& runtime, size_t i) const { - return runtime.getValueAtIndex(*this, i); -} - -inline Function Function::createFromHostFunction( - Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func) { - return runtime.createFunctionFromHostFunction( - name, paramCount, std::move(func)); -} - -inline Value Function::call(Runtime& runtime, const Value* args, size_t count) - const { - return runtime.call(*this, Value::undefined(), args, count); -} - -inline Value Function::call(Runtime& runtime, std::initializer_list args) - const { - return call(runtime, args.begin(), args.size()); -} - -template -inline Value Function::call(Runtime& runtime, Args&&... args) const { - // A more awesome version of this would be able to create raw values - // which can be used directly without wrapping and unwrapping, but - // this will do for now. - return call(runtime, {detail::toValue(runtime, std::forward(args))...}); -} - -inline Value Function::callWithThis( - Runtime& runtime, - const Object& jsThis, - const Value* args, - size_t count) const { - return runtime.call(*this, Value(runtime, jsThis), args, count); -} - -inline Value Function::callWithThis( - Runtime& runtime, - const Object& jsThis, - std::initializer_list args) const { - return callWithThis(runtime, jsThis, args.begin(), args.size()); -} - -template -inline Value Function::callWithThis( - Runtime& runtime, - const Object& jsThis, - Args&&... args) const { - // A more awesome version of this would be able to create raw values - // which can be used directly without wrapping and unwrapping, but - // this will do for now. - return callWithThis( - runtime, jsThis, {detail::toValue(runtime, std::forward(args))...}); -} - -template -inline Array Array::createWithElements(Runtime& runtime, Args&&... args) { - return createWithElements( - runtime, {detail::toValue(runtime, std::forward(args))...}); -} - -template -inline std::vector PropNameID::names( - Runtime& runtime, - Args&&... args) { - return names({detail::toPropNameID(runtime, std::forward(args))...}); -} - -template -inline std::vector PropNameID::names( - PropNameID (&&propertyNames)[N]) { - std::vector result; - result.reserve(N); - for (auto& name : propertyNames) { - result.push_back(std::move(name)); - } - return result; -} - -inline Value Function::callAsConstructor( - Runtime& runtime, - const Value* args, - size_t count) const { - return runtime.callAsConstructor(*this, args, count); -} - -inline Value Function::callAsConstructor( - Runtime& runtime, - std::initializer_list args) const { - return callAsConstructor(runtime, args.begin(), args.size()); -} - -template -inline Value Function::callAsConstructor(Runtime& runtime, Args&&... args) - const { - return callAsConstructor( - runtime, {detail::toValue(runtime, std::forward(args))...}); -} - -String BigInt::toString(Runtime& runtime, int radix) const { - return runtime.bigintToString(*this, radix); -} - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/jsi/jsi.h b/NativeScript/napi/hermes/include_old/jsi/jsi.h deleted file mode 100644 index be48bb824..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/jsi.h +++ /dev/null @@ -1,1549 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#ifndef JSI_EXPORT -#ifdef _MSC_VER -#ifdef CREATE_SHARED_LIBRARY -#define JSI_EXPORT __declspec(dllexport) -#else -#define JSI_EXPORT -#endif // CREATE_SHARED_LIBRARY -#else // _MSC_VER -#define JSI_EXPORT __attribute__((visibility("default"))) -#endif // _MSC_VER -#endif // !defined(JSI_EXPORT) - -class FBJSRuntime; -namespace facebook { -namespace jsi { - -/// Base class for buffers of data or bytecode that need to be passed to the -/// runtime. The buffer is expected to be fully immutable, so the result of -/// size(), data(), and the contents of the pointer returned by data() must not -/// change after construction. -class JSI_EXPORT Buffer { - public: - virtual ~Buffer(); - virtual size_t size() const = 0; - virtual const uint8_t* data() const = 0; -}; - -class JSI_EXPORT StringBuffer : public Buffer { - public: - StringBuffer(std::string s) : s_(std::move(s)) {} - size_t size() const override { - return s_.size(); - } - const uint8_t* data() const override { - return reinterpret_cast(s_.data()); - } - - private: - std::string s_; -}; - -/// Base class for buffers of data that need to be passed to the runtime. The -/// result of size() and data() must not change after construction. However, the -/// region pointed to by data() may be modified by the user or the runtime. The -/// user must ensure that access to the contents of the buffer is properly -/// synchronised. -class JSI_EXPORT MutableBuffer { - public: - virtual ~MutableBuffer(); - virtual size_t size() const = 0; - virtual uint8_t* data() = 0; -}; - -/// PreparedJavaScript is a base class representing JavaScript which is in a -/// form optimized for execution, in a runtime-specific way. Construct one via -/// jsi::Runtime::prepareJavaScript(). -/// ** This is an experimental API that is subject to change. ** -class JSI_EXPORT PreparedJavaScript { - protected: - PreparedJavaScript() = default; - - public: - virtual ~PreparedJavaScript() = 0; -}; - -class Runtime; -class Pointer; -class PropNameID; -class Symbol; -class BigInt; -class String; -class Object; -class WeakObject; -class Array; -class ArrayBuffer; -class Function; -class Value; -class Instrumentation; -class Scope; -class JSIException; -class JSError; - -/// A function which has this type can be registered as a function -/// callable from JavaScript using Function::createFromHostFunction(). -/// When the function is called, args will point to the arguments, and -/// count will indicate how many arguments are passed. The function -/// can return a Value to the caller, or throw an exception. If a C++ -/// exception is thrown, a JS Error will be created and thrown into -/// JS; if the C++ exception extends std::exception, the Error's -/// message will be whatever what() returns. Note that it is undefined whether -/// HostFunctions may or may not be called in strict mode; that is `thisVal` -/// can be any value - it will not necessarily be coerced to an object or -/// or set to the global object. -using HostFunctionType = std::function< - Value(Runtime& rt, const Value& thisVal, const Value* args, size_t count)>; - -/// An object which implements this interface can be registered as an -/// Object with the JS runtime. -class JSI_EXPORT HostObject { - public: - // The C++ object's dtor will be called when the GC finalizes this - // object. (This may be as late as when the Runtime is shut down.) - // You have no control over which thread it is called on. This will - // be called from inside the GC, so it is unsafe to do any VM - // operations which require a Runtime&. Derived classes' dtors - // should also avoid doing anything expensive. Calling the dtor on - // a jsi object is explicitly ok. If you want to do JS operations, - // or any nontrivial work, you should add it to a work queue, and - // manage it externally. - virtual ~HostObject(); - - // When JS wants a property with a given name from the HostObject, - // it will call this method. If it throws an exception, the call - // will throw a JS \c Error object. By default this returns undefined. - // \return the value for the property. - virtual Value get(Runtime&, const PropNameID& name); - - // When JS wants to set a property with a given name on the HostObject, - // it will call this method. If it throws an exception, the call will - // throw a JS \c Error object. By default this throws a type error exception - // mimicking the behavior of a frozen object in strict mode. - virtual void set(Runtime&, const PropNameID& name, const Value& value); - - // When JS wants a list of property names for the HostObject, it will - // call this method. If it throws an exception, the call will throw a - // JS \c Error object. The default implementation returns empty vector. - virtual std::vector getPropertyNames(Runtime& rt); -}; - -/// Native state (and destructor) that can be attached to any JS object -/// using setNativeState. -class JSI_EXPORT NativeState { - public: - virtual ~NativeState(); -}; - -/// Represents a JS runtime. Movable, but not copyable. Note that -/// this object may not be thread-aware, but cannot be used safely from -/// multiple threads at once. The application is responsible for -/// ensuring that it is used safely. This could mean using the -/// Runtime from a single thread, using a mutex, doing all work on a -/// serial queue, etc. This restriction applies to the methods of -/// this class, and any method in the API which take a Runtime& as an -/// argument. Destructors (all but ~Scope), operators, or other methods -/// which do not take Runtime& as an argument are safe to call from any -/// thread, but it is still forbidden to make write operations on a single -/// instance of any class from more than one thread. In addition, to -/// make shutdown safe, destruction of objects associated with the Runtime -/// must be destroyed before the Runtime is destroyed, or from the -/// destructor of a managed HostObject or HostFunction. Informally, this -/// means that the main source of unsafe behavior is to hold a jsi object -/// in a non-Runtime-managed object, and not clean it up before the Runtime -/// is shut down. If your lifecycle is such that avoiding this is hard, -/// you will probably need to do use your own locks. -class JSI_EXPORT Runtime { - public: - virtual ~Runtime(); - - /// Evaluates the given JavaScript \c buffer. \c sourceURL is used - /// to annotate the stack trace if there is an exception. The - /// contents may be utf8-encoded JS source code, or binary bytecode - /// whose format is specific to the implementation. If the input - /// format is unknown, or evaluation causes an error, a JSIException - /// will be thrown. - /// Note this function should ONLY be used when there isn't another means - /// through the JSI API. For example, it will be much slower to use this to - /// call a global function than using the JSI APIs to read the function - /// property from the global object and then calling it explicitly. - virtual Value evaluateJavaScript( - const std::shared_ptr& buffer, - const std::string& sourceURL) = 0; - - /// Prepares to evaluate the given JavaScript \c buffer by processing it into - /// a form optimized for execution. This may include pre-parsing, compiling, - /// etc. If the input is invalid (for example, cannot be parsed), a - /// JSIException will be thrown. The resulting object is tied to the - /// particular concrete type of Runtime from which it was created. It may be - /// used (via evaluatePreparedJavaScript) in any Runtime of the same concrete - /// type. - /// The PreparedJavaScript object may be passed to multiple VM instances, so - /// they can all share and benefit from the prepared script. - /// As with evaluateJavaScript(), using JavaScript code should be avoided - /// when the JSI API is sufficient. - virtual std::shared_ptr prepareJavaScript( - const std::shared_ptr& buffer, - std::string sourceURL) = 0; - - /// Evaluates a PreparedJavaScript. If evaluation causes an error, a - /// JSIException will be thrown. - /// As with evaluateJavaScript(), using JavaScript code should be avoided - /// when the JSI API is sufficient. - virtual Value evaluatePreparedJavaScript( - const std::shared_ptr& js) = 0; - - /// Queues a microtask in the JavaScript VM internal Microtask (a.k.a. Job in - /// ECMA262) queue, to be executed when the host drains microtasks in - /// its event loop implementation. - /// - /// \param callback a function to be executed as a microtask. - virtual void queueMicrotask(const jsi::Function& callback) = 0; - - /// Drain the JavaScript VM internal Microtask (a.k.a. Job in ECMA262) queue. - /// - /// \param maxMicrotasksHint a hint to tell an implementation that it should - /// make a best effort not execute more than the given number. It's default - /// to -1 for infinity (unbounded execution). - /// \return true if the queue is drained or false if there is more work to do. - /// - /// When there were exceptions thrown from the execution of microtasks, - /// implementations shall discard the exceptional jobs. An implementation may - /// \throw a \c JSError object to signal the hosts to handle. In that case, an - /// implementation may or may not suspend the draining. - /// - /// Hosts may call this function again to resume the draining if it was - /// suspended due to either exceptions or the \p maxMicrotasksHint bound. - /// E.g. a host may repetitively invoke this function until the queue is - /// drained to implement the "microtask checkpoint" defined in WHATWG HTML - /// event loop: https://html.spec.whatwg.org/C#perform-a-microtask-checkpoint. - /// - /// Note that error propagation is only a concern if a host needs to implement - /// `queueMicrotask`, a recent API that allows enqueueing arbitrary functions - /// (hence may throw) as microtasks. Exceptions from ECMA-262 Promise Jobs are - /// handled internally to VMs and are never propagated to hosts. - /// - /// This API offers some queue management to hosts at its best effort due to - /// different behaviors and limitations imposed by different VMs and APIs. By - /// the time this is written, An implementation may swallow exceptions (JSC), - /// may not pause (V8), and may not support bounded executions. - virtual bool drainMicrotasks(int maxMicrotasksHint = -1) = 0; - - /// \return the global object - virtual Object global() = 0; - - /// \return a short printable description of the instance. It should - /// at least include some human-readable indication of the runtime - /// implementation. This should only be used by logging, debugging, - /// and other developer-facing callers. - virtual std::string description() = 0; - - /// \return whether or not the underlying runtime supports debugging via the - /// Chrome remote debugging protocol. - /// - /// NOTE: the API for determining whether a runtime is debuggable and - /// registering a runtime with the debugger is still in flux, so please don't - /// use this API unless you know what you're doing. - virtual bool isInspectable() = 0; - - /// \return an interface to extract metrics from this \c Runtime. The default - /// implementation of this function returns an \c Instrumentation instance - /// which returns no metrics. - virtual Instrumentation& instrumentation(); - - protected: - friend class Pointer; - friend class PropNameID; - friend class Symbol; - friend class BigInt; - friend class String; - friend class Object; - friend class WeakObject; - friend class Array; - friend class ArrayBuffer; - friend class Function; - friend class Value; - friend class Scope; - friend class JSError; - - // Potential optimization: avoid the cloneFoo() virtual dispatch, - // and instead just fix the number of fields, and copy them, since - // in practice they are trivially copyable. Sufficient use of - // rvalue arguments/methods would also reduce the number of clones. - - struct PointerValue { - virtual void invalidate() noexcept = 0; - - protected: - virtual ~PointerValue() = default; - }; - - virtual PointerValue* cloneSymbol(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneBigInt(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneString(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* cloneObject(const Runtime::PointerValue* pv) = 0; - virtual PointerValue* clonePropNameID(const Runtime::PointerValue* pv) = 0; - - virtual PropNameID createPropNameIDFromAscii( - const char* str, - size_t length) = 0; - virtual PropNameID createPropNameIDFromUtf8( - const uint8_t* utf8, - size_t length) = 0; - virtual PropNameID createPropNameIDFromString(const String& str) = 0; - virtual PropNameID createPropNameIDFromSymbol(const Symbol& sym) = 0; - virtual std::string utf8(const PropNameID&) = 0; - virtual bool compare(const PropNameID&, const PropNameID&) = 0; - - virtual std::string symbolToString(const Symbol&) = 0; - - virtual BigInt createBigIntFromInt64(int64_t) = 0; - virtual BigInt createBigIntFromUint64(uint64_t) = 0; - virtual bool bigintIsInt64(const BigInt&) = 0; - virtual bool bigintIsUint64(const BigInt&) = 0; - virtual uint64_t truncate(const BigInt&) = 0; - virtual String bigintToString(const BigInt&, int) = 0; - - virtual String createStringFromAscii(const char* str, size_t length) = 0; - virtual String createStringFromUtf8(const uint8_t* utf8, size_t length) = 0; - virtual std::string utf8(const String&) = 0; - - // \return a \c Value created from a utf8-encoded JSON string. The default - // implementation creates a \c String and invokes JSON.parse. - virtual Value createValueFromJsonUtf8(const uint8_t* json, size_t length); - - virtual Object createObject() = 0; - virtual Object createObject(std::shared_ptr ho) = 0; - virtual std::shared_ptr getHostObject(const jsi::Object&) = 0; - virtual HostFunctionType& getHostFunction(const jsi::Function&) = 0; - - virtual bool hasNativeState(const jsi::Object&) = 0; - virtual std::shared_ptr getNativeState(const jsi::Object&) = 0; - virtual void setNativeState( - const jsi::Object&, - std::shared_ptr state) = 0; - - virtual Value getProperty(const Object&, const PropNameID& name) = 0; - virtual Value getProperty(const Object&, const String& name) = 0; - virtual bool hasProperty(const Object&, const PropNameID& name) = 0; - virtual bool hasProperty(const Object&, const String& name) = 0; - virtual void setPropertyValue( - const Object&, - const PropNameID& name, - const Value& value) = 0; - virtual void - setPropertyValue(const Object&, const String& name, const Value& value) = 0; - - virtual bool isArray(const Object&) const = 0; - virtual bool isArrayBuffer(const Object&) const = 0; - virtual bool isFunction(const Object&) const = 0; - virtual bool isHostObject(const jsi::Object&) const = 0; - virtual bool isHostFunction(const jsi::Function&) const = 0; - virtual Array getPropertyNames(const Object&) = 0; - - virtual WeakObject createWeakObject(const Object&) = 0; - virtual Value lockWeakObject(const WeakObject&) = 0; - - virtual Array createArray(size_t length) = 0; - virtual ArrayBuffer createArrayBuffer( - std::shared_ptr buffer) = 0; - virtual size_t size(const Array&) = 0; - virtual size_t size(const ArrayBuffer&) = 0; - virtual uint8_t* data(const ArrayBuffer&) = 0; - virtual Value getValueAtIndex(const Array&, size_t i) = 0; - virtual void - setValueAtIndexImpl(const Array&, size_t i, const Value& value) = 0; - - virtual Function createFunctionFromHostFunction( - const PropNameID& name, - unsigned int paramCount, - HostFunctionType func) = 0; - virtual Value call( - const Function&, - const Value& jsThis, - const Value* args, - size_t count) = 0; - virtual Value - callAsConstructor(const Function&, const Value* args, size_t count) = 0; - - // Private data for managing scopes. - struct ScopeState; - virtual ScopeState* pushScope(); - virtual void popScope(ScopeState*); - - virtual bool strictEquals(const Symbol& a, const Symbol& b) const = 0; - virtual bool strictEquals(const BigInt& a, const BigInt& b) const = 0; - virtual bool strictEquals(const String& a, const String& b) const = 0; - virtual bool strictEquals(const Object& a, const Object& b) const = 0; - - virtual bool instanceOf(const Object& o, const Function& f) = 0; - - /// See Object::setExternalMemoryPressure. - virtual void setExternalMemoryPressure( - const jsi::Object& obj, - size_t amount) = 0; - - virtual std::u16string utf16(const String& str); - virtual std::u16string utf16(const PropNameID& sym); - - // These exist so derived classes can access the private parts of - // Value, Symbol, String, and Object, which are all friends of Runtime. - template - static T make(PointerValue* pv); - static PointerValue* getPointerValue(Pointer& pointer); - static const PointerValue* getPointerValue(const Pointer& pointer); - static const PointerValue* getPointerValue(const Value& value); - - friend class ::FBJSRuntime; - template - friend class RuntimeDecorator; -}; - -// Base class for pointer-storing types. -class JSI_EXPORT Pointer { - protected: - explicit Pointer(Pointer&& other) noexcept : ptr_(other.ptr_) { - other.ptr_ = nullptr; - } - - ~Pointer() { - if (ptr_) { - ptr_->invalidate(); - } - } - - Pointer& operator=(Pointer&& other) noexcept; - - friend class Runtime; - friend class Value; - - explicit Pointer(Runtime::PointerValue* ptr) : ptr_(ptr) {} - - typename Runtime::PointerValue* ptr_; -}; - -/// Represents something that can be a JS property key. Movable, not copyable. -class JSI_EXPORT PropNameID : public Pointer { - public: - using Pointer::Pointer; - - PropNameID(Runtime& runtime, const PropNameID& other) - : Pointer(runtime.clonePropNameID(other.ptr_)) {} - - PropNameID(PropNameID&& other) = default; - PropNameID& operator=(PropNameID&& other) = default; - - /// Create a JS property name id from ascii values. The data is - /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str, size_t length) { - return runtime.createPropNameIDFromAscii(str, length); - } - - /// Create a property name id from a nul-terminated C ascii name. The data is - /// copied. - static PropNameID forAscii(Runtime& runtime, const char* str) { - return forAscii(runtime, str, strlen(str)); - } - - /// Create a PropNameID from a C++ string. The string is copied. - static PropNameID forAscii(Runtime& runtime, const std::string& str) { - return forAscii(runtime, str.c_str(), str.size()); - } - - /// Create a PropNameID from utf8 values. The data is copied. - /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID - forUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) { - return runtime.createPropNameIDFromUtf8(utf8, length); - } - - /// Create a PropNameID from utf8-encoded octets stored in a - /// std::string. The string data is transformed and copied. - /// Results are undefined if \p utf8 contains invalid code points. - static PropNameID forUtf8(Runtime& runtime, const std::string& utf8) { - return runtime.createPropNameIDFromUtf8( - reinterpret_cast(utf8.data()), utf8.size()); - } - - /// Create a PropNameID from a JS string. - static PropNameID forString(Runtime& runtime, const jsi::String& str) { - return runtime.createPropNameIDFromString(str); - } - - /// Create a PropNameID from a JS symbol. - static PropNameID forSymbol(Runtime& runtime, const jsi::Symbol& sym) { - return runtime.createPropNameIDFromSymbol(sym); - } - - // Creates a vector of PropNameIDs constructed from given arguments. - template - static std::vector names(Runtime& runtime, Args&&... args); - - // Creates a vector of given PropNameIDs. - template - static std::vector names(PropNameID (&&propertyNames)[N]); - - /// Copies the data in a PropNameID as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { - return runtime.utf8(*this); - } - - /// Copies the data in a PropNameID as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { - return runtime.utf16(*this); - } - - static bool compare( - Runtime& runtime, - const jsi::PropNameID& a, - const jsi::PropNameID& b) { - return runtime.compare(a, b); - } - - friend class Runtime; - friend class Value; -}; - -/// Represents a JS Symbol (es6). Movable, not copyable. -/// TODO T40778724: this is a limited implementation sufficient for -/// the debugger not to crash when a Symbol is a property in an Object -/// or element in an array. Complete support for creating will come -/// later. -class JSI_EXPORT Symbol : public Pointer { - public: - using Pointer::Pointer; - - Symbol(Symbol&& other) = default; - Symbol& operator=(Symbol&& other) = default; - - /// \return whether a and b refer to the same symbol. - static bool strictEquals(Runtime& runtime, const Symbol& a, const Symbol& b) { - return runtime.strictEquals(a, b); - } - - /// Converts a Symbol into a C++ string as JS .toString would. The output - /// will look like \c Symbol(description) . - std::string toString(Runtime& runtime) const { - return runtime.symbolToString(*this); - } - - friend class Runtime; - friend class Value; -}; - -/// Represents a JS BigInt. Movable, not copyable. -class JSI_EXPORT BigInt : public Pointer { - public: - using Pointer::Pointer; - - BigInt(BigInt&& other) = default; - BigInt& operator=(BigInt&& other) = default; - - /// Create a BigInt representing the signed 64-bit \p value. - static BigInt fromInt64(Runtime& runtime, int64_t value) { - return runtime.createBigIntFromInt64(value); - } - - /// Create a BigInt representing the unsigned 64-bit \p value. - static BigInt fromUint64(Runtime& runtime, uint64_t value) { - return runtime.createBigIntFromUint64(value); - } - - /// \return whether a === b. - static bool strictEquals(Runtime& runtime, const BigInt& a, const BigInt& b) { - return runtime.strictEquals(a, b); - } - - /// \returns This bigint truncated to a signed 64-bit integer. - int64_t getInt64(Runtime& runtime) const { - return runtime.truncate(*this); - } - - /// \returns Whether this bigint can be losslessly converted to int64_t. - bool isInt64(Runtime& runtime) const { - return runtime.bigintIsInt64(*this); - } - - /// \returns This bigint truncated to a signed 64-bit integer. Throws a - /// JSIException if the truncation is lossy. - int64_t asInt64(Runtime& runtime) const; - - /// \returns This bigint truncated to an unsigned 64-bit integer. - uint64_t getUint64(Runtime& runtime) const { - return runtime.truncate(*this); - } - - /// \returns Whether this bigint can be losslessly converted to uint64_t. - bool isUint64(Runtime& runtime) const { - return runtime.bigintIsUint64(*this); - } - - /// \returns This bigint truncated to an unsigned 64-bit integer. Throws a - /// JSIException if the truncation is lossy. - uint64_t asUint64(Runtime& runtime) const; - - /// \returns this BigInt converted to a String in base \p radix. Throws a - /// JSIException if radix is not in the [2, 36] range. - inline String toString(Runtime& runtime, int radix = 10) const; - - friend class Runtime; - friend class Value; -}; - -/// Represents a JS String. Movable, not copyable. -class JSI_EXPORT String : public Pointer { - public: - using Pointer::Pointer; - - String(String&& other) = default; - String& operator=(String&& other) = default; - - /// Create a JS string from ascii values. The string data is - /// copied. - static String - createFromAscii(Runtime& runtime, const char* str, size_t length) { - return runtime.createStringFromAscii(str, length); - } - - /// Create a JS string from a nul-terminated C ascii string. The - /// string data is copied. - static String createFromAscii(Runtime& runtime, const char* str) { - return createFromAscii(runtime, str, strlen(str)); - } - - /// Create a JS string from a C++ string. The string data is - /// copied. - static String createFromAscii(Runtime& runtime, const std::string& str) { - return createFromAscii(runtime, str.c_str(), str.size()); - } - - /// Create a JS string from utf8-encoded octets. The string data is - /// transformed and copied. Results are undefined if \p utf8 contains invalid - /// code points. - static String - createFromUtf8(Runtime& runtime, const uint8_t* utf8, size_t length) { - return runtime.createStringFromUtf8(utf8, length); - } - - /// Create a JS string from utf8-encoded octets stored in a - /// std::string. The string data is transformed and copied. Results are - /// undefined if \p utf8 contains invalid code points. - static String createFromUtf8(Runtime& runtime, const std::string& utf8) { - return runtime.createStringFromUtf8( - reinterpret_cast(utf8.data()), utf8.length()); - } - - /// \return whether a and b contain the same characters. - static bool strictEquals(Runtime& runtime, const String& a, const String& b) { - return runtime.strictEquals(a, b); - } - - /// Copies the data in a JS string as utf8 into a C++ string. - std::string utf8(Runtime& runtime) const { - return runtime.utf8(*this); - } - - /// Copies the data in a JS string as utf16 into a C++ string. - std::u16string utf16(Runtime& runtime) const { - return runtime.utf16(*this); - } - - friend class Runtime; - friend class Value; -}; - -class Array; -class Function; - -/// Represents a JS Object. Movable, not copyable. -class JSI_EXPORT Object : public Pointer { - public: - using Pointer::Pointer; - - Object(Object&& other) = default; - Object& operator=(Object&& other) = default; - - /// Creates a new Object instance, like '{}' in JS. - Object(Runtime& runtime) : Object(runtime.createObject()) {} - - static Object createFromHostObject( - Runtime& runtime, - std::shared_ptr ho) { - return runtime.createObject(ho); - } - - /// \return whether this and \c obj are the same JSObject or not. - static bool strictEquals(Runtime& runtime, const Object& a, const Object& b) { - return runtime.strictEquals(a, b); - } - - /// \return the result of `this instanceOf ctor` in JS. - bool instanceOf(Runtime& rt, const Function& ctor) const { - return rt.instanceOf(*this, ctor); - } - - /// \return the property of the object with the given ascii name. - /// If the name isn't a property on the object, returns the - /// undefined value. - Value getProperty(Runtime& runtime, const char* name) const; - - /// \return the property of the object with the String name. - /// If the name isn't a property on the object, returns the - /// undefined value. - Value getProperty(Runtime& runtime, const String& name) const; - - /// \return the property of the object with the given JS PropNameID - /// name. If the name isn't a property on the object, returns the - /// undefined value. - Value getProperty(Runtime& runtime, const PropNameID& name) const; - - /// \return true if and only if the object has a property with the - /// given ascii name. - bool hasProperty(Runtime& runtime, const char* name) const; - - /// \return true if and only if the object has a property with the - /// given String name. - bool hasProperty(Runtime& runtime, const String& name) const; - - /// \return true if and only if the object has a property with the - /// given PropNameID name. - bool hasProperty(Runtime& runtime, const PropNameID& name) const; - - /// Sets the property value from a Value or anything which can be - /// used to make one: nullptr_t, bool, double, int, const char*, - /// String, or Object. - template - void setProperty(Runtime& runtime, const char* name, T&& value) const; - - /// Sets the property value from a Value or anything which can be - /// used to make one: nullptr_t, bool, double, int, const char*, - /// String, or Object. - template - void setProperty(Runtime& runtime, const String& name, T&& value) const; - - /// Sets the property value from a Value or anything which can be - /// used to make one: nullptr_t, bool, double, int, const char*, - /// String, or Object. - template - void setProperty(Runtime& runtime, const PropNameID& name, T&& value) const; - - /// \return true iff JS \c Array.isArray() would return \c true. If - /// so, then \c getArray() will succeed. - bool isArray(Runtime& runtime) const { - return runtime.isArray(*this); - } - - /// \return true iff the Object is an ArrayBuffer. If so, then \c - /// getArrayBuffer() will succeed. - bool isArrayBuffer(Runtime& runtime) const { - return runtime.isArrayBuffer(*this); - } - - /// \return true iff the Object is callable. If so, then \c - /// getFunction will succeed. - bool isFunction(Runtime& runtime) const { - return runtime.isFunction(*this); - } - - /// \return true iff the Object was initialized with \c createFromHostObject - /// and the HostObject passed is of type \c T. If returns \c true then - /// \c getHostObject will succeed. - template - bool isHostObject(Runtime& runtime) const; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) const&; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will assert. - Array getArray(Runtime& runtime) &&; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will throw - /// JSIException. - Array asArray(Runtime& runtime) const&; - - /// \return an Array instance which refers to the same underlying - /// object. If \c isArray() would return false, this will throw - /// JSIException. - Array asArray(Runtime& runtime) &&; - - /// \return an ArrayBuffer instance which refers to the same underlying - /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) const&; - - /// \return an ArrayBuffer instance which refers to the same underlying - /// object. If \c isArrayBuffer() would return false, this will assert. - ArrayBuffer getArrayBuffer(Runtime& runtime) &&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) const&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will assert. - Function getFunction(Runtime& runtime) &&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will throw - /// JSIException. - Function asFunction(Runtime& runtime) const&; - - /// \return a Function instance which refers to the same underlying - /// object. If \c isFunction() would return false, this will throw - /// JSIException. - Function asFunction(Runtime& runtime) &&; - - /// \return a shared_ptr which refers to the same underlying - /// \c HostObject that was used to create this object. If \c isHostObject - /// is false, this will assert. Note that this does a type check and will - /// assert if the underlying HostObject isn't of type \c T - template - std::shared_ptr getHostObject(Runtime& runtime) const; - - /// \return a shared_ptr which refers to the same underlying - /// \c HostObject that was used to create this object. If \c isHostObject - /// is false, this will throw. - template - std::shared_ptr asHostObject(Runtime& runtime) const; - - /// \return whether this object has native state of type T previously set by - /// \c setNativeState. - template - bool hasNativeState(Runtime& runtime) const; - - /// \return a shared_ptr to the state previously set by \c setNativeState. - /// If \c hasNativeState is false, this will assert. Note that this does a - /// type check and will assert if the native state isn't of type \c T - template - std::shared_ptr getNativeState(Runtime& runtime) const; - - /// Set the internal native state property of this object, overwriting any old - /// value. Creates a new shared_ptr to the object managed by \p state, which - /// will live until the value at this property becomes unreachable. - /// - /// Throws a type error if this object is a proxy or host object. - void setNativeState(Runtime& runtime, std::shared_ptr state) - const; - - /// \return same as \c getProperty(name).asObject(), except with - /// a better exception message. - Object getPropertyAsObject(Runtime& runtime, const char* name) const; - - /// \return similar to \c - /// getProperty(name).getObject().getFunction(), except it will - /// throw JSIException instead of asserting if the property is - /// not an object, or the object is not callable. - Function getPropertyAsFunction(Runtime& runtime, const char* name) const; - - /// \return an Array consisting of all enumerable property names in - /// the object and its prototype chain. All values in the return - /// will be isString(). (This is probably not optimal, but it - /// works. I only need it in one place.) - Array getPropertyNames(Runtime& runtime) const; - - /// Inform the runtime that there is additional memory associated with a given - /// JavaScript object that is not visible to the GC. This can be used if an - /// object is known to retain some native memory, and may be used to guide - /// decisions about when to run garbage collection. - /// This method may be invoked multiple times on an object, and subsequent - /// calls will overwrite any previously set value. Once the object is garbage - /// collected, the associated external memory will be considered freed and may - /// no longer factor into GC decisions. - void setExternalMemoryPressure(Runtime& runtime, size_t amt) const; - - protected: - void setPropertyValue( - Runtime& runtime, - const String& name, - const Value& value) const { - return runtime.setPropertyValue(*this, name, value); - } - - void setPropertyValue( - Runtime& runtime, - const PropNameID& name, - const Value& value) const { - return runtime.setPropertyValue(*this, name, value); - } - - friend class Runtime; - friend class Value; -}; - -/// Represents a weak reference to a JS Object. If the only reference -/// to an Object are these, the object is eligible for GC. Method -/// names are inspired by C++ weak_ptr. Movable, not copyable. -class JSI_EXPORT WeakObject : public Pointer { - public: - using Pointer::Pointer; - - WeakObject(WeakObject&& other) = default; - WeakObject& operator=(WeakObject&& other) = default; - - /// Create a WeakObject from an Object. - WeakObject(Runtime& runtime, const Object& o) - : WeakObject(runtime.createWeakObject(o)) {} - - /// \return a Value representing the underlying Object if it is still valid; - /// otherwise returns \c undefined. Note that this method has nothing to do - /// with threads or concurrency. The name is based on std::weak_ptr::lock() - /// which serves a similar purpose. - Value lock(Runtime& runtime) const; - - friend class Runtime; -}; - -/// Represents a JS Object which can be efficiently used as an array -/// with integral indices. -class JSI_EXPORT Array : public Object { - public: - Array(Array&&) = default; - /// Creates a new Array instance, with \c length undefined elements. - Array(Runtime& runtime, size_t length) : Array(runtime.createArray(length)) {} - - Array& operator=(Array&&) = default; - - /// \return the size of the Array, according to its length property. - /// (C++ naming convention) - size_t size(Runtime& runtime) const { - return runtime.size(*this); - } - - /// \return the size of the Array, according to its length property. - /// (JS naming convention) - size_t length(Runtime& runtime) const { - return size(runtime); - } - - /// \return the property of the array at index \c i. If there is no - /// such property, returns the undefined value. If \c i is out of - /// range [ 0..\c length ] throws a JSIException. - Value getValueAtIndex(Runtime& runtime, size_t i) const; - - /// Sets the property of the array at index \c i. The argument - /// value behaves as with Object::setProperty(). If \c i is out of - /// range [ 0..\c length ] throws a JSIException. - template - void setValueAtIndex(Runtime& runtime, size_t i, T&& value) const; - - /// There is no current API for changing the size of an array once - /// created. We'll probably need that eventually. - - /// Creates a new Array instance from provided values - template - static Array createWithElements(Runtime&, Args&&... args); - - /// Creates a new Array instance from initializer list. - static Array createWithElements( - Runtime& runtime, - std::initializer_list elements); - - private: - friend class Object; - friend class Value; - friend class Runtime; - - void setValueAtIndexImpl(Runtime& runtime, size_t i, const Value& value) - const { - return runtime.setValueAtIndexImpl(*this, i, value); - } - - Array(Runtime::PointerValue* value) : Object(value) {} -}; - -/// Represents a JSArrayBuffer -class JSI_EXPORT ArrayBuffer : public Object { - public: - ArrayBuffer(ArrayBuffer&&) = default; - ArrayBuffer& operator=(ArrayBuffer&&) = default; - - ArrayBuffer(Runtime& runtime, std::shared_ptr buffer) - : ArrayBuffer(runtime.createArrayBuffer(std::move(buffer))) {} - - /// \return the size of the ArrayBuffer storage. This is not affected by - /// overriding the byteLength property. - /// (C++ naming convention) - size_t size(Runtime& runtime) const { - return runtime.size(*this); - } - - size_t length(Runtime& runtime) const { - return runtime.size(*this); - } - - uint8_t* data(Runtime& runtime) const { - return runtime.data(*this); - } - - private: - friend class Object; - friend class Value; - friend class Runtime; - - ArrayBuffer(Runtime::PointerValue* value) : Object(value) {} -}; - -/// Represents a JS Object which is guaranteed to be Callable. -class JSI_EXPORT Function : public Object { - public: - Function(Function&&) = default; - Function& operator=(Function&&) = default; - - /// Create a function which, when invoked, calls C++ code. If the - /// function throws an exception, a JS Error will be created and - /// thrown. - /// \param name the name property for the function. - /// \param paramCount the length property for the function, which - /// may not be the number of arguments the function is passed. - /// \note The std::function's dtor will be called when the GC finalizes this - /// function. As with HostObject, this may be as late as when the Runtime is - /// shut down, and may occur on an arbitrary thread. If the function contains - /// any captured values, you are responsible for ensuring that their - /// destructors are safe to call on any thread. - static Function createFromHostFunction( - Runtime& runtime, - const jsi::PropNameID& name, - unsigned int paramCount, - jsi::HostFunctionType func); - - /// Calls the function with \c count \c args. The \c this value of the JS - /// function will not be set by the C++ caller, similar to calling - /// Function.prototype.apply(undefined, args) in JS. - /// \b Note: as with Function.prototype.apply, \c this may not always be - /// \c undefined in the function itself. If the function is non-strict, - /// \c this will be set to the global object. - Value call(Runtime& runtime, const Value* args, size_t count) const; - - /// Calls the function with a \c std::initializer_list of Value - /// arguments. The \c this value of the JS function will not be set by the - /// C++ caller, similar to calling Function.prototype.apply(undefined, args) - /// in JS. - /// \b Note: as with Function.prototype.apply, \c this may not always be - /// \c undefined in the function itself. If the function is non-strict, - /// \c this will be set to the global object. - Value call(Runtime& runtime, std::initializer_list args) const; - - /// Calls the function with any number of arguments similarly to - /// Object::setProperty(). The \c this value of the JS function will not be - /// set by the C++ caller, similar to calling - /// Function.prototype.call(undefined, ...args) in JS. - /// \b Note: as with Function.prototype.call, \c this may not always be - /// \c undefined in the function itself. If the function is non-strict, - /// \c this will be set to the global object. - template - Value call(Runtime& runtime, Args&&... args) const; - - /// Calls the function with \c count \c args and \c jsThis value passed - /// as the \c this value. - Value callWithThis( - Runtime& Runtime, - const Object& jsThis, - const Value* args, - size_t count) const; - - /// Calls the function with a \c std::initializer_list of Value - /// arguments and \c jsThis passed as the \c this value. - Value callWithThis( - Runtime& runtime, - const Object& jsThis, - std::initializer_list args) const; - - /// Calls the function with any number of arguments similarly to - /// Object::setProperty(), and with \c jsThis passed as the \c this value. - template - Value callWithThis(Runtime& runtime, const Object& jsThis, Args&&... args) - const; - - /// Calls the function as a constructor with \c count \c args. Equivalent - /// to calling `new Func` where `Func` is the js function reqresented by - /// this. - Value callAsConstructor(Runtime& runtime, const Value* args, size_t count) - const; - - /// Same as above `callAsConstructor`, except use an initializer_list to - /// supply the arguments. - Value callAsConstructor(Runtime& runtime, std::initializer_list args) - const; - - /// Same as above `callAsConstructor`, but automatically converts/wraps - /// any argument with a jsi Value. - template - Value callAsConstructor(Runtime& runtime, Args&&... args) const; - - /// Returns whether this was created with Function::createFromHostFunction. - /// If true then you can use getHostFunction to get the underlying - /// HostFunctionType. - bool isHostFunction(Runtime& runtime) const { - return runtime.isHostFunction(*this); - } - - /// Returns the underlying HostFunctionType iff isHostFunction returns true - /// and asserts otherwise. You can use this to use std::function<>::target - /// to get the object that was passed to create the HostFunctionType. - /// - /// Note: The reference returned is borrowed from the JS object underlying - /// \c this, and thus only lasts as long as the object underlying - /// \c this does. - HostFunctionType& getHostFunction(Runtime& runtime) const { - assert(isHostFunction(runtime)); - return runtime.getHostFunction(*this); - } - - private: - friend class Object; - friend class Value; - friend class Runtime; - - Function(Runtime::PointerValue* value) : Object(value) {} -}; - -/// Represents any JS Value (undefined, null, boolean, number, symbol, -/// string, or object). Movable, or explicitly copyable (has no copy -/// ctor). -class JSI_EXPORT Value { - public: - /// Default ctor creates an \c undefined JS value. - Value() noexcept : Value(UndefinedKind) {} - - /// Creates a \c null JS value. - /* implicit */ Value(std::nullptr_t) : kind_(NullKind) {} - - /// Creates a boolean JS value. - /* implicit */ Value(bool b) : Value(BooleanKind) { - data_.boolean = b; - } - - /// Creates a number JS value. - /* implicit */ Value(double d) : Value(NumberKind) { - data_.number = d; - } - - /// Creates a number JS value. - /* implicit */ Value(int i) : Value(NumberKind) { - data_.number = i; - } - - /// Moves a Symbol, String, or Object rvalue into a new JS value. - template < - typename T, - typename = std::enable_if_t< - std::is_base_of::value || - std::is_base_of::value || - std::is_base_of::value || - std::is_base_of::value>> - /* implicit */ Value(T&& other) : Value(kindOf(other)) { - new (&data_.pointer) T(std::move(other)); - } - - /// Value("foo") will treat foo as a bool. This makes doing that a - /// compile error. - template - Value(const char*) { - static_assert( - !std::is_same::value, - "Value cannot be constructed directly from const char*"); - } - - Value(Value&& other) noexcept; - - /// Copies a Symbol lvalue into a new JS value. - Value(Runtime& runtime, const Symbol& sym) : Value(SymbolKind) { - new (&data_.pointer) Symbol(runtime.cloneSymbol(sym.ptr_)); - } - - /// Copies a BigInt lvalue into a new JS value. - Value(Runtime& runtime, const BigInt& bigint) : Value(BigIntKind) { - new (&data_.pointer) BigInt(runtime.cloneBigInt(bigint.ptr_)); - } - - /// Copies a String lvalue into a new JS value. - Value(Runtime& runtime, const String& str) : Value(StringKind) { - new (&data_.pointer) String(runtime.cloneString(str.ptr_)); - } - - /// Copies a Object lvalue into a new JS value. - Value(Runtime& runtime, const Object& obj) : Value(ObjectKind) { - new (&data_.pointer) Object(runtime.cloneObject(obj.ptr_)); - } - - /// Creates a JS value from another Value lvalue. - Value(Runtime& runtime, const Value& value); - - /// Value(rt, "foo") will treat foo as a bool. This makes doing - /// that a compile error. - template - Value(Runtime&, const char*) { - static_assert( - !std::is_same::value, - "Value cannot be constructed directly from const char*"); - } - - ~Value(); - // \return the undefined \c Value. - static Value undefined() { - return Value(); - } - - // \return the null \c Value. - static Value null() { - return Value(nullptr); - } - - // \return a \c Value created from a utf8-encoded JSON string. - static Value - createFromJsonUtf8(Runtime& runtime, const uint8_t* json, size_t length) { - return runtime.createValueFromJsonUtf8(json, length); - } - - /// \return according to the Strict Equality Comparison algorithm, see: - /// https://262.ecma-international.org/11.0/#sec-strict-equality-comparison - static bool strictEquals(Runtime& runtime, const Value& a, const Value& b); - - Value& operator=(Value&& other) noexcept { - this->~Value(); - new (this) Value(std::move(other)); - return *this; - } - - bool isUndefined() const { - return kind_ == UndefinedKind; - } - - bool isNull() const { - return kind_ == NullKind; - } - - bool isBool() const { - return kind_ == BooleanKind; - } - - bool isNumber() const { - return kind_ == NumberKind; - } - - bool isString() const { - return kind_ == StringKind; - } - - bool isBigInt() const { - return kind_ == BigIntKind; - } - - bool isSymbol() const { - return kind_ == SymbolKind; - } - - bool isObject() const { - return kind_ == ObjectKind; - } - - /// \return the boolean value, or asserts if not a boolean. - bool getBool() const { - assert(isBool()); - return data_.boolean; - } - - /// \return the boolean value, or throws JSIException if not a - /// boolean. - bool asBool() const; - - /// \return the number value, or asserts if not a number. - double getNumber() const { - assert(isNumber()); - return data_.number; - } - - /// \return the number value, or throws JSIException if not a - /// number. - double asNumber() const; - - /// \return the Symbol value, or asserts if not a symbol. - Symbol getSymbol(Runtime& runtime) const& { - assert(isSymbol()); - return Symbol(runtime.cloneSymbol(data_.pointer.ptr_)); - } - - /// \return the Symbol value, or asserts if not a symbol. - /// Can be used on rvalue references to avoid cloning more symbols. - Symbol getSymbol(Runtime&) && { - assert(isSymbol()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the Symbol value, or throws JSIException if not a - /// symbol - Symbol asSymbol(Runtime& runtime) const&; - Symbol asSymbol(Runtime& runtime) &&; - - /// \return the BigInt value, or asserts if not a bigint. - BigInt getBigInt(Runtime& runtime) const& { - assert(isBigInt()); - return BigInt(runtime.cloneBigInt(data_.pointer.ptr_)); - } - - /// \return the BigInt value, or asserts if not a bigint. - /// Can be used on rvalue references to avoid cloning more bigints. - BigInt getBigInt(Runtime&) && { - assert(isBigInt()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the BigInt value, or throws JSIException if not a - /// bigint - BigInt asBigInt(Runtime& runtime) const&; - BigInt asBigInt(Runtime& runtime) &&; - - /// \return the String value, or asserts if not a string. - String getString(Runtime& runtime) const& { - assert(isString()); - return String(runtime.cloneString(data_.pointer.ptr_)); - } - - /// \return the String value, or asserts if not a string. - /// Can be used on rvalue references to avoid cloning more strings. - String getString(Runtime&) && { - assert(isString()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the String value, or throws JSIException if not a - /// string. - String asString(Runtime& runtime) const&; - String asString(Runtime& runtime) &&; - - /// \return the Object value, or asserts if not an object. - Object getObject(Runtime& runtime) const& { - assert(isObject()); - return Object(runtime.cloneObject(data_.pointer.ptr_)); - } - - /// \return the Object value, or asserts if not an object. - /// Can be used on rvalue references to avoid cloning more objects. - Object getObject(Runtime&) && { - assert(isObject()); - auto ptr = data_.pointer.ptr_; - data_.pointer.ptr_ = nullptr; - return static_cast(ptr); - } - - /// \return the Object value, or throws JSIException if not an - /// object. - Object asObject(Runtime& runtime) const&; - Object asObject(Runtime& runtime) &&; - - // \return a String like JS .toString() would do. - String toString(Runtime& runtime) const; - - private: - friend class Runtime; - - enum ValueKind { - UndefinedKind, - NullKind, - BooleanKind, - NumberKind, - SymbolKind, - BigIntKind, - StringKind, - ObjectKind, - PointerKind = SymbolKind, - }; - - union Data { - // Value's ctor and dtor will manage the lifecycle of the contained Data. - Data() { - static_assert( - sizeof(Data) == sizeof(uint64_t), - "Value data should fit in a 64-bit register"); - } - ~Data() {} - - // scalars - bool boolean; - double number; - // pointers - Pointer pointer; // Symbol, String, Object, Array, Function - }; - - Value(ValueKind kind) : kind_(kind) {} - - constexpr static ValueKind kindOf(const Symbol&) { - return SymbolKind; - } - constexpr static ValueKind kindOf(const BigInt&) { - return BigIntKind; - } - constexpr static ValueKind kindOf(const String&) { - return StringKind; - } - constexpr static ValueKind kindOf(const Object&) { - return ObjectKind; - } - - ValueKind kind_; - Data data_; - - // In the future: Value becomes NaN-boxed. See T40538354. -}; - -/// Not movable and not copyable RAII marker advising the underlying -/// JavaScript VM to track resources allocated since creation until -/// destruction so that they can be recycled eagerly when the Scope -/// goes out of scope instead of floating in the air until the next -/// garbage collection or any other delayed release occurs. -/// -/// This API should be treated only as advice, implementations can -/// choose to ignore the fact that Scopes are created or destroyed. -/// -/// This class is an exception to the rule allowing destructors to be -/// called without proper synchronization (see Runtime documentation). -/// The whole point of this class is to enable all sorts of clean ups -/// when the destructor is called and this proper synchronization is -/// required at that time. -/// -/// Instances of this class are intended to be created as automatic stack -/// variables in which case destructor calls don't require any additional -/// locking, provided that the lock (if any) is managed with RAII helpers. -class JSI_EXPORT Scope { - public: - explicit Scope(Runtime& rt) : rt_(rt), prv_(rt.pushScope()) {} - ~Scope() { - rt_.popScope(prv_); - } - - Scope(const Scope&) = delete; - Scope(Scope&&) = delete; - - Scope& operator=(const Scope&) = delete; - Scope& operator=(Scope&&) = delete; - - template - static auto callInNewScope(Runtime& rt, F f) -> decltype(f()) { - Scope s(rt); - return f(); - } - - private: - Runtime& rt_; - Runtime::ScopeState* prv_; -}; - -/// Base class for jsi exceptions -class JSI_EXPORT JSIException : public std::exception { - protected: - JSIException() {} - JSIException(std::string what) : what_(std::move(what)) {} - - public: - JSIException(const JSIException&) = default; - - virtual const char* what() const noexcept override { - return what_.c_str(); - } - - virtual ~JSIException() override; - - protected: - std::string what_; -}; - -/// This exception will be thrown by API functions on errors not related to -/// JavaScript execution. -class JSI_EXPORT JSINativeException : public JSIException { - public: - JSINativeException(std::string what) : JSIException(std::move(what)) {} - - JSINativeException(const JSINativeException&) = default; - - virtual ~JSINativeException(); -}; - -/// This exception will be thrown by API functions whenever a JS -/// operation causes an exception as described by the spec, or as -/// otherwise described. -class JSI_EXPORT JSError : public JSIException { - public: - /// Creates a JSError referring to provided \c value - JSError(Runtime& r, Value&& value); - - /// Creates a JSError referring to new \c Error instance capturing current - /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, std::string message); - - /// Creates a JSError referring to new \c Error instance capturing current - /// JavaScript stack. The error message property is set to given \c message. - JSError(Runtime& rt, const char* message) - : JSError(rt, std::string(message)) {} - - /// Creates a JSError referring to a JavaScript Object having message and - /// stack properties set to provided values. - JSError(Runtime& rt, std::string message, std::string stack); - - /// Creates a JSError referring to provided value and what string - /// set to provided message. This argument order is a bit weird, - /// but necessary to avoid ambiguity with the above. - JSError(std::string what, Runtime& rt, Value&& value); - - /// Creates a JSError referring to the provided value, message and stack. This - /// constructor does not take a Runtime parameter, and therefore cannot result - /// in recursively invoking the JSError constructor. - JSError(Value&& value, std::string message, std::string stack); - - JSError(const JSError&) = default; - - virtual ~JSError(); - - const std::string& getStack() const { - return stack_; - } - - const std::string& getMessage() const { - return message_; - } - - const jsi::Value& value() const { - assert(value_); - return *value_; - } - - private: - // This initializes the value_ member and does some other - // validation, so it must be called by every branch through the - // constructors. - void setValue(Runtime& rt, Value&& value); - - // This needs to be on the heap, because throw requires the object - // be copyable, and Value is not. - std::shared_ptr value_; - std::string message_; - std::string stack_; -}; - -} // namespace jsi -} // namespace facebook - -#include diff --git a/NativeScript/napi/hermes/include_old/jsi/jsilib.h b/NativeScript/napi/hermes/include_old/jsi/jsilib.h deleted file mode 100644 index c94de89f6..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/jsilib.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -namespace facebook { -namespace jsi { - -class FileBuffer : public Buffer { - public: - FileBuffer(const std::string& path); - ~FileBuffer() override; - - size_t size() const override { - return size_; - } - - const uint8_t* data() const override { - return data_; - } - - private: - size_t size_; - uint8_t* data_; -}; - -// A trivial implementation of PreparedJavaScript that simply stores the source -// buffer and URL. -class SourceJavaScriptPreparation final : public jsi::PreparedJavaScript, - public jsi::Buffer { - std::shared_ptr buf_; - std::string sourceURL_; - - public: - SourceJavaScriptPreparation( - std::shared_ptr buf, - std::string sourceURL) - : buf_(std::move(buf)), sourceURL_(std::move(sourceURL)) {} - - const std::string& sourceURL() const { - return sourceURL_; - } - - size_t size() const override { - return buf_->size(); - } - const uint8_t* data() const override { - return buf_->data(); - } -}; - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/include_old/jsi/threadsafe.h b/NativeScript/napi/hermes/include_old/jsi/threadsafe.h deleted file mode 100644 index cb10a335f..000000000 --- a/NativeScript/napi/hermes/include_old/jsi/threadsafe.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include - -namespace facebook { -namespace jsi { - -class ThreadSafeRuntime : public Runtime { - public: - virtual void lock() const = 0; - virtual void unlock() const = 0; - virtual Runtime& getUnsafeRuntime() = 0; -}; - -namespace detail { - -template -struct WithLock { - L lock; - WithLock(R& r) : lock(r) {} - void before() { - lock.lock(); - } - void after() { - lock.unlock(); - } -}; - -// The actual implementation of a given ThreadSafeRuntime. It's parameterized -// by: -// -// - R: The actual Runtime type that this wraps -// - L: A lock type that has three members: -// - L(R& r) // ctor -// - void lock() -// - void unlock() -template -class ThreadSafeRuntimeImpl final - : public WithRuntimeDecorator, R, ThreadSafeRuntime> { - public: - template - ThreadSafeRuntimeImpl(Args&&... args) - : WithRuntimeDecorator, R, ThreadSafeRuntime>( - unsafe_, - lock_), - unsafe_(std::forward(args)...), - lock_(unsafe_) {} - - R& getUnsafeRuntime() override { - return WithRuntimeDecorator, R, ThreadSafeRuntime>::plain(); - } - - void lock() const override { - lock_.before(); - } - - void unlock() const override { - lock_.after(); - } - - private: - R unsafe_; - mutable WithLock lock_; -}; - -} // namespace detail - -} // namespace jsi -} // namespace facebook diff --git a/NativeScript/napi/hermes/js_native_api.h b/NativeScript/napi/hermes/js_native_api.h deleted file mode 100644 index 9e7073cf3..000000000 --- a/NativeScript/napi/hermes/js_native_api.h +++ /dev/null @@ -1,600 +0,0 @@ -#ifndef SRC_JS_NATIVE_API_H_ -#define SRC_JS_NATIVE_API_H_ - -// This file needs to be compatible with C compilers. -#include // NOLINT(modernize-deprecated-headers) -#include // NOLINT(modernize-deprecated-headers) - -// Use INT_MAX, this should only be consumed by the pre-processor anyway. -#define NAPI_VERSION_EXPERIMENTAL 2147483647 -#ifndef NAPI_VERSION -// The baseline version for N-API. -// The NAPI_VERSION controls which version will be used by default when -// compilling a native addon. If the addon developer specifically wants to use -// functions available in a new version of N-API that is not yet ported in all -// LTS versions, they can set NAPI_VERSION knowing that they have specifically -// depended on that version. -#define NAPI_VERSION 8 -#endif - -#include "js_native_api_types.h" - -// If you need __declspec(dllimport), either include instead, or -// define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. -#ifndef NAPI_EXTERN -#ifdef _WIN32 -#define NAPI_EXTERN __declspec(dllexport) -#elif defined(__wasm__) -#define NAPI_EXTERN \ - __attribute__((visibility("default"))) \ - __attribute__((__import_module__("napi"))) -#else -#define NAPI_EXTERN __attribute__((visibility("default"))) -#endif -#endif - -#define NAPI_AUTO_LENGTH SIZE_MAX - -#ifdef __cplusplus -#define EXTERN_C_START extern "C" { -#define EXTERN_C_END } -#else -#define EXTERN_C_START -#define EXTERN_C_END -#endif - -EXTERN_C_START - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info( - node_api_basic_env env, const napi_extended_error_info** result); - -// Getters for defined singletons -NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_null(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_global(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_boolean(napi_env env, - bool value, - napi_value* result); - -// Methods to create Primitive types/Objects -NAPI_EXTERN napi_status NAPI_CDECL napi_create_object(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_array(napi_env env, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_array_with_length(napi_env env, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_double(napi_env env, - double value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_int32(napi_env env, - int32_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_uint32(napi_env env, - uint32_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_int64(napi_env env, - int64_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_latin1( - napi_env env, const char* str, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf8(napi_env env, - const char* str, - size_t length, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, - const char16_t* str, - size_t length, - napi_value* result); -#if NAPI_VERSION >= 10 -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1( - napi_env env, - char* str, - size_t length, - node_api_basic_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied); -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_external_string_utf16(napi_env env, - char16_t* str, - size_t length, - node_api_basic_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied); - -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_latin1( - napi_env env, const char* str, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf8( - napi_env env, const char* str, size_t length, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_property_key_utf16( - napi_env env, const char16_t* str, size_t length, napi_value* result); -#endif // NAPI_VERSION >= 10 - -NAPI_EXTERN napi_status NAPI_CDECL napi_create_symbol(napi_env env, - napi_value description, - napi_value* result); -#if NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL -node_api_symbol_for(napi_env env, - const char* utf8description, - size_t length, - napi_value* result); -#endif // NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL napi_create_function(napi_env env, - const char* utf8name, - size_t length, - napi_callback cb, - void* data, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_type_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_range_error(napi_env env, - napi_value code, - napi_value msg, - napi_value* result); -#if NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL node_api_create_syntax_error( - napi_env env, napi_value code, napi_value msg, napi_value* result); -#endif // NAPI_VERSION >= 9 - -// Methods to get the native napi_value from Primitive type -NAPI_EXTERN napi_status NAPI_CDECL napi_typeof(napi_env env, - napi_value value, - napi_valuetype* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_double(napi_env env, - napi_value value, - double* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int32(napi_env env, - napi_value value, - int32_t* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_uint32(napi_env env, - napi_value value, - uint32_t* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_int64(napi_env env, - napi_value value, - int64_t* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bool(napi_env env, - napi_value value, - bool* result); - -// Copies LATIN-1 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_latin1( - napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); - -// Copies UTF-8 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf8( - napi_env env, napi_value value, char* buf, size_t bufsize, size_t* result); - -// Copies UTF-16 encoded bytes from a string into a buffer. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_string_utf16(napi_env env, - napi_value value, - char16_t* buf, - size_t bufsize, - size_t* result); - -// Methods to coerce values -// These APIs may execute user scripts -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_bool(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_number(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_object(napi_env env, - napi_value value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_coerce_to_string(napi_env env, - napi_value value, - napi_value* result); - -// Methods to work with Objects -NAPI_EXTERN napi_status NAPI_CDECL napi_get_prototype(napi_env env, - napi_value object, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_property_names(napi_env env, - napi_value object, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_set_property(napi_env env, - napi_value object, - napi_value key, - napi_value value); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_property(napi_env env, - napi_value object, - napi_value key, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_own_property(napi_env env, - napi_value object, - napi_value key, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_set_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value value); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_named_property(napi_env env, - napi_value object, - const char* utf8name, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_named_property(napi_env env, - napi_value object, - const char* utf8name, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_set_element(napi_env env, - napi_value object, - uint32_t index, - napi_value value); -NAPI_EXTERN napi_status NAPI_CDECL napi_has_element(napi_env env, - napi_value object, - uint32_t index, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_element(napi_env env, - napi_value object, - uint32_t index, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_element(napi_env env, - napi_value object, - uint32_t index, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_define_properties(napi_env env, - napi_value object, - size_t property_count, - const napi_property_descriptor* properties); - -// Methods to work with Arrays -NAPI_EXTERN napi_status NAPI_CDECL napi_is_array(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_array_length(napi_env env, - napi_value value, - uint32_t* result); - -// Methods to compare values -NAPI_EXTERN napi_status NAPI_CDECL napi_strict_equals(napi_env env, - napi_value lhs, - napi_value rhs, - bool* result); - -// Methods to work with Functions -NAPI_EXTERN napi_status NAPI_CDECL napi_call_function(napi_env env, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_new_instance(napi_env env, - napi_value constructor, - size_t argc, - const napi_value* argv, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env, - napi_value object, - napi_value constructor, - bool* result); - -// Methods to work with napi_callbacks - -// Gets all callback info in a single call. (Ugly, but faster.) -NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info( - napi_env env, // [in] Node-API environment handle - napi_callback_info cbinfo, // [in] Opaque callback-info handle - size_t* argc, // [in-out] Specifies the size of the provided argv array - // and receives the actual count of args. - napi_value* argv, // [out] Array of values - napi_value* this_arg, // [out] Receives the JS 'this' arg for the call - void** data); // [out] Receives the data pointer for the callback. - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_new_target( - napi_env env, napi_callback_info cbinfo, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_define_class(napi_env env, - const char* utf8name, - size_t length, - napi_callback constructor, - void* data, - size_t property_count, - const napi_property_descriptor* properties, - napi_value* result); - -// Methods to work with external data objects -NAPI_EXTERN napi_status NAPI_CDECL -napi_wrap(napi_env env, - napi_value js_object, - void* native_object, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, - napi_value js_object, - void** result); -NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, - napi_value js_object, - void** result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_external(napi_env env, - void* data, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, - napi_value value, - void** result); - -// Methods to control object lifespan - -// Set initial_refcount to 0 for a weak reference, >0 for a strong reference. -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_reference(napi_env env, - napi_value value, - uint32_t initial_refcount, - napi_ref* result); - -// Deletes a reference. The referenced value is released, and may -// be GC'd unless there are other references to it. -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_reference(napi_env env, - napi_ref ref); - -// Increments the reference count, optionally returning the resulting count. -// After this call the reference will be a strong reference because its -// refcount is >0, and the referenced object is effectively "pinned". -// Calling this when the refcount is 0 and the object is unavailable -// results in an error. -NAPI_EXTERN napi_status NAPI_CDECL napi_reference_ref(napi_env env, - napi_ref ref, - uint32_t* result); - -// Decrements the reference count, optionally returning the resulting count. -// If the result is 0 the reference is now weak and the object may be GC'd -// at any time if there are no other references. Calling this when the -// refcount is already 0 results in an error. -NAPI_EXTERN napi_status NAPI_CDECL napi_reference_unref(napi_env env, - napi_ref ref, - uint32_t* result); - -// Attempts to get a referenced value. If the reference is weak, -// the value might no longer be available, in that case the call -// is still successful but the result is NULL. -NAPI_EXTERN napi_status NAPI_CDECL napi_get_reference_value(napi_env env, - napi_ref ref, - napi_value* result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_open_handle_scope(napi_env env, napi_handle_scope* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_close_handle_scope(napi_env env, napi_handle_scope scope); -NAPI_EXTERN napi_status NAPI_CDECL napi_open_escapable_handle_scope( - napi_env env, napi_escapable_handle_scope* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_close_escapable_handle_scope( - napi_env env, napi_escapable_handle_scope scope); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_escape_handle(napi_env env, - napi_escapable_handle_scope scope, - napi_value escapee, - napi_value* result); - -// Methods to support error handling -NAPI_EXTERN napi_status NAPI_CDECL napi_throw(napi_env env, napi_value error); -NAPI_EXTERN napi_status NAPI_CDECL napi_throw_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status NAPI_CDECL napi_throw_type_error(napi_env env, - const char* code, - const char* msg); -NAPI_EXTERN napi_status NAPI_CDECL napi_throw_range_error(napi_env env, - const char* code, - const char* msg); -#if NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL node_api_throw_syntax_error(napi_env env, - const char* code, - const char* msg); -#endif // NAPI_VERSION >= 9 -NAPI_EXTERN napi_status NAPI_CDECL napi_is_error(napi_env env, - napi_value value, - bool* result); - -// Methods to support catching exceptions -NAPI_EXTERN napi_status NAPI_CDECL napi_is_exception_pending(napi_env env, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_and_clear_last_exception(napi_env env, napi_value* result); - -// Methods to work with array buffers and typed arrays -NAPI_EXTERN napi_status NAPI_CDECL napi_is_arraybuffer(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_create_arraybuffer(napi_env env, - size_t byte_length, - void** data, - napi_value* result); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_external_arraybuffer(napi_env env, - void* external_data, - size_t byte_length, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -NAPI_EXTERN napi_status NAPI_CDECL napi_get_arraybuffer_info( - napi_env env, napi_value arraybuffer, void** data, size_t* byte_length); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_typedarray(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_typedarray(napi_env env, - napi_typedarray_type type, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_typedarray_info(napi_env env, - napi_value typedarray, - napi_typedarray_type* type, - size_t* length, - void** data, - napi_value* arraybuffer, - size_t* byte_offset); - -NAPI_EXTERN napi_status NAPI_CDECL napi_create_dataview(napi_env env, - size_t length, - napi_value arraybuffer, - size_t byte_offset, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_dataview(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_dataview_info(napi_env env, - napi_value dataview, - size_t* bytelength, - void** data, - napi_value* arraybuffer, - size_t* byte_offset); - -// version management -NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_basic_env env, - uint32_t* result); - -// Promises -NAPI_EXTERN napi_status NAPI_CDECL napi_create_promise(napi_env env, - napi_deferred* deferred, - napi_value* promise); -NAPI_EXTERN napi_status NAPI_CDECL napi_resolve_deferred(napi_env env, - napi_deferred deferred, - napi_value resolution); -NAPI_EXTERN napi_status NAPI_CDECL napi_reject_deferred(napi_env env, - napi_deferred deferred, - napi_value rejection); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_promise(napi_env env, - napi_value value, - bool* is_promise); - -// Running a script -NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, - napi_value script, - napi_value* result); - -// Memory management -NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( - node_api_basic_env env, int64_t change_in_bytes, int64_t* adjusted_value); - -#if NAPI_VERSION >= 5 - -// Dates -NAPI_EXTERN napi_status NAPI_CDECL napi_create_date(napi_env env, - double time, - napi_value* result); - -NAPI_EXTERN napi_status NAPI_CDECL napi_is_date(napi_env env, - napi_value value, - bool* is_date); - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env, - napi_value value, - double* result); - -// Add finalizer for pointer -NAPI_EXTERN napi_status NAPI_CDECL -napi_add_finalizer(napi_env env, - napi_value js_object, - void* finalize_data, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); - -#endif // NAPI_VERSION >= 5 - -#if NAPI_VERSION >= 6 - -// BigInt -NAPI_EXTERN napi_status NAPI_CDECL napi_create_bigint_int64(napi_env env, - int64_t value, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_bigint_uint64(napi_env env, uint64_t value, napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_bigint_words(napi_env env, - int sign_bit, - size_t word_count, - const uint64_t* words, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_int64(napi_env env, - napi_value value, - int64_t* result, - bool* lossless); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_bigint_uint64( - napi_env env, napi_value value, uint64_t* result, bool* lossless); -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_value_bigint_words(napi_env env, - napi_value value, - int* sign_bit, - size_t* word_count, - uint64_t* words); - -// Object -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_all_property_names(napi_env env, - napi_value object, - napi_key_collection_mode key_mode, - napi_key_filter key_filter, - napi_key_conversion key_conversion, - napi_value* result); - -// Instance data -NAPI_EXTERN napi_status NAPI_CDECL -napi_set_instance_data(node_api_basic_env env, - void* data, - napi_finalize finalize_cb, - void* finalize_hint); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_instance_data(node_api_basic_env env, void** data); -#endif // NAPI_VERSION >= 6 - -#if NAPI_VERSION >= 7 -// ArrayBuffer detaching -NAPI_EXTERN napi_status NAPI_CDECL -napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result); -#endif // NAPI_VERSION >= 7 - -#if NAPI_VERSION >= 8 -// Type tagging -NAPI_EXTERN napi_status NAPI_CDECL napi_type_tag_object( - napi_env env, napi_value value, const napi_type_tag* type_tag); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_check_object_type_tag(napi_env env, - napi_value value, - const napi_type_tag* type_tag, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_object_freeze(napi_env env, - napi_value object); -NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, - napi_value object); -#endif // NAPI_VERSION >= 8 - -EXTERN_C_END - -#endif // SRC_JS_NATIVE_API_H_ diff --git a/NativeScript/napi/hermes/js_native_api_types.h b/NativeScript/napi/hermes/js_native_api_types.h deleted file mode 100644 index 7853a8d7a..000000000 --- a/NativeScript/napi/hermes/js_native_api_types.h +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef SRC_JS_NATIVE_API_TYPES_H_ -#define SRC_JS_NATIVE_API_TYPES_H_ - -// This file needs to be compatible with C compilers. -// This is a public include file, and these includes have essentially -// became part of it's API. -#include // NOLINT(modernize-deprecated-headers) -#include // NOLINT(modernize-deprecated-headers) - -#if !defined __cplusplus || (defined(_MSC_VER) && _MSC_VER < 1900) -typedef uint16_t char16_t; -#endif - -#ifndef NAPI_CDECL -#ifdef _WIN32 -#define NAPI_CDECL __cdecl -#else -#define NAPI_CDECL -#endif -#endif - -// JSVM API types are all opaque pointers for ABI stability -// typedef undefined structs instead of void* for compile time type safety -typedef struct napi_env__* napi_env; - -// We need to mark APIs which can be called during garbage collection (GC), -// meaning that they do not affect the state of the JS engine, and can -// therefore be called synchronously from a finalizer that itself runs -// synchronously during GC. Such APIs can receive either a `napi_env` or a -// `node_api_basic_env` as their first parameter, because we should be able to -// also call them during normal, non-garbage-collecting operations, whereas -// APIs that affect the state of the JS engine can only receive a `napi_env` as -// their first parameter, because we must not call them during GC. In lieu of -// inheritance, we use the properties of the const qualifier to accomplish -// this, because both a const and a non-const value can be passed to an API -// expecting a const value, but only a non-const value can be passed to an API -// expecting a non-const value. -// -// In conjunction with appropriate CFLAGS to warn us if we're passing a const -// (basic) environment into an API that expects a non-const environment, and -// the definition of basic finalizer function pointer types below, which -// receive a basic environment as their first parameter, and can thus only call -// basic APIs (unless the user explicitly casts the environment), we achieve -// the ability to ensure at compile time that we do not call APIs that affect -// the state of the JS engine from a synchronous (basic) finalizer. -typedef struct napi_env__* node_api_nogc_env; -typedef node_api_nogc_env node_api_basic_env; - -typedef struct napi_value__* napi_value; -typedef struct napi_ref__* napi_ref; -typedef struct napi_handle_scope__* napi_handle_scope; -typedef struct napi_escapable_handle_scope__* napi_escapable_handle_scope; -typedef struct napi_callback_info__* napi_callback_info; -typedef struct napi_deferred__* napi_deferred; - -typedef enum { - napi_default = 0, - napi_writable = 1 << 0, - napi_enumerable = 1 << 1, - napi_configurable = 1 << 2, - - // Used with napi_define_class to distinguish static properties - // from instance properties. Ignored by napi_define_properties. - napi_static = 1 << 10, - -#if NAPI_VERSION >= 8 - // Default for class methods. - napi_default_method = napi_writable | napi_configurable, - - // Default for object properties, like in JS obj[prop]. - napi_default_jsproperty = napi_writable | napi_enumerable | napi_configurable, -#endif // NAPI_VERSION >= 8 -} napi_property_attributes; - -typedef enum { - // ES6 types (corresponds to typeof) - napi_undefined, - napi_null, - napi_boolean, - napi_number, - napi_string, - napi_symbol, - napi_object, - napi_function, - napi_external, - napi_bigint, -} napi_valuetype; - -typedef enum { - napi_int8_array, - napi_uint8_array, - napi_uint8_clamped_array, - napi_int16_array, - napi_uint16_array, - napi_int32_array, - napi_uint32_array, - napi_float32_array, - napi_float64_array, - napi_bigint64_array, - napi_biguint64_array, -} napi_typedarray_type; - -typedef enum { - napi_ok, - napi_invalid_arg, - napi_object_expected, - napi_string_expected, - napi_name_expected, - napi_function_expected, - napi_number_expected, - napi_boolean_expected, - napi_array_expected, - napi_generic_failure, - napi_pending_exception, - napi_cancelled, - napi_escape_called_twice, - napi_handle_scope_mismatch, - napi_callback_scope_mismatch, - napi_queue_full, - napi_closing, - napi_bigint_expected, - napi_date_expected, - napi_arraybuffer_expected, - napi_detachable_arraybuffer_expected, - napi_would_deadlock, // unused - napi_no_external_buffers_allowed, - napi_cannot_run_js, -} napi_status; -// Note: when adding a new enum value to `napi_status`, please also update -// * `const int last_status` in the definition of `napi_get_last_error_info()' -// in file js_native_api_v8.cc. -// * `const char* error_messages[]` in file js_native_api_v8.cc with a brief -// message explaining the error. -// * the definition of `napi_status` in doc/api/n-api.md to reflect the newly -// added value(s). - -typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, - napi_callback_info info); -typedef void(NAPI_CDECL* napi_finalize)(napi_env env, - void* finalize_data, - void* finalize_hint); - -typedef napi_finalize node_api_nogc_finalize; -typedef node_api_nogc_finalize node_api_basic_finalize; - -typedef struct { - // One of utf8name or name should be NULL. - const char* utf8name; - napi_value name; - - napi_callback method; - napi_callback getter; - napi_callback setter; - napi_value value; - - napi_property_attributes attributes; - void* data; -} napi_property_descriptor; - -typedef struct { - const char* error_message; - void* engine_reserved; - uint32_t engine_error_code; - napi_status error_code; -} napi_extended_error_info; - -#if NAPI_VERSION >= 6 -typedef enum { - napi_key_include_prototypes, - napi_key_own_only -} napi_key_collection_mode; - -typedef enum { - napi_key_all_properties = 0, - napi_key_writable = 1, - napi_key_enumerable = 1 << 1, - napi_key_configurable = 1 << 2, - napi_key_skip_strings = 1 << 3, - napi_key_skip_symbols = 1 << 4 -} napi_key_filter; - -typedef enum { - napi_key_keep_numbers, - napi_key_numbers_to_strings -} napi_key_conversion; -#endif // NAPI_VERSION >= 6 - -#if NAPI_VERSION >= 8 -typedef struct { - uint64_t lower; - uint64_t upper; -} napi_type_tag; -#endif // NAPI_VERSION >= 8 - -#endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/NativeScript/napi/hermes/jsr.cpp b/NativeScript/napi/hermes/jsr.cpp index ad23e9b0c..0da333809 100644 --- a/NativeScript/napi/hermes/jsr.cpp +++ b/NativeScript/napi/hermes/jsr.cpp @@ -1,11 +1,75 @@ #include "jsr.h" +#include + +#include "jsr_common.h" + +// Node-API surface exported by the prebuilt Hermes. Included here rather than +// in jsr.h on purpose: it drags in Hermes' own node_api_types.h, whose +// napi_threadsafe_function (struct pointer) and napi_tsfn_* enums collide with +// NativeScript's authoritative definitions in napi/common/js_native_tsfn.h. +// Keeping it out of the header confines it to this file, so translation units +// that include jsr.h -- notably runtime/apple/ThreadSafeFunction.mm -- never +// see it. jsr.h needs nothing from it. +#include "napi/hermes_napi.h" + +#ifdef __ANDROID__ +#include +#include + +#include "File.h" +#include "NativeScriptAssert.h" +#include "bytecode_container.h" +#else #include "js_runtime.h" +#endif using namespace facebook::jsi; std::unordered_map JSR::env_to_jsr_cache; +std::mutex JSR::env_to_jsr_mutex; + +JSR* JSR::FromEnv(napi_env env) { + std::lock_guard guard(env_to_jsr_mutex); + auto it = env_to_jsr_cache.find(env); + return it != env_to_jsr_cache.end() ? it->second : nullptr; +} + +void JSR::RegisterEnv(napi_env env, JSR* jsr) { + std::lock_guard guard(env_to_jsr_mutex); + env_to_jsr_cache[env] = jsr; +} + +void JSR::UnregisterEnv(napi_env env) { + std::lock_guard guard(env_to_jsr_mutex); + env_to_jsr_cache.erase(env); +} namespace { +std::mutex g_unsafe_to_threadsafe_mutex; +std::unordered_map& UnsafeToThreadSafe() { + static std::unordered_map map; + return map; +} +} // namespace + +JSR* js_jsr_for_runtime(facebook::jsi::Runtime* runtime) { + if (runtime == nullptr) return nullptr; + std::lock_guard guard(g_unsafe_to_threadsafe_mutex); + auto& map = UnsafeToThreadSafe(); + auto it = map.find(runtime); + return it != map.end() ? it->second : nullptr; +} + +namespace { +// Deliberately leaked rather than a plain thread_local object: Runtime's +// destructor opens a NapiScope, and on the main thread it runs from a static +// destructor at process exit -- by which point a thread_local with automatic +// storage has already been torn down, so touching it faults. +std::unordered_map& RuntimeLockDepths() { + static thread_local auto* depths = new std::unordered_map(); + return *depths; +} + class RuntimeLockGuard { public: explicit RuntimeLockGuard(JSR* runtime) : runtime_(runtime) { @@ -19,76 +83,137 @@ class RuntimeLockGuard { }; } // namespace -int js_current_env_lock_depth(napi_env env) { - auto itFound = JSR::env_to_jsr_cache.find(env); - if (itFound == JSR::env_to_jsr_cache.end() || itFound->second == nullptr) { +void JSR::lock() { + runtime->lock(); + js_mutex.lock(); + RuntimeLockDepths()[this] += 1; +} + +void JSR::unlock() { + auto depth = RuntimeLockDepths().find(this); + if (depth != RuntimeLockDepths().end()) { + depth->second -= 1; + if (depth->second <= 0) { + RuntimeLockDepths().erase(depth); + } + } + js_mutex.unlock(); + runtime->unlock(); +} + +int JSR::currentLockDepth() const { + auto depth = RuntimeLockDepths().find(const_cast(this)); + if (depth == RuntimeLockDepths().end()) { return 0; } - return itFound->second->currentLockDepth(); + return depth->second; +} + +int js_current_env_lock_depth(napi_env env) { + JSR* jsr = JSR::FromEnv(env); + return jsr != nullptr ? jsr->currentLockDepth() : 0; } JSR::JSR() { +#ifdef __ANDROID__ + hermes::vm::RuntimeConfig config = hermes::vm::RuntimeConfig::Builder() + .withMicrotaskQueue(true) + .withES6BlockScoping(true) + .withEnableAsyncGenerators(true) + .withAsyncBreakCheckInEval(true) + .build(); + runtime = facebook::hermes::makeThreadSafeHermesRuntime(config); + rt = static_cast( + &runtime->getUnsafeRuntime()); +#else hermes::vm::RuntimeConfig config = hermes::vm::RuntimeConfig::Builder() .withMicrotaskQueue(true) .withEnableEval(true) .build(); runtime = facebook::hermes::makeThreadSafeHermesRuntime(config); - rt = &runtime->getUnsafeRuntime(); + rt = static_cast( + &runtime->getUnsafeRuntime()); +#endif + std::lock_guard guard(g_unsafe_to_threadsafe_mutex); + UnsafeToThreadSafe()[rt] = this; } -napi_status js_create_runtime(napi_runtime* runtime) { +napi_status js_create_runtime(jsr_ns_runtime* runtime) { if (runtime == nullptr) return napi_invalid_arg; - *runtime = new napi_runtime__(); + *runtime = new jsr_ns_runtime__(); (*runtime)->hermes = new JSR(); return napi_ok; } napi_status js_lock_env(napi_env env) { - auto itFound = JSR::env_to_jsr_cache.find(env); - if (itFound == JSR::env_to_jsr_cache.end()) { + JSR* jsr = JSR::FromEnv(env); + if (jsr == nullptr) { return napi_invalid_arg; } - itFound->second->lock(); + jsr->lock(); return napi_ok; } napi_status js_unlock_env(napi_env env) { - auto itFound = JSR::env_to_jsr_cache.find(env); - if (itFound == JSR::env_to_jsr_cache.end()) { + JSR* jsr = JSR::FromEnv(env); + if (jsr == nullptr) { return napi_invalid_arg; } - itFound->second->unlock(); + jsr->unlock(); return napi_ok; } -napi_status js_create_napi_env(napi_env* env, napi_runtime runtime) { +napi_status js_create_napi_env(napi_env* env, jsr_ns_runtime runtime) { if (env == nullptr) return napi_invalid_arg; RuntimeLockGuard lock(runtime->hermes); - *env = (napi_env)runtime->hermes->rt->createNodeApiEnv(9); - JSR::env_to_jsr_cache.insert(std::make_pair(*env, runtime->hermes)); + // Extract the underlying hermes::vm::Runtime from the JSI HermesRuntime via + // the IHermes interface, then create the Node-API env on top of it. This is + // the same path Hermes' own tools (repl, test-runner, napi-runner) use and + // relies only on symbols the prebuilt Hermes exports. + // + // Apple used to take a different route through a NativeScript-local + // jsi::Runtime::createNodeApiEnv hook. That hook no longer exists upstream, + // and both platforms now build against the same headers, so there is one path. + auto hermesInterface = + facebook::jsi::castInterface( + runtime->hermes->rt); + if (!hermesInterface) { + // The linked Hermes does not expose IHermes, so there is no way to reach + // the VM runtime. Fail here rather than dereferencing null. + return napi_generic_failure; + } + void* vmRuntime = hermesInterface->getVMRuntimeUnsafe(); + if (vmRuntime == nullptr) return napi_generic_failure; + *env = hermes_napi_create_env(vmRuntime); + if (*env == nullptr) return napi_generic_failure; + JSR::RegisterEnv(*env, runtime->hermes); return napi_ok; } facebook::jsi::Runtime* js_get_jsi_runtime(napi_env env) { - auto itFound = JSR::env_to_jsr_cache.find(env); - if (itFound == JSR::env_to_jsr_cache.end()) { - return nullptr; - } - return itFound->second->rt; + JSR* jsr = JSR::FromEnv(env); + return jsr != nullptr ? jsr->rt : nullptr; } napi_status js_set_runtime_flags(const char* flags) { return napi_ok; } napi_status js_free_napi_env(napi_env env) { - JSR::env_to_jsr_cache.erase(env); +#ifndef NS_HERMES_SKIP_ENV_CLEANUP_HOOKS + js_run_env_cleanup_hooks(env); +#endif + JSR::UnregisterEnv(env); return napi_ok; } -napi_status js_free_runtime(napi_runtime runtime) { +napi_status js_free_runtime(jsr_ns_runtime runtime) { if (runtime == nullptr) return napi_invalid_arg; + { + std::lock_guard guard(g_unsafe_to_threadsafe_mutex); + UnsafeToThreadSafe().erase(runtime->hermes->rt); + } runtime->hermes->runtime.reset(); runtime->hermes->rt = nullptr; delete runtime->hermes; @@ -99,12 +224,98 @@ napi_status js_free_runtime(napi_runtime runtime) { napi_status js_execute_script(napi_env env, napi_value script, const char* file, napi_value* result) { +#ifdef __ANDROID__ + // Pull the UTF-8 source out of the napi string value and compile+run it via + // the Hermes NAPI entry point so we can attach the source URL for stack + // traces. + size_t len = 0; + napi_status status = + napi_get_value_string_utf8(env, script, nullptr, 0, &len); + if (status != napi_ok) return status; + + DEBUG_WRITE("[script] loading script: %s", file); + + uint8_t* source = new uint8_t[len + 1]; + status = napi_get_value_string_utf8( + env, script, reinterpret_cast(source), len + 1, &len); + if (status != napi_ok) { + delete[] source; + return status; + } + + hermes_run_script_flags flags{}; + flags.struct_size = sizeof(flags); + // Pass size = len + 1 so the trailing '\0' lets Hermes run the source + // zero-copy. Hermes takes ownership of the buffer and frees it via the + // finalizer below. + return hermes_run_script( + env, source, len + 1, + [](const uint8_t* data, size_t, void*) { + delete[] const_cast(data); + }, + nullptr, file, &flags, result); +#else return napi_run_script_source(env, script, file, result); +#endif +} + +#ifdef __ANDROID__ +// Hermes bytecode (HBC) magic, first 8 bytes little-endian +// (0x1F1903C103BC1FC6). Hermes stores raw HBC (no NativeScript container), so +// the whole file is the bytecode buffer. +static const uint8_t kHermesMagic[8] = {0xc6, 0x1f, 0xbc, 0x03, + 0xc1, 0x03, 0x19, 0x1f}; + +napi_status js_run_bytecode_file(napi_env env, const char* file, + napi_value* result) { + std::string path; + if (!nsbc::ResolvePath(file, path)) { + DEBUG_WRITE("[bytecode] Unable to resolve file: %s", path.c_str()); + return napi_cannot_run_js; + } + if (!nsbc::HasMagic(path, reinterpret_cast(kHermesMagic))) { + DEBUG_WRITE("[bytecode] Unable to find hermes header: %s", path.c_str()); + return napi_cannot_run_js; + } + + int length = 0; + auto data = tns::File::ReadBinary(path, length); + if (!data) return napi_cannot_run_js; + + DEBUG_WRITE("[bytecode] loading Hermes HBC bytecode: %s (%d bytes)", file, + length); + + hermes_bytecode_flags flags{}; + flags.struct_size = sizeof(flags); + // App modules live for the whole runtime lifetime, so keep the bytecode + // resident and let Hermes reference it zero-copy for faster loads. + flags.persistent = true; + // Hermes takes ownership of the buffer and frees it via the finalizer. + return hermes_run_bytecode( + env, static_cast(data), static_cast(length), + [](const uint8_t* d, size_t, void*) { delete[] const_cast(d); }, + nullptr, file, &flags, result); } +#else +napi_status js_run_bytecode_file(napi_env env, const char* file, + napi_value* result) { + // The Apple Hermes build does not expose the bytecode entry points. + return napi_cannot_run_js; +} +#endif napi_status js_execute_pending_jobs(napi_env env) { +#ifdef __ANDROID__ + JSR* jsr = JSR::FromEnv(env); + if (jsr == nullptr) { + return napi_invalid_arg; + } + jsr->rt->drainMicrotasks(); + return napi_ok; +#else bool result; return jsr_drain_microtasks(env, -1, &result); +#endif } napi_status js_get_engine_ptr(napi_env env, int64_t* engine_ptr) { @@ -125,7 +336,24 @@ napi_status js_cache_script(napi_env env, const char* source, napi_status js_run_cached_script(napi_env env, const char* file, napi_value script, void* cache, napi_value* result) { +#ifdef __ANDROID__ + int length = 0; + // tns::File::ReadBinary allocates with new uint8_t[length]. + auto data = tns::File::ReadBinary(file, length); + if (!data) { + return napi_cannot_run_js; + } + + hermes_bytecode_flags flags{}; + flags.struct_size = sizeof(flags); + // Hermes takes ownership of the buffer and frees it via the finalizer. + return hermes_run_bytecode( + env, static_cast(data), static_cast(length), + [](const uint8_t* d, size_t, void*) { delete[] const_cast(d); }, + nullptr, file, &flags, result); +#else return napi_ok; +#endif } napi_status js_get_runtime_version(napi_env env, napi_value* version) { @@ -149,12 +377,31 @@ extern "C" napi_status jsr_run_script(napi_env env, napi_value source, extern "C" napi_status jsr_drain_microtasks(napi_env env, int32_t max_count_hint, bool* result) { - auto itFound = JSR::env_to_jsr_cache.find(env); - if (itFound == JSR::env_to_jsr_cache.end() || result == nullptr) { + JSR* jsr = JSR::FromEnv(env); + if (jsr == nullptr || result == nullptr) { return napi_invalid_arg; } NapiScope scope(env, false); - *result = itFound->second->rt->drainMicrotasks(max_count_hint); + *result = false; + + // drainMicrotasks() is JSI, so it reports a failed job by *throwing* a C++ + // jsi::JSError (HermesRuntimeImpl::checkStatus -> throwPendingError). Callers + // here are C and Objective-C block contexts -- notably the CFRunLoop block in + // runtime/apple/Runtime.cpp -- and letting a C++ exception unwind through + // those aborts the process. Worse, building the JSError runs + // JSError::recordStackTrace against a runtime that is already in a thrown + // state, which is where this used to die. + // + // Catching converts the failure into a napi status, and consuming the + // exception is what returns the runtime to a usable state. + try { + *result = jsr->rt->drainMicrotasks(max_count_hint); + } catch (const facebook::jsi::JSError& e) { + return napi_pending_exception; + } catch (const facebook::jsi::JSIException& e) { + return napi_generic_failure; + } + return napi_ok; } diff --git a/NativeScript/napi/hermes/jsr.h b/NativeScript/napi/hermes/jsr.h index cb6221ab3..28b5f40a9 100644 --- a/NativeScript/napi/hermes/jsr.h +++ b/NativeScript/napi/hermes/jsr.h @@ -5,57 +5,90 @@ #ifndef TEST_APP_JSR_H #define TEST_APP_JSR_H +// Both platforms link the same Hermes build (scripts/download_hermes.sh +// installs the xcframework and the Android .so files from one release) and use +// the upstream C ABI: hermes_napi_create_env / hermes_run_script / +// hermes_run_bytecode, reaching the VM runtime through +// facebook::hermes::IHermes. +// +// Apple previously went through a NativeScript-local +// jsi::Runtime::createNodeApiEnv() hook. That is gone upstream, so the two +// entry paths have collapsed into one. + +#include +#include +#include + #include "hermes/hermes.h" #include "jsi/threadsafe.h" #include "jsr_common.h" -#include - class JSR { public: JSR(); std::unique_ptr runtime; - facebook::jsi::Runtime* rt; + // Both platforms reach IHermes through the concrete HermesRuntime. + facebook::hermes::HermesRuntime* rt; +#ifdef __ANDROID__ + // Depth of nested JS scopes entered from the host (see NapiScope). Hermes is + // configured with an explicit microtask queue, so promise jobs only run when + // we drain them; we drain once this returns to 0, i.e. when the native call + // stack has fully unwound back out of JS. + int jsEnterState = 0; +#endif std::recursive_mutex js_mutex; - static inline thread_local std::unordered_map lock_depth; - void lock() { - runtime->lock(); - js_mutex.lock(); - lock_depth[this] += 1; - } - void unlock() { - auto depth = lock_depth.find(this); - if (depth != lock_depth.end()) { - depth->second -= 1; - if (depth->second <= 0) { - lock_depth.erase(depth); - } - } - js_mutex.unlock(); - runtime->unlock(); - } - int currentLockDepth() const { - auto depth = lock_depth.find(const_cast(this)); - if (depth == lock_depth.end()) { - return 0; - } - return depth->second; - } + void lock(); + void unlock(); + int currentLockDepth() const; + + // Workers each build their own runtime and env on their own thread + // (WorkerImpl::BackgroundLooper), so this map is inserted into and erased + // from off the main thread while other threads are looking envs up. An + // unsynchronized rehash during insert frees the bucket list a concurrent + // find() is walking, which hands back a garbage JSR* and crashes deep inside + // the VM. Reach it only through these three accessors, which hold the mutex. + static JSR* FromEnv(napi_env env); + static void RegisterEnv(napi_env env, JSR* jsr); + static void UnregisterEnv(napi_env env); + private: static std::unordered_map env_to_jsr_cache; + static std::mutex env_to_jsr_mutex; }; int js_current_env_lock_depth(napi_env env); facebook::jsi::Runtime* js_get_jsi_runtime(napi_env env); -typedef struct napi_runtime__ { +// The Objective-C bridge (ffi/objc/hermes) only ever holds the jsi::Runtime& +// that JSI handed it, which is ThreadSafeRuntime::getUnsafeRuntime() -- the +// lock is not reachable through it. Native callbacks arrive on whatever thread +// the platform picks (an NSOperationQueue worker, a URLSession delegate +// queue), so the bridge needs a way back to the ThreadSafeRuntime guarding +// that runtime before it enters the VM. V8 has the same problem and solves it +// with v8::Locker; this is the Hermes equivalent. Returns null for a runtime +// this layer did not create. +// +// Lock through the returned JSR rather than its ThreadSafeRuntime directly: +// JSR::lock() also maintains the per-thread depth counter that +// js_current_env_lock_depth() reports, and callers such as +// shouldAvoidMainQueueSyncWhileHoldingHermesLock() in Timers.mm use that count +// to decide whether a synchronous hop to the main queue would deadlock. +JSR* js_jsr_for_runtime(facebook::jsi::Runtime* runtime); + +typedef struct jsr_ns_runtime__ { JSR* hermes; -} napi_runtime__; +} jsr_ns_runtime__; class NapiScope { public: explicit NapiScope(napi_env env, bool openHandle = true) : env_(env) { js_lock_env(env_); +#ifdef __ANDROID__ + jsr_ = JSR::FromEnv(env_); + if (jsr_) { + jsr_->jsEnterState++; + } +#endif if (openHandle) { napi_open_handle_scope(env_, &napiHandleScope_); } else { @@ -64,6 +97,20 @@ class NapiScope { } ~NapiScope() { +#ifdef __ANDROID__ + // Drain the microtask queue only when the outermost JS scope unwinds so + // that promise continuations (async/await) run — mirroring how a JS engine + // empties its job queue once control returns to the host. Draining at a + // nested depth would run continuations while JS is still on the stack. + // A throwing microtask must never escape a destructor. + if (jsr_ && --jsr_->jsEnterState <= 0) { + jsr_->jsEnterState = 0; + try { + js_execute_pending_jobs(env_); + } catch (...) { + } + } +#endif if (napiHandleScope_) { napi_close_handle_scope(env_, napiHandleScope_); } @@ -73,6 +120,9 @@ class NapiScope { private: napi_env env_; napi_handle_scope napiHandleScope_; +#ifdef __ANDROID__ + JSR* jsr_ = nullptr; +#endif }; #define JSEnterScope diff --git a/NativeScript/napi/hermes/node_api.h b/NativeScript/napi/hermes/node_api.h deleted file mode 100644 index 4ebfbd46d..000000000 --- a/NativeScript/napi/hermes/node_api.h +++ /dev/null @@ -1,270 +0,0 @@ -#ifndef SRC_NODE_API_H_ -#define SRC_NODE_API_H_ - -#if defined(BUILDING_NODE_EXTENSION) && !defined(NAPI_EXTERN) -#ifdef _WIN32 -// Building native addon against node -#define NAPI_EXTERN __declspec(dllimport) -#elif defined(__wasm__) -#define NAPI_EXTERN __attribute__((__import_module__("napi"))) -#endif -#endif -#include "js_native_api.h" -#include "node_api_types.h" - -struct uv_loop_s; // Forward declaration. - -#ifdef _WIN32 -#define NAPI_MODULE_EXPORT __declspec(dllexport) -#else -#ifdef __EMSCRIPTEN__ -#define NAPI_MODULE_EXPORT \ - __attribute__((visibility("default"))) __attribute__((used)) -#else -#define NAPI_MODULE_EXPORT __attribute__((visibility("default"))) -#endif -#endif - -#if defined(__GNUC__) -#define NAPI_NO_RETURN __attribute__((noreturn)) -#elif defined(_WIN32) -#define NAPI_NO_RETURN __declspec(noreturn) -#else -#define NAPI_NO_RETURN -#endif - -typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env, - napi_value exports); -typedef int32_t(NAPI_CDECL* node_api_addon_get_api_version_func)(void); - -// Used by deprecated registration method napi_module_register. -typedef struct napi_module { - int nm_version; - unsigned int nm_flags; - const char* nm_filename; - napi_addon_register_func nm_register_func; - const char* nm_modname; - void* nm_priv; - void* reserved[4]; -} napi_module; - -#define NAPI_MODULE_VERSION 1 - -#define NAPI_MODULE_INITIALIZER_X(base, version) \ - NAPI_MODULE_INITIALIZER_X_HELPER(base, version) -#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version - -#ifdef __wasm__ -#define NAPI_MODULE_INITIALIZER_BASE napi_register_wasm_v -#else -#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v -#endif - -#define NODE_API_MODULE_GET_API_VERSION_BASE node_api_module_get_api_version_v - -#define NAPI_MODULE_INITIALIZER \ - NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, NAPI_MODULE_VERSION) - -#define NODE_API_MODULE_GET_API_VERSION \ - NAPI_MODULE_INITIALIZER_X(NODE_API_MODULE_GET_API_VERSION_BASE, \ - NAPI_MODULE_VERSION) - -#define NAPI_MODULE_INIT() \ - EXTERN_C_START \ - NAPI_MODULE_EXPORT int32_t NODE_API_MODULE_GET_API_VERSION(void) { \ - return NAPI_VERSION; \ - } \ - NAPI_MODULE_EXPORT napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ - napi_value exports); \ - EXTERN_C_END \ - napi_value NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports) - -#define NAPI_MODULE(modname, regfunc) \ - NAPI_MODULE_INIT() { \ - return regfunc(env, exports); \ - } - -// Deprecated. Use NAPI_MODULE. -#define NAPI_MODULE_X(modname, regfunc, priv, flags) \ - NAPI_MODULE(modname, regfunc) - -EXTERN_C_START - -// Deprecated. Replaced by symbol-based registration defined by NAPI_MODULE -// and NAPI_MODULE_INIT macros. -NAPI_EXTERN void NAPI_CDECL napi_module_register(napi_module* mod); - -NAPI_EXTERN NAPI_NO_RETURN void NAPI_CDECL -napi_fatal_error(const char* location, - size_t location_len, - const char* message, - size_t message_len); - -// Methods for custom handling of async operations -NAPI_EXTERN napi_status NAPI_CDECL -napi_async_init(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_context* result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_async_destroy(napi_env env, napi_async_context async_context); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_make_callback(napi_env env, - napi_async_context async_context, - napi_value recv, - napi_value func, - size_t argc, - const napi_value* argv, - napi_value* result); - -// Methods to provide node::Buffer functionality with napi types -NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer(napi_env env, - size_t length, - void** data, - napi_value* result); -#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_external_buffer(napi_env env, - size_t length, - void* data, - node_api_basic_finalize finalize_cb, - void* finalize_hint, - napi_value* result); -#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED - -#if NAPI_VERSION >= 10 - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_create_buffer_from_arraybuffer(napi_env env, - napi_value arraybuffer, - size_t byte_offset, - size_t byte_length, - napi_value* result); -#endif // NAPI_VERSION >= 10 - -NAPI_EXTERN napi_status NAPI_CDECL napi_create_buffer_copy(napi_env env, - size_t length, - const void* data, - void** result_data, - napi_value* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_is_buffer(napi_env env, - napi_value value, - bool* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_buffer_info(napi_env env, - napi_value value, - void** data, - size_t* length); - -// Methods to manage simple async operations -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_async_work(napi_env env, - napi_value async_resource, - napi_value async_resource_name, - napi_async_execute_callback execute, - napi_async_complete_callback complete, - void* data, - napi_async_work* result); -NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, - napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_basic_env env, - napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL -napi_cancel_async_work(node_api_basic_env env, napi_async_work work); - -// version management -NAPI_EXTERN napi_status NAPI_CDECL napi_get_node_version( - node_api_basic_env env, const napi_node_version** version); - -#if NAPI_VERSION >= 2 - -// Return the current libuv event loop for a given environment -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_uv_event_loop(node_api_basic_env env, struct uv_loop_s** loop); - -#endif // NAPI_VERSION >= 2 - -#if NAPI_VERSION >= 3 - -NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env, - napi_value err); - -NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook( - node_api_basic_env env, napi_cleanup_hook fun, void* arg); - -NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook( - node_api_basic_env env, napi_cleanup_hook fun, void* arg); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_open_callback_scope(napi_env env, - napi_value resource_object, - napi_async_context context, - napi_callback_scope* result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_close_callback_scope(napi_env env, napi_callback_scope scope); - -#endif // NAPI_VERSION >= 3 - -#if NAPI_VERSION >= 4 - -// Calling into JS from other threads -NAPI_EXTERN napi_status NAPI_CDECL -napi_create_threadsafe_function(napi_env env, - napi_value func, - napi_value async_resource, - napi_value async_resource_name, - size_t max_queue_size, - size_t initial_thread_count, - void* thread_finalize_data, - napi_finalize thread_finalize_cb, - void* context, - napi_threadsafe_function_call_js call_js_cb, - napi_threadsafe_function* result); - -NAPI_EXTERN napi_status NAPI_CDECL napi_get_threadsafe_function_context( - napi_threadsafe_function func, void** result); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_call_threadsafe_function(napi_threadsafe_function func, - void* data, - napi_threadsafe_function_call_mode is_blocking); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_acquire_threadsafe_function(napi_threadsafe_function func); - -NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function( - napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); - -NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function( - node_api_basic_env env, napi_threadsafe_function func); - -NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function( - node_api_basic_env env, napi_threadsafe_function func); - -#endif // NAPI_VERSION >= 4 - -#if NAPI_VERSION >= 8 - -NAPI_EXTERN napi_status NAPI_CDECL -napi_add_async_cleanup_hook(node_api_basic_env env, - napi_async_cleanup_hook hook, - void* arg, - napi_async_cleanup_hook_handle* remove_handle); - -NAPI_EXTERN napi_status NAPI_CDECL -napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle); - -#endif // NAPI_VERSION >= 8 - -#if NAPI_VERSION >= 9 - -NAPI_EXTERN napi_status NAPI_CDECL -node_api_get_module_file_name(node_api_basic_env env, const char** result); - -#endif // NAPI_VERSION >= 9 - -EXTERN_C_END - -#endif // SRC_NODE_API_H_ diff --git a/NativeScript/napi/hermes/node_api_types.h b/NativeScript/napi/hermes/node_api_types.h deleted file mode 100644 index 9c2f03f4d..000000000 --- a/NativeScript/napi/hermes/node_api_types.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef SRC_NODE_API_TYPES_H_ -#define SRC_NODE_API_TYPES_H_ - -#include "js_native_api_types.h" - -typedef struct napi_callback_scope__* napi_callback_scope; -typedef struct napi_async_context__* napi_async_context; -typedef struct napi_async_work__* napi_async_work; - -#if NAPI_VERSION >= 3 -typedef void(NAPI_CDECL* napi_cleanup_hook)(void* arg); -#endif // NAPI_VERSION >= 3 - -#if NAPI_VERSION >= 4 -typedef struct napi_threadsafe_function__* napi_threadsafe_function; -#endif // NAPI_VERSION >= 4 - -#if NAPI_VERSION >= 4 -typedef enum { - napi_tsfn_release, - napi_tsfn_abort -} napi_threadsafe_function_release_mode; - -typedef enum { - napi_tsfn_nonblocking, - napi_tsfn_blocking -} napi_threadsafe_function_call_mode; -#endif // NAPI_VERSION >= 4 - -typedef void(NAPI_CDECL* napi_async_execute_callback)(napi_env env, void* data); -typedef void(NAPI_CDECL* napi_async_complete_callback)(napi_env env, - napi_status status, - void* data); -#if NAPI_VERSION >= 4 -typedef void(NAPI_CDECL* napi_threadsafe_function_call_js)( - napi_env env, napi_value js_callback, void* context, void* data); -#endif // NAPI_VERSION >= 4 - -typedef struct { - uint32_t major; - uint32_t minor; - uint32_t patch; - const char* release; -} napi_node_version; - -#if NAPI_VERSION >= 8 -typedef struct napi_async_cleanup_hook_handle__* napi_async_cleanup_hook_handle; -typedef void(NAPI_CDECL* napi_async_cleanup_hook)( - napi_async_cleanup_hook_handle handle, void* data); -#endif // NAPI_VERSION >= 8 - -#endif // SRC_NODE_API_TYPES_H_ diff --git a/NativeScript/napi/jsc/jsc-api.h b/NativeScript/napi/jsc/jsc-api.h deleted file mode 100644 index 36e91f1ae..000000000 --- a/NativeScript/napi/jsc/jsc-api.h +++ /dev/null @@ -1,108 +0,0 @@ -// -// Created by Ammar Ahmed on 01/12/2024. -// - -#ifndef TEST_APP_JSC_API_H -#define TEST_APP_JSC_API_H - -#include - -#include -#include -#include -#include -#include - -#include "js_native_api.h" -#include "js_native_api_types.h" - -extern "C" bool nativescript_jsc_try_unwrap_native(napi_env env, - napi_value value, - void** result); - -struct napi_env__ { - JSGlobalContextRef context{}; - JSValueRef last_exception{}; - napi_extended_error_info last_error{nullptr, nullptr, 0, napi_ok}; - std::unordered_set active_ref_values{}; - std::unordered_map wrapper_info_cache{}; - std::list strong_refs{}; - void* instance_data{}; - napi_finalize instance_data_finalize_cb; - void* instance_data_finalize_hint; - - JSValueRef constructor_info_symbol{}; - JSValueRef function_info_symbol{}; - JSValueRef reference_info_symbol{}; - JSValueRef wrapper_info_symbol{}; - - const std::thread::id thread_id{std::this_thread::get_id()}; - - napi_env__(JSGlobalContextRef context) : context{context} { - napi_envs[context] = this; - JSGlobalContextRetain(context); - init_symbol(constructor_info_symbol, "NS_ConstructorInfo"); - init_symbol(function_info_symbol, "NS_FunctionInfo"); - init_symbol(reference_info_symbol, "NS_ReferenceInfo"); - init_symbol(wrapper_info_symbol, "NS_WrapperInfo"); - } - - ~napi_env__() { - deinit_refs(); - deinit_symbol(wrapper_info_symbol); - deinit_symbol(reference_info_symbol); - deinit_symbol(function_info_symbol); - deinit_symbol(constructor_info_symbol); - napi_envs.erase(context); - JSGlobalContextRelease(context); - } - - static napi_env get(JSGlobalContextRef context) { - auto it = napi_envs.find(context); - if (it != napi_envs.end()) { - return it->second; - } else { - return nullptr; - } - } - - private: - static inline std::unordered_map napi_envs{}; - void deinit_refs(); - void init_symbol(JSValueRef& symbol, const char* description); - void deinit_symbol(JSValueRef symbol); -}; - -#define RETURN_STATUS_IF_FALSE(env, condition, status) \ - do { \ - if (!(condition)) { \ - return napi_set_last_error((env), (status)); \ - } \ - } while (0) - -#define CHECK_ENV(env) \ - do { \ - if ((env) == nullptr) { \ - return napi_invalid_arg; \ - } \ - } while (0) - -#define CHECK_ARG(env, arg) \ - RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg) - -#define CHECK_JSC(env, exception) \ - do { \ - if ((exception) != nullptr) { \ - return napi_set_exception(env, exception); \ - } \ - } while (0) - -// This does not call napi_set_last_error because the expression -// is assumed to be a NAPI function call that already did. -#define CHECK_NAPI(expr) \ - do { \ - napi_status status = (expr); \ - if (status != napi_ok) return status; \ - } while (0) - -#endif // TEST_APP_JSC_API_H diff --git a/NativeScript/napi/jsc/jsr.cpp b/NativeScript/napi/jsc/jsr.cpp index aa265fe96..93b224300 100644 --- a/NativeScript/napi/jsc/jsr.cpp +++ b/NativeScript/napi/jsc/jsr.cpp @@ -1,91 +1,372 @@ #include "jsr.h" -napi_status js_create_runtime(napi_runtime *runtime) { - if (!runtime) return napi_invalid_arg; - *runtime = (napi_runtime) JSGlobalContextCreateInGroup(nullptr, nullptr); - return napi_ok; +#ifdef __ANDROID__ +// Both of these are jsc-android specific. JSContextRefPrivate.h is JSC SPI -- +// Apple exports JSGlobalContextSetUnhandledRejectionCallback from +// JavaScriptCore.tbd but ships no public header for it. JSBytecodeCache.h does +// not exist on Apple at all: the on-disk program bytecode cache is added by the +// jsc-android build via jsc_android_bytecode_cache.patch. So the whole cache +// path below, and the rejection tracker, are Android-only; Apple runs from +// source and reports rejections through its own FFI layer. +#include +#include +#include +#include + +#include +#include +#include + +#include "NativeScriptAssert.h" +#endif + +#ifdef __ANDROID__ +// Native trampoline for JSC's unhandled-promise-rejection callback. JSC invokes +// it with (promise, reason); we forward to the JS-side +// globalThis.onUnhandledPromiseRejectionTracker (installed by ts_helpers.js), +// mirroring how the V8 path routes rejections. +static napi_value JscUnhandledRejectionCallback(napi_env env, + napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + napi_value global, tracker; + napi_get_global(env, &global); + napi_get_named_property(env, global, "onUnhandledPromiseRejectionTracker", + &tracker); + + napi_valuetype type; + napi_typeof(env, tracker, &type); + if (type == napi_function) { + napi_call_function(env, global, tracker, argc, args, nullptr); + } + return nullptr; +} +#endif // __ANDROID__ + +napi_status js_create_runtime(jsr_ns_runtime* runtime) { + if (!runtime) return napi_invalid_arg; + *runtime = (jsr_ns_runtime)JSGlobalContextCreateInGroup(nullptr, nullptr); + return napi_ok; } -napi_status js_create_napi_env(napi_env* env, napi_runtime runtime) { - if (env == nullptr) return napi_invalid_arg; +napi_status js_create_napi_env(napi_env* env, jsr_ns_runtime runtime) { + if (env == nullptr) return napi_invalid_arg; - *env = new napi_env__((JSGlobalContextRef) runtime); - JSGlobalContextRelease((JSGlobalContextRef) runtime); + *env = new napi_env__((JSGlobalContextRef)runtime); + JSGlobalContextRelease((JSGlobalContextRef)runtime); - napi_value gc; - napi_create_function(*env, "gc", strlen("gc"), [](napi_env env, napi_callback_info info) -> napi_value { + napi_value gc; + napi_create_function( + *env, "gc", strlen("gc"), + [](napi_env env, napi_callback_info info) -> napi_value { + // JSGarbageCollect only hints: JSC may defer or decline the collection, + // so an unreachable object need not be reclaimed by the time this + // returns. That is how the engine is designed rather than a defect, and + // the specs that observe reclamation account for it instead of forcing + // a collection through JSC's debug-only synchronous entry point. JSGarbageCollect(env->context); napi_value undefined; napi_get_undefined(env, &undefined); return undefined; - }, nullptr, &gc); - napi_value global; - napi_get_global(*env, &global); - napi_set_named_property(*env, global, "gc", gc); + }, + nullptr, &gc); + napi_value global; + napi_get_global(*env, &global); + napi_set_named_property(*env, global, "gc", gc); +#ifdef __ANDROID__ + // Report unhandled promise rejections. JSC keeps the callback alive (it is + // stored on and marked by the global object), so no extra protection is + // needed. JSC only surfaces the "unhandled" event, not a later "handled" + // retraction, so the JS tracker treats every call as unhandled. + napi_value rejectionCallback; + napi_create_function(*env, "onUnhandledRejection", NAPI_AUTO_LENGTH, + JscUnhandledRejectionCallback, nullptr, + &rejectionCallback); + JSValueRef rejectionException = nullptr; + JSGlobalContextSetUnhandledRejectionCallback( + (*env)->context, reinterpret_cast(rejectionCallback), + &rejectionException); +#endif - return napi_ok; - + return napi_ok; } -napi_status js_set_runtime_flags(const char* flags) { - return napi_ok; -} +napi_status js_set_runtime_flags(const char* flags) { return napi_ok; } napi_status js_lock_env(napi_env env) { - return napi_ok; + if (env == nullptr) return napi_invalid_arg; + env->js_mutex.lock(); + return napi_ok; } napi_status js_unlock_env(napi_env env) { - return napi_ok; + if (env == nullptr) return napi_invalid_arg; + env->js_mutex.unlock(); + return napi_ok; } napi_status js_free_napi_env(napi_env env) { - if (env == nullptr) return napi_invalid_arg; - js_run_env_cleanup_hooks(env); - delete env; - return napi_ok; + if (env == nullptr) return napi_invalid_arg; +#ifndef __ANDROID__ + // Defined in the Apple runtime (ThreadSafeFunction.mm); Android has no + // equivalent, same as the quickjs and hermes backends. + js_run_env_cleanup_hooks(env); +#endif + delete env; + return napi_ok; } -napi_status js_free_runtime(napi_runtime runtime) { -// JSContextGroupRelease((JSContextGroupRef) runtime); - return napi_ok; +napi_status js_free_runtime(jsr_ns_runtime runtime) { + // JSContextGroupRelease((JSContextGroupRef) runtime); + return napi_ok; } -napi_status js_execute_script(napi_env env, - napi_value script, - const char *file, - napi_value *result) { +#ifdef __ANDROID__ +// --------------------------------------------------------------------------- +// Bytecode code cache +// +// JSC has no ahead-of-time bytecode format, so — exactly like the V8 path — we +// cache the engine's own serialized bytecode next to each source file and reuse +// it on later launches. The heavy lifting (serialize / validate / +// run-from-cache) lives in JSC behind the JSBytecodeCache.h C API; here we only +// manage the cache file: where it lives, when it is stale, and publishing it +// atomically. +// +// The .cache file is stamped with its source file's mtime; a mismatch means the +// source changed and the cache is ignored (and JSC re-validates internally +// too). +// --------------------------------------------------------------------------- - return napi_run_script_source(env, script, file, result); +// Turn a script's source URL into the on-disk JS path we cache next to. Returns +// false for sources that aren't real files (e.g. the synthetic +// ""), which must never be cached. +static bool NormalizeScriptPath(const char* file, std::string& out) { + if (file == nullptr) return false; + std::string f(file); + static const std::string scheme = "file://"; + if (f.rfind(scheme, 0) == 0) { + out = f.substr(scheme.size()); + } else if (!f.empty() && f[0] == '/') { + out = f; // already a plain absolute path + } else { + return false; + } + return !out.empty(); +} + +// Publish a freshly written temp cache file: stamp it with the source's mtime +// so js_run_cached_script accepts it, then atomically rename it into place. Any +// failure leaves no cache behind. Best-effort. +static void PublishCacheFile(const std::string& tmpPath, + const std::string& cachePath, + const std::string& fsPath) { + struct stat srcStat; + struct utimbuf new_times; + new_times.actime = time(nullptr); + new_times.modtime = + (stat(fsPath.c_str(), &srcStat) == 0) ? srcStat.st_mtime : time(nullptr); + utime(tmpPath.c_str(), &new_times); + if (rename(tmpPath.c_str(), cachePath.c_str()) != 0) { + DEBUG_WRITE("[code-cache] failed to publish cache file: %s", + cachePath.c_str()); + remove(tmpPath.c_str()); + return; + } + DEBUG_WRITE("[code-cache] wrote JSC bytecode cache: %s", cachePath.c_str()); } -napi_status js_execute_pending_jobs(napi_env env) { - return napi_ok; +// Serialize `sourceStr` to a bytecode cache published next to `fsPath`, stamped +// with the source's mtime. Writes to a temp file first then renames, so a crash +// or a concurrent reader never sees a half-written cache. Best-effort: any +// failure just leaves no cache behind. Returns true if a cache was published. +static bool WriteCacheForSource(napi_env env, JSStringRef sourceStr, + JSStringRef sourceUrl, + const std::string& fsPath) { + auto cachePath = fsPath + ".cache"; + auto tmpPath = cachePath + ".tmp"; + + JSStringRef errorMessage = nullptr; + bool ok = JSWriteBytecodeCacheForProgram(env->context, sourceStr, sourceUrl, + tmpPath.c_str(), &errorMessage); + if (!ok) { + if (errorMessage) { + size_t maxSize = JSStringGetMaximumUTF8CStringSize(errorMessage); + std::string msg(maxSize, '\0'); + JSStringGetUTF8CString(errorMessage, &msg[0], maxSize); + DEBUG_WRITE("[code-cache] failed to generate bytecode for %s: %s", + fsPath.c_str(), msg.c_str()); + JSStringRelease(errorMessage); + } else { + DEBUG_WRITE( + "[code-cache] failed to generate bytecode for %s (no error message)", + fsPath.c_str()); + } + remove(tmpPath.c_str()); + return false; + } + + PublishCacheFile(tmpPath, cachePath, fsPath); + return true; } +#endif // __ANDROID__ -napi_status js_get_engine_ptr(napi_env env, int64_t *engine_ptr) { - *engine_ptr = (int64_t) 0; - return napi_ok; +napi_status js_execute_script(napi_env env, napi_value script, const char* file, + napi_value* result) { +#ifdef __ANDROID__ + // Fast path: run JSC's on-disk bytecode cache when one is present and + // current. js_run_cached_script returns napi_cannot_run_js on a cache miss + // (so we fall back to source), or any other status when it actually ran the + // script (success or a thrown exception) — in which case we must NOT run it + // again. + napi_status status = js_run_cached_script(env, file, script, nullptr, result); + if (status != napi_cannot_run_js) { + return status; + } + + // Cold path: publish a bytecode cache for the next launch, then run. When the + // cache is written we run straight from it (JSC decodes the bytecode instead + // of parsing again), so the source is compiled only once even on this first + // launch; otherwise we fall back to running the source directly. + std::string fsPath; + if (NormalizeScriptPath(file, fsPath)) { + JSValueRef exception = nullptr; + JSStringRef sourceStr = JSValueToStringCopy( + env->context, reinterpret_cast(script), &exception); + if (sourceStr != nullptr && exception == nullptr) { + JSStringRef sourceUrl = JSStringCreateWithUTF8CString(file); + DEBUG_WRITE("[code-cache] compiling from source (cold): %s", file); + bool wrote = WriteCacheForSource(env, sourceStr, sourceUrl, fsPath); + JSStringRelease(sourceUrl); + JSStringRelease(sourceStr); + if (wrote) { + napi_status cached = + js_run_cached_script(env, file, script, nullptr, result); + if (cached != napi_cannot_run_js) { + return cached; + } + } + } else if (sourceStr != nullptr) { + JSStringRelease(sourceStr); + } + } +#endif // __ANDROID__ + + return napi_run_script_source(env, script, file, result); } -napi_status js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) { - return napi_ok; +napi_status js_execute_pending_jobs(napi_env env) { return napi_ok; } + +napi_status js_get_engine_ptr(napi_env env, int64_t* engine_ptr) { + *engine_ptr = (int64_t)0; + return napi_ok; } -napi_status js_cache_script(napi_env env, const char *source, const char *file) { - return napi_ok; +napi_status js_adjust_external_memory(napi_env env, int64_t changeInBytes, + int64_t* externalMemory) { + return napi_ok; } -napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache, - napi_value *result) { +// Compile `source` and publish a bytecode cache next to `file`. Retained for +// the jsr interface; the hot path (js_execute_script) caches inline. No-op for +// synthetic / non-file sources. +napi_status js_cache_script(napi_env env, const char* source, + const char* file) { +#ifdef __ANDROID__ + std::string fsPath; + if (!NormalizeScriptPath(file, fsPath)) { return napi_ok; + } + + JSStringRef sourceStr = JSStringCreateWithUTF8CString(source); + JSStringRef sourceUrl = JSStringCreateWithUTF8CString(file); + WriteCacheForSource(env, sourceStr, sourceUrl, fsPath); + JSStringRelease(sourceUrl); + JSStringRelease(sourceStr); +#endif // __ANDROID__ + return napi_ok; } +napi_status js_run_cached_script(napi_env env, const char* file, + napi_value script, void* cache, + napi_value* result) { + if (env == nullptr || script == nullptr) return napi_invalid_arg; +#ifndef __ANDROID__ + return napi_cannot_run_js; // no bytecode cache API on Apple's JSC +#else + + std::string fsPath; + if (!NormalizeScriptPath(file, fsPath)) { + return napi_cannot_run_js; // synthetic / non-file source: no cache + } + + auto cachePath = fsPath + ".cache"; + struct stat cacheStat; + if (stat(cachePath.c_str(), &cacheStat) != 0) { + DEBUG_WRITE("[code-cache] miss (no cache on disk): %s", fsPath.c_str()); + return napi_cannot_run_js; // no cache written yet + } + struct stat srcStat; + if (stat(fsPath.c_str(), &srcStat) == 0 && + srcStat.st_mtime != cacheStat.st_mtime) { + // Source changed since the cache was written — ignore the stale cache. + DEBUG_WRITE("[code-cache] miss (source newer than cache): %s", + fsPath.c_str()); + return napi_cannot_run_js; + } + + JSValueRef exception = nullptr; + JSStringRef sourceStr = JSValueToStringCopy( + env->context, reinterpret_cast(script), &exception); + if (sourceStr == nullptr || exception != nullptr) { + if (sourceStr) JSStringRelease(sourceStr); + return napi_cannot_run_js; + } + JSStringRef sourceUrl = JSStringCreateWithUTF8CString(file); + + bool cacheRejected = false; + JSValueRef exc = nullptr; + JSValueRef ret = JSEvaluateProgramWithBytecodeCacheFile( + env->context, sourceStr, sourceUrl, cachePath.c_str(), nullptr, + &cacheRejected, &exc); + JSStringRelease(sourceUrl); + JSStringRelease(sourceStr); + + if (cacheRejected) { + // The cache was stale/incompatible; JSC did NOT run anything. Report a + // miss so the caller runs the source and refreshes the cache. + DEBUG_WRITE("[code-cache] miss (JSC rejected cache as incompatible): %s", + fsPath.c_str()); + return napi_cannot_run_js; + } + + if (exc != nullptr) { + // The script threw while executing. This is a real run — surface the + // exception; the caller must NOT execute the source a second time. + return napi_set_pending_exception(env, exc); + } + + DEBUG_WRITE("[code-cache] loaded JSC bytecode cache: %s", fsPath.c_str()); + if (result != nullptr) { + *result = reinterpret_cast(const_cast(ret)); + } + return napi_ok; +#endif // __ANDROID__ +} + +napi_status js_run_bytecode_file(napi_env env, const char* file, + napi_value* result) { + // JSC has no compile-time bytecode format (it caches serialized bytecode at + // runtime instead, see js_cache_script/js_run_cached_script); always fall + // back to source. + return napi_cannot_run_js; +} napi_status js_get_runtime_version(napi_env env, napi_value* version) { - napi_create_string_utf8(env, "JSC", NAPI_AUTO_LENGTH, version); + napi_create_string_utf8(env, "JSC", NAPI_AUTO_LENGTH, version); - return napi_ok; + return napi_ok; } diff --git a/NativeScript/napi/jsc/jsr.h b/NativeScript/napi/jsc/jsr.h index 3bbc53130..d7ab99c8b 100644 --- a/NativeScript/napi/jsc/jsr.h +++ b/NativeScript/napi/jsc/jsr.h @@ -5,28 +5,31 @@ #ifndef TEST_APP_JSR_H #define TEST_APP_JSR_H -#include "jsr_common.h" #include "jsc-api.h" - -typedef struct napi_runtime__ *napi_runtime; +#include "jsr_common.h" class NapiScope { -public: - explicit NapiScope(napi_env env, bool openHandle = true) - : env_(env) - { -// napi_open_handle_scope(env_, &napiHandleScope_); - } - - ~NapiScope() { -// napi_close_handle_scope(env_, napiHandleScope_); - } - -private: - napi_env env_; - napi_handle_scope napiHandleScope_; + public: + explicit NapiScope(napi_env env, bool openHandle = true) : env_(env) { + // Serialize this host->JS entry against all other threads + // (background-thread JNI callbacks, timers, workers). The lock is a per-env + // recursive mutex, so a nested NapiScope on the same thread re-enters + // rather than deadlocking. JSC drains its own microtask queue when the + // outermost API call returns, so unlike QuickJS we don't drain jobs here. + js_lock_env(env_); + // napi_open_handle_scope(env_, &napiHandleScope_); + } + + ~NapiScope() { + // napi_close_handle_scope(env_, napiHandleScope_); + js_unlock_env(env_); + } + + private: + napi_env env_; + napi_handle_scope napiHandleScope_; }; #define JSEnterScope -#endif //TEST_APP_JSR_H +#endif // TEST_APP_JSR_H diff --git a/NativeScript/napi/primjs/jsr.cpp b/NativeScript/napi/primjs/jsr.cpp new file mode 100644 index 000000000..2005428d3 --- /dev/null +++ b/NativeScript/napi/primjs/jsr.cpp @@ -0,0 +1,179 @@ +#include "napi_env_quickjs.h" +#include "napi_env.h" +#include "jsr.h" +#include "bytecode_container.h" +#include "File.h" +#include "NativeScriptAssert.h" +#include + +JSR::JSR() = default; +tns::SimpleMap JSR::env_to_jsr_cache; + +struct jsr_ns_runtime__ { + LEPUSRuntime* runtime; + LEPUSContext* context; +}; + +static LEPUSValue gc_function(LEPUSContext *ctx, LEPUSValueConst this_val, int argc, LEPUSValueConst *argv) { + LEPUSRuntime *rt = LEPUS_GetRuntime(ctx); + LEPUS_RunGC(rt); + return LEPUS_UNDEFINED; +} + +napi_status js_create_runtime(jsr_ns_runtime *runtime) { + auto _runtime = new jsr_ns_runtime__(); + LEPUSRuntime* rt = LEPUS_NewRuntimeWithMode(0); + LEPUS_SetRuntimeInfo(rt, "Lynx_LepusNG"); + _runtime->context = LEPUS_NewContext(rt); + LEPUS_SetMaxStackSize(_runtime->context, 1024 * 1024 * 1024); + _runtime->runtime = rt; + *runtime = _runtime; + + DEBUG_WRITE_FORCE("[primjs] GC mode: %d, template interpreter (use_primjs): %d", + LEPUS_IsGCModeRT(rt) ? 1 : 0, + LEPUS_IsPrimjsEnabled(rt) ? 1 : 0); + + LEPUSValue global_obj = LEPUS_GetGlobalObject(_runtime->context); + LEPUSValue gc_func = LEPUS_NewCFunction(_runtime->context, gc_function, "gc", 0); + LEPUS_SetPropertyStr(_runtime->context, global_obj, "gc", gc_func); +// LEPUS_FreeValue(_runtime->context, gc_func); +// LEPUS_FreeValue(_runtime->context, global_obj); + + return napi_ok; +} +napi_status js_create_napi_env(napi_env *env, jsr_ns_runtime runtime) { + *env = napi_new_env(); + napi_attach_quickjs((*env), runtime->context); + JSR::env_to_jsr_cache.Insert((*env), new JSR()); + return napi_ok; +} + +napi_status js_set_runtime_flags(const char *flags) { + return napi_ok; +} + +napi_status js_lock_env(napi_env env) { + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->lock(); + return napi_ok; +} + +napi_status js_unlock_env(napi_env env) { + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->unlock(); + + return napi_ok; +} + +napi_status js_free_napi_env(napi_env env) { + JSR* jsr = JSR::env_to_jsr_cache.Get(env); + delete jsr; + JSR::env_to_jsr_cache.Remove(env); + napi_detach_quickjs(env); + return napi_ok; +} + +napi_status js_free_runtime(jsr_ns_runtime runtime) { + LEPUS_FreeContext(runtime->context); + LEPUS_FreeRuntime(runtime->runtime); + return napi_ok; +} + +static const char *kBytecodeMagic = "NSBCPJS"; // 7 chars + NUL = 8-byte magic + +napi_status js_run_bytecode_file(napi_env env, const char *file, napi_value *result) { + std::string path; + if (!nsbc::ResolvePath(file, path)) { + DEBUG_WRITE("[bytecode] Unable to resolve file: %s", path.c_str()); + return napi_cannot_run_js; + } + if (!nsbc::HasMagic(path, kBytecodeMagic)) { + DEBUG_WRITE("[bytecode] Unable to find PrimJS header: %s", path.c_str()); + return napi_cannot_run_js; + } + + int length = 0; + auto data = tns::File::ReadBinary(path, length); + if (!data) return napi_cannot_run_js; + if (static_cast(length) <= nsbc::kHeaderLen) { + delete[] static_cast(data); + return napi_cannot_run_js; + } + + DEBUG_WRITE("[bytecode] loading PrimJS bytecode: %s (%d bytes)", file, length); + + LEPUSContext *ctx = napi_get_env_context_quickjs(env); + // LEPUS_ReadObject copies what it needs, so the buffer can be freed after. + LEPUSValue fun_obj = LEPUS_ReadObject( + ctx, static_cast(data) + nsbc::kHeaderLen, + static_cast(length) - nsbc::kHeaderLen, LEPUS_READ_OBJ_BYTECODE); + delete[] static_cast(data); + + // Errors return napi_pending_exception (not napi_cannot_run_js): the file IS + // bytecode, so surface the error instead of falling back to source. The + // exception stays pending in the context so napi_is_exception_pending picks + // it up at the call site. + if (LEPUS_IsException(fun_obj)) { + return napi_pending_exception; + } + + // LEPUS_EvalFunction consumes fun_obj; the completion value is the module + // wrapper function. + LEPUSValue eval_result = LEPUS_EvalFunction(ctx, fun_obj, LEPUS_UNDEFINED); + if (LEPUS_IsException(eval_result)) { + return napi_pending_exception; + } + + // napi_quickjs_value_to_js_value takes ownership of eval_result. + *result = napi_quickjs_value_to_js_value(env, eval_result); + return napi_ok; +} + +napi_status js_execute_script(napi_env env, + napi_value script, + const char *file, + napi_value *result) { + DEBUG_WRITE("[script] loading script: %s", file); + + // PrimJS exposes napi_run_script as a raw (source, length, filename) entry + // point, which lets us pass the script source and its filename directly + // instead of round-tripping through napi_run_script_source. + size_t length = 0; + napi_status status = napi_get_value_string_utf8(env, script, nullptr, 0, &length); + if (status != napi_ok) { + return status; + } + + std::string source(length + 1, '\0'); + status = napi_get_value_string_utf8(env, script, &source[0], length + 1, &length); + if (status != napi_ok) { + return status; + } + + return napi_run_script(env, source.c_str(), length, file, result); +} + +napi_status js_execute_pending_jobs(napi_env env) { + return primjs_execute_pending_jobs(env); +} + +napi_status +js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) { + napi_adjust_external_memory(env, changeInBytes, externalMemory); + return napi_ok; +} + +napi_status js_cache_script(napi_env env, const char *source, const char *file) { + return napi_ok; +} + +napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache, + napi_value *result) { + return napi_ok; +} + + +napi_status js_get_runtime_version(napi_env env, napi_value *version) { + napi_create_string_utf8(env, "PrimJS", NAPI_AUTO_LENGTH, version); + return napi_ok; +} diff --git a/NativeScript/napi/primjs/jsr.h b/NativeScript/napi/primjs/jsr.h new file mode 100644 index 000000000..a3345e20f --- /dev/null +++ b/NativeScript/napi/primjs/jsr.h @@ -0,0 +1,76 @@ +// +// Created by Ammar Ahmed on 01/12/2024. +// + +#ifndef TEST_APP_JSR_H +#define TEST_APP_JSR_H +#include "jsr_common.h" +#include "napi_env_quickjs.h" +#include "mutex" +#include +#include "ConcurrentMap.h" + +class JSR { +public: + JSR(); + std::recursive_mutex js_mutex; + // Depth of nested JS scopes entered from the host (see NapiScope). We drain + // the pending-job (microtask) queue once this returns to 0, i.e. when the + // native call stack has fully unwound back out of JS. + int jsEnterState = 0; + void lock() { + js_mutex.lock(); + } + void unlock() { + js_mutex.unlock(); + } + + static tns::SimpleMap env_to_jsr_cache; +}; + +class NapiScope { +public: + explicit NapiScope(napi_env env, bool open_handle = true) + : env_(env) + { + js_lock_env(env_); + // TODO: UPDATE STACK TOP HERE + jsr_ = JSR::env_to_jsr_cache.Get(env_); + if (jsr_) { + jsr_->jsEnterState++; + } + if (open_handle) { + napi_open_handle_scope(env_, &napiHandleScope_); + } else { + napiHandleScope_ = nullptr; + } + } + + ~NapiScope() { + // Drain the microtask queue only when the outermost JS scope unwinds so + // that promise continuations (async/await) run — mirroring how a JS + // engine empties its job queue once control returns to the host. + // Draining at a nested depth would run continuations while JS is still + // on the stack. A throwing job must never escape a destructor. + if (jsr_ && --jsr_->jsEnterState <= 0) { + jsr_->jsEnterState = 0; + try { + js_execute_pending_jobs(env_); + } catch (...) { + } + } + if (napiHandleScope_) { + napi_close_handle_scope(env_, napiHandleScope_); + } + js_unlock_env(env_); + } + +private: + napi_env env_; + napi_handle_scope napiHandleScope_; + JSR* jsr_ = nullptr; +}; + +#define JSEnterScope + +#endif //TEST_APP_JSR_H diff --git a/NativeScript/napi/quickjs/jsr.cpp b/NativeScript/napi/quickjs/jsr.cpp index 19cabf08a..8f217e7c1 100644 --- a/NativeScript/napi/quickjs/jsr.cpp +++ b/NativeScript/napi/quickjs/jsr.cpp @@ -1,76 +1,147 @@ #include "jsr.h" + +#ifdef __ANDROID__ +#include "File.h" +#include "NativeScriptAssert.h" +#include "bytecode_container.h" +#endif + #include "quicks-runtime.h" JSR::JSR() = default; -tns::SimpleMap JSR::env_to_jsr_cache; - -napi_status js_create_runtime(napi_runtime *runtime) { - return qjs_create_runtime(runtime); -} -napi_status js_create_napi_env(napi_env *env, napi_runtime runtime) { - napi_status status = qjs_create_napi_env(env, runtime); - JSR::env_to_jsr_cache.Insert((*env), new JSR()); +tns::SimpleMap JSR::env_to_jsr_cache; + +// Engine-agnostic runtime handle for the jsr layer. QuickJS keeps its own +// napi_runtime (the real engine runtime defined in quickjs-api.c); this wrapper +// just points at it so the jsr API can speak jsr_ns_runtime while +// quickjs-api.c / quicks-runtime.h stay unchanged. +struct jsr_ns_runtime__ { + napi_runtime rt; +}; + +napi_status js_create_runtime(jsr_ns_runtime* runtime) { + if (!runtime) return napi_invalid_arg; + auto* wrapper = new jsr_ns_runtime__(); + napi_status status = qjs_create_runtime(&wrapper->rt); + if (status != napi_ok) { + delete wrapper; return status; + } + *runtime = wrapper; + return napi_ok; } - -napi_status js_set_runtime_flags(const char *flags) { - return napi_ok; +napi_status js_create_napi_env(napi_env* env, jsr_ns_runtime runtime) { + napi_status status = qjs_create_napi_env(env, runtime->rt); + JSR::env_to_jsr_cache.Insert((*env), new JSR()); + return status; } +napi_status js_set_runtime_flags(const char* flags) { return napi_ok; } + napi_status js_lock_env(napi_env env) { - auto jsr = JSR::env_to_jsr_cache.Get(env); - if (jsr) jsr->lock(); - return napi_ok; + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->lock(); + return napi_ok; } napi_status js_unlock_env(napi_env env) { - auto jsr = JSR::env_to_jsr_cache.Get(env); - if (jsr) jsr->unlock(); + auto jsr = JSR::env_to_jsr_cache.Get(env); + if (jsr) jsr->unlock(); - return napi_ok; + return napi_ok; } napi_status js_free_napi_env(napi_env env) { - JSR* jsr = JSR::env_to_jsr_cache.Get(env); - delete jsr; - JSR::env_to_jsr_cache.Remove(env); - js_run_env_cleanup_hooks(env); - return qjs_free_napi_env(env); + JSR* jsr = JSR::env_to_jsr_cache.Get(env); + delete jsr; + JSR::env_to_jsr_cache.Remove(env); +#ifndef __ANDROID__ + js_run_env_cleanup_hooks(env); +#endif + return qjs_free_napi_env(env); } -napi_status js_free_runtime(napi_runtime runtime) { - return qjs_free_runtime(runtime); +napi_status js_free_runtime(jsr_ns_runtime runtime) { + napi_status status = qjs_free_runtime(runtime->rt); + delete runtime; + return status; } -napi_status js_execute_script(napi_env env, - napi_value script, - const char *file, - napi_value *result) { - return qjs_execute_script(env, script, file, result); +#ifdef __ANDROID__ + +#ifdef __QUICKJS_NG__ +static const char* kBytecodeMagic = "NSBCNGS"; // 7 chars + NUL = 8-byte magic +static const char* kEngineName = "QuickJS-NG"; +#else +static const char* kBytecodeMagic = "NSBCQJS"; +static const char* kEngineName = "QuickJS"; +#endif + +napi_status js_run_bytecode_file(napi_env env, const char* file, + napi_value* result) { + std::string path; + if (!nsbc::ResolvePath(file, path)) { + DEBUG_WRITE("[bytecode] Unable to resolve file: %s", path.c_str()); + return napi_cannot_run_js; + } + if (!nsbc::HasMagic(path, kBytecodeMagic)) { + DEBUG_WRITE("[bytecode] Unable to find %s header: %s", kEngineName, + path.c_str()); + return napi_cannot_run_js; + } + + int length = 0; + auto data = tns::File::ReadBinary(path, length); + if (!data) return napi_cannot_run_js; + if (static_cast(length) <= nsbc::kHeaderLen) { + delete[] static_cast(data); + return napi_cannot_run_js; + } + + DEBUG_WRITE("[bytecode] loading %s bytecode: %s (%d bytes)", kEngineName, + file, length); + + // JS_ReadObject copies what it needs, so the buffer can be freed after. + napi_status status = qjs_run_bytecode( + env, static_cast(data) + nsbc::kHeaderLen, + static_cast(length) - nsbc::kHeaderLen, file, result); + delete[] static_cast(data); + return status; } -napi_status js_execute_pending_jobs(napi_env env) { - return qjs_execute_pending_jobs(env); +#endif // __ANDROID__ + +napi_status js_execute_script(napi_env env, napi_value script, const char* file, + napi_value* result) { +#ifdef __ANDROID__ + DEBUG_WRITE("[script] loading script: %s", file); +#endif + return qjs_execute_script(env, script, file, result); } -napi_status -js_adjust_external_memory(napi_env env, int64_t changeInBytes, int64_t *externalMemory) { - napi_adjust_external_memory(env, changeInBytes, externalMemory); - return napi_ok; +napi_status js_execute_pending_jobs(napi_env env) { + return qjs_execute_pending_jobs(env); } -napi_status js_cache_script(napi_env env, const char *source, const char *file) { - return napi_ok; +napi_status js_adjust_external_memory(napi_env env, int64_t changeInBytes, + int64_t* externalMemory) { + napi_adjust_external_memory(env, changeInBytes, externalMemory); + return napi_ok; } -napi_status js_run_cached_script(napi_env env, const char *file, napi_value script, void *cache, - napi_value *result) { - return napi_ok; +napi_status js_cache_script(napi_env env, const char* source, + const char* file) { + return napi_ok; } +napi_status js_run_cached_script(napi_env env, const char* file, + napi_value script, void* cache, + napi_value* result) { + return napi_ok; +} -napi_status js_get_runtime_version(napi_env env, napi_value *version) { - napi_create_string_utf8(env, "QuickJS", NAPI_AUTO_LENGTH, version); +napi_status js_get_runtime_version(napi_env env, napi_value* version) { + napi_create_string_utf8(env, "QuickJS", NAPI_AUTO_LENGTH, version); - return napi_ok; + return napi_ok; } diff --git a/NativeScript/napi/quickjs/jsr.h b/NativeScript/napi/quickjs/jsr.h index e6d62c9f5..ca1cdc869 100644 --- a/NativeScript/napi/quickjs/jsr.h +++ b/NativeScript/napi/quickjs/jsr.h @@ -4,53 +4,69 @@ #ifndef TEST_APP_JSR_H #define TEST_APP_JSR_H +#include + +#include "ConcurrentMap.h" #include "js_native_api.h" #include "jsr_common.h" -#include "quicks-runtime.h" #include "mutex" -#include -#include "ConcurrentMap.h" +#include "quicks-runtime.h" class JSR { -public: - JSR(); - std::recursive_mutex js_mutex; - void lock() { - js_mutex.lock(); - } - void unlock() { - js_mutex.unlock(); - } + public: + JSR(); + std::recursive_mutex js_mutex; + // Depth of nested JS scopes entered from the host (see NapiScope). We drain + // the pending-job (microtask) queue once this returns to 0, i.e. when the + // native call stack has fully unwound back out of JS. + int jsEnterState = 0; + void lock() { js_mutex.lock(); } + void unlock() { js_mutex.unlock(); } - static tns::SimpleMap env_to_jsr_cache; + static tns::SimpleMap env_to_jsr_cache; }; class NapiScope { -public: - explicit NapiScope(napi_env env, bool open_handle = true) - : env_(env) - { - js_lock_env(env_); - qjs_update_stack_top(env); - if (open_handle) { - napi_open_handle_scope(env_, &napiHandleScope_); - } else { - napiHandleScope_ = nullptr; - } + public: + explicit NapiScope(napi_env env, bool open_handle = true) : env_(env) { + js_lock_env(env_); + qjs_update_stack_top(env); + jsr_ = JSR::env_to_jsr_cache.Get(env_); + if (jsr_) { + jsr_->jsEnterState++; } + if (open_handle) { + napi_open_handle_scope(env_, &napiHandleScope_); + } else { + napiHandleScope_ = nullptr; + } + } - ~NapiScope() { - if (napiHandleScope_) { - napi_close_handle_scope(env_, napiHandleScope_); - } - js_unlock_env(env_); + ~NapiScope() { + // Drain the microtask queue only when the outermost JS scope unwinds so + // that promise continuations (async/await) run — mirroring how a JS + // engine empties its job queue once control returns to the host. + // Draining at a nested depth would run continuations while JS is still + // on the stack. A throwing job must never escape a destructor. + if (jsr_ && --jsr_->jsEnterState <= 0) { + jsr_->jsEnterState = 0; + try { + js_execute_pending_jobs(env_); + } catch (...) { + } + } + if (napiHandleScope_) { + napi_close_handle_scope(env_, napiHandleScope_); } + js_unlock_env(env_); + } -private: - napi_env env_; - napi_handle_scope napiHandleScope_; + private: + napi_env env_; + napi_handle_scope napiHandleScope_; + JSR* jsr_ = nullptr; }; #define JSEnterScope -#endif //TEST_APP_JSR_H +#endif // TEST_APP_JSR_H diff --git a/NativeScript/napi/quickjs/mimalloc-dev/.gitattributes b/NativeScript/napi/quickjs/mimalloc-dev/.gitattributes deleted file mode 100644 index 0332e0315..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/.gitattributes +++ /dev/null @@ -1,12 +0,0 @@ -# default behavior is to always use unix style line endings -* text eol=lf -*.png binary -*.pdn binary -*.jpg binary -*.sln binary -*.suo binary -*.vcproj binary -*.patch binary -*.dll binary -*.lib binary -*.exe binary diff --git a/NativeScript/napi/quickjs/mimalloc-dev/.gitignore b/NativeScript/napi/quickjs/mimalloc-dev/.gitignore deleted file mode 100644 index df1d58eb2..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -ide/vs20??/*.db -ide/vs20??/*.opendb -ide/vs20??/*.user -ide/vs20??/*.vcxproj.filters -ide/vs20??/.vs -ide/vs20??/VTune* -out/ -docs/ -*.zip -*.tar -*.gz diff --git a/NativeScript/napi/quickjs/mimalloc-dev/CMakeLists.txt b/NativeScript/napi/quickjs/mimalloc-dev/CMakeLists.txt deleted file mode 100644 index bcfe91d86..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/CMakeLists.txt +++ /dev/null @@ -1,596 +0,0 @@ -cmake_minimum_required(VERSION 3.18) -project(libmimalloc C CXX) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 17) - -option(MI_SECURE "Use full security mitigations (like guard pages, allocation randomization, double-free mitigation, and free-list corruption detection)" OFF) -option(MI_DEBUG_FULL "Use full internal heap invariant checking in DEBUG mode (expensive)" OFF) -option(MI_PADDING "Enable padding to detect heap block overflow (always on in DEBUG or SECURE mode, or with Valgrind/ASAN)" OFF) -option(MI_OVERRIDE "Override the standard malloc interface (e.g. define entry points for malloc() etc)" ON) -option(MI_XMALLOC "Enable abort() call on memory allocation failure by default" OFF) -option(MI_SHOW_ERRORS "Show error and warning messages by default (only enabled by default in DEBUG mode)" OFF) -option(MI_TRACK_VALGRIND "Compile with Valgrind support (adds a small overhead)" OFF) -option(MI_TRACK_ASAN "Compile with address sanitizer support (adds a small overhead)" OFF) -option(MI_TRACK_ETW "Compile with Windows event tracing (ETW) support (adds a small overhead)" OFF) -option(MI_USE_CXX "Use the C++ compiler to compile the library (instead of the C compiler)" OFF) -option(MI_SEE_ASM "Generate assembly files" OFF) -option(MI_OSX_INTERPOSE "Use interpose to override standard malloc on macOS" ON) -option(MI_OSX_ZONE "Use malloc zone to override standard malloc on macOS" ON) -option(MI_WIN_REDIRECT "Use redirection module ('mimalloc-redirect') on Windows if compiling mimalloc as a DLL" ON) -option(MI_LOCAL_DYNAMIC_TLS "Use slightly slower, dlopen-compatible TLS mechanism (Unix)" OFF) -option(MI_LIBC_MUSL "Set this when linking with musl libc" OFF) -option(MI_BUILD_SHARED "Build shared library" ON) -option(MI_BUILD_STATIC "Build static library" ON) -option(MI_BUILD_OBJECT "Build object library" ON) -option(MI_BUILD_TESTS "Build test executables" ON) -option(MI_DEBUG_TSAN "Build with thread sanitizer (needs clang)" OFF) -option(MI_DEBUG_UBSAN "Build with undefined-behavior sanitizer (needs clang++)" OFF) -option(MI_SKIP_COLLECT_ON_EXIT "Skip collecting memory on program exit" OFF) -option(MI_NO_PADDING "Force no use of padding even in DEBUG mode etc." OFF) -option(MI_INSTALL_TOPLEVEL "Install directly into $CMAKE_INSTALL_PREFIX instead of PREFIX/lib/mimalloc-version" OFF) -option(MI_NO_THP "Disable transparent huge pages support on Linux/Android for the mimalloc process only" OFF) - -# deprecated options -option(MI_CHECK_FULL "Use full internal invariant checking in DEBUG mode (deprecated, use MI_DEBUG_FULL instead)" OFF) -option(MI_USE_LIBATOMIC "Explicitly link with -latomic (on older systems) (deprecated and detected automatically)" OFF) - -include(CheckLinkerFlag) # requires cmake 3.18 -include(CheckIncludeFiles) -include(GNUInstallDirs) -include("cmake/mimalloc-config-version.cmake") - -set(mi_sources - src/alloc.c - src/alloc-aligned.c - src/alloc-posix.c - src/arena.c - src/bitmap.c - src/heap.c - src/init.c - src/libc.c - src/options.c - src/os.c - src/page.c - src/random.c - src/segment.c - src/segment-map.c - src/stats.c - src/prim/prim.c) - -set(mi_cflags "") -set(mi_cflags_static "") # extra flags for a static library build -set(mi_cflags_dynamic "") # extra flags for a shared-object library build -set(mi_defines "") -set(mi_libraries "") - -# ----------------------------------------------------------------------------- -# Convenience: set default build type depending on the build directory -# ----------------------------------------------------------------------------- - -message(STATUS "") -if (NOT CMAKE_BUILD_TYPE) - if ("${CMAKE_BINARY_DIR}" MATCHES ".*(D|d)ebug$" OR MI_DEBUG_FULL) - message(STATUS "No build type selected, default to: Debug") - set(CMAKE_BUILD_TYPE "Debug") - else() - message(STATUS "No build type selected, default to: Release") - set(CMAKE_BUILD_TYPE "Release") - endif() -endif() - -if("${CMAKE_BINARY_DIR}" MATCHES ".*(S|s)ecure$") - message(STATUS "Default to secure build") - set(MI_SECURE "ON") -endif() - - -# ----------------------------------------------------------------------------- -# Process options -# ----------------------------------------------------------------------------- - -# put -Wall early so other warnings can be disabled selectively -if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang") - list(APPEND mi_cflags -Wall -Wextra -Wpedantic) -endif() -if(CMAKE_C_COMPILER_ID MATCHES "GNU") - list(APPEND mi_cflags -Wall -Wextra) -endif() -if(CMAKE_C_COMPILER_ID MATCHES "Intel") - list(APPEND mi_cflags -Wall) -endif() - -if(CMAKE_C_COMPILER_ID MATCHES "MSVC|Intel") - set(MI_USE_CXX "ON") -endif() - -if(MI_OVERRIDE) - message(STATUS "Override standard malloc (MI_OVERRIDE=ON)") - if(APPLE) - if(MI_OSX_ZONE) - # use zone's on macOS - message(STATUS " Use malloc zone to override malloc (MI_OSX_ZONE=ON)") - list(APPEND mi_sources src/prim/osx/alloc-override-zone.c) - list(APPEND mi_defines MI_OSX_ZONE=1) - if (NOT MI_OSX_INTERPOSE) - message(STATUS " WARNING: zone overriding usually also needs interpose (use -DMI_OSX_INTERPOSE=ON)") - endif() - endif() - if(MI_OSX_INTERPOSE) - # use interpose on macOS - message(STATUS " Use interpose to override malloc (MI_OSX_INTERPOSE=ON)") - list(APPEND mi_defines MI_OSX_INTERPOSE=1) - if (NOT MI_OSX_ZONE) - message(STATUS " WARNING: interpose usually also needs zone overriding (use -DMI_OSX_INTERPOSE=ON)") - endif() - endif() - if(MI_USE_CXX AND MI_OSX_INTERPOSE) - message(STATUS " WARNING: if dynamically overriding malloc/free, it is more reliable to build mimalloc as C code (use -DMI_USE_CXX=OFF)") - endif() - endif() -endif() - -if(WIN32) - if (MI_WIN_REDIRECT) - if (MSVC_C_ARCHITECTURE_ID MATCHES "ARM") - message(STATUS "Cannot use redirection on Windows ARM (MI_WIN_REDIRECT=OFF)") - set(MI_WIN_REDIRECT OFF) - endif() - endif() - if (NOT MI_WIN_REDIRECT) - # use a negative define for backward compatibility - list(APPEND mi_defines MI_WIN_NOREDIRECT=1) - endif() -endif() - -if(MI_SECURE) - message(STATUS "Set full secure build (MI_SECURE=ON)") - list(APPEND mi_defines MI_SECURE=4) -endif() - -if(MI_TRACK_VALGRIND) - CHECK_INCLUDE_FILES("valgrind/valgrind.h;valgrind/memcheck.h" MI_HAS_VALGRINDH) - if (NOT MI_HAS_VALGRINDH) - set(MI_TRACK_VALGRIND OFF) - message(WARNING "Cannot find the 'valgrind/valgrind.h' and 'valgrind/memcheck.h' -- install valgrind first") - message(STATUS "Compile **without** Valgrind support (MI_TRACK_VALGRIND=OFF)") - else() - message(STATUS "Compile with Valgrind support (MI_TRACK_VALGRIND=ON)") - list(APPEND mi_defines MI_TRACK_VALGRIND=1) - endif() -endif() - -if(MI_TRACK_ASAN) - if (APPLE AND MI_OVERRIDE) - set(MI_TRACK_ASAN OFF) - message(WARNING "Cannot enable address sanitizer support on macOS if MI_OVERRIDE is ON (MI_TRACK_ASAN=OFF)") - endif() - if (MI_TRACK_VALGRIND) - set(MI_TRACK_ASAN OFF) - message(WARNING "Cannot enable address sanitizer support with also Valgrind support enabled (MI_TRACK_ASAN=OFF)") - endif() - if(MI_TRACK_ASAN) - CHECK_INCLUDE_FILES("sanitizer/asan_interface.h" MI_HAS_ASANH) - if (NOT MI_HAS_ASANH) - set(MI_TRACK_ASAN OFF) - message(WARNING "Cannot find the 'sanitizer/asan_interface.h' -- install address sanitizer support first") - message(STATUS "Compile **without** address sanitizer support (MI_TRACK_ASAN=OFF)") - else() - message(STATUS "Compile with address sanitizer support (MI_TRACK_ASAN=ON)") - list(APPEND mi_defines MI_TRACK_ASAN=1) - list(APPEND mi_cflags -fsanitize=address) - list(APPEND mi_libraries -fsanitize=address) - endif() - endif() -endif() - -if(MI_TRACK_ETW) - if(NOT WIN32) - set(MI_TRACK_ETW OFF) - message(WARNING "Can only enable ETW support on Windows (MI_TRACK_ETW=OFF)") - endif() - if (MI_TRACK_VALGRIND OR MI_TRACK_ASAN) - set(MI_TRACK_ETW OFF) - message(WARNING "Cannot enable ETW support with also Valgrind or ASAN support enabled (MI_TRACK_ETW=OFF)") - endif() - if(MI_TRACK_ETW) - message(STATUS "Compile with Windows event tracing support (MI_TRACK_ETW=ON)") - list(APPEND mi_defines MI_TRACK_ETW=1) - endif() -endif() - -if(MI_SEE_ASM) - message(STATUS "Generate assembly listings (MI_SEE_ASM=ON)") - list(APPEND mi_cflags -save-temps) - if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang") - message(STATUS "No GNU Line marker") - list(APPEND mi_cflags -Wno-gnu-line-marker) - endif() -endif() - -if(MI_CHECK_FULL) - message(STATUS "The MI_CHECK_FULL option is deprecated, use MI_DEBUG_FULL instead") - set(MI_DEBUG_FULL "ON") -endif() - -if (MI_SKIP_COLLECT_ON_EXIT) - message(STATUS "Skip collecting memory on program exit (MI_SKIP_COLLECT_ON_EXIT=ON)") - list(APPEND mi_defines MI_SKIP_COLLECT_ON_EXIT=1) -endif() - -if(MI_DEBUG_FULL) - message(STATUS "Set debug level to full internal invariant checking (MI_DEBUG_FULL=ON)") - list(APPEND mi_defines MI_DEBUG=3) # full invariant checking -endif() - -if(MI_NO_PADDING) - message(STATUS "Suppress any padding of heap blocks (MI_NO_PADDING=ON)") - list(APPEND mi_defines MI_PADDING=0) -else() - if(MI_PADDING) - message(STATUS "Enable explicit padding of heap blocks (MI_PADDING=ON)") - list(APPEND mi_defines MI_PADDING=1) - endif() -endif() - -if(MI_XMALLOC) - message(STATUS "Enable abort() calls on memory allocation failure (MI_XMALLOC=ON)") - list(APPEND mi_defines MI_XMALLOC=1) -endif() - -if(MI_SHOW_ERRORS) - message(STATUS "Enable printing of error and warning messages by default (MI_SHOW_ERRORS=ON)") - list(APPEND mi_defines MI_SHOW_ERRORS=1) -endif() - -if(MI_DEBUG_TSAN) - if(CMAKE_C_COMPILER_ID MATCHES "Clang") - message(STATUS "Build with thread sanitizer (MI_DEBUG_TSAN=ON)") - list(APPEND mi_defines MI_TSAN=1) - list(APPEND mi_cflags -fsanitize=thread -g -O1) - list(APPEND mi_libraries -fsanitize=thread) - else() - message(WARNING "Can only use thread sanitizer with clang (MI_DEBUG_TSAN=ON but ignored)") - endif() -endif() - -if(MI_DEBUG_UBSAN) - if(CMAKE_BUILD_TYPE MATCHES "Debug") - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - message(STATUS "Build with undefined-behavior sanitizer (MI_DEBUG_UBSAN=ON)") - list(APPEND mi_cflags -fsanitize=undefined -g -fno-sanitize-recover=undefined) - list(APPEND mi_libraries -fsanitize=undefined) - if (NOT MI_USE_CXX) - message(STATUS "(switch to use C++ due to MI_DEBUG_UBSAN)") - set(MI_USE_CXX "ON") - endif() - else() - message(WARNING "Can only use undefined-behavior sanitizer with clang++ (MI_DEBUG_UBSAN=ON but ignored)") - endif() - else() - message(WARNING "Can only use undefined-behavior sanitizer with a debug build (CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})") - endif() -endif() - -if(MI_USE_CXX) - message(STATUS "Use the C++ compiler to compile (MI_USE_CXX=ON)") - set_source_files_properties(${mi_sources} PROPERTIES LANGUAGE CXX ) - set_source_files_properties(src/static.c test/test-api.c test/test-api-fill test/test-stress PROPERTIES LANGUAGE CXX ) - if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang|Clang") - list(APPEND mi_cflags -Wno-deprecated) - endif() - if(CMAKE_CXX_COMPILER_ID MATCHES "Intel" AND NOT CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM") - list(APPEND mi_cflags -Kc++) - endif() -endif() - -if(CMAKE_SYSTEM_NAME MATCHES "Linux|Android") - if(MI_NO_THP) - message(STATUS "Disable transparent huge pages support (MI_NO_THP=ON)") - list(APPEND mi_defines MI_NO_THP=1) - endif() -endif() - -if(MI_LIBC_MUSL) - message(STATUS "Assume using musl libc (MI_LIBC_MUSL=ON)") - list(APPEND mi_defines MI_LIBC_MUSL=1) -endif() - -# On Haiku use `-DCMAKE_INSTALL_PREFIX` instead, issue #788 -# if(CMAKE_SYSTEM_NAME MATCHES "Haiku") -# SET(CMAKE_INSTALL_LIBDIR ~/config/non-packaged/lib) -# SET(CMAKE_INSTALL_INCLUDEDIR ~/config/non-packaged/headers) -# endif() - -# Compiler flags -if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang|GNU") - list(APPEND mi_cflags -Wno-unknown-pragmas -fvisibility=hidden) - if(NOT MI_USE_CXX) - list(APPEND mi_cflags -Wstrict-prototypes) - endif() - if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang") - list(APPEND mi_cflags -Wno-static-in-inline) - endif() -endif() - -if(CMAKE_C_COMPILER_ID MATCHES "Intel") - list(APPEND mi_cflags -fvisibility=hidden) -endif() - -if(CMAKE_C_COMPILER_ID MATCHES "AppleClang|Clang|GNU|Intel" AND NOT CMAKE_SYSTEM_NAME MATCHES "Haiku") - if(MI_LOCAL_DYNAMIC_TLS) - list(APPEND mi_cflags -ftls-model=local-dynamic) - else() - if(MI_LIBC_MUSL) - # with musl we use local-dynamic for the static build, see issue #644 - list(APPEND mi_cflags_static -ftls-model=local-dynamic) - list(APPEND mi_cflags_dynamic -ftls-model=initial-exec) - message(STATUS "Use local dynamic TLS for the static build (since MI_LIBC_MUSL=ON)") - else() - list(APPEND mi_cflags -ftls-model=initial-exec) - endif() - endif() - if(MI_OVERRIDE) - list(APPEND mi_cflags -fno-builtin-malloc) - endif() -endif() - -if (MSVC AND MSVC_VERSION GREATER_EQUAL 1914) - list(APPEND mi_cflags /Zc:__cplusplus) -endif() - -if(MINGW) - add_definitions(-D_WIN32_WINNT=0x600) -endif() - -# extra needed libraries - -# we prefer -l test over `find_library` as sometimes core libraries -# like `libatomic` are not on the system path (see issue #898) -function(find_link_library libname outlibname) - check_linker_flag(C "-l${libname}" mi_has_lib${libname}) - if (mi_has_lib${libname}) - message(VERBOSE "link library: -l${libname}") - set(${outlibname} ${libname} PARENT_SCOPE) - else() - find_library(MI_LIBPATH libname) - if (MI_LIBPATH) - message(VERBOSE "link library ${libname} at ${MI_LIBPATH}") - set(${outlibname} ${MI_LIBPATH} PARENT_SCOPE) - else() - message(VERBOSE "link library not found: ${libname}") - set(${outlibname} "" PARENT_SCOPE) - endif() - endif() -endfunction() - -if(WIN32) - list(APPEND mi_libraries psapi shell32 user32 advapi32 bcrypt) -else() - find_link_library("pthread" MI_LIB_PTHREAD) - if(MI_LIB_PTHREAD) - list(APPEND mi_libraries "${MI_LIB_PTHREAD}") - endif() - find_link_library("rt" MI_LIB_RT) - if(MI_LIB_RT) - list(APPEND mi_libraries "${MI_LIB_RT}") - endif() - find_link_library("atomic" MI_LIB_ATOMIC) - if(MI_LIB_ATOMIC) - list(APPEND mi_libraries "${MI_LIB_ATOMIC}") - endif() -endif() - -# ----------------------------------------------------------------------------- -# Install and output names -# ----------------------------------------------------------------------------- - -# dynamic/shared library and symlinks always go to /usr/local/lib equivalent -set(mi_install_libdir "${CMAKE_INSTALL_LIBDIR}") -set(mi_install_bindir "${CMAKE_INSTALL_BINDIR}") - -# static libraries and object files, includes, and cmake config files -# are either installed at top level, or use versioned directories for side-by-side installation (default) -if (MI_INSTALL_TOPLEVEL) - set(mi_install_objdir "${CMAKE_INSTALL_LIBDIR}") - set(mi_install_incdir "${CMAKE_INSTALL_INCLUDEDIR}") - set(mi_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/mimalloc") -else() - set(mi_install_objdir "${CMAKE_INSTALL_LIBDIR}/mimalloc-${mi_version}") # for static library and object files - set(mi_install_incdir "${CMAKE_INSTALL_INCLUDEDIR}/mimalloc-${mi_version}") # for includes - set(mi_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/mimalloc-${mi_version}") # for cmake package info -endif() - -set(mi_basename "mimalloc") -if(MI_SECURE) - set(mi_basename "${mi_basename}-secure") -endif() -if(MI_TRACK_VALGRIND) - set(mi_basename "${mi_basename}-valgrind") -endif() -if(MI_TRACK_ASAN) - set(mi_basename "${mi_basename}-asan") -endif() -string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LC) -if(NOT(CMAKE_BUILD_TYPE_LC MATCHES "^(release|relwithdebinfo|minsizerel|none)$")) - set(mi_basename "${mi_basename}-${CMAKE_BUILD_TYPE_LC}") #append build type (e.g. -debug) if not a release version -endif() - -if(MI_BUILD_SHARED) - list(APPEND mi_build_targets "shared") -endif() -if(MI_BUILD_STATIC) - list(APPEND mi_build_targets "static") -endif() -if(MI_BUILD_OBJECT) - list(APPEND mi_build_targets "object") -endif() -if(MI_BUILD_TESTS) - list(APPEND mi_build_targets "tests") -endif() - -message(STATUS "") -message(STATUS "Library base name: ${mi_basename}") -message(STATUS "Version : ${mi_version}") -message(STATUS "Build type : ${CMAKE_BUILD_TYPE_LC}") -if(MI_USE_CXX) - message(STATUS "C++ Compiler : ${CMAKE_CXX_COMPILER}") -else() - message(STATUS "C Compiler : ${CMAKE_C_COMPILER}") -endif() -message(STATUS "Compiler flags : ${mi_cflags}") -message(STATUS "Compiler defines : ${mi_defines}") -message(STATUS "Link libraries : ${mi_libraries}") -message(STATUS "Build targets : ${mi_build_targets}") -message(STATUS "") - -# ----------------------------------------------------------------------------- -# Main targets -# ----------------------------------------------------------------------------- - -# shared library -if(MI_BUILD_SHARED) - add_library(mimalloc SHARED ${mi_sources}) - set_target_properties(mimalloc PROPERTIES VERSION ${mi_version} SOVERSION ${mi_version_major} OUTPUT_NAME ${mi_basename} ) - target_compile_definitions(mimalloc PRIVATE ${mi_defines} MI_SHARED_LIB MI_SHARED_LIB_EXPORT) - target_compile_options(mimalloc PRIVATE ${mi_cflags} ${mi_cflags_dynamic}) - target_link_libraries(mimalloc PRIVATE ${mi_libraries}) - target_include_directories(mimalloc PUBLIC - $ - $ - ) - if(WIN32 AND MI_WIN_REDIRECT) - # On windows, link and copy the mimalloc redirection dll too. - if(CMAKE_SIZEOF_VOID_P EQUAL 4) - set(MIMALLOC_REDIRECT_SUFFIX "32") - else() - set(MIMALLOC_REDIRECT_SUFFIX "") - endif() - - target_link_libraries(mimalloc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.lib) - add_custom_command(TARGET mimalloc POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/bin/mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.dll" $ - COMMENT "Copy mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.dll to output directory") - install(FILES "$/mimalloc-redirect${MIMALLOC_REDIRECT_SUFFIX}.dll" DESTINATION ${mi_install_bindir}) - endif() - - install(TARGETS mimalloc EXPORT mimalloc ARCHIVE DESTINATION ${mi_install_libdir} RUNTIME DESTINATION ${mi_install_bindir} LIBRARY DESTINATION ${mi_install_libdir}) - install(EXPORT mimalloc DESTINATION ${mi_install_cmakedir}) -endif() - -# static library -if (MI_BUILD_STATIC) - add_library(mimalloc-static STATIC ${mi_sources}) - set_property(TARGET mimalloc-static PROPERTY POSITION_INDEPENDENT_CODE ON) - target_compile_definitions(mimalloc-static PRIVATE ${mi_defines} MI_STATIC_LIB) - target_compile_options(mimalloc-static PRIVATE ${mi_cflags} ${mi_cflags_static}) - target_link_libraries(mimalloc-static PRIVATE ${mi_libraries}) - target_include_directories(mimalloc-static PUBLIC - $ - $ - ) - if(WIN32) - # When building both static and shared libraries on Windows, a static library should use a - # different output name to avoid the conflict with the import library of a shared one. - string(REPLACE "mimalloc" "mimalloc-static" mi_output_name ${mi_basename}) - set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_output_name}) - else() - set_target_properties(mimalloc-static PROPERTIES OUTPUT_NAME ${mi_basename}) - endif() - - install(TARGETS mimalloc-static EXPORT mimalloc DESTINATION ${mi_install_objdir} LIBRARY) - install(EXPORT mimalloc DESTINATION ${mi_install_cmakedir}) -endif() - -# install include files -install(FILES include/mimalloc.h DESTINATION ${mi_install_incdir}) -install(FILES include/mimalloc-override.h DESTINATION ${mi_install_incdir}) -install(FILES include/mimalloc-new-delete.h DESTINATION ${mi_install_incdir}) -install(FILES cmake/mimalloc-config.cmake DESTINATION ${mi_install_cmakedir}) -install(FILES cmake/mimalloc-config-version.cmake DESTINATION ${mi_install_cmakedir}) - - -# single object file for more predictable static overriding -if (MI_BUILD_OBJECT) - add_library(mimalloc-obj OBJECT src/static.c) - set_property(TARGET mimalloc-obj PROPERTY POSITION_INDEPENDENT_CODE ON) - target_compile_definitions(mimalloc-obj PRIVATE ${mi_defines}) - target_compile_options(mimalloc-obj PRIVATE ${mi_cflags} ${mi_cflags_static}) - target_include_directories(mimalloc-obj PUBLIC - $ - $ - ) - - # Copy the generated object file (`static.o`) to the output directory (as `mimalloc.o`) - if(NOT WIN32) - set(mimalloc-obj-static "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/mimalloc-obj.dir/src/static.c${CMAKE_C_OUTPUT_EXTENSION}") - set(mimalloc-obj-out "${CMAKE_CURRENT_BINARY_DIR}/${mi_basename}${CMAKE_C_OUTPUT_EXTENSION}") - add_custom_command(OUTPUT ${mimalloc-obj-out} DEPENDS mimalloc-obj COMMAND "${CMAKE_COMMAND}" -E copy "${mimalloc-obj-static}" "${mimalloc-obj-out}") - add_custom_target(mimalloc-obj-target ALL DEPENDS ${mimalloc-obj-out}) - endif() - - # the following seems to lead to cmake warnings/errors on some systems, disable for now :-( - # install(TARGETS mimalloc-obj EXPORT mimalloc DESTINATION ${mi_install_objdir}) - - # the FILES expression can also be: $ - # but that fails cmake versions less than 3.10 so we leave it as is for now - install(FILES ${mimalloc-obj-static} - DESTINATION ${mi_install_objdir} - RENAME ${mi_basename}${CMAKE_C_OUTPUT_EXTENSION} ) -endif() - -# pkg-config file support -set(pc_libraries "") -foreach(item IN LISTS mi_libraries) - if(item MATCHES " *[-].*") - set(pc_libraries "${pc_libraries} ${item}") - else() - set(pc_libraries "${pc_libraries} -l${item}") - endif() -endforeach() - -include("cmake/JoinPaths.cmake") -join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") -join_paths(libdir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_LIBDIR}") - -configure_file(mimalloc.pc.in mimalloc.pc @ONLY) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mimalloc.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig/") - - - -# ----------------------------------------------------------------------------- -# API surface testing -# ----------------------------------------------------------------------------- - -if (MI_BUILD_TESTS) - enable_testing() - - foreach(TEST_NAME api api-fill stress) - add_executable(mimalloc-test-${TEST_NAME} test/test-${TEST_NAME}.c) - target_compile_definitions(mimalloc-test-${TEST_NAME} PRIVATE ${mi_defines}) - target_compile_options(mimalloc-test-${TEST_NAME} PRIVATE ${mi_cflags}) - target_include_directories(mimalloc-test-${TEST_NAME} PRIVATE include) - target_link_libraries(mimalloc-test-${TEST_NAME} PRIVATE mimalloc ${mi_libraries}) - - add_test(NAME test-${TEST_NAME} COMMAND mimalloc-test-${TEST_NAME}) - endforeach() -endif() - -# ----------------------------------------------------------------------------- -# Set override properties -# ----------------------------------------------------------------------------- -if (MI_OVERRIDE) - if (MI_BUILD_SHARED) - target_compile_definitions(mimalloc PRIVATE MI_MALLOC_OVERRIDE) - endif() - if(NOT WIN32) - # It is only possible to override malloc on Windows when building as a DLL. - if (MI_BUILD_STATIC) - target_compile_definitions(mimalloc-static PRIVATE MI_MALLOC_OVERRIDE) - endif() - if (MI_BUILD_OBJECT) - target_compile_definitions(mimalloc-obj PRIVATE MI_MALLOC_OVERRIDE) - endif() - endif() -endif() diff --git a/NativeScript/napi/quickjs/mimalloc-dev/LICENSE b/NativeScript/napi/quickjs/mimalloc-dev/LICENSE deleted file mode 100644 index 670b668a0..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/NativeScript/napi/quickjs/mimalloc-dev/SECURITY.md b/NativeScript/napi/quickjs/mimalloc-dev/SECURITY.md deleted file mode 100644 index b3c89efc8..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/SECURITY.md +++ /dev/null @@ -1,41 +0,0 @@ - - -## Security - -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). - -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). - - diff --git a/NativeScript/napi/quickjs/mimalloc-dev/azure-pipelines.yml b/NativeScript/napi/quickjs/mimalloc-dev/azure-pipelines.yml deleted file mode 100644 index 0247c76fd..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/azure-pipelines.yml +++ /dev/null @@ -1,197 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -trigger: - branches: - include: - - master - - dev - - dev-slice - tags: - include: - - v* - -jobs: -- job: - displayName: Windows - pool: - vmImage: - windows-2022 - strategy: - matrix: - Debug: - BuildType: debug - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON - MSBuildConfiguration: Debug - Release: - BuildType: release - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release - MSBuildConfiguration: Release - Secure: - BuildType: secure - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON - MSBuildConfiguration: Release - steps: - - task: CMake@1 - inputs: - workingDirectory: $(BuildType) - cmakeArgs: .. $(cmakeExtraArgs) - - task: MSBuild@1 - inputs: - solution: $(BuildType)/libmimalloc.sln - configuration: '$(MSBuildConfiguration)' - msbuildArguments: -m - - script: ctest --verbose --timeout 120 -C $(MSBuildConfiguration) - workingDirectory: $(BuildType) - displayName: CTest - #- script: $(BuildType)\$(BuildType)\mimalloc-test-stress - # displayName: TestStress - #- upload: $(Build.SourcesDirectory)/$(BuildType) - # artifact: mimalloc-windows-$(BuildType) - -- job: - displayName: Linux - pool: - vmImage: - ubuntu-22.04 - strategy: - matrix: - Debug: - CC: gcc - CXX: g++ - BuildType: debug - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON - Release: - CC: gcc - CXX: g++ - BuildType: release - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release - Secure: - CC: gcc - CXX: g++ - BuildType: secure - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON - Debug++: - CC: gcc - CXX: g++ - BuildType: debug-cxx - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON - Debug Clang: - CC: clang - CXX: clang++ - BuildType: debug-clang - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON - Release Clang: - CC: clang - CXX: clang++ - BuildType: release-clang - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release - Secure Clang: - CC: clang - CXX: clang++ - BuildType: secure-clang - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON - Debug++ Clang: - CC: clang - CXX: clang++ - BuildType: debug-clang-cxx - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_USE_CXX=ON - Debug ASAN Clang: - CC: clang - CXX: clang++ - BuildType: debug-asan-clang - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_TRACK_ASAN=ON - Debug UBSAN Clang: - CC: clang - CXX: clang++ - BuildType: debug-ubsan-clang - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -DMI_DEBUG_UBSAN=ON - Debug TSAN Clang++: - CC: clang - CXX: clang++ - BuildType: debug-tsan-clang-cxx - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_USE_CXX=ON -DMI_DEBUG_TSAN=ON - - steps: - - task: CMake@1 - inputs: - workingDirectory: $(BuildType) - cmakeArgs: .. $(cmakeExtraArgs) - - script: make -j$(nproc) -C $(BuildType) - displayName: Make - - script: ctest --verbose --timeout 180 - workingDirectory: $(BuildType) - displayName: CTest -# - upload: $(Build.SourcesDirectory)/$(BuildType) -# artifact: mimalloc-ubuntu-$(BuildType) - -- job: - displayName: macOS - pool: - vmImage: - macOS-latest - strategy: - matrix: - Debug: - BuildType: debug - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON - Release: - BuildType: release - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release - Secure: - BuildType: secure - cmakeExtraArgs: -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON - steps: - - task: CMake@1 - inputs: - workingDirectory: $(BuildType) - cmakeArgs: .. $(cmakeExtraArgs) - - script: make -j$(sysctl -n hw.ncpu) -C $(BuildType) - displayName: Make - # - script: MIMALLOC_VERBOSE=1 ./mimalloc-test-api - # workingDirectory: $(BuildType) - # displayName: TestAPI - # - script: MIMALLOC_VERBOSE=1 ./mimalloc-test-stress - # workingDirectory: $(BuildType) - # displayName: TestStress - - script: ctest --verbose --timeout 120 - workingDirectory: $(BuildType) - displayName: CTest - -# - upload: $(Build.SourcesDirectory)/$(BuildType) -# artifact: mimalloc-macos-$(BuildType) - -# - job: -# displayName: Windows-2017 -# pool: -# vmImage: -# vs2017-win2016 -# strategy: -# matrix: -# Debug: -# BuildType: debug -# cmakeExtraArgs: -A x64 -DCMAKE_BUILD_TYPE=Debug -DMI_DEBUG_FULL=ON -# MSBuildConfiguration: Debug -# Release: -# BuildType: release -# cmakeExtraArgs: -A x64 -DCMAKE_BUILD_TYPE=Release -# MSBuildConfiguration: Release -# Secure: -# BuildType: secure -# cmakeExtraArgs: -A x64 -DCMAKE_BUILD_TYPE=Release -DMI_SECURE=ON -# MSBuildConfiguration: Release -# steps: -# - task: CMake@1 -# inputs: -# workingDirectory: $(BuildType) -# cmakeArgs: .. $(cmakeExtraArgs) -# - task: MSBuild@1 -# inputs: -# solution: $(BuildType)/libmimalloc.sln -# configuration: '$(MSBuildConfiguration)' -# - script: | -# cd $(BuildType) -# ctest --verbose --timeout 120 -# displayName: CTest diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect.dll b/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect.dll deleted file mode 100644 index a3a3591ff..000000000 Binary files a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect.dll and /dev/null differ diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect.lib b/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect.lib deleted file mode 100644 index de128bb94..000000000 Binary files a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect.lib and /dev/null differ diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect32.dll b/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect32.dll deleted file mode 100644 index 522723e50..000000000 Binary files a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect32.dll and /dev/null differ diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect32.lib b/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect32.lib deleted file mode 100644 index 87f19b8ec..000000000 Binary files a/NativeScript/napi/quickjs/mimalloc-dev/bin/mimalloc-redirect32.lib and /dev/null differ diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/minject.exe b/NativeScript/napi/quickjs/mimalloc-dev/bin/minject.exe deleted file mode 100644 index dba8f80fd..000000000 Binary files a/NativeScript/napi/quickjs/mimalloc-dev/bin/minject.exe and /dev/null differ diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/minject32.exe b/NativeScript/napi/quickjs/mimalloc-dev/bin/minject32.exe deleted file mode 100644 index f837383b9..000000000 Binary files a/NativeScript/napi/quickjs/mimalloc-dev/bin/minject32.exe and /dev/null differ diff --git a/NativeScript/napi/quickjs/mimalloc-dev/bin/readme.md b/NativeScript/napi/quickjs/mimalloc-dev/bin/readme.md deleted file mode 100644 index 9b121bda5..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/bin/readme.md +++ /dev/null @@ -1,71 +0,0 @@ -# Windows Override - -Dynamically overriding on mimalloc on Windows -is robust and has the particular advantage to be able to redirect all malloc/free calls that go through -the (dynamic) C runtime allocator, including those from other DLL's or libraries. -As it intercepts all allocation calls on a low level, it can be used reliably -on large programs that include other 3rd party components. -There are four requirements to make the overriding work robustly: - -1. Use the C-runtime library as a DLL (using the `/MD` or `/MDd` switch). - -2. Link your program explicitly with `mimalloc-override.dll` library. - To ensure the `mimalloc-override.dll` is loaded at run-time it is easiest to insert some - call to the mimalloc API in the `main` function, like `mi_version()` - (or use the `/INCLUDE:mi_version` switch on the linker). See the `mimalloc-override-test` project - for an example on how to use this. - -3. The `mimalloc-redirect.dll` (or `mimalloc-redirect32.dll`) must be put - in the same folder as the main `mimalloc-override.dll` at runtime (as it is a dependency of that DLL). - The redirection DLL ensures that all calls to the C runtime malloc API get redirected to - mimalloc functions (which reside in `mimalloc-override.dll`). - -4. Ensure the `mimalloc-override.dll` comes as early as possible in the import - list of the final executable (so it can intercept all potential allocations). - -For best performance on Windows with C++, it -is also recommended to also override the `new`/`delete` operations (by including -[`mimalloc-new-delete.h`](../include/mimalloc-new-delete.h) -a single(!) source file in your project). - -The environment variable `MIMALLOC_DISABLE_REDIRECT=1` can be used to disable dynamic -overriding at run-time. Use `MIMALLOC_VERBOSE=1` to check if mimalloc was successfully redirected. - -## Minject - -We cannot always re-link an executable with `mimalloc-override.dll`, and similarly, we cannot always -ensure the the DLL comes first in the import table of the final executable. -In many cases though we can patch existing executables without any recompilation -if they are linked with the dynamic C runtime (`ucrtbase.dll`) -- just put the `mimalloc-override.dll` -into the import table (and put `mimalloc-redirect.dll` in the same folder) -Such patching can be done for example with [CFF Explorer](https://ntcore.com/?page_id=388). - -The `minject` program can also do this from the command line, use `minject --help` for options: - -``` -> minject --help - -minject: - Injects the mimalloc dll into the import table of a 64-bit executable, - and/or ensures that it comes first in het import table. - -usage: - > minject [options] - -options: - -h --help show this help - -v --verbose be verbose - -l --list only list imported modules - -i --inplace update the exe in-place (make sure there is a backup!) - -f --force always overwrite without prompting - --postfix=

use

as a postfix to the mimalloc dll (default is 'override') - e.g. use --postfix=override-debug to link with mimalloc-override-debug.dll - -notes: - Without '--inplace' an injected is generated with the same name ending in '-mi'. - Ensure 'mimalloc-redirect.dll' is in the same folder as the mimalloc dll. - -examples: - > minject --list myprogram.exe - > minject --force --inplace myprogram.exe -``` diff --git a/NativeScript/napi/quickjs/mimalloc-dev/cmake/JoinPaths.cmake b/NativeScript/napi/quickjs/mimalloc-dev/cmake/JoinPaths.cmake deleted file mode 100644 index c68d91b84..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/cmake/JoinPaths.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# This module provides function for joining paths -# known from most languages -# -# SPDX-License-Identifier: (MIT OR CC0-1.0) -# Copyright 2020 Jan Tojnar -# https://github.com/jtojnar/cmake-snips -# -# Modelled after Python’s os.path.join -# https://docs.python.org/3.7/library/os.path.html#os.path.join -# Windows not supported -function(join_paths joined_path first_path_segment) - set(temp_path "${first_path_segment}") - foreach(current_segment IN LISTS ARGN) - if(NOT ("${current_segment}" STREQUAL "")) - if(IS_ABSOLUTE "${current_segment}") - set(temp_path "${current_segment}") - else() - set(temp_path "${temp_path}/${current_segment}") - endif() - endif() - endforeach() - set(${joined_path} "${temp_path}" PARENT_SCOPE) -endfunction() diff --git a/NativeScript/napi/quickjs/mimalloc-dev/cmake/mimalloc-config-version.cmake b/NativeScript/napi/quickjs/mimalloc-dev/cmake/mimalloc-config-version.cmake deleted file mode 100644 index 81fd3c9da..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/cmake/mimalloc-config-version.cmake +++ /dev/null @@ -1,19 +0,0 @@ -set(mi_version_major 2) -set(mi_version_minor 1) -set(mi_version_patch 7) -set(mi_version ${mi_version_major}.${mi_version_minor}) - -set(PACKAGE_VERSION ${mi_version}) -if(PACKAGE_FIND_VERSION_MAJOR) - if("${PACKAGE_FIND_VERSION_MAJOR}" EQUAL "${mi_version_major}") - if ("${PACKAGE_FIND_VERSION_MINOR}" EQUAL "${mi_version_minor}") - set(PACKAGE_VERSION_EXACT TRUE) - elseif("${PACKAGE_FIND_VERSION_MINOR}" LESS "${mi_version_minor}") - set(PACKAGE_VERSION_COMPATIBLE TRUE) - else() - set(PACKAGE_VERSION_UNSUITABLE TRUE) - endif() - else() - set(PACKAGE_VERSION_UNSUITABLE TRUE) - endif() -endif() diff --git a/NativeScript/napi/quickjs/mimalloc-dev/cmake/mimalloc-config.cmake b/NativeScript/napi/quickjs/mimalloc-dev/cmake/mimalloc-config.cmake deleted file mode 100644 index a49b02a25..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/cmake/mimalloc-config.cmake +++ /dev/null @@ -1,14 +0,0 @@ -include(${CMAKE_CURRENT_LIST_DIR}/mimalloc.cmake) -get_filename_component(MIMALLOC_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}" PATH) # one up from the cmake dir, e.g. /usr/local/lib/cmake/mimalloc-2.0 -get_filename_component(MIMALLOC_VERSION_DIR "${CMAKE_CURRENT_LIST_DIR}" NAME) -string(REPLACE "/lib/cmake" "/lib" MIMALLOC_LIBRARY_DIR "${MIMALLOC_CMAKE_DIR}") -if("${MIMALLOC_VERSION_DIR}" EQUAL "mimalloc") - # top level install - string(REPLACE "/lib/cmake" "/include" MIMALLOC_INCLUDE_DIR "${MIMALLOC_CMAKE_DIR}") - set(MIMALLOC_OBJECT_DIR "${MIMALLOC_LIBRARY_DIR}") -else() - # versioned - string(REPLACE "/lib/cmake/" "/include/" MIMALLOC_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}") - string(REPLACE "/lib/cmake/" "/lib/" MIMALLOC_OBJECT_DIR "${CMAKE_CURRENT_LIST_DIR}") -endif() -set(MIMALLOC_TARGET_DIR "${MIMALLOC_LIBRARY_DIR}") # legacy diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-a.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-a.svg deleted file mode 100644 index 900509742..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-a.svg +++ /dev/null @@ -1,887 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-b.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-b.svg deleted file mode 100644 index 2d853edcb..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-b.svg +++ /dev/null @@ -1,1185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-a.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-a.svg deleted file mode 100644 index 393bfad97..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-a.svg +++ /dev/null @@ -1,757 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-b.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-b.svg deleted file mode 100644 index 419dc250f..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-c5-18xlarge-2020-01-20-rss-b.svg +++ /dev/null @@ -1,1028 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-1.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-1.svg deleted file mode 100644 index c296a0489..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-1.svg +++ /dev/null @@ -1,769 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-12xlarge-2020-01-16-a.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-12xlarge-2020-01-16-a.svg deleted file mode 100644 index b8a2f20e5..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-12xlarge-2020-01-16-a.svg +++ /dev/null @@ -1,868 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-12xlarge-2020-01-16-b.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-12xlarge-2020-01-16-b.svg deleted file mode 100644 index 4a7e21e71..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-12xlarge-2020-01-16-b.svg +++ /dev/null @@ -1,1157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-2.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-2.svg deleted file mode 100644 index 917ea5730..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-2.svg +++ /dev/null @@ -1,983 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-rss-1.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-rss-1.svg deleted file mode 100644 index 375ebd204..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-rss-1.svg +++ /dev/null @@ -1,683 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-rss-2.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-rss-2.svg deleted file mode 100644 index cb2bbc89e..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-r5a-rss-2.svg +++ /dev/null @@ -1,854 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-spec-rss.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-spec-rss.svg deleted file mode 100644 index 2c936166c..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-spec-rss.svg +++ /dev/null @@ -1,713 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-spec.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-spec.svg deleted file mode 100644 index af2b41ba9..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-spec.svg +++ /dev/null @@ -1,713 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-1.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-1.svg deleted file mode 100644 index dacd8ab94..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-1.svg +++ /dev/null @@ -1,890 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-2.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-2.svg deleted file mode 100644 index 9990cdcc3..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-2.svg +++ /dev/null @@ -1,1146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-rss-1.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-rss-1.svg deleted file mode 100644 index 891f7d68f..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-rss-1.svg +++ /dev/null @@ -1,796 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-rss-2.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-rss-2.svg deleted file mode 100644 index f4265378a..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2020/bench-z4-rss-2.svg +++ /dev/null @@ -1,974 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-amd5950x-2021-01-30-a.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-amd5950x-2021-01-30-a.svg deleted file mode 100644 index 86a97bfd2..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-amd5950x-2021-01-30-a.svg +++ /dev/null @@ -1,952 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-amd5950x-2021-01-30-b.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-amd5950x-2021-01-30-b.svg deleted file mode 100644 index c74887702..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-amd5950x-2021-01-30-b.svg +++ /dev/null @@ -1,1255 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-a.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-a.svg deleted file mode 100644 index bc91c218c..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-a.svg +++ /dev/null @@ -1,955 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-b.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-b.svg deleted file mode 100644 index e8b04a0d9..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-b.svg +++ /dev/null @@ -1,1269 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-a.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-a.svg deleted file mode 100644 index 6cd36aaab..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-a.svg +++ /dev/null @@ -1,836 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-b.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-b.svg deleted file mode 100644 index c81072e9b..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-c5-18xlarge-2021-01-30-rss-b.svg +++ /dev/null @@ -1,1131 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-macmini-2021-01-30.svg b/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-macmini-2021-01-30.svg deleted file mode 100644 index ece64185f..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/bench-2021/bench-macmini-2021-01-30.svg +++ /dev/null @@ -1,766 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NativeScript/napi/quickjs/mimalloc-dev/doc/doxyfile b/NativeScript/napi/quickjs/mimalloc-dev/doc/doxyfile deleted file mode 100644 index d03a70f57..000000000 --- a/NativeScript/napi/quickjs/mimalloc-dev/doc/doxyfile +++ /dev/null @@ -1,2659 +0,0 @@ -# Doxyfile 1.9.1 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = mi-malloc - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = 1.8/2.1 - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = mimalloc-logo.svg - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = .. - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = YES - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# By default Python docstrings are displayed as preformatted text and doxygen's -# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. -# The default value is: YES. - -PYTHON_DOCSTRING = YES - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 2 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files -# as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add -# * to the FILE_PATTERNS. -# -# Note see also the list of default file extension mappings. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 0 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = YES - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = YES - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of -# cores available in the system. You can set it explicitly to a value larger -# than 0 to get more control over the balance between CPU load and processing -# speed. At this moment only the input processing can be done using multiple -# threads. Since this is still an experimental feature the default is set to 1, -# which effectively disables parallel processing. Please report any issues you -# encounter. Generating dot graphs in parallel is controlled by the -# DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. - -NUM_PROC_THREADS = 1 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If this flag is set to YES, the name of an unnamed parameter in a declaration -# will be determined by the corresponding definition. By default unnamed -# parameters remain unnamed in the output. -# The default value is: YES. - -RESOLVE_UNNAMED_PARAMS = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be -# able to match the capabilities of the underlying filesystem. In case the -# filesystem is case sensitive (i.e. it supports files in the same directory -# whose names only differ in casing), the option must be set to YES to properly -# deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with -# output files written for symbols that only differ in casing, such as for two -# classes, one named CLASS and the other named Class, and to also support -# references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option -# to NO, whereas on Linux or other Unix flavors it should typically be set to -# YES. -# The default value is: system dependent. - -CASE_SENSE_NAMES = NO - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 0 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = NO - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = mimalloc-doc.h - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: -# https://www.gnu.org/software/libiconv/) for the list of possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# Note the list of default checked file patterns might differ from the list of -# default file extension mappings. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.pyw \ - *.f90 \ - *.f95 \ - *.f03 \ - *.f08 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = * - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: -# http://clang.llvm.org/) for more accurate parsing at the cost of reduced -# performance. This can be particularly helpful with template rich C++ code for -# which doxygen's built-in parser lacks the necessary type information. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to -# YES then doxygen will add the directory of each input to the include path. -# The default value is: YES. - -CLANG_ADD_INC_PATHS = YES - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -# If clang assisted parsing is enabled you can provide the clang parser with the -# path to the directory containing a file called compile_commands.json. This -# file is the compilation database (see: -# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the -# options used when the source files were built. This is equivalent to -# specifying the -p option to a clang tool, such as clang-check. These options -# will then be passed to the parser. Any options specified with CLANG_OPTIONS -# will be added as well. -# Note: The availability of this option depends on whether or not doxygen was -# generated with the -Duse_libclang=ON option for CMake. - -CLANG_DATABASE_PATH = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = docs - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = mimalloc-doxygen.css - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 189 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 12 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 240 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = NO - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: -# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML -# output directory. Running make will produce the docset in that directory and -# running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the main .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to -# run qhelpgenerator on the generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = YES - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = YES - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 180 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FORMULA_FORMAT = png - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /

- // - // - // - // etc. - inplace_bswap16(&p[RE_HEADER_FLAGS]); - - n = get_u32(&p[RE_HEADER_BYTECODE_LEN]); - inplace_bswap32(&p[RE_HEADER_BYTECODE_LEN]); - if (is_byte_swapped) - n = bswap32(n); - if (n > len - RE_HEADER_LEN) - abort(); - - p = &buf[RE_HEADER_LEN]; - pe = &p[n]; - - while (p < pe) { - n = reopcode_info[*p].size; - switch (n) { - case 1: - case 2: - break; - case 3: - switch (*p) { - case REOP_save_reset: // has two 8 bit arguments - break; - case REOP_range32: // variable length - nw = get_u16(&p[1]); // number of pairs of uint32_t - if (is_byte_swapped) - n = bswap16(n); - for (r = 3 + 8 * nw; n < r; n += 4) - inplace_bswap32(&p[n]); - goto doswap16; - case REOP_range: // variable length - nw = get_u16(&p[1]); // number of pairs of uint16_t - if (is_byte_swapped) - n = bswap16(n); - for (r = 3 + 4 * nw; n < r; n += 2) - inplace_bswap16(&p[n]); - goto doswap16; - default: - doswap16: - inplace_bswap16(&p[1]); - break; - } - break; - case 5: - inplace_bswap32(&p[1]); - break; - case 17: - assert(*p == REOP_simple_greedy_quant); - inplace_bswap32(&p[1]); - inplace_bswap32(&p[5]); - inplace_bswap32(&p[9]); - inplace_bswap32(&p[13]); - break; - default: - abort(); - } - p = &p[n]; - } -} - -#ifdef TEST - -bool lre_check_stack_overflow(void *opaque, size_t alloca_size) -{ - return false; -} - -void *lre_realloc(void *opaque, void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -int main(int argc, char **argv) -{ - int len, flags, ret, i; - uint8_t *bc; - char error_msg[64]; - uint8_t *capture[CAPTURE_COUNT_MAX * 2]; - const char *input; - int input_len, capture_count; - - if (argc < 4) { - printf("usage: %s regexp flags input\n", argv[0]); - exit(1); - } - flags = atoi(argv[2]); - bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], - strlen(argv[1]), flags, NULL); - if (!bc) { - fprintf(stderr, "error: %s\n", error_msg); - exit(1); - } - - input = argv[3]; - input_len = strlen(input); - - ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); - printf("ret=%d\n", ret); - if (ret == 1) { - capture_count = lre_get_capture_count(bc); - for(i = 0; i < 2 * capture_count; i++) { - uint8_t *ptr; - ptr = capture[i]; - printf("%d: ", i); - if (!ptr) - printf(""); - else - printf("%u", (int)(ptr - (uint8_t *)input)); - printf("\n"); - } - } - return 0; -} -#endif diff --git a/NativeScript/napi/quickjs/source/libregexp.h b/NativeScript/napi/quickjs/source/libregexp.h deleted file mode 100644 index 0b8fec52b..000000000 --- a/NativeScript/napi/quickjs/source/libregexp.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Regular Expression Engine - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIBREGEXP_H -#define LIBREGEXP_H - -#include -#include - -#include "libunicode.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define LRE_FLAG_GLOBAL (1 << 0) -#define LRE_FLAG_IGNORECASE (1 << 1) -#define LRE_FLAG_MULTILINE (1 << 2) -#define LRE_FLAG_DOTALL (1 << 3) -#define LRE_FLAG_UNICODE (1 << 4) -#define LRE_FLAG_STICKY (1 << 5) -#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ -#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ -#define LRE_FLAG_UNICODE_SETS (1 << 8) - -uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, - const char *buf, size_t buf_len, int re_flags, - void *opaque); -int lre_get_capture_count(const uint8_t *bc_buf); -int lre_get_flags(const uint8_t *bc_buf); -const char *lre_get_groupnames(const uint8_t *bc_buf); -int lre_exec(uint8_t **capture, - const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, - int cbuf_type, void *opaque); - -int lre_parse_escape(const uint8_t **pp, int allow_utf16); -bool lre_is_space(int c); - -void lre_byte_swap(uint8_t *buf, size_t len, bool is_byte_swapped); - -/* must be provided by the user */ -bool lre_check_stack_overflow(void *opaque, size_t alloca_size); -void *lre_realloc(void *opaque, void *ptr, size_t size); - -/* JS identifier test */ -extern uint32_t const lre_id_start_table_ascii[4]; -extern uint32_t const lre_id_continue_table_ascii[4]; - -static inline int lre_js_is_ident_first(int c) -{ - if ((uint32_t)c < 128) { - return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; - } else { - return lre_is_id_start(c); - } -} - -static inline int lre_js_is_ident_next(int c) -{ - if ((uint32_t)c < 128) { - return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; - } else { - /* ZWNJ and ZWJ are accepted in identifiers */ - return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; - } -} - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* LIBREGEXP_H */ diff --git a/NativeScript/napi/quickjs/source/libunicode-table.h b/NativeScript/napi/quickjs/source/libunicode-table.h deleted file mode 100644 index b48a3a746..000000000 --- a/NativeScript/napi/quickjs/source/libunicode-table.h +++ /dev/null @@ -1,4658 +0,0 @@ -/* Compressed unicode tables */ -/* Automatically generated file - do not edit */ - -#include - -static const uint32_t case_conv_table1[378] = { - 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, - 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, - 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, - 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, - 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, - 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, - 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, - 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, - 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, - 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, - 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, - 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, - 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, - 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, - 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, - 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, - 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, - 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, - 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, - 0x01220130, 0x01228130, 0x01230a40, 0x01280101, - 0x01288101, 0x01290101, 0x01298100, 0x012a0100, - 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, - 0x01300100, 0x01308101, 0x01318100, 0x01320101, - 0x01328101, 0x01330101, 0x01340100, 0x01348100, - 0x01350101, 0x01358101, 0x01360101, 0x01378100, - 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, - 0x01400100, 0x01410101, 0x01418100, 0x01438101, - 0x01440100, 0x01448100, 0x01450200, 0x01460100, - 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, - 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, - 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, - 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, - 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, - 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, - 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, - 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, - 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, - 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, - 0x02001030, 0x02082030, 0x02182000, 0x02281000, - 0x02302240, 0x02453640, 0x02600130, 0x02608e40, - 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, - 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, - 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, - 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, - 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, - 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, - 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, - 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, - 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, - 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, - 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, - 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, - 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, - 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, - 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, - 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, - 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, - 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, - 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, - 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, - 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, - 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, - 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, - 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, - 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, - 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, - 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, - 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, - 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, - 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, - 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, - 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, - 0x16300240, 0x16310130, 0x16318130, 0x16320130, - 0x16328100, 0x16330100, 0x16338640, 0x16368130, - 0x16370130, 0x16378130, 0x16380130, 0x16390240, - 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, - 0x16790240, 0x16802600, 0x16938100, 0x16968100, - 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, - 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, - 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, - 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, - 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, - 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, - 0x53e30130, 0x53e38440, 0x53e58130, 0x53e60240, - 0x53e80240, 0x53eb0640, 0x53ee0130, 0x53fa8240, - 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, - 0x7d8101b2, 0x7d8181da, 0x7d8201da, 0x7d8281b3, - 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, - 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, - 0x82002831, 0x82142801, 0x82582431, 0x826c2401, - 0x82b80b31, 0x82be0f31, 0x82c60731, 0x82ca0231, - 0x82cb8b01, 0x82d18f01, 0x82d98701, 0x82dd8201, - 0x86403331, 0x86603301, 0x86a81631, 0x86b81601, - 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, - 0xf4802231, 0xf4912201, -}; - -static const uint8_t case_conv_table2[378] = { - 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, - 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, - 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, - 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, - 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x52, 0x63, 0x65, - 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, - 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, - 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, - 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, - 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, - 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, - 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, - 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, - 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, - 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, - 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, - 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, - 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, - 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, - 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, - 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, - 0xb7, 0xb8, 0x55, 0xac, 0xab, 0x12, 0x14, 0x1e, - 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, - 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, - 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, - 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, - 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, - 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, - 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, - 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, - 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, - 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, - 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, - 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, - 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, - 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, - 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, - 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, - 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, - 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, - 0x54, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, - 0x00, 0x00, 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, - 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, - 0x6f, 0xb2, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66, - 0x6c, 0x6d, 0x6e, 0x6f, 0x68, 0x69, 0x6a, 0x6b, - 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, - 0x79, 0x78, -}; - -static const uint16_t case_conv_ext[58] = { - 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, - 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, - 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, - 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, - 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, - 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, - 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, - 0x006b, 0x00e5, -}; - -static const uint8_t unicode_prop_Cased1_table[193] = { - 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, - 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, - 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, - 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, - 0x20, 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, - 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, - 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, - 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, - 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, - 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, - 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, - 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, - 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, - 0x05, 0x80, 0x98, 0x80, 0xa2, 0x00, 0x80, 0x9b, - 0x12, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8d, - 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, - 0x60, 0xcc, 0x44, 0xd4, 0x80, 0xc6, 0x01, 0x08, - 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, - 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, - 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x07, 0x47, 0x33, 0x89, 0x80, 0x93, 0x2d, 0x41, - 0x04, 0xbd, 0x50, 0xc1, 0x99, 0x85, 0x99, 0x85, - 0x99, -}; - -static const uint8_t unicode_prop_Cased1_index[18] = { - 0xb9, 0x02, 0x80, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, - 0x40, 0xbb, 0x07, 0x01, 0xdb, 0xd6, 0x01, 0x8a, - 0xf1, 0x01, -}; - -static const uint8_t unicode_prop_Case_Ignorable_table[764] = { - 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, - 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, - 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, - 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, - 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, - 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, - 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, - 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, - 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, - 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, - 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, - 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, - 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, - 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, - 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, - 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, - 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, - 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, - 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, - 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, - 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, - 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, - 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, - 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, - 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, - 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, - 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, - 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, - 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, - 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, - 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, - 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, - 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, - 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, - 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, - 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, - 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, - 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, - 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, - 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, - 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, - 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, - 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, - 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, - 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, - 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, - 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, - 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, - 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, - 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, - 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, - 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, - 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, - 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, - 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, - 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, - 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, - 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, - 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, - 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, - 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, - 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, - 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, 0x3a, 0x81, - 0xce, 0x83, 0xc5, 0x8a, 0xb0, 0x83, 0xfa, 0x80, - 0xb5, 0x8e, 0xa8, 0x01, 0x81, 0x89, 0x82, 0xb0, - 0x19, 0x09, 0x03, 0x80, 0x89, 0x80, 0xb1, 0x82, - 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, 0x81, 0xb3, - 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, 0x00, 0x0d, - 0x01, 0x80, 0x40, 0x9c, 0x02, 0x87, 0x94, 0x81, - 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0xc5, 0x85, - 0x8c, 0x00, 0x00, 0x80, 0x8d, 0x81, 0xd4, 0x39, - 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, - 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a, 0x81, - 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, 0x28, - 0x80, 0xe4, 0x00, 0x01, 0x18, 0x84, 0x41, 0x02, - 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, 0x40, - 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, - 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, 0x01, - 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, - 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, - 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, 0x8a, - 0x81, 0xb3, 0x24, 0x00, 0x80, 0x96, 0x80, 0x54, - 0xd4, 0x90, 0x85, 0x8e, 0x60, 0x2c, 0xc7, 0x8b, - 0x12, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x88, 0x83, - 0x41, 0xfb, 0x82, 0xa7, 0x81, 0x41, 0xe1, 0x80, - 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, - 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, - 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, - 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, - 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, - 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, - 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, - 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, - 0x40, 0xfd, 0x81, 0x42, 0xdf, 0x86, 0xec, 0x87, - 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, 0x80, 0x9d, - 0xdf, 0xff, 0x40, 0xef, -}; - -static const uint8_t unicode_prop_Case_Ignorable_index[72] = { - 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, - 0xa0, 0xc1, 0x0b, 0x00, 0x82, 0x0d, 0x00, 0x3f, - 0x10, 0x80, 0xd4, 0x17, 0x40, 0xcf, 0x1a, 0x20, - 0xf5, 0x1c, 0x00, 0x80, 0x20, 0x00, 0x16, 0xa0, - 0x00, 0xc6, 0xa8, 0x00, 0xc2, 0xaa, 0x60, 0x56, - 0xfe, 0x20, 0xb1, 0x07, 0x01, 0x02, 0x10, 0x01, - 0x42, 0x12, 0x41, 0xc4, 0x14, 0x21, 0xe1, 0x19, - 0x81, 0x48, 0x1d, 0x01, 0x44, 0x6b, 0x01, 0x83, - 0xd1, 0x21, 0x3e, 0xe1, 0x01, 0xf0, 0x01, 0x0e, -}; - -static const uint8_t unicode_prop_ID_Start_table[1133] = { - 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, - 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, - 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, - 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, - 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, - 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, - 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, - 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, - 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, - 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, - 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, - 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, - 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, - 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, - 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, - 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, - 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, - 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, - 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, - 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, - 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, - 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, - 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, - 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, - 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, - 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, - 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, - 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, - 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, - 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, - 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, - 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, - 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, - 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, - 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, - 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, - 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, - 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, - 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, - 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, - 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, - 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, - 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, - 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, - 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, - 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, - 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, - 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, - 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, - 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, - 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, - 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, - 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, - 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, - 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, - 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, - 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, - 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, - 0x88, 0x81, 0xe6, 0x81, 0xc2, 0x09, 0x00, 0x07, - 0x94, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, - 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, - 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, - 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, - 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, - 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, - 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, - 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, - 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, - 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, - 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, - 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, - 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, - 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, - 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, - 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, - 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, - 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, - 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, - 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, - 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x82, 0xb3, - 0x8b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, - 0x28, 0xa9, 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, - 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, - 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, - 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, - 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, - 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, - 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, - 0x9b, 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, - 0x82, 0xba, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, - 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, - 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, - 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, - 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, - 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, - 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, - 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, - 0x00, 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, - 0xab, 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, - 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, - 0xa3, 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, - 0xc6, 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, - 0xbf, 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, - 0x00, 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, - 0x9b, 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, - 0xad, 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, - 0x9e, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, - 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, - 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, 0x8e, - 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, 0x43, - 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0x4b, - 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, 0x4f, - 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, 0x46, - 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, 0x90, - 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, - 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, 0xff, - 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, - 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa8, 0x89, 0x60, - 0x22, 0xe6, 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, - 0x80, 0x9c, 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, - 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, - 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, - 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, - 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, - 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, - 0x07, 0x47, 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, - 0x40, 0x91, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, - 0x40, 0x9d, 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, - 0xe3, 0x9d, 0x08, 0x41, 0xee, 0x30, 0x18, 0x08, - 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, - 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, - 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, - 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, - 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, - 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, - 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, - 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, 0xe1, - 0x53, 0x4a, 0x84, 0x50, 0x5f, -}; - -static const uint8_t unicode_prop_ID_Start_index[108] = { - 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, - 0x20, 0xb1, 0x0a, 0x00, 0xba, 0x0b, 0x20, 0x3b, - 0x0d, 0x20, 0xc7, 0x0e, 0x20, 0x49, 0x12, 0x00, - 0x9b, 0x16, 0x00, 0xac, 0x19, 0x00, 0xc0, 0x1d, - 0x80, 0x80, 0x20, 0x20, 0x70, 0x2d, 0x00, 0x00, - 0x32, 0x00, 0xdd, 0xa7, 0x00, 0x4c, 0xaa, 0x20, - 0xc7, 0xd7, 0x20, 0xfc, 0xfd, 0x20, 0x9d, 0x02, - 0x21, 0x96, 0x05, 0x01, 0x9f, 0x08, 0x01, 0x49, - 0x0c, 0x21, 0x76, 0x10, 0x21, 0xa9, 0x12, 0x01, - 0xb0, 0x14, 0x01, 0x42, 0x19, 0x41, 0x90, 0x1c, - 0x01, 0xf1, 0x2f, 0x21, 0x90, 0x6b, 0x21, 0x33, - 0xb1, 0x21, 0x06, 0xd5, 0x01, 0xc3, 0xd7, 0x01, - 0xff, 0xe7, 0x21, 0x63, 0xee, 0x01, 0x5e, 0xee, - 0x42, 0xb0, 0x23, 0x03, -}; - -static const uint8_t unicode_prop_ID_Continue1_table[695] = { - 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, - 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, - 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, - 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, - 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, - 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, - 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, - 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, - 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, - 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, - 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, - 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, - 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, - 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, - 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, - 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, - 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, - 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, - 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, - 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, - 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, - 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, - 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, - 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, - 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, - 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, - 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, - 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, - 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, - 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, - 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, - 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, - 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, - 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, - 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, - 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x0b, 0x81, - 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, - 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, - 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0xdf, 0x80, - 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, 0x89, 0x9f, - 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, - 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, 0x91, 0x89, - 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, - 0x8c, 0xab, 0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, - 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, 0x07, 0x09, - 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, - 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, - 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, - 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, - 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, 0x80, 0x40, - 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, - 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, - 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x85, - 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, 0xce, 0x83, - 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, - 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, - 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, - 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, - 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, - 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, - 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0xc2, 0x88, - 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, 0x81, 0xd1, - 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, - 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, - 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0x85, 0x93, - 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, - 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, 0x00, 0x81, - 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, 0x9b, 0x89, - 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, - 0x41, 0x55, 0x89, 0xb4, 0x38, 0x87, 0x8f, 0x89, - 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, - 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, 0x89, - 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, - 0x84, 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, - 0x2c, 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, - 0xa5, 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, - 0x89, 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, - 0x8a, 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, - 0x89, 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, - 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, - 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, - 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, - 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, - 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, - 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, - 0x89, 0x42, 0xd4, 0x86, 0xec, 0x34, 0x89, 0x52, - 0x95, 0x89, 0x6c, 0x05, 0x05, 0x40, 0xef, -}; - -static const uint8_t unicode_prop_ID_Continue1_index[66] = { - 0xfa, 0x06, 0x00, 0x70, 0x09, 0x00, 0xf0, 0x0a, - 0x40, 0x57, 0x0c, 0x00, 0xf0, 0x0d, 0x60, 0xc7, - 0x0f, 0x20, 0xea, 0x17, 0x40, 0x05, 0x1b, 0x00, - 0x0e, 0x20, 0x00, 0xa0, 0xa6, 0x20, 0xe6, 0xa9, - 0x20, 0x10, 0xfe, 0x00, 0x40, 0x0a, 0x01, 0xc3, - 0x10, 0x01, 0x4e, 0x13, 0x01, 0x41, 0x16, 0x01, - 0x0b, 0x1a, 0x01, 0xaa, 0x1d, 0x01, 0x7a, 0x6d, - 0x21, 0x45, 0xd2, 0x21, 0xaf, 0xe2, 0x01, 0xf0, - 0x01, 0x0e, -}; - -static const uint8_t unicode_prop_White_Space_table[22] = { - 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, - 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, - 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, -}; - -static const uint8_t unicode_prop_White_Space_index[3] = { - 0x01, 0x30, 0x00, -}; - -static const uint8_t unicode_cc_table[916] = { - 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, - 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, - 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, - 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, - 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, - 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, - 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, - 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, - 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, - 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, - 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, - 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, - 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, - 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, - 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, - 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, - 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, - 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, - 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, - 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, - 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, - 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, - 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, - 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, - 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, - 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, - 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, - 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, - 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, - 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, - 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, - 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, - 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, - 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, - 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, - 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, - 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, - 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, - 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, - 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, - 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, - 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, - 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, - 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, - 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, - 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, - 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, - 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, - 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, - 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, - 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, - 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, - 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, - 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, - 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, - 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, - 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, 0xc0, - 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, 0x85, - 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, - 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, - 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, - 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, - 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, - 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, - 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, - 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, - 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, - 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, - 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, - 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, - 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, - 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, - 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, - 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, - 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, - 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, - 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, - 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, - 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, - 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, - 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, - 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb0, - 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1f, 0x02, - 0xdc, 0xb0, 0x15, 0x01, 0xdc, 0xc2, 0x00, 0xdc, - 0xc0, 0x03, 0xdc, 0xb0, 0x00, 0xc0, 0x00, 0xdc, - 0xc0, 0x00, 0xdc, 0xb0, 0x8f, 0x00, 0x09, 0xa8, - 0x00, 0x09, 0x8d, 0x00, 0x09, 0xb0, 0x08, 0x00, - 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, - 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, - 0x09, 0x88, 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, - 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, - 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, - 0xc6, 0x82, 0xc4, 0xb0, 0x28, 0x02, 0x09, 0xb0, - 0x40, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, - 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, 0xca, - 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, 0x09, - 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, - 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, 0x07, - 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, 0xb0, - 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, 0x91, - 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, - 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, - 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, 0x01, - 0x09, 0xb8, 0x39, 0xbb, 0x00, 0x09, 0xb8, 0x01, - 0x8f, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, 0x88, - 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, 0xb8, - 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, - 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, 0xc4, - 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, 0xb8, - 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, - 0xc1, 0x80, 0xc4, 0xb0, 0x33, 0xc0, 0xb0, 0x6f, - 0xc6, 0xb1, 0x46, 0xc0, 0xb0, 0x0c, 0xc3, 0xb1, - 0xcb, 0x01, 0xe8, 0x00, 0xdc, 0xc0, 0xb0, 0xcd, - 0xc0, 0x00, 0xdc, 0xb2, 0xaf, 0x06, 0xdc, 0xb0, - 0x3c, 0xc5, 0x00, 0x07, -}; - -static const uint8_t unicode_cc_index[87] = { - 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, - 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0x9c, - 0x08, 0x00, 0x4d, 0x09, 0x00, 0x3c, 0x0b, 0x00, - 0x3d, 0x0d, 0x00, 0x36, 0x0f, 0x00, 0x38, 0x10, - 0x20, 0x3a, 0x19, 0x00, 0xcb, 0x1a, 0x20, 0xd3, - 0x1c, 0x00, 0xcf, 0x1d, 0x00, 0xe2, 0x20, 0x00, - 0x2e, 0x30, 0x20, 0x2b, 0xa9, 0x20, 0xed, 0xab, - 0x00, 0x39, 0x0a, 0x01, 0x4c, 0x0f, 0x01, 0x35, - 0x11, 0x21, 0x66, 0x13, 0x01, 0x40, 0x16, 0x01, - 0x47, 0x1a, 0x01, 0xf0, 0x6a, 0x21, 0x8a, 0xd1, - 0x01, 0xec, 0xe4, 0x21, 0x4b, 0xe9, 0x01, -}; - -static const uint32_t unicode_decomp_table1[709] = { - 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, - 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, - 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, - 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, - 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, - 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, - 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, - 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, - 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, - 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, - 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, - 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, - 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, - 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, - 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, - 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, - 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, - 0x011d8144, 0x01304144, 0x01340244, 0x01358144, - 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, - 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, - 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, - 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, - 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, - 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, - 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, - 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, - 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, - 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, - 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, - 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, - 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, - 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, - 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, - 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, - 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, - 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, - 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, - 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, - 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, - 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, - 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, - 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, - 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, - 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, - 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, - 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, - 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, - 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, - 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, - 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, - 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, - 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, - 0x0805c097, 0x08090081, 0x08094097, 0x08098099, - 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, - 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, - 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, - 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, - 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, - 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, - 0x08458097, 0x08464295, 0x08480097, 0x08484099, - 0x08488097, 0x08490081, 0x08498080, 0x084a0081, - 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, - 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, - 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, - 0x08584097, 0x08588099, 0x0858c097, 0x08590081, - 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, - 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, - 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, - 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, - 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, - 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, - 0x088100be, 0x088240be, 0x088300be, 0x088901be, - 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, - 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, - 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, - 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, - 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, - 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, - 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, - 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, - 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, - 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, - 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, - 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, - 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, - 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, - 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, - 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, - 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, - 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, - 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, - 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, - 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, - 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, - 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, - 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, - 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, - 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, - 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, - 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, - 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, - 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, - 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, - 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, - 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, - 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, - 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, - 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, - 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, - 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, - 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, - 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, - 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, - 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, - 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, - 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, - 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, - 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, - 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, - 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, - 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, - 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, - 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, - 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, - 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, - 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, - 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, - 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, - 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, - 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, - 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, - 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, - 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, - 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, - 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, - 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, - 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, - 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, - 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, - 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, - 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, - 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, - 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, - 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, - 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, - 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, - 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, - 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, - 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, - 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, - 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, - 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, - 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, - 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, - 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, - 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, - 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, - 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, - 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, - 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, - 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, - 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, - 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, - 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, - 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, - 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, - 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, - 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, - 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, - 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, - 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, - 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, - 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, - 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, - 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, - 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, - 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, - 0xbe69bc10, -}; - -static const uint16_t unicode_decomp_table2[709] = { - 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, - 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, - 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, - 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, - 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, - 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, - 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, - 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, - 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, - 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, - 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, - 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, - 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, - 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, - 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, - 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, - 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, - 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, - 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, - 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, - 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, - 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, - 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, - 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, - 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, - 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, - 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, - 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, - 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, - 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, - 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, - 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, - 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, - 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, - 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, - 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, - 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, - 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, - 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, - 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, - 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, - 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, - 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, - 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, - 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, - 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, - 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, - 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, - 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, - 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, - 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, - 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, - 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, - 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, - 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, - 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, - 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, - 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, - 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, - 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, - 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, - 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, - 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, - 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, - 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, - 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, - 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, - 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, - 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, - 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, - 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, - 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c4d, 0x1c52, 0x1c88, 0x1cce, - 0x1cdc, 0x1ce1, 0x1cea, 0x1cf3, 0x1d01, 0x1d06, 0x1d0b, 0x1d1d, - 0x1d2f, 0x1d38, 0x1d3d, 0x1d61, 0x1d6f, 0x1d71, 0x1d73, 0x1d93, - 0x1dae, 0x1db0, 0x1db2, 0x1db4, 0x1db6, 0x1db8, 0x1dba, 0x1dbc, - 0x1ddc, 0x1dde, 0x1de0, 0x1de2, 0x1de4, 0x1deb, 0x1ded, 0x1def, - 0x1df1, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, - 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, - 0x1e20, 0x03f4, 0x1e22, 0x2207, 0x1e24, 0x2202, 0x1e26, 0x1e2e, - 0x03f4, 0x1e30, 0x2207, 0x1e32, 0x2202, 0x1e34, 0x1e3c, 0x03f4, - 0x1e3e, 0x2207, 0x1e40, 0x2202, 0x1e42, 0x1e4a, 0x03f4, 0x1e4c, - 0x2207, 0x1e4e, 0x2202, 0x1e50, 0x1e58, 0x03f4, 0x1e5a, 0x2207, - 0x1e5c, 0x2202, 0x1e5e, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, - 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e80, 0x1ea3, 0x1ea7, 0x1ead, - 0x1eca, 0x062d, 0x1ed2, 0x1ede, 0x062c, 0x1eee, 0x1f5e, 0x1f6a, - 0x1f7d, 0x1f8f, 0x1fa2, 0x1fa4, 0x1fa8, 0x1fae, 0x1fb4, 0x1fb6, - 0x1fba, 0x1fbc, 0x1fc4, 0x1fc7, 0x1fc9, 0x1fcf, 0x1fd1, 0x30b5, - 0x1fd7, 0x202f, 0x2045, 0x2049, 0x204b, 0x2050, 0x209d, 0x20ae, - 0x21af, 0x21bf, 0x21c5, 0x22bf, 0x23dd, -}; - -static const uint8_t unicode_decomp_data[9451] = { - 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, - 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, - 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, - 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, - 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, - 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, - 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, - 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, - 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, - 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, - 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, - 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, - 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, - 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, - 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, - 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, - 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, - 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, - 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, - 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, - 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, - 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, - 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, - 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, - 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, - 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, - 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, - 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, - 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, - 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, - 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, - 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, - 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, - 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, - 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, - 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, - 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, - 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, - 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, - 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, - 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, - 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, - 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, - 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, - 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, - 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, - 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, - 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, - 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, - 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, - 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, - 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, - 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, - 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, - 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, - 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, - 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, - 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, - 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, - 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, - 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, - 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, - 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, - 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, - 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, - 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, - 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, - 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, - 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, - 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, - 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, - 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, - 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, - 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, - 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, - 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, - 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, - 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, - 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, - 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, - 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, - 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, - 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, - 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, - 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, - 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, - 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, - 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, - 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, - 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, - 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, - 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, - 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, - 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, - 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, - 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, - 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, - 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, - 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, - 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, - 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, - 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, - 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, - 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, - 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, - 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, - 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, - 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, - 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, - 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, - 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, - 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, - 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, - 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, - 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, - 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, - 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, - 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, - 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, - 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, - 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, - 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, - 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, - 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, - 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, - 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, - 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, - 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, - 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, - 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, - 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, - 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, - 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, - 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, - 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, - 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, - 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, - 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, - 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, - 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, - 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, - 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, - 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, - 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, - 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, - 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, - 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, - 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, - 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, - 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, - 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, - 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, - 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, - 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, - 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, - 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, - 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, - 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, - 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, - 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, - 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, - 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, - 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, - 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, - 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, - 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, - 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, - 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, - 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, - 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, - 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, - 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, - 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, - 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, - 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, - 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, - 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, - 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, - 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, - 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, - 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, - 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, - 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, - 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, - 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, - 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, - 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, - 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, - 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, - 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, - 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, - 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, - 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, - 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, - 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, - 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, - 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, - 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, - 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, - 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, - 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, - 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, - 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, - 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, - 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, - 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, - 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, - 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, - 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, - 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, - 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, - 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, - 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, - 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, - 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, - 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, - 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, - 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, - 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, - 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, - 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, - 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, - 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, - 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, - 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, - 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, - 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, - 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, - 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, - 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, - 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, - 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, - 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, - 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, - 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, - 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, - 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, - 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, - 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, - 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, - 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, - 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, - 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, - 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, - 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, - 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, - 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, - 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, - 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, - 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, - 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, - 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, - 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, - 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, - 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, - 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, - 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, - 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, - 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, - 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, - 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, - 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, - 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, - 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, - 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, - 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, - 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, - 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, - 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, - 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, - 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, - 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, - 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, - 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, - 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, - 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, - 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, - 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, - 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, - 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, - 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, - 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, - 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, - 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, - 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, - 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, - 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, - 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, - 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, - 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, - 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, - 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, - 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, - 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, - 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, - 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, - 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, - 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, - 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, - 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, - 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, - 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, - 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, - 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, - 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, - 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, - 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, - 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, - 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, - 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, - 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, - 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, - 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, - 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, - 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, - 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, - 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, - 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, - 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, - 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, - 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, - 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, - 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, - 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, - 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, - 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, - 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, - 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, - 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, - 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, - 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, - 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, - 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, - 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, - 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, - 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, - 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, - 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, - 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, - 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, - 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, - 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, - 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, - 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, - 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, - 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, - 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, - 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, - 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, - 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, - 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, - 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, - 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, - 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, - 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, - 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, - 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, - 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, - 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, - 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, - 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, - 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, - 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, - 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, - 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, - 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, - 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, - 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, - 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, - 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, - 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, - 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, - 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, - 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, - 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, - 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, - 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, - 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, - 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, - 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, - 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, - 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, - 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, - 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, - 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, - 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, - 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, - 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, - 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, - 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, - 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, - 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, - 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, - 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, - 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, - 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, - 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, - 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, - 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, - 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, - 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, - 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, - 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, - 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, - 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, - 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, - 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, - 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, - 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, - 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, - 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, - 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, - 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, - 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, - 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, - 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, - 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, - 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, - 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, - 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, - 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, - 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, - 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, - 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, - 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, - 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, - 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, - 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, - 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, - 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, - 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, - 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, - 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, - 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, - 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, - 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, - 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, - 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, - 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, - 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, - 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, - 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, - 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, - 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, - 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, - 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, - 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, - 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, - 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, - 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, - 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, - 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, - 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, - 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, - 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, - 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, - 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, - 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, - 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, - 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, - 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, - 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, - 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, - 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, - 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, - 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, - 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, - 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, - 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, - 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, - 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, - 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, - 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, - 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, - 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, - 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, - 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, - 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, - 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, - 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, - 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, - 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, - 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, - 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, - 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, - 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, - 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, - 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, - 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, - 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, - 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, - 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, - 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, - 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, - 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, - 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, - 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, - 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, - 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, - 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, - 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, - 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, - 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, - 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, - 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, - 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, - 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, - 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, - 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, - 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, - 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, - 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, - 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, - 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, - 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, - 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, - 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, - 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, - 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, - 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, - 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, - 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, - 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, - 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, - 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, - 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, - 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, - 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, - 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, - 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, - 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, - 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, - 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, - 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, - 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, - 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, - 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, - 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, - 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, - 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, - 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, - 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, - 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, - 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, - 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, - 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, - 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, - 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, - 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, - 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, - 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, - 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, - 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, - 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, - 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, - 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, - 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, - 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, - 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, - 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, - 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, - 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, - 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, - 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, - 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, - 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, - 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, - 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, - 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, - 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, - 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, - 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, - 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, - 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, - 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, - 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, - 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, - 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, - 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, - 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, - 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, - 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, - 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, - 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, - 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, - 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, - 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, - 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, - 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, - 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, - 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, - 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, - 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, - 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, - 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, - 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, - 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, - 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, - 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, - 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, - 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, - 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, - 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, - 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, - 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, - 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, - 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, - 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, - 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, - 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, - 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, - 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, - 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, - 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, - 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, - 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, - 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, - 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, - 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, - 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, - 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, - 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, - 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, - 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, - 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, - 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, - 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, - 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, - 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, - 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, - 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, - 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, - 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, - 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, - 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, - 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, - 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, - 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, - 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, - 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, - 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, - 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, - 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, - 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, - 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, - 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, - 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, - 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, - 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, - 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, - 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, - 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, - 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, - 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, - 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, - 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, - 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, - 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, - 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, - 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, - 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, - 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, - 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, - 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, - 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, - 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, - 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, - 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, - 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, - 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, - 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, - 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, - 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, - 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, - 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, - 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, - 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, - 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, - 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, - 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, - 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, - 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, - 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, - 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, - 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, - 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, - 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, - 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, - 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, - 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, - 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, - 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, - 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, - 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, - 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, - 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, - 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, - 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, - 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, - 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, - 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, - 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, - 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, - 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, - 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, - 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, - 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, - 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, - 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, - 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, - 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, - 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, - 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, - 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, - 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, - 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, - 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, - 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, - 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, - 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, - 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, - 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, - 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, - 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, - 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, - 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, - 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, - 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, - 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, - 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, - 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, - 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, - 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, - 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, - 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, - 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, - 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, - 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, - 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, - 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, - 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, - 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, - 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, - 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, - 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, - 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, - 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, - 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, - 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, - 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, - 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, - 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, - 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, - 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, - 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, - 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, - 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, - 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, - 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, - 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, - 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, - 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, - 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, - 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, - 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, - 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, - 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, - 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, - 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, - 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, - 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, - 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, - 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, - 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, - 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, - 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, - 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, - 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, - 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, - 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, - 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, - 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, - 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, - 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, - 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, - 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, - 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, - 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, - 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, - 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, - 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, - 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, - 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, - 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, - 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, - 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, - 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, - 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, - 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, - 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, - 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, - 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, - 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, - 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, - 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, - 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, - 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, - 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, - 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, - 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, - 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, - 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, - 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, - 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, - 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, - 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, - 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, - 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, - 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, - 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, - 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, - 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, - 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, - 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, - 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, - 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, - 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, - 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, - 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, - 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, - 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, - 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, - 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, - 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, - 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, - 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, - 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, - 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, - 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, - 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, - 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, - 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, - 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, - 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, - 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, - 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, - 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, - 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, - 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, - 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, - 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, - 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, - 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, - 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, - 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, - 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, - 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, - 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, - 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, - 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, - 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, - 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, - 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, - 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, - 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, - 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, - 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, - 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, - 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, 0x07, - 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, - 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, - 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, - 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, - 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, - 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, - 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, - 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, - 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, - 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, - 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, - 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, - 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, - 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, - 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, - 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, - 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, - 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, - 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, - 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, - 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, 0x00, - 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, 0x05, - 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, 0x13, - 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, 0x00, - 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, 0xc2, - 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, 0xba, - 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, 0x00, - 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, - 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, 0x55, - 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, 0x1e, - 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, 0x1f, - 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, 0x20, - 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, 0x1f, - 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, 0x55, - 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, 0x67, - 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, 0x41, - 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, 0x58, - 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, - 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, - 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, - 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, - 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, - 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, - 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, - 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, - 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, - 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, - 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, - 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, - 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, - 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, - 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, - 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, - 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, - 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, - 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, - 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, - 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, - 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, - 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, - 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, - 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, - 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, - 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, - 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, - 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, - 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, - 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, - 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, - 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, - 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, - 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, - 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, - 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, - 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, - 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, - 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, - 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, - 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, - 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, - 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, - 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, - 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, - 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, - 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, - 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, - 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, - 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, - 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, - 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, - 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, - 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, - 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, - 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, - 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, - 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, - 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, - 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, - 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, - 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, - 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, - 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, - 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, - 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, - 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, - 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, - 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, - 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, - 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, - 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, - 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, - 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, - 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, - 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, - 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, - 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, - 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, - 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, - 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, - 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, - 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, - 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, - 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, - 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, - 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, - 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, - 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, - 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, - 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, - 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, - 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, - 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, - 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, - 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, - 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, - 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, - 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, - 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, - 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, - 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, - 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, - 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, - 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, - 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, - 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, - 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, - 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, - 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, - 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, - 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, - 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, - 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, - 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, - 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, - 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, - 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, - 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, - 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, - 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, - 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, - 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, - 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, - 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, - 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, - 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, - 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, - 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, - 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, - 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, - 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, - 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, - 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, - 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, - 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, - 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, - 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, - 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, - 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, - 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, - 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, - 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, - 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, - 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, - 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, - 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, - 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, - 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, - 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, - 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, - 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, - 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, - 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, - 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, - 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, - 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, - 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, - 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, - 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, - 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, - 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, - 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, - 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, - 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, - 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, - 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, - 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, - 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, - 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, - 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, - 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, - 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, - 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, - 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, - 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, - 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, - 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, - 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, - 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, - 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, - 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, - 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, - 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, - 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, - 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, - 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, - 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, - 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, - 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, - 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, - 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, - 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, - 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, - 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, - 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, - 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, - 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, - 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, - 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, - 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, - 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, - 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, - 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, - 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, - 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, - 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, - 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, - 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, - 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, - 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, - 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, - 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, - 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, - 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, - 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, - 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, - 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, - 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, - 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, - 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, - 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, - 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, - 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, - 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, - 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, - 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, - 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, - 0x2a, 0x00, 0x80, -}; - -static const uint16_t unicode_comp_table[965] = { - 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, - 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, - 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, - 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, - 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, - 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, - 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, - 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, - 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, - 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, - 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, - 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, - 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, - 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, - 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, - 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, - 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, - 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, - 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, - 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, - 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, - 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, - 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, - 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, - 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, - 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, - 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, - 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, - 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, - 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, - 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, - 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, - 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, - 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, - 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, - 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, - 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, - 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, - 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, - 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, - 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, - 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, - 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, - 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, - 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, - 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, - 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, - 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, - 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, - 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, - 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, - 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, - 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, - 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, - 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, - 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, - 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, - 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, - 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, - 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, - 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, - 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, - 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, - 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, - 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, - 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, - 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, - 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, - 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, - 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, - 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, - 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, - 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, - 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, - 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, - 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, - 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, - 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, - 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, - 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, - 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, - 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, - 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, - 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, - 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, - 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, - 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, - 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, - 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, - 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, - 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, - 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, - 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, - 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, - 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, - 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, - 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, - 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, - 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, - 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, - 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, - 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, - 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, - 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, - 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, - 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, - 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, - 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, - 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, - 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, - 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, - 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, - 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, - 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, - 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, - 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, - 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, - 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, - 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, - 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, - 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, -}; - -typedef enum { - UNICODE_GC_Cn, - UNICODE_GC_Lu, - UNICODE_GC_Ll, - UNICODE_GC_Lt, - UNICODE_GC_Lm, - UNICODE_GC_Lo, - UNICODE_GC_Mn, - UNICODE_GC_Mc, - UNICODE_GC_Me, - UNICODE_GC_Nd, - UNICODE_GC_Nl, - UNICODE_GC_No, - UNICODE_GC_Sm, - UNICODE_GC_Sc, - UNICODE_GC_Sk, - UNICODE_GC_So, - UNICODE_GC_Pc, - UNICODE_GC_Pd, - UNICODE_GC_Ps, - UNICODE_GC_Pe, - UNICODE_GC_Pi, - UNICODE_GC_Pf, - UNICODE_GC_Po, - UNICODE_GC_Zs, - UNICODE_GC_Zl, - UNICODE_GC_Zp, - UNICODE_GC_Cc, - UNICODE_GC_Cf, - UNICODE_GC_Cs, - UNICODE_GC_Co, - UNICODE_GC_LC, - UNICODE_GC_L, - UNICODE_GC_M, - UNICODE_GC_N, - UNICODE_GC_S, - UNICODE_GC_P, - UNICODE_GC_Z, - UNICODE_GC_C, - UNICODE_GC_COUNT, -} UnicodeGCEnum; - -static const char unicode_gc_name_table[] = - "Cn,Unassigned" "\0" - "Lu,Uppercase_Letter" "\0" - "Ll,Lowercase_Letter" "\0" - "Lt,Titlecase_Letter" "\0" - "Lm,Modifier_Letter" "\0" - "Lo,Other_Letter" "\0" - "Mn,Nonspacing_Mark" "\0" - "Mc,Spacing_Mark" "\0" - "Me,Enclosing_Mark" "\0" - "Nd,Decimal_Number,digit" "\0" - "Nl,Letter_Number" "\0" - "No,Other_Number" "\0" - "Sm,Math_Symbol" "\0" - "Sc,Currency_Symbol" "\0" - "Sk,Modifier_Symbol" "\0" - "So,Other_Symbol" "\0" - "Pc,Connector_Punctuation" "\0" - "Pd,Dash_Punctuation" "\0" - "Ps,Open_Punctuation" "\0" - "Pe,Close_Punctuation" "\0" - "Pi,Initial_Punctuation" "\0" - "Pf,Final_Punctuation" "\0" - "Po,Other_Punctuation" "\0" - "Zs,Space_Separator" "\0" - "Zl,Line_Separator" "\0" - "Zp,Paragraph_Separator" "\0" - "Cc,Control,cntrl" "\0" - "Cf,Format" "\0" - "Cs,Surrogate" "\0" - "Co,Private_Use" "\0" - "LC,Cased_Letter" "\0" - "L,Letter" "\0" - "M,Mark,Combining_Mark" "\0" - "N,Number" "\0" - "S,Symbol" "\0" - "P,Punctuation,punct" "\0" - "Z,Separator" "\0" - "C,Other" "\0" -; - -static const uint8_t unicode_gc_table[4070] = { - 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, - 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, - 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, - 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, - 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, - 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, - 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, - 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, - 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, - 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, - 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, - 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, - 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, - 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, - 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, - 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, - 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, - 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, - 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, - 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, - 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, - 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, - 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, - 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, - 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, - 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, - 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, - 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, - 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, - 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, - 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, - 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, - 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, - 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, - 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, - 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, - 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, - 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, - 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, - 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, - 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, - 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, - 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, - 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0x80, 0xe6, - 0x01, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, - 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, - 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, - 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, - 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, - 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, - 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, - 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, - 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, - 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, - 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, - 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, - 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, - 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, - 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, - 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, - 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, - 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, - 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, - 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, - 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, - 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, - 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, - 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, - 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, - 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, - 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, - 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, - 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, - 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, - 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, - 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, - 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, - 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, - 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, - 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, - 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, - 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, - 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, - 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, - 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, - 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, - 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, - 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, - 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, - 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, - 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, - 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, - 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, - 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, - 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, - 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, - 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, - 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, - 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, - 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, - 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, - 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, - 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, - 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, - 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, - 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, - 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, - 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, - 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, - 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, - 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, - 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, - 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, - 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, - 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, - 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, - 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, - 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, - 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, - 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, - 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, - 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, - 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, - 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, - 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, - 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, - 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, - 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, - 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, - 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, - 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, - 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, - 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, - 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, - 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, - 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, - 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, - 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, - 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, - 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, - 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, - 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, - 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, - 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, 0xe9, 0x02, - 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x56, - 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, - 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, - 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, - 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, - 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, - 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, - 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, - 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, - 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, - 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, - 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, - 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, - 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, - 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, - 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, - 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, - 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, - 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, - 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, - 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, - 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, - 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, - 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, - 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, - 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, - 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, - 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, - 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, - 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, - 0xe0, 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, - 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, - 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, - 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, - 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, - 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, - 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, - 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, - 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, - 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, - 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, - 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, - 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, - 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, 0x03, 0xe0, - 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, - 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, - 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, - 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, - 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, - 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, - 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, - 0x61, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, - 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, - 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, - 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, - 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, - 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, - 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, - 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, - 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, - 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, - 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, - 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, - 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, - 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, - 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, - 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, - 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, - 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, - 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, - 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, - 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, - 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, - 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, - 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, - 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, - 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, - 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, - 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, - 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, - 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, - 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, - 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, - 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, - 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, - 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, - 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, - 0x5f, 0x3f, 0x20, 0x3f, 0x00, 0x02, 0x00, 0x02, - 0xdf, 0xe0, 0x0d, 0x44, 0x3f, 0x05, 0x24, 0x02, - 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, - 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, - 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, - 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, - 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, - 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, - 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, - 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, - 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, - 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, - 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, - 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, - 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, - 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, - 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, - 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, - 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, - 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, - 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, - 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, - 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, - 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, - 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, - 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, - 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, - 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, - 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, - 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, - 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, - 0x09, 0xe0, 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, - 0xef, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, - 0x0f, 0xe0, 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, - 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, - 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, - 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, - 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, - 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, - 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, - 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, - 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, - 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, - 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, - 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, - 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, - 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, - 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, - 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, - 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, - 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, - 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, - 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, - 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, - 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, - 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, - 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, - 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, - 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, - 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, - 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, - 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, - 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, - 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, - 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, 0x22, 0x40, - 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, 0xaf, 0xe0, - 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, - 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, 0xe4, 0x01, - 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, - 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, - 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, - 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, - 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, - 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, - 0xe5, 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, - 0xeb, 0x26, 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, - 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, - 0x60, 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, - 0xe5, 0x15, 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, - 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, - 0x8b, 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, - 0xe5, 0x0e, 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, - 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, - 0xcb, 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, - 0x2b, 0xe0, 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, - 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xe9, - 0x02, 0x65, 0x04, 0x05, 0xe1, 0x0e, 0x40, 0x86, - 0x11, 0x04, 0xe2, 0x0e, 0xe0, 0x00, 0x2c, 0xe0, - 0x80, 0x48, 0xeb, 0x17, 0x00, 0xe5, 0x22, 0x00, - 0x26, 0x11, 0x20, 0x25, 0xe0, 0x08, 0x45, 0xe0, - 0x2f, 0x66, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, - 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, - 0x0e, 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, - 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, - 0x07, 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, - 0x60, 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, - 0x05, 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, - 0x66, 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, - 0x02, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, - 0xa0, 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, - 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, - 0x00, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, - 0x26, 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, - 0x65, 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, - 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, - 0x03, 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, - 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, - 0xe0, 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, - 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, - 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, - 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, - 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, - 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, - 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, - 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x03, - 0xe5, 0x02, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, - 0x1e, 0x00, 0x05, 0x47, 0xa6, 0x00, 0x07, 0x20, - 0x07, 0x00, 0x67, 0x00, 0x27, 0x06, 0x07, 0x06, - 0x05, 0x06, 0x05, 0x36, 0x00, 0x36, 0xe0, 0x00, - 0x26, 0xe0, 0x15, 0xe5, 0x2d, 0x47, 0xe6, 0x00, - 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02, - 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, 0x16, 0xe5, - 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, - 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9, 0x02, - 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, 0x20, - 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, - 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00, 0x27, - 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, 0xe9, - 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, - 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06, 0x05, - 0x16, 0xa0, 0xe9, 0x02, 0xa0, 0xe9, 0x0c, 0xe0, - 0x14, 0xe5, 0x13, 0x20, 0x06, 0x07, 0x06, 0x27, - 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, - 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, - 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, - 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, - 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, - 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, - 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, - 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, - 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, - 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, - 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, - 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, - 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, - 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, - 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x2e, 0xe5, 0x19, - 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x01, - 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, - 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, - 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, - 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, 0x26, 0xe0, - 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, - 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, - 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, - 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, - 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, - 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, - 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, - 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, - 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, 0x05, 0xe0, - 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, - 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, - 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, - 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, - 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, - 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, 0x13, 0x80, - 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, 0xe5, 0x16, - 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, 0xe0, 0x86, - 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, - 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, 0xe9, - 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, - 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, 0x16, - 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, - 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x81, 0x28, - 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, 0x02, 0xe0, - 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, - 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, - 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, - 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, - 0x06, 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, - 0x4e, 0xe0, 0x21, 0xe5, 0x02, 0xe0, 0xa2, 0x5f, - 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, - 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, - 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, - 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, - 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, - 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, 0xef, - 0x80, 0x68, 0xe9, 0x02, 0xa0, 0xef, 0x81, 0x2c, - 0xe0, 0x44, 0xe6, 0x26, 0x20, 0xe6, 0x0f, 0xe0, - 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, 0x80, 0x6e, - 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, - 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, - 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, 0xe0, 0x0d, - 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, 0xeb, 0x0c, - 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, 0xef, 0x4f, - 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, - 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, 0xe2, 0x0a, - 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, 0x20, - 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, - 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, 0xe2, 0x03, - 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x20, - 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, - 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, 0x40, 0xc1, - 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, - 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, 0xe2, - 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, - 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, - 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, - 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, - 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, 0xe6, 0x2f, - 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, 0x06, - 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, - 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, 0x05, 0xe2, - 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, 0xc6, 0x00, - 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, - 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, 0xe0, 0x68, - 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, - 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, 0xe5, 0x16, - 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, 0xe9, 0x02, - 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, 0x13, 0x04, - 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, 0xe5, 0x16, - 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, 0xe0, 0x81, - 0x58, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, 0xe5, - 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, - 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, 0xc6, - 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, - 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, - 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, 0x80, - 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, - 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, - 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, 0x05, - 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, - 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, - 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, 0x05, - 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, - 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, - 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, 0xe0, - 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, - 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, - 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, 0xe0, - 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, 0x30, - 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, - 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, - 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x50, - 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, 0xef, - 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, 0x60, - 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, 0x30, - 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, - 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, 0x60, 0x2f, - 0xe0, 0x36, 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, - 0x06, 0x20, 0xef, 0x05, 0x40, 0xef, 0x02, 0x80, - 0xef, 0x30, 0xc0, 0xef, 0x07, 0x20, 0xef, 0x03, - 0xa0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, 0x00, - 0xef, 0x54, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, - 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x8f, 0xb2, - 0xa0, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, - 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x07, 0xe5, - 0x81, 0xe6, 0xe0, 0x89, 0x1a, 0xe5, 0x81, 0x96, - 0xe0, 0x85, 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, - 0x8f, 0xd8, 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, - 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, - 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, - 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, -}; - -typedef enum { - UNICODE_SCRIPT_Unknown, - UNICODE_SCRIPT_Adlam, - UNICODE_SCRIPT_Ahom, - UNICODE_SCRIPT_Anatolian_Hieroglyphs, - UNICODE_SCRIPT_Arabic, - UNICODE_SCRIPT_Armenian, - UNICODE_SCRIPT_Avestan, - UNICODE_SCRIPT_Balinese, - UNICODE_SCRIPT_Bamum, - UNICODE_SCRIPT_Bassa_Vah, - UNICODE_SCRIPT_Batak, - UNICODE_SCRIPT_Bengali, - UNICODE_SCRIPT_Bhaiksuki, - UNICODE_SCRIPT_Bopomofo, - UNICODE_SCRIPT_Brahmi, - UNICODE_SCRIPT_Braille, - UNICODE_SCRIPT_Buginese, - UNICODE_SCRIPT_Buhid, - UNICODE_SCRIPT_Canadian_Aboriginal, - UNICODE_SCRIPT_Carian, - UNICODE_SCRIPT_Caucasian_Albanian, - UNICODE_SCRIPT_Chakma, - UNICODE_SCRIPT_Cham, - UNICODE_SCRIPT_Cherokee, - UNICODE_SCRIPT_Chorasmian, - UNICODE_SCRIPT_Common, - UNICODE_SCRIPT_Coptic, - UNICODE_SCRIPT_Cuneiform, - UNICODE_SCRIPT_Cypriot, - UNICODE_SCRIPT_Cyrillic, - UNICODE_SCRIPT_Cypro_Minoan, - UNICODE_SCRIPT_Deseret, - UNICODE_SCRIPT_Devanagari, - UNICODE_SCRIPT_Dives_Akuru, - UNICODE_SCRIPT_Dogra, - UNICODE_SCRIPT_Duployan, - UNICODE_SCRIPT_Egyptian_Hieroglyphs, - UNICODE_SCRIPT_Elbasan, - UNICODE_SCRIPT_Elymaic, - UNICODE_SCRIPT_Ethiopic, - UNICODE_SCRIPT_Georgian, - UNICODE_SCRIPT_Glagolitic, - UNICODE_SCRIPT_Gothic, - UNICODE_SCRIPT_Garay, - UNICODE_SCRIPT_Grantha, - UNICODE_SCRIPT_Greek, - UNICODE_SCRIPT_Gujarati, - UNICODE_SCRIPT_Gunjala_Gondi, - UNICODE_SCRIPT_Gurmukhi, - UNICODE_SCRIPT_Gurung_Khema, - UNICODE_SCRIPT_Han, - UNICODE_SCRIPT_Hangul, - UNICODE_SCRIPT_Hanifi_Rohingya, - UNICODE_SCRIPT_Hanunoo, - UNICODE_SCRIPT_Hatran, - UNICODE_SCRIPT_Hebrew, - UNICODE_SCRIPT_Hiragana, - UNICODE_SCRIPT_Imperial_Aramaic, - UNICODE_SCRIPT_Inherited, - UNICODE_SCRIPT_Inscriptional_Pahlavi, - UNICODE_SCRIPT_Inscriptional_Parthian, - UNICODE_SCRIPT_Javanese, - UNICODE_SCRIPT_Kaithi, - UNICODE_SCRIPT_Kannada, - UNICODE_SCRIPT_Katakana, - UNICODE_SCRIPT_Kawi, - UNICODE_SCRIPT_Kayah_Li, - UNICODE_SCRIPT_Kharoshthi, - UNICODE_SCRIPT_Khmer, - UNICODE_SCRIPT_Khojki, - UNICODE_SCRIPT_Khitan_Small_Script, - UNICODE_SCRIPT_Khudawadi, - UNICODE_SCRIPT_Kirat_Rai, - UNICODE_SCRIPT_Lao, - UNICODE_SCRIPT_Latin, - UNICODE_SCRIPT_Lepcha, - UNICODE_SCRIPT_Limbu, - UNICODE_SCRIPT_Linear_A, - UNICODE_SCRIPT_Linear_B, - UNICODE_SCRIPT_Lisu, - UNICODE_SCRIPT_Lycian, - UNICODE_SCRIPT_Lydian, - UNICODE_SCRIPT_Makasar, - UNICODE_SCRIPT_Mahajani, - UNICODE_SCRIPT_Malayalam, - UNICODE_SCRIPT_Mandaic, - UNICODE_SCRIPT_Manichaean, - UNICODE_SCRIPT_Marchen, - UNICODE_SCRIPT_Masaram_Gondi, - UNICODE_SCRIPT_Medefaidrin, - UNICODE_SCRIPT_Meetei_Mayek, - UNICODE_SCRIPT_Mende_Kikakui, - UNICODE_SCRIPT_Meroitic_Cursive, - UNICODE_SCRIPT_Meroitic_Hieroglyphs, - UNICODE_SCRIPT_Miao, - UNICODE_SCRIPT_Modi, - UNICODE_SCRIPT_Mongolian, - UNICODE_SCRIPT_Mro, - UNICODE_SCRIPT_Multani, - UNICODE_SCRIPT_Myanmar, - UNICODE_SCRIPT_Nabataean, - UNICODE_SCRIPT_Nag_Mundari, - UNICODE_SCRIPT_Nandinagari, - UNICODE_SCRIPT_New_Tai_Lue, - UNICODE_SCRIPT_Newa, - UNICODE_SCRIPT_Nko, - UNICODE_SCRIPT_Nushu, - UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, - UNICODE_SCRIPT_Ogham, - UNICODE_SCRIPT_Ol_Chiki, - UNICODE_SCRIPT_Ol_Onal, - UNICODE_SCRIPT_Old_Hungarian, - UNICODE_SCRIPT_Old_Italic, - UNICODE_SCRIPT_Old_North_Arabian, - UNICODE_SCRIPT_Old_Permic, - UNICODE_SCRIPT_Old_Persian, - UNICODE_SCRIPT_Old_Sogdian, - UNICODE_SCRIPT_Old_South_Arabian, - UNICODE_SCRIPT_Old_Turkic, - UNICODE_SCRIPT_Old_Uyghur, - UNICODE_SCRIPT_Oriya, - UNICODE_SCRIPT_Osage, - UNICODE_SCRIPT_Osmanya, - UNICODE_SCRIPT_Pahawh_Hmong, - UNICODE_SCRIPT_Palmyrene, - UNICODE_SCRIPT_Pau_Cin_Hau, - UNICODE_SCRIPT_Phags_Pa, - UNICODE_SCRIPT_Phoenician, - UNICODE_SCRIPT_Psalter_Pahlavi, - UNICODE_SCRIPT_Rejang, - UNICODE_SCRIPT_Runic, - UNICODE_SCRIPT_Samaritan, - UNICODE_SCRIPT_Saurashtra, - UNICODE_SCRIPT_Sharada, - UNICODE_SCRIPT_Shavian, - UNICODE_SCRIPT_Siddham, - UNICODE_SCRIPT_SignWriting, - UNICODE_SCRIPT_Sinhala, - UNICODE_SCRIPT_Sogdian, - UNICODE_SCRIPT_Sora_Sompeng, - UNICODE_SCRIPT_Soyombo, - UNICODE_SCRIPT_Sundanese, - UNICODE_SCRIPT_Sunuwar, - UNICODE_SCRIPT_Syloti_Nagri, - UNICODE_SCRIPT_Syriac, - UNICODE_SCRIPT_Tagalog, - UNICODE_SCRIPT_Tagbanwa, - UNICODE_SCRIPT_Tai_Le, - UNICODE_SCRIPT_Tai_Tham, - UNICODE_SCRIPT_Tai_Viet, - UNICODE_SCRIPT_Takri, - UNICODE_SCRIPT_Tamil, - UNICODE_SCRIPT_Tangut, - UNICODE_SCRIPT_Telugu, - UNICODE_SCRIPT_Thaana, - UNICODE_SCRIPT_Thai, - UNICODE_SCRIPT_Tibetan, - UNICODE_SCRIPT_Tifinagh, - UNICODE_SCRIPT_Tirhuta, - UNICODE_SCRIPT_Tangsa, - UNICODE_SCRIPT_Todhri, - UNICODE_SCRIPT_Toto, - UNICODE_SCRIPT_Tulu_Tigalari, - UNICODE_SCRIPT_Ugaritic, - UNICODE_SCRIPT_Vai, - UNICODE_SCRIPT_Vithkuqi, - UNICODE_SCRIPT_Wancho, - UNICODE_SCRIPT_Warang_Citi, - UNICODE_SCRIPT_Yezidi, - UNICODE_SCRIPT_Yi, - UNICODE_SCRIPT_Zanabazar_Square, - UNICODE_SCRIPT_COUNT, -} UnicodeScriptEnum; - -static const char unicode_script_name_table[] = - "Adlam,Adlm" "\0" - "Ahom,Ahom" "\0" - "Anatolian_Hieroglyphs,Hluw" "\0" - "Arabic,Arab" "\0" - "Armenian,Armn" "\0" - "Avestan,Avst" "\0" - "Balinese,Bali" "\0" - "Bamum,Bamu" "\0" - "Bassa_Vah,Bass" "\0" - "Batak,Batk" "\0" - "Bengali,Beng" "\0" - "Bhaiksuki,Bhks" "\0" - "Bopomofo,Bopo" "\0" - "Brahmi,Brah" "\0" - "Braille,Brai" "\0" - "Buginese,Bugi" "\0" - "Buhid,Buhd" "\0" - "Canadian_Aboriginal,Cans" "\0" - "Carian,Cari" "\0" - "Caucasian_Albanian,Aghb" "\0" - "Chakma,Cakm" "\0" - "Cham,Cham" "\0" - "Cherokee,Cher" "\0" - "Chorasmian,Chrs" "\0" - "Common,Zyyy" "\0" - "Coptic,Copt,Qaac" "\0" - "Cuneiform,Xsux" "\0" - "Cypriot,Cprt" "\0" - "Cyrillic,Cyrl" "\0" - "Cypro_Minoan,Cpmn" "\0" - "Deseret,Dsrt" "\0" - "Devanagari,Deva" "\0" - "Dives_Akuru,Diak" "\0" - "Dogra,Dogr" "\0" - "Duployan,Dupl" "\0" - "Egyptian_Hieroglyphs,Egyp" "\0" - "Elbasan,Elba" "\0" - "Elymaic,Elym" "\0" - "Ethiopic,Ethi" "\0" - "Georgian,Geor" "\0" - "Glagolitic,Glag" "\0" - "Gothic,Goth" "\0" - "Garay,Gara" "\0" - "Grantha,Gran" "\0" - "Greek,Grek" "\0" - "Gujarati,Gujr" "\0" - "Gunjala_Gondi,Gong" "\0" - "Gurmukhi,Guru" "\0" - "Gurung_Khema,Gukh" "\0" - "Han,Hani" "\0" - "Hangul,Hang" "\0" - "Hanifi_Rohingya,Rohg" "\0" - "Hanunoo,Hano" "\0" - "Hatran,Hatr" "\0" - "Hebrew,Hebr" "\0" - "Hiragana,Hira" "\0" - "Imperial_Aramaic,Armi" "\0" - "Inherited,Zinh,Qaai" "\0" - "Inscriptional_Pahlavi,Phli" "\0" - "Inscriptional_Parthian,Prti" "\0" - "Javanese,Java" "\0" - "Kaithi,Kthi" "\0" - "Kannada,Knda" "\0" - "Katakana,Kana" "\0" - "Kawi,Kawi" "\0" - "Kayah_Li,Kali" "\0" - "Kharoshthi,Khar" "\0" - "Khmer,Khmr" "\0" - "Khojki,Khoj" "\0" - "Khitan_Small_Script,Kits" "\0" - "Khudawadi,Sind" "\0" - "Kirat_Rai,Krai" "\0" - "Lao,Laoo" "\0" - "Latin,Latn" "\0" - "Lepcha,Lepc" "\0" - "Limbu,Limb" "\0" - "Linear_A,Lina" "\0" - "Linear_B,Linb" "\0" - "Lisu,Lisu" "\0" - "Lycian,Lyci" "\0" - "Lydian,Lydi" "\0" - "Makasar,Maka" "\0" - "Mahajani,Mahj" "\0" - "Malayalam,Mlym" "\0" - "Mandaic,Mand" "\0" - "Manichaean,Mani" "\0" - "Marchen,Marc" "\0" - "Masaram_Gondi,Gonm" "\0" - "Medefaidrin,Medf" "\0" - "Meetei_Mayek,Mtei" "\0" - "Mende_Kikakui,Mend" "\0" - "Meroitic_Cursive,Merc" "\0" - "Meroitic_Hieroglyphs,Mero" "\0" - "Miao,Plrd" "\0" - "Modi,Modi" "\0" - "Mongolian,Mong" "\0" - "Mro,Mroo" "\0" - "Multani,Mult" "\0" - "Myanmar,Mymr" "\0" - "Nabataean,Nbat" "\0" - "Nag_Mundari,Nagm" "\0" - "Nandinagari,Nand" "\0" - "New_Tai_Lue,Talu" "\0" - "Newa,Newa" "\0" - "Nko,Nkoo" "\0" - "Nushu,Nshu" "\0" - "Nyiakeng_Puachue_Hmong,Hmnp" "\0" - "Ogham,Ogam" "\0" - "Ol_Chiki,Olck" "\0" - "Ol_Onal,Onao" "\0" - "Old_Hungarian,Hung" "\0" - "Old_Italic,Ital" "\0" - "Old_North_Arabian,Narb" "\0" - "Old_Permic,Perm" "\0" - "Old_Persian,Xpeo" "\0" - "Old_Sogdian,Sogo" "\0" - "Old_South_Arabian,Sarb" "\0" - "Old_Turkic,Orkh" "\0" - "Old_Uyghur,Ougr" "\0" - "Oriya,Orya" "\0" - "Osage,Osge" "\0" - "Osmanya,Osma" "\0" - "Pahawh_Hmong,Hmng" "\0" - "Palmyrene,Palm" "\0" - "Pau_Cin_Hau,Pauc" "\0" - "Phags_Pa,Phag" "\0" - "Phoenician,Phnx" "\0" - "Psalter_Pahlavi,Phlp" "\0" - "Rejang,Rjng" "\0" - "Runic,Runr" "\0" - "Samaritan,Samr" "\0" - "Saurashtra,Saur" "\0" - "Sharada,Shrd" "\0" - "Shavian,Shaw" "\0" - "Siddham,Sidd" "\0" - "SignWriting,Sgnw" "\0" - "Sinhala,Sinh" "\0" - "Sogdian,Sogd" "\0" - "Sora_Sompeng,Sora" "\0" - "Soyombo,Soyo" "\0" - "Sundanese,Sund" "\0" - "Sunuwar,Sunu" "\0" - "Syloti_Nagri,Sylo" "\0" - "Syriac,Syrc" "\0" - "Tagalog,Tglg" "\0" - "Tagbanwa,Tagb" "\0" - "Tai_Le,Tale" "\0" - "Tai_Tham,Lana" "\0" - "Tai_Viet,Tavt" "\0" - "Takri,Takr" "\0" - "Tamil,Taml" "\0" - "Tangut,Tang" "\0" - "Telugu,Telu" "\0" - "Thaana,Thaa" "\0" - "Thai,Thai" "\0" - "Tibetan,Tibt" "\0" - "Tifinagh,Tfng" "\0" - "Tirhuta,Tirh" "\0" - "Tangsa,Tnsa" "\0" - "Todhri,Todr" "\0" - "Toto,Toto" "\0" - "Tulu_Tigalari,Tutg" "\0" - "Ugaritic,Ugar" "\0" - "Vai,Vaii" "\0" - "Vithkuqi,Vith" "\0" - "Wancho,Wcho" "\0" - "Warang_Citi,Wara" "\0" - "Yezidi,Yezi" "\0" - "Yi,Yiii" "\0" - "Zanabazar_Square,Zanb" "\0" -; - -static const uint8_t unicode_script_table[2803] = { - 0xc0, 0x19, 0x99, 0x4a, 0x85, 0x19, 0x99, 0x4a, - 0xae, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x80, 0x4a, - 0x84, 0x19, 0x96, 0x4a, 0x80, 0x19, 0x9e, 0x4a, - 0x80, 0x19, 0xe1, 0x60, 0x4a, 0xa6, 0x19, 0x84, - 0x4a, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, - 0x0f, 0x3a, 0x83, 0x2d, 0x80, 0x19, 0x82, 0x2d, - 0x01, 0x83, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x03, - 0x80, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x80, 0x19, - 0x82, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x93, 0x2d, - 0x00, 0xbe, 0x2d, 0x8d, 0x1a, 0x8f, 0x2d, 0xe0, - 0x24, 0x1d, 0x81, 0x3a, 0xe0, 0x48, 0x1d, 0x00, - 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, - 0x00, 0xb6, 0x37, 0x07, 0x9a, 0x37, 0x03, 0x85, - 0x37, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, - 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, - 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, - 0x8a, 0x3a, 0x99, 0x04, 0x80, 0x3a, 0xe0, 0x0b, - 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x90, 0x00, - 0xbb, 0x90, 0x01, 0x82, 0x90, 0xaf, 0x04, 0xb1, - 0x9a, 0x0d, 0xba, 0x69, 0x01, 0x82, 0x69, 0xad, - 0x83, 0x01, 0x8e, 0x83, 0x00, 0x9b, 0x55, 0x01, - 0x80, 0x55, 0x00, 0x8a, 0x90, 0x04, 0x9e, 0x04, - 0x00, 0x81, 0x04, 0x04, 0xca, 0x04, 0x80, 0x19, - 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x3a, 0x8e, 0x20, - 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, - 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, - 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, - 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, - 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, - 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x30, - 0x00, 0x85, 0x30, 0x03, 0x81, 0x30, 0x01, 0x95, - 0x30, 0x00, 0x86, 0x30, 0x00, 0x81, 0x30, 0x00, - 0x81, 0x30, 0x00, 0x81, 0x30, 0x01, 0x80, 0x30, - 0x00, 0x84, 0x30, 0x03, 0x81, 0x30, 0x01, 0x82, - 0x30, 0x02, 0x80, 0x30, 0x06, 0x83, 0x30, 0x00, - 0x80, 0x30, 0x06, 0x90, 0x30, 0x09, 0x82, 0x2e, - 0x00, 0x88, 0x2e, 0x00, 0x82, 0x2e, 0x00, 0x95, - 0x2e, 0x00, 0x86, 0x2e, 0x00, 0x81, 0x2e, 0x00, - 0x84, 0x2e, 0x01, 0x89, 0x2e, 0x00, 0x82, 0x2e, - 0x00, 0x82, 0x2e, 0x01, 0x80, 0x2e, 0x0e, 0x83, - 0x2e, 0x01, 0x8b, 0x2e, 0x06, 0x86, 0x2e, 0x00, - 0x82, 0x78, 0x00, 0x87, 0x78, 0x01, 0x81, 0x78, - 0x01, 0x95, 0x78, 0x00, 0x86, 0x78, 0x00, 0x81, - 0x78, 0x00, 0x84, 0x78, 0x01, 0x88, 0x78, 0x01, - 0x81, 0x78, 0x01, 0x82, 0x78, 0x06, 0x82, 0x78, - 0x03, 0x81, 0x78, 0x00, 0x84, 0x78, 0x01, 0x91, - 0x78, 0x09, 0x81, 0x97, 0x00, 0x85, 0x97, 0x02, - 0x82, 0x97, 0x00, 0x83, 0x97, 0x02, 0x81, 0x97, - 0x00, 0x80, 0x97, 0x00, 0x81, 0x97, 0x02, 0x81, - 0x97, 0x02, 0x82, 0x97, 0x02, 0x8b, 0x97, 0x03, - 0x84, 0x97, 0x02, 0x82, 0x97, 0x00, 0x83, 0x97, - 0x01, 0x80, 0x97, 0x05, 0x80, 0x97, 0x0d, 0x94, - 0x97, 0x04, 0x8c, 0x99, 0x00, 0x82, 0x99, 0x00, - 0x96, 0x99, 0x00, 0x8f, 0x99, 0x01, 0x88, 0x99, - 0x00, 0x82, 0x99, 0x00, 0x83, 0x99, 0x06, 0x81, - 0x99, 0x00, 0x82, 0x99, 0x01, 0x80, 0x99, 0x01, - 0x83, 0x99, 0x01, 0x89, 0x99, 0x06, 0x88, 0x99, - 0x8c, 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x96, 0x3f, - 0x00, 0x89, 0x3f, 0x00, 0x84, 0x3f, 0x01, 0x88, - 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x83, 0x3f, 0x06, - 0x81, 0x3f, 0x05, 0x81, 0x3f, 0x00, 0x83, 0x3f, - 0x01, 0x89, 0x3f, 0x00, 0x82, 0x3f, 0x0b, 0x8c, - 0x54, 0x00, 0x82, 0x54, 0x00, 0xb2, 0x54, 0x00, - 0x82, 0x54, 0x00, 0x85, 0x54, 0x03, 0x8f, 0x54, - 0x01, 0x99, 0x54, 0x00, 0x82, 0x89, 0x00, 0x91, - 0x89, 0x02, 0x97, 0x89, 0x00, 0x88, 0x89, 0x00, - 0x80, 0x89, 0x01, 0x86, 0x89, 0x02, 0x80, 0x89, - 0x03, 0x85, 0x89, 0x00, 0x80, 0x89, 0x00, 0x87, - 0x89, 0x05, 0x89, 0x89, 0x01, 0x82, 0x89, 0x0b, - 0xb9, 0x9b, 0x03, 0x80, 0x19, 0x9b, 0x9b, 0x24, - 0x81, 0x49, 0x00, 0x80, 0x49, 0x00, 0x84, 0x49, - 0x00, 0x97, 0x49, 0x00, 0x80, 0x49, 0x00, 0x96, - 0x49, 0x01, 0x84, 0x49, 0x00, 0x80, 0x49, 0x00, - 0x86, 0x49, 0x00, 0x89, 0x49, 0x01, 0x83, 0x49, - 0x1f, 0xc7, 0x9c, 0x00, 0xa3, 0x9c, 0x03, 0xa6, - 0x9c, 0x00, 0xa3, 0x9c, 0x00, 0x8e, 0x9c, 0x00, - 0x86, 0x9c, 0x83, 0x19, 0x81, 0x9c, 0x24, 0xe0, - 0x3f, 0x63, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, - 0x80, 0x28, 0x01, 0xaa, 0x28, 0x80, 0x19, 0x83, - 0x28, 0xe0, 0x9f, 0x33, 0xc8, 0x27, 0x00, 0x83, - 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, - 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, - 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, - 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, - 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, - 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, - 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, - 0xe2, 0x1f, 0x12, 0x9c, 0x6c, 0x02, 0xca, 0x82, - 0x82, 0x19, 0x8a, 0x82, 0x06, 0x95, 0x91, 0x08, - 0x80, 0x91, 0x94, 0x35, 0x81, 0x19, 0x08, 0x93, - 0x11, 0x0b, 0x8c, 0x92, 0x00, 0x82, 0x92, 0x00, - 0x81, 0x92, 0x0b, 0xdd, 0x44, 0x01, 0x89, 0x44, - 0x05, 0x89, 0x44, 0x05, 0x81, 0x60, 0x81, 0x19, - 0x80, 0x60, 0x80, 0x19, 0x93, 0x60, 0x05, 0xd8, - 0x60, 0x06, 0xaa, 0x60, 0x04, 0xc5, 0x12, 0x09, - 0x9e, 0x4c, 0x00, 0x8b, 0x4c, 0x03, 0x8b, 0x4c, - 0x03, 0x80, 0x4c, 0x02, 0x8b, 0x4c, 0x9d, 0x93, - 0x01, 0x84, 0x93, 0x0a, 0xab, 0x67, 0x03, 0x99, - 0x67, 0x05, 0x8a, 0x67, 0x02, 0x81, 0x67, 0x9f, - 0x44, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x94, - 0x00, 0x9c, 0x94, 0x01, 0x8a, 0x94, 0x05, 0x89, - 0x94, 0x05, 0x8d, 0x94, 0x01, 0x9e, 0x3a, 0x30, - 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x8d, 0xb3, - 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4b, 0x02, 0x8e, - 0x4b, 0x02, 0x82, 0x4b, 0xaf, 0x6d, 0x8a, 0x1d, - 0x04, 0xaa, 0x28, 0x01, 0x82, 0x28, 0x87, 0x8d, - 0x07, 0x82, 0x3a, 0x80, 0x19, 0x8c, 0x3a, 0x80, - 0x19, 0x86, 0x3a, 0x83, 0x19, 0x80, 0x3a, 0x85, - 0x19, 0x80, 0x3a, 0x82, 0x19, 0x81, 0x3a, 0x80, - 0x19, 0x04, 0xa5, 0x4a, 0x84, 0x2d, 0x80, 0x1d, - 0xb0, 0x4a, 0x84, 0x2d, 0x83, 0x4a, 0x84, 0x2d, - 0x8c, 0x4a, 0x80, 0x1d, 0xc5, 0x4a, 0x80, 0x2d, - 0xbf, 0x3a, 0xe0, 0x9f, 0x4a, 0x95, 0x2d, 0x01, - 0x85, 0x2d, 0x01, 0xa5, 0x2d, 0x01, 0x85, 0x2d, - 0x01, 0x87, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x80, - 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x9e, 0x2d, 0x01, - 0xb4, 0x2d, 0x00, 0x8e, 0x2d, 0x00, 0x8d, 0x2d, - 0x01, 0x85, 0x2d, 0x00, 0x92, 0x2d, 0x01, 0x82, - 0x2d, 0x00, 0x88, 0x2d, 0x00, 0x8b, 0x19, 0x81, - 0x3a, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x4a, - 0x01, 0x8a, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x00, - 0x8c, 0x4a, 0x02, 0xa0, 0x19, 0x0e, 0xa0, 0x3a, - 0x0e, 0xa5, 0x19, 0x80, 0x2d, 0x82, 0x19, 0x81, - 0x4a, 0x85, 0x19, 0x80, 0x4a, 0x9a, 0x19, 0x80, - 0x4a, 0x90, 0x19, 0xa8, 0x4a, 0x82, 0x19, 0x03, - 0xe2, 0x39, 0x19, 0x15, 0x8a, 0x19, 0x14, 0xe3, - 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x19, - 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xdf, - 0x29, 0x9f, 0x4a, 0xe0, 0x13, 0x1a, 0x04, 0x86, - 0x1a, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, 0x80, - 0x28, 0x01, 0xb7, 0x9d, 0x06, 0x81, 0x9d, 0x0d, - 0x80, 0x9d, 0x96, 0x27, 0x08, 0x86, 0x27, 0x00, - 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, - 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, - 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, 0xdd, - 0x19, 0x21, 0x99, 0x32, 0x00, 0xd8, 0x32, 0x0b, - 0xe0, 0x75, 0x32, 0x19, 0x94, 0x19, 0x80, 0x32, - 0x80, 0x19, 0x80, 0x32, 0x98, 0x19, 0x88, 0x32, - 0x83, 0x3a, 0x81, 0x33, 0x87, 0x19, 0x83, 0x32, - 0x83, 0x19, 0x00, 0xd5, 0x38, 0x01, 0x81, 0x3a, - 0x81, 0x19, 0x82, 0x38, 0x80, 0x19, 0xd9, 0x40, - 0x81, 0x19, 0x82, 0x40, 0x04, 0xaa, 0x0d, 0x00, - 0xdd, 0x33, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa5, - 0x19, 0x08, 0x80, 0x19, 0x8f, 0x40, 0x9e, 0x33, - 0x00, 0xbf, 0x19, 0x9e, 0x33, 0xd0, 0x19, 0xae, - 0x40, 0x80, 0x19, 0xd7, 0x40, 0xe0, 0x47, 0x19, - 0xf0, 0x09, 0x5f, 0x32, 0xbf, 0x19, 0xf0, 0x41, - 0x9f, 0x32, 0xe4, 0x2c, 0xa9, 0x02, 0xb6, 0xa9, - 0x08, 0xaf, 0x4f, 0xe0, 0xcb, 0xa4, 0x13, 0xdf, - 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, - 0x4a, 0x82, 0x19, 0xc2, 0x4a, 0x01, 0x81, 0x4a, - 0x00, 0x80, 0x4a, 0x00, 0x87, 0x4a, 0x14, 0x8d, - 0x4a, 0xac, 0x8f, 0x02, 0x89, 0x19, 0x05, 0xb7, - 0x7e, 0x07, 0xc5, 0x84, 0x07, 0x8b, 0x84, 0x05, - 0x9f, 0x20, 0xad, 0x42, 0x80, 0x19, 0x80, 0x42, - 0xa3, 0x81, 0x0a, 0x80, 0x81, 0x9c, 0x33, 0x02, - 0xcd, 0x3d, 0x00, 0x80, 0x19, 0x89, 0x3d, 0x03, - 0x81, 0x3d, 0x9e, 0x63, 0x00, 0xb6, 0x16, 0x08, - 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83, 0x16, - 0x9f, 0x63, 0xc2, 0x95, 0x17, 0x84, 0x95, 0x96, - 0x5a, 0x09, 0x85, 0x27, 0x01, 0x85, 0x27, 0x01, - 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, 0x86, 0x27, - 0x00, 0xaa, 0x4a, 0x80, 0x19, 0x88, 0x4a, 0x80, - 0x2d, 0x83, 0x4a, 0x81, 0x19, 0x03, 0xcf, 0x17, - 0xad, 0x5a, 0x01, 0x89, 0x5a, 0x05, 0xf0, 0x1b, - 0x43, 0x33, 0x0b, 0x96, 0x33, 0x03, 0xb0, 0x33, - 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x32, 0x01, 0xe0, - 0x09, 0x32, 0x25, 0x86, 0x4a, 0x0b, 0x84, 0x05, - 0x04, 0x99, 0x37, 0x00, 0x84, 0x37, 0x00, 0x80, - 0x37, 0x00, 0x81, 0x37, 0x00, 0x81, 0x37, 0x00, - 0x89, 0x37, 0xe0, 0x12, 0x04, 0x0f, 0xe1, 0x0a, - 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, 0xb5, 0x04, - 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, 0x8f, 0x3a, - 0x89, 0x19, 0x05, 0x8d, 0x3a, 0x81, 0x1d, 0xa2, - 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, - 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, - 0x19, 0x00, 0x9f, 0x19, 0x99, 0x4a, 0x85, 0x19, - 0x99, 0x4a, 0x8a, 0x19, 0x89, 0x40, 0x80, 0x19, - 0xac, 0x40, 0x81, 0x19, 0x9e, 0x33, 0x02, 0x85, - 0x33, 0x01, 0x85, 0x33, 0x01, 0x85, 0x33, 0x01, - 0x82, 0x33, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, - 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4e, 0x00, 0x99, - 0x4e, 0x00, 0x92, 0x4e, 0x00, 0x81, 0x4e, 0x00, - 0x8e, 0x4e, 0x01, 0x8d, 0x4e, 0x21, 0xe0, 0x1a, - 0x4e, 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, - 0x88, 0x19, 0xce, 0x2d, 0x00, 0x8c, 0x19, 0x02, - 0x80, 0x2d, 0x2e, 0xac, 0x19, 0x80, 0x3a, 0x60, - 0x21, 0x9c, 0x50, 0x02, 0xb0, 0x13, 0x0e, 0x80, - 0x3a, 0x9a, 0x19, 0x03, 0xa3, 0x70, 0x08, 0x82, - 0x70, 0x9a, 0x2a, 0x04, 0xaa, 0x72, 0x04, 0x9d, - 0xa3, 0x00, 0x80, 0xa3, 0xa3, 0x73, 0x03, 0x8d, - 0x73, 0x29, 0xcf, 0x1f, 0xaf, 0x86, 0x9d, 0x7a, - 0x01, 0x89, 0x7a, 0x05, 0xa3, 0x79, 0x03, 0xa3, - 0x79, 0x03, 0xa7, 0x25, 0x07, 0xb3, 0x14, 0x0a, - 0x80, 0x14, 0x8a, 0xa5, 0x00, 0x8e, 0xa5, 0x00, - 0x86, 0xa5, 0x00, 0x81, 0xa5, 0x00, 0x8a, 0xa5, - 0x00, 0x8e, 0xa5, 0x00, 0x86, 0xa5, 0x00, 0x81, - 0xa5, 0x02, 0xb3, 0xa0, 0x0b, 0xe0, 0xd6, 0x4d, - 0x08, 0x95, 0x4d, 0x09, 0x87, 0x4d, 0x17, 0x85, - 0x4a, 0x00, 0xa9, 0x4a, 0x00, 0x88, 0x4a, 0x44, - 0x85, 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, - 0x00, 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, - 0x1c, 0x95, 0x39, 0x00, 0x88, 0x39, 0x9f, 0x7c, - 0x9e, 0x64, 0x07, 0x88, 0x64, 0x2f, 0x92, 0x36, - 0x00, 0x81, 0x36, 0x04, 0x84, 0x36, 0x9b, 0x7f, - 0x02, 0x80, 0x7f, 0x99, 0x51, 0x04, 0x80, 0x51, - 0x3f, 0x9f, 0x5d, 0x97, 0x5c, 0x03, 0x93, 0x5c, - 0x01, 0xad, 0x5c, 0x83, 0x43, 0x00, 0x81, 0x43, - 0x04, 0x87, 0x43, 0x00, 0x82, 0x43, 0x00, 0x9c, - 0x43, 0x01, 0x82, 0x43, 0x03, 0x89, 0x43, 0x06, - 0x88, 0x43, 0x06, 0x9f, 0x75, 0x9f, 0x71, 0x1f, - 0xa6, 0x56, 0x03, 0x8b, 0x56, 0x08, 0xb5, 0x06, - 0x02, 0x86, 0x06, 0x95, 0x3c, 0x01, 0x87, 0x3c, - 0x92, 0x3b, 0x04, 0x87, 0x3b, 0x91, 0x80, 0x06, - 0x83, 0x80, 0x0b, 0x86, 0x80, 0x4f, 0xc8, 0x76, - 0x36, 0xb2, 0x6f, 0x0c, 0xb2, 0x6f, 0x06, 0x85, - 0x6f, 0xa7, 0x34, 0x07, 0x89, 0x34, 0x05, 0xa5, - 0x2b, 0x02, 0x9c, 0x2b, 0x07, 0x81, 0x2b, 0x60, - 0x6f, 0x9e, 0x04, 0x00, 0xa9, 0xa8, 0x00, 0x82, - 0xa8, 0x01, 0x81, 0xa8, 0x0f, 0x82, 0x04, 0x36, - 0x83, 0x04, 0xa7, 0x74, 0x07, 0xa9, 0x8a, 0x15, - 0x99, 0x77, 0x25, 0x9b, 0x18, 0x13, 0x96, 0x26, - 0x08, 0xcd, 0x0e, 0x03, 0xa3, 0x0e, 0x08, 0x80, - 0x0e, 0xc2, 0x3e, 0x09, 0x80, 0x3e, 0x01, 0x98, - 0x8b, 0x06, 0x89, 0x8b, 0x05, 0xb4, 0x15, 0x00, - 0x91, 0x15, 0x07, 0xa6, 0x53, 0x08, 0xdf, 0x85, - 0x00, 0x93, 0x89, 0x0a, 0x91, 0x45, 0x00, 0xae, - 0x45, 0x3d, 0x86, 0x62, 0x00, 0x80, 0x62, 0x00, - 0x83, 0x62, 0x00, 0x8e, 0x62, 0x00, 0x8a, 0x62, - 0x05, 0xba, 0x47, 0x04, 0x89, 0x47, 0x05, 0x83, - 0x2c, 0x00, 0x87, 0x2c, 0x01, 0x81, 0x2c, 0x01, - 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, - 0x00, 0x84, 0x2c, 0x00, 0x80, 0x3a, 0x88, 0x2c, - 0x01, 0x81, 0x2c, 0x01, 0x82, 0x2c, 0x01, 0x80, - 0x2c, 0x05, 0x80, 0x2c, 0x04, 0x86, 0x2c, 0x01, - 0x86, 0x2c, 0x02, 0x84, 0x2c, 0x0a, 0x89, 0xa2, - 0x00, 0x80, 0xa2, 0x01, 0x80, 0xa2, 0x00, 0xa5, - 0xa2, 0x00, 0x89, 0xa2, 0x00, 0x80, 0xa2, 0x01, - 0x80, 0xa2, 0x00, 0x83, 0xa2, 0x00, 0x89, 0xa2, - 0x00, 0x81, 0xa2, 0x07, 0x81, 0xa2, 0x1c, 0xdb, - 0x68, 0x00, 0x84, 0x68, 0x1d, 0xc7, 0x9e, 0x07, - 0x89, 0x9e, 0x60, 0x45, 0xb5, 0x87, 0x01, 0xa5, - 0x87, 0x21, 0xc4, 0x5f, 0x0a, 0x89, 0x5f, 0x05, - 0x8c, 0x60, 0x12, 0xb9, 0x96, 0x05, 0x89, 0x96, - 0x05, 0x93, 0x63, 0x1b, 0x9a, 0x02, 0x01, 0x8e, - 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, - 0x60, 0x03, 0xd2, 0xa7, 0x0b, 0x80, 0xa7, 0x86, - 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, - 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, - 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, - 0x66, 0x01, 0xad, 0x66, 0x01, 0x8a, 0x66, 0x1a, - 0xc7, 0xaa, 0x07, 0xd2, 0x8c, 0x0c, 0x8f, 0x12, - 0xb8, 0x7d, 0x06, 0x89, 0x20, 0x60, 0x55, 0xa1, - 0x8e, 0x0d, 0x89, 0x8e, 0x05, 0x88, 0x0c, 0x00, - 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, - 0x02, 0x9f, 0x57, 0x01, 0x95, 0x57, 0x00, 0x8d, - 0x57, 0x48, 0x86, 0x58, 0x00, 0x81, 0x58, 0x00, - 0xab, 0x58, 0x02, 0x80, 0x58, 0x00, 0x81, 0x58, - 0x00, 0x88, 0x58, 0x07, 0x89, 0x58, 0x05, 0x85, - 0x2f, 0x00, 0x81, 0x2f, 0x00, 0xa4, 0x2f, 0x00, - 0x81, 0x2f, 0x00, 0x85, 0x2f, 0x06, 0x89, 0x2f, - 0x60, 0xd5, 0x98, 0x52, 0x06, 0x90, 0x41, 0x00, - 0xa8, 0x41, 0x02, 0x9c, 0x41, 0x54, 0x80, 0x4f, - 0x0e, 0xb1, 0x97, 0x0c, 0x80, 0x97, 0xe3, 0x39, - 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, - 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, 0xeb, 0xe0, - 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, 0x09, 0xef, - 0x3a, 0x24, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, - 0x58, 0xb9, 0x31, 0x66, 0x65, 0xe1, 0xd8, 0x08, - 0x06, 0x9e, 0x61, 0x00, 0x89, 0x61, 0x03, 0x81, - 0x61, 0xce, 0x9f, 0x00, 0x89, 0x9f, 0x05, 0x9d, - 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7b, 0x09, - 0x89, 0x7b, 0x00, 0x86, 0x7b, 0x00, 0x94, 0x7b, - 0x04, 0x92, 0x7b, 0x61, 0x4f, 0xb9, 0x48, 0x60, - 0x65, 0xda, 0x59, 0x60, 0x04, 0xca, 0x5e, 0x03, - 0xb8, 0x5e, 0x06, 0x90, 0x5e, 0x3f, 0x80, 0x98, - 0x80, 0x6a, 0x81, 0x32, 0x80, 0x46, 0x0a, 0x81, - 0x32, 0x0d, 0xf0, 0x07, 0x97, 0x98, 0x07, 0xe2, - 0x9f, 0x98, 0xe1, 0x75, 0x46, 0x28, 0x80, 0x46, - 0x88, 0x98, 0x70, 0x12, 0x86, 0x83, 0x40, 0x00, - 0x86, 0x40, 0x00, 0x81, 0x40, 0x00, 0x80, 0x40, - 0xe0, 0xbe, 0x38, 0x82, 0x40, 0x0e, 0x80, 0x38, - 0x1c, 0x82, 0x38, 0x01, 0x80, 0x40, 0x0d, 0x83, - 0x40, 0x07, 0xe1, 0x2b, 0x6a, 0x68, 0xa3, 0xe0, - 0x0a, 0x23, 0x04, 0x8c, 0x23, 0x02, 0x88, 0x23, - 0x06, 0x89, 0x23, 0x01, 0x83, 0x23, 0x83, 0x19, - 0x6e, 0xfb, 0xe0, 0x99, 0x19, 0x05, 0xe1, 0x53, - 0x19, 0x4b, 0xad, 0x3a, 0x01, 0x96, 0x3a, 0x08, - 0xe0, 0x13, 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, - 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, 0x3a, 0x90, - 0x19, 0x87, 0x3a, 0x81, 0x19, 0x86, 0x3a, 0x9d, - 0x19, 0x83, 0x3a, 0xbc, 0x19, 0x14, 0xc5, 0x2d, - 0x60, 0x19, 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, - 0xd6, 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, - 0x19, 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, - 0x80, 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, - 0x00, 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, - 0x19, 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, - 0x87, 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, - 0x00, 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, - 0x19, 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, - 0x01, 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, - 0x2b, 0x88, 0x0e, 0x84, 0x88, 0x00, 0x8e, 0x88, - 0x63, 0xef, 0x9e, 0x4a, 0x05, 0x85, 0x4a, 0x60, - 0x74, 0x86, 0x29, 0x00, 0x90, 0x29, 0x01, 0x86, - 0x29, 0x00, 0x81, 0x29, 0x00, 0x84, 0x29, 0x04, - 0xbd, 0x1d, 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, - 0x6b, 0x02, 0x8d, 0x6b, 0x01, 0x89, 0x6b, 0x03, - 0x81, 0x6b, 0x60, 0xdf, 0x9e, 0xa1, 0x10, 0xb9, - 0xa6, 0x04, 0x80, 0xa6, 0x61, 0x6f, 0xa9, 0x65, - 0x60, 0x75, 0xaa, 0x6e, 0x03, 0x80, 0x6e, 0x61, - 0x7f, 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, - 0x27, 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x5b, - 0x01, 0x8f, 0x5b, 0x28, 0xcb, 0x01, 0x03, 0x89, - 0x01, 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, - 0x4b, 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, - 0x9a, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, - 0x01, 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, - 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, - 0x80, 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, - 0x00, 0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, - 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, - 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, - 0x00, 0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, - 0x04, 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, - 0x83, 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, - 0x00, 0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, - 0x04, 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, - 0x81, 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, - 0x03, 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, - 0x00, 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, - 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x38, 0x81, - 0x19, 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, - 0x81, 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, - 0x77, 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, - 0x02, 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, - 0x8b, 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, - 0x03, 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, - 0x19, 0x07, 0x9d, 0x19, 0x01, 0x8b, 0x19, 0x03, - 0x81, 0x19, 0x3d, 0xe0, 0xf3, 0x19, 0x0b, 0x8d, - 0x19, 0x01, 0x8c, 0x19, 0x02, 0x89, 0x19, 0x04, - 0xb7, 0x19, 0x06, 0x8e, 0x19, 0x01, 0x8a, 0x19, - 0x05, 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, - 0xe0, 0x05, 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7f, - 0x32, 0x1f, 0xef, 0xd9, 0x32, 0x05, 0xe0, 0x7d, - 0x32, 0x01, 0xf0, 0x06, 0x21, 0x32, 0x0d, 0xf0, - 0x0c, 0xd0, 0x32, 0x0e, 0xe2, 0x0d, 0x32, 0x69, - 0x41, 0xe1, 0xbd, 0x32, 0x65, 0x81, 0xf0, 0x02, - 0xea, 0x32, 0x04, 0xef, 0xff, 0x32, 0x7a, 0xcb, - 0xf0, 0x80, 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, - 0xe0, 0x8f, 0x3a, -}; - -static const uint8_t unicode_script_ext_table[1253] = { - 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x13, 0x1a, - 0x23, 0x25, 0x28, 0x29, 0x2f, 0x2a, 0x2d, 0x32, - 0x4a, 0x51, 0x53, 0x72, 0x86, 0x81, 0x83, 0x00, - 0x00, 0x07, 0x0b, 0x1d, 0x20, 0x4a, 0x4f, 0x9b, - 0xa1, 0x09, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x00, - 0x00, 0x02, 0x02, 0x0d, 0x4a, 0x00, 0x00, 0x00, - 0x02, 0x4a, 0x4f, 0x08, 0x00, 0x00, 0x02, 0x4a, - 0x9b, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x25, - 0x00, 0x00, 0x08, 0x17, 0x1a, 0x1d, 0x2d, 0x4a, - 0x72, 0x8e, 0x93, 0x00, 0x08, 0x17, 0x1d, 0x2d, - 0x4a, 0x79, 0x8e, 0x93, 0xa0, 0x00, 0x04, 0x17, - 0x1d, 0x4a, 0x9d, 0x00, 0x05, 0x29, 0x4a, 0x8e, - 0x90, 0x9b, 0x00, 0x0b, 0x14, 0x17, 0x1a, 0x1d, - 0x2a, 0x2d, 0x4a, 0x79, 0x90, 0x9d, 0xa0, 0x00, - 0x06, 0x1a, 0x25, 0x29, 0x2a, 0x40, 0x4a, 0x00, - 0x04, 0x1d, 0x2d, 0x4a, 0x72, 0x00, 0x09, 0x1a, - 0x23, 0x37, 0x4a, 0x72, 0x90, 0x93, 0x9d, 0xa0, - 0x00, 0x0a, 0x05, 0x1d, 0x23, 0x2a, 0x2d, 0x37, - 0x4a, 0x72, 0x90, 0x93, 0x00, 0x02, 0x4a, 0x9d, - 0x00, 0x03, 0x23, 0x4a, 0x90, 0x00, 0x04, 0x17, - 0x1d, 0x4a, 0x79, 0x00, 0x03, 0x17, 0x4a, 0x93, - 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x02, 0x27, 0x4a, - 0x00, 0x00, 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x03, - 0x1d, 0x4a, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x2d, - 0x4a, 0x72, 0xa0, 0x0b, 0x00, 0x00, 0x02, 0x4a, - 0x90, 0x01, 0x00, 0x00, 0x05, 0x17, 0x23, 0x40, - 0x4a, 0x90, 0x00, 0x04, 0x17, 0x23, 0x4a, 0x90, - 0x00, 0x02, 0x4a, 0x90, 0x06, 0x00, 0x00, 0x03, - 0x4a, 0x8e, 0x90, 0x00, 0x02, 0x4a, 0x90, 0x00, - 0x00, 0x00, 0x03, 0x17, 0x4a, 0x90, 0x00, 0x06, - 0x14, 0x17, 0x2a, 0x4a, 0x8e, 0x9b, 0x0f, 0x00, - 0x00, 0x01, 0x2d, 0x01, 0x00, 0x00, 0x01, 0x2d, - 0x11, 0x00, 0x00, 0x02, 0x4a, 0x79, 0x04, 0x00, - 0x00, 0x03, 0x14, 0x4a, 0xa0, 0x03, 0x00, 0x0c, - 0x01, 0x4a, 0x03, 0x00, 0x01, 0x02, 0x1a, 0x2d, - 0x80, 0x8c, 0x00, 0x00, 0x02, 0x1d, 0x72, 0x00, - 0x02, 0x1d, 0x29, 0x01, 0x02, 0x1d, 0x4a, 0x00, - 0x02, 0x1d, 0x29, 0x80, 0x80, 0x00, 0x00, 0x03, - 0x05, 0x28, 0x29, 0x80, 0x01, 0x00, 0x00, 0x07, - 0x04, 0x2b, 0x69, 0x34, 0x90, 0x9a, 0xa8, 0x0d, - 0x00, 0x00, 0x07, 0x04, 0x2b, 0x69, 0x34, 0x90, - 0x9a, 0xa8, 0x00, 0x03, 0x04, 0x90, 0x9a, 0x01, - 0x00, 0x00, 0x08, 0x01, 0x04, 0x2b, 0x69, 0x34, - 0x90, 0x9a, 0xa8, 0x1f, 0x00, 0x00, 0x09, 0x01, - 0x04, 0x55, 0x56, 0x77, 0x80, 0x34, 0x8a, 0x90, - 0x09, 0x00, 0x0a, 0x02, 0x04, 0x90, 0x09, 0x00, - 0x09, 0x03, 0x04, 0x9a, 0xa8, 0x05, 0x00, 0x00, - 0x02, 0x04, 0x90, 0x62, 0x00, 0x00, 0x02, 0x04, - 0x34, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x20, - 0x2c, 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x85, - 0x97, 0x99, 0x9e, 0x00, 0x0c, 0x0b, 0x20, 0x2c, - 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x97, 0x99, - 0x9e, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x20, 0x22, - 0x2f, 0x58, 0x2c, 0x2e, 0x30, 0x3f, 0x53, 0x54, - 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, 0x97, - 0x99, 0x9e, 0x00, 0x17, 0x0b, 0x20, 0x22, 0x2f, - 0x58, 0x2c, 0x2e, 0x31, 0x30, 0x3f, 0x4c, 0x53, - 0x54, 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, - 0x97, 0x99, 0x9e, 0x09, 0x04, 0x20, 0x22, 0x3e, - 0x53, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x8f, - 0x75, 0x00, 0x09, 0x02, 0x30, 0x62, 0x75, 0x00, - 0x09, 0x02, 0x2e, 0x45, 0x80, 0x75, 0x00, 0x0d, - 0x02, 0x2c, 0x97, 0x80, 0x71, 0x00, 0x09, 0x03, - 0x3f, 0x66, 0xa2, 0x82, 0xcf, 0x00, 0x09, 0x03, - 0x15, 0x63, 0x93, 0x80, 0x30, 0x00, 0x00, 0x03, - 0x28, 0x29, 0x4a, 0x85, 0x6e, 0x00, 0x02, 0x01, - 0x82, 0x46, 0x00, 0x01, 0x04, 0x11, 0x35, 0x92, - 0x91, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x60, 0x7e, - 0x00, 0x00, 0x00, 0x02, 0x60, 0x7e, 0x84, 0x49, - 0x00, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, 0x00, - 0x01, 0x20, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, - 0x00, 0x03, 0x20, 0x2c, 0x3f, 0x00, 0x01, 0x20, - 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, - 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, - 0x00, 0x06, 0x20, 0x3f, 0x54, 0x78, 0x97, 0x99, - 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, 0x85, 0x01, - 0x01, 0x20, 0x00, 0x02, 0x20, 0x85, 0x00, 0x02, - 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, 0x02, 0x20, - 0x66, 0x00, 0x02, 0x0b, 0x20, 0x01, 0x01, 0x20, - 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, 0x20, 0x00, - 0x0b, 0x0b, 0x20, 0x2c, 0x3f, 0x54, 0x66, 0x78, - 0x89, 0x99, 0x9e, 0xa2, 0x00, 0x02, 0x20, 0x2c, - 0x00, 0x04, 0x20, 0x2c, 0x3f, 0xa2, 0x01, 0x02, - 0x0b, 0x20, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, - 0x2c, 0x00, 0x01, 0x66, 0x80, 0x44, 0x00, 0x01, - 0x01, 0x2d, 0x35, 0x00, 0x00, 0x03, 0x1d, 0x4a, - 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x81, 0xb3, - 0x00, 0x00, 0x03, 0x4a, 0x60, 0x7e, 0x1e, 0x00, - 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, 0x06, - 0x13, 0x28, 0x29, 0x6f, 0x50, 0x76, 0x01, 0x00, - 0x00, 0x04, 0x13, 0x2d, 0x6f, 0x5d, 0x80, 0x11, - 0x00, 0x00, 0x03, 0x20, 0x2c, 0x4a, 0x8c, 0xa5, - 0x00, 0x00, 0x02, 0x1a, 0x4a, 0x17, 0x00, 0x00, - 0x02, 0x06, 0x76, 0x00, 0x07, 0x06, 0x13, 0x28, - 0x6f, 0x3e, 0x51, 0x83, 0x09, 0x00, 0x00, 0x01, - 0x23, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, 0x6f, - 0x00, 0x00, 0x00, 0x02, 0x1d, 0x29, 0x81, 0x2b, - 0x00, 0x0f, 0x02, 0x32, 0x98, 0x00, 0x00, 0x00, - 0x07, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, 0xa9, - 0x00, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, - 0x7e, 0xa9, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, - 0x40, 0x01, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, - 0x01, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, - 0x9c, 0xa9, 0x01, 0x09, 0x0d, 0x33, 0x32, 0x38, - 0x40, 0x4f, 0x60, 0x9c, 0xa9, 0x05, 0x06, 0x0d, - 0x33, 0x32, 0x38, 0x40, 0xa9, 0x00, 0x00, 0x00, - 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x07, 0x06, - 0x0d, 0x33, 0x32, 0x38, 0x40, 0xa9, 0x03, 0x05, - 0x0d, 0x33, 0x32, 0x38, 0x40, 0x09, 0x00, 0x03, - 0x02, 0x0d, 0x32, 0x01, 0x00, 0x00, 0x05, 0x0d, - 0x33, 0x32, 0x38, 0x40, 0x04, 0x02, 0x38, 0x40, - 0x00, 0x00, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, - 0x40, 0x03, 0x00, 0x01, 0x03, 0x32, 0x38, 0x40, - 0x01, 0x01, 0x32, 0x58, 0x00, 0x03, 0x02, 0x38, - 0x40, 0x02, 0x00, 0x00, 0x02, 0x38, 0x40, 0x59, - 0x00, 0x00, 0x06, 0x0d, 0x33, 0x32, 0x38, 0x40, - 0xa9, 0x00, 0x02, 0x38, 0x40, 0x80, 0x12, 0x00, - 0x0f, 0x01, 0x32, 0x1f, 0x00, 0x25, 0x01, 0x32, - 0x08, 0x00, 0x00, 0x02, 0x32, 0x98, 0x2f, 0x00, - 0x27, 0x01, 0x32, 0x37, 0x00, 0x30, 0x01, 0x32, - 0x0e, 0x00, 0x0b, 0x01, 0x32, 0x32, 0x00, 0x00, - 0x01, 0x32, 0x57, 0x00, 0x18, 0x01, 0x32, 0x09, - 0x00, 0x04, 0x01, 0x32, 0x5f, 0x00, 0x1e, 0x01, - 0x32, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, - 0x29, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x32, 0x4a, - 0x80, 0xa7, 0x00, 0x02, 0x10, 0x20, 0x22, 0x2e, - 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x54, 0x5f, 0x66, - 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x02, 0x0f, 0x20, - 0x22, 0x2e, 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x5f, - 0x66, 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x01, 0x0b, - 0x20, 0x22, 0x2e, 0x30, 0x45, 0x3e, 0x53, 0x5f, - 0x47, 0x96, 0x9e, 0x00, 0x0c, 0x20, 0x22, 0x2e, - 0x30, 0x45, 0x3e, 0x53, 0x5f, 0x85, 0x47, 0x96, - 0x9e, 0x00, 0x0b, 0x20, 0x22, 0x2e, 0x30, 0x45, - 0x3e, 0x53, 0x5f, 0x47, 0x96, 0x9e, 0x80, 0x36, - 0x00, 0x00, 0x03, 0x0b, 0x20, 0xa2, 0x00, 0x00, - 0x00, 0x02, 0x20, 0x97, 0x39, 0x00, 0x00, 0x03, - 0x42, 0x4a, 0x63, 0x80, 0x1f, 0x00, 0x00, 0x02, - 0x10, 0x3d, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, - 0x04, 0x69, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, - 0x9a, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9a, 0x46, - 0x00, 0x01, 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, - 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x33, 0x32, - 0x38, 0x40, 0xa9, 0x09, 0x00, 0x00, 0x02, 0x38, - 0x40, 0x2c, 0x00, 0x01, 0x02, 0x38, 0x40, 0x80, - 0xdf, 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4e, 0x00, - 0x02, 0x1c, 0x4e, 0x03, 0x00, 0x2c, 0x03, 0x1c, - 0x4d, 0x4e, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4e, - 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, - 0x75, 0x00, 0x00, 0x02, 0x56, 0x77, 0x87, 0x8d, - 0x00, 0x00, 0x02, 0x2c, 0x97, 0x00, 0x00, 0x00, - 0x02, 0x2c, 0x97, 0x36, 0x00, 0x01, 0x02, 0x2c, - 0x97, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2c, 0x97, - 0x00, 0x00, 0x00, 0x02, 0x2c, 0x97, 0xc0, 0x5c, - 0x4b, 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, - 0x11, 0x01, 0x32, 0x9e, 0x5d, 0x00, 0x01, 0x01, - 0x32, 0xce, 0xcd, 0x2d, 0x00, -}; - -static const uint8_t unicode_prop_Hyphen_table[28] = { - 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, - 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, - 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, - 0xa8, 0x80, 0xd6, 0x80, -}; - -static const uint8_t unicode_prop_Other_Math_table[200] = { - 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, - 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, - 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, - 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, - 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, - 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, - 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, - 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, - 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, - 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, - 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, - 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, - 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, - 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, - 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, - 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, - 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, - 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, - 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, - 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, - 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, - 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, - 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, - 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, -}; - -static const uint8_t unicode_prop_Other_Alphabetic_table[443] = { - 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, - 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, - 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, - 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, - 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, - 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, - 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, - 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, - 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, - 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, - 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, - 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, - 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, - 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, - 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, - 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, - 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, - 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, - 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, - 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, - 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, - 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, - 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, - 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, - 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, - 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, - 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, - 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, - 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, - 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, - 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, - 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, - 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, - 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, - 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, - 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xce, - 0x80, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, - 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, - 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, - 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, - 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, - 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, - 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, - 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, - 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, - 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, - 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, - 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, - 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, - 0x10, 0x02, 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, - 0x5b, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x82, - 0x60, 0x41, 0xdc, 0x90, 0x4e, 0x1f, 0x00, 0xb6, - 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, 0x60, - 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, - 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, 0x85, - 0x99, 0x85, 0x99, -}; - -static const uint8_t unicode_prop_Other_Lowercase_table[69] = { - 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, - 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, - 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, - 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, - 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, - 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, - 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, - 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, - 0x88, 0x60, 0xd8, 0x74, 0xbd, -}; - -static const uint8_t unicode_prop_Other_Uppercase_table[15] = { - 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, - 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, -}; - -static const uint8_t unicode_prop_Other_Grapheme_Extend_table[112] = { - 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, - 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, - 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, - 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, - 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, - 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, - 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, - 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, - 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, - 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, - 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, - 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, - 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, - 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, -}; - -static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { - 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, - 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, - 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, - 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, -}; - -static const uint8_t unicode_prop_Other_ID_Start_table[11] = { - 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, - 0x4f, 0x6b, 0x81, -}; - -static const uint8_t unicode_prop_Other_ID_Continue_table[22] = { - 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, - 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, - 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, -}; - -static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { - 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, - 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, - 0x80, 0x8e, 0x80, -}; - -static const uint8_t unicode_prop_XID_Start1_table[31] = { - 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, - 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, - 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, -}; - -static const uint8_t unicode_prop_XID_Continue1_table[23] = { - 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, - 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, -}; - -static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { - 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, - 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, - 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, -}; - -static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { - 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, - 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, - 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, - 0x89, 0x10, 0x81, 0x8d, 0x80, -}; - -static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[450] = { - 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, - 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, - 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, - 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, - 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, - 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, - 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, - 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, - 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, - 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, - 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, - 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, - 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, - 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, - 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, - 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, - 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, - 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, - 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, - 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, - 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, - 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, - 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, - 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, - 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, - 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, - 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, - 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, - 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, - 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, - 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, - 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, - 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, - 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, - 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, - 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, - 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, - 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, - 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, - 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, - 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, - 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, - 0x31, 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, - 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, - 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, - 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, - 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, - 0xb1, 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, - 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, - 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, - 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, - 0x04, 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, - 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, - 0x8c, 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, - 0x60, 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, - 0x4f, 0xff, -}; - -static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { - 0xaf, 0x89, 0x35, 0x99, 0x85, -}; - -static const uint8_t unicode_prop_Bidi_Control_table[10] = { - 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, - 0xb6, 0x83, -}; - -static const uint8_t unicode_prop_Dash_table[58] = { - 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, - 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, - 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, - 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, - 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, - 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, - 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, - 0x3d, 0x80, -}; - -static const uint8_t unicode_prop_Deprecated_table[23] = { - 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, - 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, - 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, -}; - -static const uint8_t unicode_prop_Diacritic_table[438] = { - 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, - 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, - 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, - 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, - 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, - 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, - 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, - 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, - 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, - 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, - 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, - 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, - 0x80, 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, - 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, - 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, - 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, - 0x81, 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, - 0x80, 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, - 0x82, 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, - 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, 0x80, 0xa5, - 0x88, 0xb5, 0x81, 0xb9, 0x80, 0x8a, 0x81, 0xc1, - 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, - 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, - 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, - 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, - 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, - 0x75, 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, - 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, - 0x8b, 0x80, 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, - 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, - 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, - 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, - 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, - 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, - 0x80, 0x42, 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, - 0x80, 0x88, 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, - 0x81, 0x42, 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, - 0x41, 0x8e, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, - 0xbf, 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, - 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, - 0x40, 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, - 0x32, 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, - 0x02, 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, - 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, - 0x41, 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, - 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, - 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x41, 0xa8, - 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, 0x2c, - 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x42, - 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, 0x60, - 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, 0xad, - 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, 0x86, - 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, 0x86, - 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, 0x81, - 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, -}; - -static const uint8_t unicode_prop_Extender_table[111] = { - 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, - 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, - 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, - 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, - 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, - 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, - 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, - 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, - 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, - 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, - 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, - 0xf1, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, - 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, - 0x81, 0x44, 0xb0, 0x80, 0x43, 0x53, 0x82, -}; - -static const uint8_t unicode_prop_Hex_Digit_table[12] = { - 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, - 0x89, 0x35, 0x99, 0x85, -}; - -static const uint8_t unicode_prop_IDS_Unary_Operator_table[4] = { - 0x60, 0x2f, 0xfd, 0x81, -}; - -static const uint8_t unicode_prop_IDS_Binary_Operator_table[8] = { - 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, -}; - -static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { - 0x60, 0x2f, 0xf1, 0x81, -}; - -static const uint8_t unicode_prop_Ideographic_table[72] = { - 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, - 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, - 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, - 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, - 0xd5, 0xa8, 0x89, 0x60, 0x24, 0x66, 0x41, 0x8b, - 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, - 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, - 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, - 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x50, 0x5f, -}; - -static const uint8_t unicode_prop_Join_Control_table[4] = { - 0x60, 0x20, 0x0b, 0x81, -}; - -static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { - 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, - 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, -}; - -static const uint8_t unicode_prop_Modifier_Combining_Mark_table[16] = { - 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, - 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, -}; - -static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { - 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, - 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, -}; - -static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { - 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, - 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, - 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, - 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, - 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, - 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, - 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, - 0x04, 0x81, -}; - -static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { - 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, - 0x81, 0x97, 0x81, -}; - -static const uint8_t unicode_prop_Quotation_Mark_table[31] = { - 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, - 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, - 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, - 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, -}; - -static const uint8_t unicode_prop_Radical_table[9] = { - 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, - 0xd5, -}; - -static const uint8_t unicode_prop_Regional_Indicator_table[4] = { - 0x61, 0xf1, 0xe5, 0x99, -}; - -static const uint8_t unicode_prop_Sentence_Terminal_table[213] = { - 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, - 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, - 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, - 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, - 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, - 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, - 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, - 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, - 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, - 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, - 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, - 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, - 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, - 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, - 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, - 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, - 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, - 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, - 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, - 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, - 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, - 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, - 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, - 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, - 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, - 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, - 0x05, 0x80, 0x5d, 0xe7, 0x80, -}; - -static const uint8_t unicode_prop_Soft_Dotted_table[79] = { - 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, - 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, - 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, - 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, - 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, - 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, - 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, - 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, -}; - -static const uint8_t unicode_prop_Terminal_Punctuation_table[264] = { - 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, - 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, - 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, - 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, - 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, - 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, - 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, - 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, - 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, - 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, - 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, - 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, - 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, - 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, - 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, - 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, - 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, - 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, - 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, - 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, - 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, - 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, - 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, - 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, - 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, - 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, - 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, - 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, - 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, - 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, - 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, - 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, - 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, -}; - -static const uint8_t unicode_prop_Unified_Ideograph_table[48] = { - 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, - 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, - 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, - 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, - 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, 0x42, - 0x6d, 0x51, 0xa1, 0x53, 0x4a, 0x84, 0x50, 0x5f, -}; - -static const uint8_t unicode_prop_Variation_Selector_table[13] = { - 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, - 0x6d, 0x02, 0xef, 0x40, 0xef, -}; - -static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { - 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, - 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, - 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, - 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, - 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, - 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, - 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, - 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, - 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, - 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, - 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, - 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, - 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, - 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, - 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, - 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, - 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, - 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, - 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, - 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, - 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, - 0x80, 0xb8, 0x80, 0xb8, 0x80, -}; - -static const uint8_t unicode_prop_Emoji_table[238] = { - 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, - 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, - 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, - 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, - 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, - 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, - 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, - 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, - 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, - 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, - 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, - 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, - 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, - 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, - 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, - 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, - 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, - 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, - 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, - 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, - 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, - 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, - 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, - 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, - 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, - 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, - 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, - 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, - 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, 0xb7, - 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, -}; - -static const uint8_t unicode_prop_Emoji_Component_table[28] = { - 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, - 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, - 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, - 0x6c, 0x06, 0x6b, 0xdf, -}; - -static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { - 0x61, 0xf3, 0xfa, 0x84, -}; - -static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { - 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, - 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, - 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, - 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, - 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, - 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, - 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, - 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, - 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, -}; - -static const uint8_t unicode_prop_Emoji_Presentation_table[144] = { - 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, - 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, - 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, - 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, - 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, - 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, - 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, - 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, - 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, - 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, - 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, - 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, - 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, - 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, - 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, - 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, - 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, - 0x84, 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, -}; - -static const uint8_t unicode_prop_Extended_Pictographic_table[156] = { - 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, - 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, - 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, - 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, - 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, - 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, - 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, - 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, - 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, - 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, - 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, - 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, - 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, - 0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8, - 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, - 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, - 0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b, - 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, - 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x41, 0xb8, - 0x40, 0xff, 0x43, 0xfd, -}; - -static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { - 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, - 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, - 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, - 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, - 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, - 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, - 0x84, 0x4f, 0xff, -}; - -typedef enum { - UNICODE_PROP_Hyphen, - UNICODE_PROP_Other_Math, - UNICODE_PROP_Other_Alphabetic, - UNICODE_PROP_Other_Lowercase, - UNICODE_PROP_Other_Uppercase, - UNICODE_PROP_Other_Grapheme_Extend, - UNICODE_PROP_Other_Default_Ignorable_Code_Point, - UNICODE_PROP_Other_ID_Start, - UNICODE_PROP_Other_ID_Continue, - UNICODE_PROP_Prepended_Concatenation_Mark, - UNICODE_PROP_ID_Continue1, - UNICODE_PROP_XID_Start1, - UNICODE_PROP_XID_Continue1, - UNICODE_PROP_Changes_When_Titlecased1, - UNICODE_PROP_Changes_When_Casefolded1, - UNICODE_PROP_Changes_When_NFKC_Casefolded1, - UNICODE_PROP_ASCII_Hex_Digit, - UNICODE_PROP_Bidi_Control, - UNICODE_PROP_Dash, - UNICODE_PROP_Deprecated, - UNICODE_PROP_Diacritic, - UNICODE_PROP_Extender, - UNICODE_PROP_Hex_Digit, - UNICODE_PROP_IDS_Unary_Operator, - UNICODE_PROP_IDS_Binary_Operator, - UNICODE_PROP_IDS_Trinary_Operator, - UNICODE_PROP_Ideographic, - UNICODE_PROP_Join_Control, - UNICODE_PROP_Logical_Order_Exception, - UNICODE_PROP_Modifier_Combining_Mark, - UNICODE_PROP_Noncharacter_Code_Point, - UNICODE_PROP_Pattern_Syntax, - UNICODE_PROP_Pattern_White_Space, - UNICODE_PROP_Quotation_Mark, - UNICODE_PROP_Radical, - UNICODE_PROP_Regional_Indicator, - UNICODE_PROP_Sentence_Terminal, - UNICODE_PROP_Soft_Dotted, - UNICODE_PROP_Terminal_Punctuation, - UNICODE_PROP_Unified_Ideograph, - UNICODE_PROP_Variation_Selector, - UNICODE_PROP_White_Space, - UNICODE_PROP_Bidi_Mirrored, - UNICODE_PROP_Emoji, - UNICODE_PROP_Emoji_Component, - UNICODE_PROP_Emoji_Modifier, - UNICODE_PROP_Emoji_Modifier_Base, - UNICODE_PROP_Emoji_Presentation, - UNICODE_PROP_Extended_Pictographic, - UNICODE_PROP_Default_Ignorable_Code_Point, - UNICODE_PROP_ID_Start, - UNICODE_PROP_Case_Ignorable, - UNICODE_PROP_ASCII, - UNICODE_PROP_Alphabetic, - UNICODE_PROP_Any, - UNICODE_PROP_Assigned, - UNICODE_PROP_Cased, - UNICODE_PROP_Changes_When_Casefolded, - UNICODE_PROP_Changes_When_Casemapped, - UNICODE_PROP_Changes_When_Lowercased, - UNICODE_PROP_Changes_When_NFKC_Casefolded, - UNICODE_PROP_Changes_When_Titlecased, - UNICODE_PROP_Changes_When_Uppercased, - UNICODE_PROP_Grapheme_Base, - UNICODE_PROP_Grapheme_Extend, - UNICODE_PROP_ID_Continue, - UNICODE_PROP_ID_Compat_Math_Start, - UNICODE_PROP_ID_Compat_Math_Continue, - UNICODE_PROP_Lowercase, - UNICODE_PROP_Math, - UNICODE_PROP_Uppercase, - UNICODE_PROP_XID_Continue, - UNICODE_PROP_XID_Start, - UNICODE_PROP_Cased1, - UNICODE_PROP_InCB, - UNICODE_PROP_COUNT, -} UnicodePropertyEnum; - -static const char unicode_prop_name_table[] = - "ASCII_Hex_Digit,AHex" "\0" - "Bidi_Control,Bidi_C" "\0" - "Dash" "\0" - "Deprecated,Dep" "\0" - "Diacritic,Dia" "\0" - "Extender,Ext" "\0" - "Hex_Digit,Hex" "\0" - "IDS_Unary_Operator,IDSU" "\0" - "IDS_Binary_Operator,IDSB" "\0" - "IDS_Trinary_Operator,IDST" "\0" - "Ideographic,Ideo" "\0" - "Join_Control,Join_C" "\0" - "Logical_Order_Exception,LOE" "\0" - "Modifier_Combining_Mark,MCM" "\0" - "Noncharacter_Code_Point,NChar" "\0" - "Pattern_Syntax,Pat_Syn" "\0" - "Pattern_White_Space,Pat_WS" "\0" - "Quotation_Mark,QMark" "\0" - "Radical" "\0" - "Regional_Indicator,RI" "\0" - "Sentence_Terminal,STerm" "\0" - "Soft_Dotted,SD" "\0" - "Terminal_Punctuation,Term" "\0" - "Unified_Ideograph,UIdeo" "\0" - "Variation_Selector,VS" "\0" - "White_Space,space" "\0" - "Bidi_Mirrored,Bidi_M" "\0" - "Emoji" "\0" - "Emoji_Component,EComp" "\0" - "Emoji_Modifier,EMod" "\0" - "Emoji_Modifier_Base,EBase" "\0" - "Emoji_Presentation,EPres" "\0" - "Extended_Pictographic,ExtPict" "\0" - "Default_Ignorable_Code_Point,DI" "\0" - "ID_Start,IDS" "\0" - "Case_Ignorable,CI" "\0" - "ASCII" "\0" - "Alphabetic,Alpha" "\0" - "Any" "\0" - "Assigned" "\0" - "Cased" "\0" - "Changes_When_Casefolded,CWCF" "\0" - "Changes_When_Casemapped,CWCM" "\0" - "Changes_When_Lowercased,CWL" "\0" - "Changes_When_NFKC_Casefolded,CWKCF" "\0" - "Changes_When_Titlecased,CWT" "\0" - "Changes_When_Uppercased,CWU" "\0" - "Grapheme_Base,Gr_Base" "\0" - "Grapheme_Extend,Gr_Ext" "\0" - "ID_Continue,IDC" "\0" - "ID_Compat_Math_Start" "\0" - "ID_Compat_Math_Continue" "\0" - "Lowercase,Lower" "\0" - "Math" "\0" - "Uppercase,Upper" "\0" - "XID_Continue,XIDC" "\0" - "XID_Start,XIDS" "\0" -; - -static const uint8_t * const unicode_prop_table[] = { - unicode_prop_Hyphen_table, - unicode_prop_Other_Math_table, - unicode_prop_Other_Alphabetic_table, - unicode_prop_Other_Lowercase_table, - unicode_prop_Other_Uppercase_table, - unicode_prop_Other_Grapheme_Extend_table, - unicode_prop_Other_Default_Ignorable_Code_Point_table, - unicode_prop_Other_ID_Start_table, - unicode_prop_Other_ID_Continue_table, - unicode_prop_Prepended_Concatenation_Mark_table, - unicode_prop_ID_Continue1_table, - unicode_prop_XID_Start1_table, - unicode_prop_XID_Continue1_table, - unicode_prop_Changes_When_Titlecased1_table, - unicode_prop_Changes_When_Casefolded1_table, - unicode_prop_Changes_When_NFKC_Casefolded1_table, - unicode_prop_ASCII_Hex_Digit_table, - unicode_prop_Bidi_Control_table, - unicode_prop_Dash_table, - unicode_prop_Deprecated_table, - unicode_prop_Diacritic_table, - unicode_prop_Extender_table, - unicode_prop_Hex_Digit_table, - unicode_prop_IDS_Unary_Operator_table, - unicode_prop_IDS_Binary_Operator_table, - unicode_prop_IDS_Trinary_Operator_table, - unicode_prop_Ideographic_table, - unicode_prop_Join_Control_table, - unicode_prop_Logical_Order_Exception_table, - unicode_prop_Modifier_Combining_Mark_table, - unicode_prop_Noncharacter_Code_Point_table, - unicode_prop_Pattern_Syntax_table, - unicode_prop_Pattern_White_Space_table, - unicode_prop_Quotation_Mark_table, - unicode_prop_Radical_table, - unicode_prop_Regional_Indicator_table, - unicode_prop_Sentence_Terminal_table, - unicode_prop_Soft_Dotted_table, - unicode_prop_Terminal_Punctuation_table, - unicode_prop_Unified_Ideograph_table, - unicode_prop_Variation_Selector_table, - unicode_prop_White_Space_table, - unicode_prop_Bidi_Mirrored_table, - unicode_prop_Emoji_table, - unicode_prop_Emoji_Component_table, - unicode_prop_Emoji_Modifier_table, - unicode_prop_Emoji_Modifier_Base_table, - unicode_prop_Emoji_Presentation_table, - unicode_prop_Extended_Pictographic_table, - unicode_prop_Default_Ignorable_Code_Point_table, - unicode_prop_ID_Start_table, - unicode_prop_Case_Ignorable_table, -}; - -static const uint16_t unicode_prop_len_table[] = { - countof(unicode_prop_Hyphen_table), - countof(unicode_prop_Other_Math_table), - countof(unicode_prop_Other_Alphabetic_table), - countof(unicode_prop_Other_Lowercase_table), - countof(unicode_prop_Other_Uppercase_table), - countof(unicode_prop_Other_Grapheme_Extend_table), - countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), - countof(unicode_prop_Other_ID_Start_table), - countof(unicode_prop_Other_ID_Continue_table), - countof(unicode_prop_Prepended_Concatenation_Mark_table), - countof(unicode_prop_ID_Continue1_table), - countof(unicode_prop_XID_Start1_table), - countof(unicode_prop_XID_Continue1_table), - countof(unicode_prop_Changes_When_Titlecased1_table), - countof(unicode_prop_Changes_When_Casefolded1_table), - countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), - countof(unicode_prop_ASCII_Hex_Digit_table), - countof(unicode_prop_Bidi_Control_table), - countof(unicode_prop_Dash_table), - countof(unicode_prop_Deprecated_table), - countof(unicode_prop_Diacritic_table), - countof(unicode_prop_Extender_table), - countof(unicode_prop_Hex_Digit_table), - countof(unicode_prop_IDS_Unary_Operator_table), - countof(unicode_prop_IDS_Binary_Operator_table), - countof(unicode_prop_IDS_Trinary_Operator_table), - countof(unicode_prop_Ideographic_table), - countof(unicode_prop_Join_Control_table), - countof(unicode_prop_Logical_Order_Exception_table), - countof(unicode_prop_Modifier_Combining_Mark_table), - countof(unicode_prop_Noncharacter_Code_Point_table), - countof(unicode_prop_Pattern_Syntax_table), - countof(unicode_prop_Pattern_White_Space_table), - countof(unicode_prop_Quotation_Mark_table), - countof(unicode_prop_Radical_table), - countof(unicode_prop_Regional_Indicator_table), - countof(unicode_prop_Sentence_Terminal_table), - countof(unicode_prop_Soft_Dotted_table), - countof(unicode_prop_Terminal_Punctuation_table), - countof(unicode_prop_Unified_Ideograph_table), - countof(unicode_prop_Variation_Selector_table), - countof(unicode_prop_White_Space_table), - countof(unicode_prop_Bidi_Mirrored_table), - countof(unicode_prop_Emoji_table), - countof(unicode_prop_Emoji_Component_table), - countof(unicode_prop_Emoji_Modifier_table), - countof(unicode_prop_Emoji_Modifier_Base_table), - countof(unicode_prop_Emoji_Presentation_table), - countof(unicode_prop_Extended_Pictographic_table), - countof(unicode_prop_Default_Ignorable_Code_Point_table), - countof(unicode_prop_ID_Start_table), - countof(unicode_prop_Case_Ignorable_table), -}; - diff --git a/NativeScript/napi/quickjs/source/libunicode.c b/NativeScript/napi/quickjs/source/libunicode.c deleted file mode 100644 index a621c5235..000000000 --- a/NativeScript/napi/quickjs/source/libunicode.c +++ /dev/null @@ -1,1746 +0,0 @@ -/* - * Unicode utilities - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include - -#include "cutils.h" -#include "libunicode.h" -#include "libunicode-table.h" - -// note: stored as 4 bit tag, not much room left -enum { - RUN_TYPE_U, - RUN_TYPE_L, - RUN_TYPE_UF, - RUN_TYPE_LF, - RUN_TYPE_UL, - RUN_TYPE_LSU, - RUN_TYPE_U2L_399_EXT2, - RUN_TYPE_UF_D20, - RUN_TYPE_UF_D1_EXT, - RUN_TYPE_U_EXT, - RUN_TYPE_LF_EXT, - RUN_TYPE_UF_EXT2, - RUN_TYPE_LF_EXT2, - RUN_TYPE_UF_EXT3, -}; - -static int lre_case_conv1(uint32_t c, int conv_type) -{ - uint32_t res[LRE_CC_RES_LEN_MAX]; - lre_case_conv(res, c, conv_type); - return res[0]; -} - -/* case conversion using the table entry 'idx' with value 'v' */ -static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) -{ - uint32_t code, data, type, a, is_lower; - is_lower = (conv_type != 0); - type = (v >> (32 - 17 - 7 - 4)) & 0xf; - data = ((v & 0xf) << 8) | case_conv_table2[idx]; - code = v >> (32 - 17); - switch(type) { - case RUN_TYPE_U: - case RUN_TYPE_L: - case RUN_TYPE_UF: - case RUN_TYPE_LF: - if (conv_type == (type & 1) || - (type >= RUN_TYPE_UF && conv_type == 2)) { - c = c - code + (case_conv_table1[data] >> (32 - 17)); - } - break; - case RUN_TYPE_UL: - a = c - code; - if ((a & 1) != (1 - is_lower)) - break; - c = (a ^ 1) + code; - break; - case RUN_TYPE_LSU: - a = c - code; - if (a == 1) { - c += 2 * is_lower - 1; - } else if (a == (1 - is_lower) * 2) { - c += (2 * is_lower - 1) * 2; - } - break; - case RUN_TYPE_U2L_399_EXT2: - if (!is_lower) { - res[0] = c - code + case_conv_ext[data >> 6]; - res[1] = 0x399; - return 2; - } else { - c = c - code + case_conv_ext[data & 0x3f]; - } - break; - case RUN_TYPE_UF_D20: - if (conv_type == 1) - break; - c = data + (conv_type == 2) * 0x20; - break; - case RUN_TYPE_UF_D1_EXT: - if (conv_type == 1) - break; - c = case_conv_ext[data] + (conv_type == 2); - break; - case RUN_TYPE_U_EXT: - case RUN_TYPE_LF_EXT: - if (is_lower != (type - RUN_TYPE_U_EXT)) - break; - c = case_conv_ext[data]; - break; - case RUN_TYPE_LF_EXT2: - if (!is_lower) - break; - res[0] = c - code + case_conv_ext[data >> 6]; - res[1] = case_conv_ext[data & 0x3f]; - return 2; - case RUN_TYPE_UF_EXT2: - if (conv_type == 1) - break; - res[0] = c - code + case_conv_ext[data >> 6]; - res[1] = case_conv_ext[data & 0x3f]; - if (conv_type == 2) { - /* convert to lower */ - res[0] = lre_case_conv1(res[0], 1); - res[1] = lre_case_conv1(res[1], 1); - } - return 2; - default: - case RUN_TYPE_UF_EXT3: - if (conv_type == 1) - break; - res[0] = case_conv_ext[data >> 8]; - res[1] = case_conv_ext[(data >> 4) & 0xf]; - res[2] = case_conv_ext[data & 0xf]; - if (conv_type == 2) { - /* convert to lower */ - res[0] = lre_case_conv1(res[0], 1); - res[1] = lre_case_conv1(res[1], 1); - res[2] = lre_case_conv1(res[2], 1); - } - return 3; - } - res[0] = c; - return 1; -} - -/* conv_type: - 0 = to upper - 1 = to lower - 2 = case folding (= to lower with modifications) -*/ -int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) -{ - if (c < 128) { - if (conv_type) { - if (c >= 'A' && c <= 'Z') { - c = c - 'A' + 'a'; - } - } else { - if (c >= 'a' && c <= 'z') { - c = c - 'a' + 'A'; - } - } - } else { - uint32_t v, code, len; - int idx, idx_min, idx_max; - - idx_min = 0; - idx_max = countof(case_conv_table1) - 1; - while (idx_min <= idx_max) { - idx = (unsigned)(idx_max + idx_min) / 2; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - return lre_case_conv_entry(res, c, conv_type, idx, v); - } - } - } - res[0] = c; - return 1; -} - -static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, bool is_unicode) -{ - uint32_t res[LRE_CC_RES_LEN_MAX]; - int len; - - if (is_unicode) { - len = lre_case_conv_entry(res, c, 2, idx, v); - if (len == 1) { - c = res[0]; - } else { - /* handle the few specific multi-character cases (see - unicode_gen.c:dump_case_folding_special_cases()) */ - if (c == 0xfb06) { - c = 0xfb05; - } else if (c == 0x01fd3) { - c = 0x390; - } else if (c == 0x01fe3) { - c = 0x3b0; - } - } - } else { - if (likely(c < 128)) { - if (c >= 'a' && c <= 'z') - c = c - 'a' + 'A'; - } else { - /* legacy regexp: to upper case if single char >= 128 */ - len = lre_case_conv_entry(res, c, false, idx, v); - if (len == 1 && res[0] >= 128) - c = res[0]; - } - } - return c; -} - -/* JS regexp specific rules for case folding */ -int lre_canonicalize(uint32_t c, bool is_unicode) -{ - if (c < 128) { - /* fast case */ - if (is_unicode) { - if (c >= 'A' && c <= 'Z') { - c = c - 'A' + 'a'; - } - } else { - if (c >= 'a' && c <= 'z') { - c = c - 'a' + 'A'; - } - } - } else { - uint32_t v, code, len; - int idx, idx_min, idx_max; - - idx_min = 0; - idx_max = countof(case_conv_table1) - 1; - while (idx_min <= idx_max) { - idx = (unsigned)(idx_max + idx_min) / 2; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - return lre_case_folding_entry(c, idx, v, is_unicode); - } - } - } - return c; -} - -static uint32_t get_le24(const uint8_t *ptr) -{ - return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); -} - -#define UNICODE_INDEX_BLOCK_LEN 32 - -/* return -1 if not in table, otherwise the offset in the block */ -static int get_index_pos(uint32_t *pcode, uint32_t c, - const uint8_t *index_table, int index_table_len) -{ - uint32_t code, v; - int idx_min, idx_max, idx; - - idx_min = 0; - v = get_le24(index_table); - code = v & ((1 << 21) - 1); - if (c < code) { - *pcode = 0; - return 0; - } - idx_max = index_table_len - 1; - code = get_le24(index_table + idx_max * 3); - if (c >= code) - return -1; - /* invariant: tab[idx_min] <= c < tab2[idx_max] */ - while ((idx_max - idx_min) > 1) { - idx = (idx_max + idx_min) / 2; - v = get_le24(index_table + idx * 3); - code = v & ((1 << 21) - 1); - if (c < code) { - idx_max = idx; - } else { - idx_min = idx; - } - } - v = get_le24(index_table + idx_min * 3); - *pcode = v & ((1 << 21) - 1); - return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); -} - -static bool lre_is_in_table(uint32_t c, const uint8_t *table, - const uint8_t *index_table, int index_table_len) -{ - uint32_t code, b, bit; - int pos; - const uint8_t *p; - - pos = get_index_pos(&code, c, index_table, index_table_len); - if (pos < 0) - return false; /* outside the table */ - p = table + pos; - bit = 0; - for(;;) { - b = *p++; - if (b < 64) { - code += (b >> 3) + 1; - if (c < code) - return bit; - bit ^= 1; - code += (b & 7) + 1; - } else if (b >= 0x80) { - code += b - 0x80 + 1; - } else if (b < 0x60) { - code += (((b - 0x40) << 8) | p[0]) + 1; - p++; - } else { - code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; - p += 2; - } - if (c < code) - return bit; - bit ^= 1; - } -} - -bool lre_is_cased(uint32_t c) -{ - uint32_t v, code, len; - int idx, idx_min, idx_max; - - idx_min = 0; - idx_max = countof(case_conv_table1) - 1; - while (idx_min <= idx_max) { - idx = (unsigned)(idx_max + idx_min) / 2; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - return true; - } - } - return lre_is_in_table(c, unicode_prop_Cased1_table, - unicode_prop_Cased1_index, - sizeof(unicode_prop_Cased1_index) / 3); -} - -bool lre_is_case_ignorable(uint32_t c) -{ - return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, - unicode_prop_Case_Ignorable_index, - sizeof(unicode_prop_Case_Ignorable_index) / 3); -} - -/* character range */ - -static __maybe_unused void cr_dump(CharRange *cr) -{ - int i; - for(i = 0; i < cr->len; i++) - printf("%d: 0x%04x\n", i, cr->points[i]); -} - -static void *cr_default_realloc(void *opaque, void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) -{ - cr->len = cr->size = 0; - cr->points = NULL; - cr->mem_opaque = mem_opaque; - cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; -} - -void cr_free(CharRange *cr) -{ - cr->realloc_func(cr->mem_opaque, cr->points, 0); -} - -int cr_realloc(CharRange *cr, int size) -{ - int new_size; - uint32_t *new_buf; - - if (size > cr->size) { - new_size = max_int(size, cr->size * 3 / 2); - new_buf = cr->realloc_func(cr->mem_opaque, cr->points, - new_size * sizeof(cr->points[0])); - if (!new_buf) - return -1; - cr->points = new_buf; - cr->size = new_size; - } - return 0; -} - -int cr_copy(CharRange *cr, const CharRange *cr1) -{ - if (cr_realloc(cr, cr1->len)) - return -1; - memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); - cr->len = cr1->len; - return 0; -} - -/* merge consecutive intervals and remove empty intervals */ -static void cr_compress(CharRange *cr) -{ - int i, j, k, len; - uint32_t *pt; - - pt = cr->points; - len = cr->len; - i = 0; - j = 0; - k = 0; - while ((i + 1) < len) { - if (pt[i] == pt[i + 1]) { - /* empty interval */ - i += 2; - } else { - j = i; - while ((j + 3) < len && pt[j + 1] == pt[j + 2]) - j += 2; - /* just copy */ - pt[k] = pt[i]; - pt[k + 1] = pt[j + 1]; - k += 2; - i = j + 2; - } - } - cr->len = k; -} - -/* union or intersection */ -int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, - const uint32_t *b_pt, int b_len, int op) -{ - int a_idx, b_idx, is_in; - uint32_t v; - - a_idx = 0; - b_idx = 0; - for(;;) { - /* get one more point from a or b in increasing order */ - if (a_idx < a_len && b_idx < b_len) { - if (a_pt[a_idx] < b_pt[b_idx]) { - goto a_add; - } else if (a_pt[a_idx] == b_pt[b_idx]) { - v = a_pt[a_idx]; - a_idx++; - b_idx++; - } else { - goto b_add; - } - } else if (a_idx < a_len) { - a_add: - v = a_pt[a_idx++]; - } else if (b_idx < b_len) { - b_add: - v = b_pt[b_idx++]; - } else { - break; - } - /* add the point if the in/out status changes */ - switch(op) { - case CR_OP_UNION: - is_in = (a_idx & 1) | (b_idx & 1); - break; - case CR_OP_INTER: - is_in = (a_idx & 1) & (b_idx & 1); - break; - case CR_OP_XOR: - is_in = (a_idx & 1) ^ (b_idx & 1); - break; - default: - abort(); - } - if (is_in != (cr->len & 1)) { - if (cr_add_point(cr, v)) - return -1; - } - } - cr_compress(cr); - return 0; -} - -int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len) -{ - CharRange a = *cr; - int ret; - cr->len = 0; - cr->size = 0; - cr->points = NULL; - ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION); - cr_free(&a); - return ret; -} - -int cr_invert(CharRange *cr) -{ - int len; - len = cr->len; - if (cr_realloc(cr, len + 2)) - return -1; - memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); - cr->points[0] = 0; - cr->points[len + 1] = UINT32_MAX; - cr->len = len + 2; - cr_compress(cr); - return 0; -} - -bool lre_is_id_start(uint32_t c) -{ - return lre_is_in_table(c, unicode_prop_ID_Start_table, - unicode_prop_ID_Start_index, - sizeof(unicode_prop_ID_Start_index) / 3); -} - -bool lre_is_id_continue(uint32_t c) -{ - return lre_is_id_start(c) || - lre_is_in_table(c, unicode_prop_ID_Continue1_table, - unicode_prop_ID_Continue1_index, - sizeof(unicode_prop_ID_Continue1_index) / 3); -} - -bool lre_is_white_space(uint32_t c) -{ - return lre_is_in_table(c, unicode_prop_White_Space_table, - unicode_prop_White_Space_index, - sizeof(unicode_prop_White_Space_index) / 3); -} - -#define UNICODE_DECOMP_LEN_MAX 18 - -typedef enum { - DECOMP_TYPE_C1, /* 16 bit char */ - DECOMP_TYPE_L1, /* 16 bit char table */ - DECOMP_TYPE_L2, - DECOMP_TYPE_L3, - DECOMP_TYPE_L4, - DECOMP_TYPE_L5, /* XXX: not used */ - DECOMP_TYPE_L6, /* XXX: could remove */ - DECOMP_TYPE_L7, /* XXX: could remove */ - DECOMP_TYPE_LL1, /* 18 bit char table */ - DECOMP_TYPE_LL2, - DECOMP_TYPE_S1, /* 8 bit char table */ - DECOMP_TYPE_S2, - DECOMP_TYPE_S3, - DECOMP_TYPE_S4, - DECOMP_TYPE_S5, - DECOMP_TYPE_I1, /* increment 16 bit char value */ - DECOMP_TYPE_I2_0, - DECOMP_TYPE_I2_1, - DECOMP_TYPE_I3_1, - DECOMP_TYPE_I3_2, - DECOMP_TYPE_I4_1, - DECOMP_TYPE_I4_2, - DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ - DECOMP_TYPE_B2, - DECOMP_TYPE_B3, - DECOMP_TYPE_B4, - DECOMP_TYPE_B5, - DECOMP_TYPE_B6, - DECOMP_TYPE_B7, - DECOMP_TYPE_B8, - DECOMP_TYPE_B18, - DECOMP_TYPE_LS2, - DECOMP_TYPE_PAT3, - DECOMP_TYPE_S2_UL, - DECOMP_TYPE_LS2_UL, -} DecompTypeEnum; - -static uint32_t unicode_get_short_code(uint32_t c) -{ - static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; - - if (c < 0x80) - return c; - else if (c < 0x80 + 0x50) - return c - 0x80 + 0x300; - else - return unicode_short_table[c - 0x80 - 0x50]; -} - -static uint32_t unicode_get_lower_simple(uint32_t c) -{ - if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) - c += 0x20; - else - c++; - return c; -} - -static uint16_t unicode_get16(const uint8_t *p) -{ - return p[0] | (p[1] << 8); -} - -static int unicode_decomp_entry(uint32_t *res, uint32_t c, - int idx, uint32_t code, uint32_t len, - uint32_t type) -{ - uint32_t c1; - int l, i, p; - const uint8_t *d; - - if (type == DECOMP_TYPE_C1) { - res[0] = unicode_decomp_table2[idx]; - return 1; - } else { - d = unicode_decomp_data + unicode_decomp_table2[idx]; - switch(type) { - case DECOMP_TYPE_L1: - case DECOMP_TYPE_L2: - case DECOMP_TYPE_L3: - case DECOMP_TYPE_L4: - case DECOMP_TYPE_L5: - case DECOMP_TYPE_L6: - case DECOMP_TYPE_L7: - l = type - DECOMP_TYPE_L1 + 1; - d += (c - code) * l * 2; - for(i = 0; i < l; i++) { - if ((res[i] = unicode_get16(d + 2 * i)) == 0) - return 0; - } - return l; - case DECOMP_TYPE_LL1: - case DECOMP_TYPE_LL2: - { - uint32_t k, p; - l = type - DECOMP_TYPE_LL1 + 1; - k = (c - code) * l; - p = len * l * 2; - for(i = 0; i < l; i++) { - c1 = unicode_get16(d + 2 * k) | - (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); - if (!c1) - return 0; - res[i] = c1; - k++; - } - } - return l; - case DECOMP_TYPE_S1: - case DECOMP_TYPE_S2: - case DECOMP_TYPE_S3: - case DECOMP_TYPE_S4: - case DECOMP_TYPE_S5: - l = type - DECOMP_TYPE_S1 + 1; - d += (c - code) * l; - for(i = 0; i < l; i++) { - if ((res[i] = unicode_get_short_code(d[i])) == 0) - return 0; - } - return l; - case DECOMP_TYPE_I1: - l = 1; - p = 0; - goto decomp_type_i; - case DECOMP_TYPE_I2_0: - case DECOMP_TYPE_I2_1: - case DECOMP_TYPE_I3_1: - case DECOMP_TYPE_I3_2: - case DECOMP_TYPE_I4_1: - case DECOMP_TYPE_I4_2: - l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); - p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); - decomp_type_i: - for(i = 0; i < l; i++) { - c1 = unicode_get16(d + 2 * i); - if (i == p) - c1 += c - code; - res[i] = c1; - } - return l; - case DECOMP_TYPE_B18: - l = 18; - goto decomp_type_b; - case DECOMP_TYPE_B1: - case DECOMP_TYPE_B2: - case DECOMP_TYPE_B3: - case DECOMP_TYPE_B4: - case DECOMP_TYPE_B5: - case DECOMP_TYPE_B6: - case DECOMP_TYPE_B7: - case DECOMP_TYPE_B8: - l = type - DECOMP_TYPE_B1 + 1; - decomp_type_b: - { - uint32_t c_min; - c_min = unicode_get16(d); - d += 2 + (c - code) * l; - for(i = 0; i < l; i++) { - c1 = d[i]; - if (c1 == 0xff) - c1 = 0x20; - else - c1 += c_min; - res[i] = c1; - } - } - return l; - case DECOMP_TYPE_LS2: - d += (c - code) * 3; - if (!(res[0] = unicode_get16(d))) - return 0; - res[1] = unicode_get_short_code(d[2]); - return 2; - case DECOMP_TYPE_PAT3: - res[0] = unicode_get16(d); - res[2] = unicode_get16(d + 2); - d += 4 + (c - code) * 2; - res[1] = unicode_get16(d); - return 3; - case DECOMP_TYPE_S2_UL: - case DECOMP_TYPE_LS2_UL: - c1 = c - code; - if (type == DECOMP_TYPE_S2_UL) { - d += c1 & ~1; - c = unicode_get_short_code(*d); - d++; - } else { - d += (c1 >> 1) * 3; - c = unicode_get16(d); - d += 2; - } - if (c1 & 1) - c = unicode_get_lower_simple(c); - res[0] = c; - res[1] = unicode_get_short_code(*d); - return 2; - } - } - return 0; -} - - -/* return the length of the decomposition (length <= - UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ -static int unicode_decomp_char(uint32_t *res, uint32_t c, bool is_compat1) -{ - uint32_t v, type, is_compat, code, len; - int idx_min, idx_max, idx; - - idx_min = 0; - idx_max = countof(unicode_decomp_table1) - 1; - while (idx_min <= idx_max) { - idx = (idx_max + idx_min) / 2; - v = unicode_decomp_table1[idx]; - code = v >> (32 - 18); - len = (v >> (32 - 18 - 7)) & 0x7f; - // printf("idx=%d code=%05x len=%d\n", idx, code, len); - if (c < code) { - idx_max = idx - 1; - } else if (c >= code + len) { - idx_min = idx + 1; - } else { - is_compat = v & 1; - if (is_compat1 < is_compat) - break; - type = (v >> (32 - 18 - 7 - 6)) & 0x3f; - return unicode_decomp_entry(res, c, idx, code, len, type); - } - } - return 0; -} - -/* return 0 if no pair found */ -static int unicode_compose_pair(uint32_t c0, uint32_t c1) -{ - uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; - int idx_min, idx_max, idx, d; - uint32_t pair[2]; - - idx_min = 0; - idx_max = countof(unicode_comp_table) - 1; - while (idx_min <= idx_max) { - idx = (idx_max + idx_min) / 2; - idx1 = unicode_comp_table[idx]; - - /* idx1 represent an entry of the decomposition table */ - d_idx = idx1 >> 6; - d_offset = idx1 & 0x3f; - v = unicode_decomp_table1[d_idx]; - code = v >> (32 - 18); - len = (v >> (32 - 18 - 7)) & 0x7f; - type = (v >> (32 - 18 - 7 - 6)) & 0x3f; - ch = code + d_offset; - unicode_decomp_entry(pair, ch, d_idx, code, len, type); - d = c0 - pair[0]; - if (d == 0) - d = c1 - pair[1]; - if (d < 0) { - idx_max = idx - 1; - } else if (d > 0) { - idx_min = idx + 1; - } else { - return ch; - } - } - return 0; -} - -/* return the combining class of character c (between 0 and 255) */ -static int unicode_get_cc(uint32_t c) -{ - uint32_t code, n, type, cc, c1, b; - int pos; - const uint8_t *p; - - pos = get_index_pos(&code, c, - unicode_cc_index, sizeof(unicode_cc_index) / 3); - if (pos < 0) - return 0; - p = unicode_cc_table + pos; - for(;;) { - b = *p++; - type = b >> 6; - n = b & 0x3f; - if (n < 48) { - } else if (n < 56) { - n = (n - 48) << 8; - n |= *p++; - n += 48; - } else { - n = (n - 56) << 8; - n |= *p++ << 8; - n |= *p++; - n += 48 + (1 << 11); - } - if (type <= 1) - p++; - c1 = code + n + 1; - if (c < c1) { - switch(type) { - case 0: - cc = p[-1]; - break; - case 1: - cc = p[-1] + c - code; - break; - case 2: - cc = 0; - break; - default: - case 3: - cc = 230; - break; - } - return cc; - } - code = c1; - } -} - -static void sort_cc(int *buf, int len) -{ - int i, j, k, cc, cc1, start, ch1; - - for(i = 0; i < len; i++) { - cc = unicode_get_cc(buf[i]); - if (cc != 0) { - start = i; - j = i + 1; - while (j < len) { - ch1 = buf[j]; - cc1 = unicode_get_cc(ch1); - if (cc1 == 0) - break; - k = j - 1; - while (k >= start) { - if (unicode_get_cc(buf[k]) <= cc1) - break; - buf[k + 1] = buf[k]; - k--; - } - buf[k + 1] = ch1; - j++; - } - i = j; - } - } -} - -static void to_nfd_rec(DynBuf *dbuf, - const int *src, int src_len, int is_compat) -{ - uint32_t c, v; - int i, l; - uint32_t res[UNICODE_DECOMP_LEN_MAX]; - - for(i = 0; i < src_len; i++) { - c = src[i]; - if (c >= 0xac00 && c < 0xd7a4) { - /* Hangul decomposition */ - c -= 0xac00; - dbuf_put_u32(dbuf, 0x1100 + c / 588); - dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); - v = c % 28; - if (v != 0) - dbuf_put_u32(dbuf, 0x11a7 + v); - } else { - l = unicode_decomp_char(res, c, is_compat); - if (l) { - to_nfd_rec(dbuf, (int *)res, l, is_compat); - } else { - dbuf_put_u32(dbuf, c); - } - } - } -} - -/* return 0 if not found */ -static int compose_pair(uint32_t c0, uint32_t c1) -{ - /* Hangul composition */ - if (c0 >= 0x1100 && c0 < 0x1100 + 19 && - c1 >= 0x1161 && c1 < 0x1161 + 21) { - return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; - } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && - (c0 - 0xac00) % 28 == 0 && - c1 >= 0x11a7 && c1 < 0x11a7 + 28) { - return c0 + c1 - 0x11a7; - } else { - return unicode_compose_pair(c0, c1); - } -} - -int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, - UnicodeNormalizationEnum n_type, - void *opaque, DynBufReallocFunc *realloc_func) -{ - int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; - bool is_compat; - DynBuf dbuf_s, *dbuf = &dbuf_s; - - is_compat = n_type >> 1; - - dbuf_init2(dbuf, opaque, realloc_func); - if (dbuf_realloc(dbuf, sizeof(int) * src_len)) - goto fail; - - /* common case: latin1 is unaffected by NFC */ - if (n_type == UNICODE_NFC) { - for(i = 0; i < src_len; i++) { - if (src[i] >= 0x100) - goto not_latin1; - } - buf = (int *)dbuf->buf; - memcpy(buf, src, src_len * sizeof(int)); - *pdst = (uint32_t *)buf; - return src_len; - not_latin1: ; - } - - to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); - if (dbuf_error(dbuf)) { - fail: - *pdst = NULL; - return -1; - } - buf = (int *)dbuf->buf; - buf_len = dbuf->size / sizeof(int); - - sort_cc(buf, buf_len); - - if (buf_len <= 1 || (n_type & 1) != 0) { - /* NFD / NFKD */ - *pdst = (uint32_t *)buf; - return buf_len; - } - - i = 1; - out_len = 1; - while (i < buf_len) { - /* find the starter character and test if it is blocked from - the character at 'i' */ - last_cc = unicode_get_cc(buf[i]); - starter_pos = out_len - 1; - while (starter_pos >= 0) { - cc = unicode_get_cc(buf[starter_pos]); - if (cc == 0) - break; - if (cc >= last_cc) - goto next; - last_cc = 256; - starter_pos--; - } - if (starter_pos >= 0 && - (p = compose_pair(buf[starter_pos], buf[i])) != 0) { - buf[starter_pos] = p; - i++; - } else { - next: - buf[out_len++] = buf[i++]; - } - } - *pdst = (uint32_t *)buf; - return out_len; -} - -/* char ranges for various unicode properties */ - -static int unicode_find_name(const char *name_table, const char *name) -{ - const char *p, *r; - int pos; - size_t name_len, len; - - p = name_table; - pos = 0; - name_len = strlen(name); - while (*p) { - for(;;) { - r = strchr(p, ','); - if (!r) - len = strlen(p); - else - len = r - p; - if (len == name_len && !memcmp(p, name, name_len)) - return pos; - p += len + 1; - if (!r) - break; - } - pos++; - } - return -1; -} - -/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 - if not found */ -int unicode_script(CharRange *cr, - const char *script_name, bool is_ext) -{ - int script_idx; - const uint8_t *p, *p_end; - uint32_t c, c1, b, n, v, v_len, i, type; - CharRange cr1_s = { 0 }, *cr1 = NULL; - CharRange cr2_s = { 0 }, *cr2 = &cr2_s; - bool is_common; - - script_idx = unicode_find_name(unicode_script_name_table, script_name); - if (script_idx < 0) - return -2; - /* Note: we remove the "Unknown" Script */ - script_idx += UNICODE_SCRIPT_Unknown + 1; - - is_common = (script_idx == UNICODE_SCRIPT_Common || - script_idx == UNICODE_SCRIPT_Inherited); - if (is_ext) { - cr1 = &cr1_s; - cr_init(cr1, cr->mem_opaque, cr->realloc_func); - cr_init(cr2, cr->mem_opaque, cr->realloc_func); - } else { - cr1 = cr; - } - - p = unicode_script_table; - p_end = unicode_script_table + countof(unicode_script_table); - c = 0; - while (p < p_end) { - b = *p++; - type = b >> 7; - n = b & 0x7f; - if (n < 96) { - } else if (n < 112) { - n = (n - 96) << 8; - n |= *p++; - n += 96; - } else { - n = (n - 112) << 16; - n |= *p++ << 8; - n |= *p++; - n += 96 + (1 << 12); - } - if (type == 0) - v = 0; - else - v = *p++; - c1 = c + n + 1; - if (v == script_idx) { - if (cr_add_interval(cr1, c, c1)) - goto fail; - } - c = c1; - } - - if (is_ext) { - /* add the script extensions */ - p = unicode_script_ext_table; - p_end = unicode_script_ext_table + countof(unicode_script_ext_table); - c = 0; - while (p < p_end) { - b = *p++; - if (b < 128) { - n = b; - } else if (b < 128 + 64) { - n = (b - 128) << 8; - n |= *p++; - n += 128; - } else { - n = (b - 128 - 64) << 16; - n |= *p++ << 8; - n |= *p++; - n += 128 + (1 << 14); - } - c1 = c + n + 1; - v_len = *p++; - if (is_common) { - if (v_len != 0) { - if (cr_add_interval(cr2, c, c1)) - goto fail; - } - } else { - for(i = 0; i < v_len; i++) { - if (p[i] == script_idx) { - if (cr_add_interval(cr2, c, c1)) - goto fail; - break; - } - } - } - p += v_len; - c = c1; - } - if (is_common) { - /* remove all the characters with script extensions */ - if (cr_invert(cr2)) - goto fail; - if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, - CR_OP_INTER)) - goto fail; - } else { - if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, - CR_OP_UNION)) - goto fail; - } - cr_free(cr1); - cr_free(cr2); - } - return 0; - fail: - if (is_ext) { - cr_free(cr1); - cr_free(cr2); - } - goto fail; -} - -#define M(id) (1U << UNICODE_GC_ ## id) - -static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) -{ - const uint8_t *p, *p_end; - uint32_t c, c0, b, n, v; - - p = unicode_gc_table; - p_end = unicode_gc_table + countof(unicode_gc_table); - c = 0; - while (p < p_end) { - b = *p++; - n = b >> 5; - v = b & 0x1f; - if (n == 7) { - n = *p++; - if (n < 128) { - n += 7; - } else if (n < 128 + 64) { - n = (n - 128) << 8; - n |= *p++; - n += 7 + 128; - } else { - n = (n - 128 - 64) << 16; - n |= *p++ << 8; - n |= *p++; - n += 7 + 128 + (1 << 14); - } - } - c0 = c; - c += n + 1; - if (v == 31) { - /* run of Lu / Ll */ - b = gc_mask & (M(Lu) | M(Ll)); - if (b != 0) { - if (b == (M(Lu) | M(Ll))) { - goto add_range; - } else { - c0 += ((gc_mask & M(Ll)) != 0); - for(; c0 < c; c0 += 2) { - if (cr_add_interval(cr, c0, c0 + 1)) - return -1; - } - } - } - } else if ((gc_mask >> v) & 1) { - add_range: - if (cr_add_interval(cr, c0, c)) - return -1; - } - } - return 0; -} - -static int unicode_prop1(CharRange *cr, int prop_idx) -{ - const uint8_t *p, *p_end; - uint32_t c, c0, b, bit; - - p = unicode_prop_table[prop_idx]; - p_end = p + unicode_prop_len_table[prop_idx]; - c = 0; - bit = 0; - while (p < p_end) { - c0 = c; - b = *p++; - if (b < 64) { - c += (b >> 3) + 1; - if (bit) { - if (cr_add_interval(cr, c0, c)) - return -1; - } - bit ^= 1; - c0 = c; - c += (b & 7) + 1; - } else if (b >= 0x80) { - c += b - 0x80 + 1; - } else if (b < 0x60) { - c += (((b - 0x40) << 8) | p[0]) + 1; - p++; - } else { - c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; - p += 2; - } - if (bit) { - if (cr_add_interval(cr, c0, c)) - return -1; - } - bit ^= 1; - } - return 0; -} - -#define CASE_U (1 << 0) -#define CASE_L (1 << 1) -#define CASE_F (1 << 2) - -/* use the case conversion table to generate range of characters. - CASE_U: set char if modified by uppercasing, - CASE_L: set char if modified by lowercasing, - CASE_F: set char if modified by case folding, - */ -static int unicode_case1(CharRange *cr, int case_mask) -{ -#define MR(x) (1 << RUN_TYPE_ ## x) - const uint32_t tab_run_mask[3] = { - MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | - MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), - - MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), - - MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), - }; -#undef MR - uint32_t mask, v, code, type, len, i, idx; - - if (case_mask == 0) - return 0; - mask = 0; - for(i = 0; i < 3; i++) { - if ((case_mask >> i) & 1) - mask |= tab_run_mask[i]; - } - for(idx = 0; idx < countof(case_conv_table1); idx++) { - v = case_conv_table1[idx]; - type = (v >> (32 - 17 - 7 - 4)) & 0xf; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - if ((mask >> type) & 1) { - // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); - switch(type) { - case RUN_TYPE_UL: - if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) - goto def_case; - code += ((case_mask & CASE_U) != 0); - for(i = 0; i < len; i += 2) { - if (cr_add_interval(cr, code + i, code + i + 1)) - return -1; - } - break; - case RUN_TYPE_LSU: - if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) - goto def_case; - if (!(case_mask & CASE_U)) { - if (cr_add_interval(cr, code, code + 1)) - return -1; - } - if (cr_add_interval(cr, code + 1, code + 2)) - return -1; - if (case_mask & CASE_U) { - if (cr_add_interval(cr, code + 2, code + 3)) - return -1; - } - break; - default: - def_case: - if (cr_add_interval(cr, code, code + len)) - return -1; - break; - } - } - } - return 0; -} - -static int point_cmp(const void *p1, const void *p2, void *arg) -{ - uint32_t v1 = *(uint32_t *)p1; - uint32_t v2 = *(uint32_t *)p2; - return (v1 > v2) - (v1 < v2); -} - -static void cr_sort_and_remove_overlap(CharRange *cr) -{ - uint32_t start, end, start1, end1, i, j; - - /* the resulting ranges are not necessarily sorted and may overlap */ - rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); - j = 0; - for(i = 0; i < cr->len; ) { - start = cr->points[i]; - end = cr->points[i + 1]; - i += 2; - while (i < cr->len) { - start1 = cr->points[i]; - end1 = cr->points[i + 1]; - if (start1 > end) { - /* |------| - * |-------| */ - break; - } else if (end1 <= end) { - /* |------| - * |--| */ - i += 2; - } else { - /* |------| - * |-------| */ - end = end1; - i += 2; - } - } - cr->points[j] = start; - cr->points[j + 1] = end; - j += 2; - } - cr->len = j; -} - -/* canonicalize a character set using the JS regex case folding rules - (see lre_canonicalize()) */ -int cr_regexp_canonicalize(CharRange *cr, bool is_unicode) -{ - CharRange cr_inter, cr_mask, cr_result, cr_sub; - uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; - - cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); - cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); - cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); - cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); - - if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) - goto fail; - if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) - goto fail; - - if (cr_invert(&cr_mask)) - goto fail; - if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) - goto fail; - - /* cr_inter = cr & cr_mask */ - /* cr_sub = cr & ~cr_mask */ - - /* use the case conversion table to compute the result */ - d_start = -1; - d_end = -1; - idx = 0; - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - for(i = 0; i < cr_inter.len; i += 2) { - start = cr_inter.points[i]; - end = cr_inter.points[i + 1]; - - for(c = start; c < end; c++) { - for(;;) { - if (c >= code && c < code + len) - break; - idx++; - assert(idx < countof(case_conv_table1)); - v = case_conv_table1[idx]; - code = v >> (32 - 17); - len = (v >> (32 - 17 - 7)) & 0x7f; - } - d = lre_case_folding_entry(c, idx, v, is_unicode); - /* try to merge with the current interval */ - if (d_start == -1) { - d_start = d; - d_end = d + 1; - } else if (d_end == d) { - d_end++; - } else { - cr_add_interval(&cr_result, d_start, d_end); - d_start = d; - d_end = d + 1; - } - } - } - if (d_start != -1) { - if (cr_add_interval(&cr_result, d_start, d_end)) - goto fail; - } - - /* the resulting ranges are not necessarily sorted and may overlap */ - cr_sort_and_remove_overlap(&cr_result); - - /* or with the character not affected by the case folding */ - cr->len = 0; - if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) - goto fail; - - cr_free(&cr_inter); - cr_free(&cr_mask); - cr_free(&cr_result); - cr_free(&cr_sub); - return 0; - fail: - cr_free(&cr_inter); - cr_free(&cr_mask); - cr_free(&cr_result); - cr_free(&cr_sub); - return -1; -} - -typedef enum { - POP_GC, - POP_PROP, - POP_CASE, - POP_UNION, - POP_INTER, - POP_XOR, - POP_INVERT, - POP_END, -} PropOPEnum; - -#define POP_STACK_LEN_MAX 4 - -static int unicode_prop_ops(CharRange *cr, ...) -{ - va_list ap; - CharRange stack[POP_STACK_LEN_MAX]; - int stack_len, op, ret, i; - uint32_t a; - - va_start(ap, cr); - stack_len = 0; - for(;;) { - op = va_arg(ap, int); - switch(op) { - case POP_GC: - assert(stack_len < POP_STACK_LEN_MAX); - a = va_arg(ap, int); - cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); - if (unicode_general_category1(&stack[stack_len - 1], a)) - goto fail; - break; - case POP_PROP: - assert(stack_len < POP_STACK_LEN_MAX); - a = va_arg(ap, int); - cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); - if (unicode_prop1(&stack[stack_len - 1], a)) - goto fail; - break; - case POP_CASE: - assert(stack_len < POP_STACK_LEN_MAX); - a = va_arg(ap, int); - cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); - if (unicode_case1(&stack[stack_len - 1], a)) - goto fail; - break; - case POP_UNION: - case POP_INTER: - case POP_XOR: - { - CharRange *cr1, *cr2, *cr3; - assert(stack_len >= 2); - assert(stack_len < POP_STACK_LEN_MAX); - cr1 = &stack[stack_len - 2]; - cr2 = &stack[stack_len - 1]; - cr3 = &stack[stack_len++]; - cr_init(cr3, cr->mem_opaque, cr->realloc_func); - if (cr_op(cr3, cr1->points, cr1->len, - cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) - goto fail; - cr_free(cr1); - cr_free(cr2); - *cr1 = *cr3; - stack_len -= 2; - } - break; - case POP_INVERT: - assert(stack_len >= 1); - if (cr_invert(&stack[stack_len - 1])) - goto fail; - break; - case POP_END: - goto done; - default: - abort(); - } - } - done: - va_end(ap); - assert(stack_len == 1); - ret = cr_copy(cr, &stack[0]); - cr_free(&stack[0]); - return ret; - fail: - va_end(ap); - for(i = 0; i < stack_len; i++) - cr_free(&stack[i]); - return -1; -} - -static const uint32_t unicode_gc_mask_table[] = { - M(Lu) | M(Ll) | M(Lt), /* LC */ - M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ - M(Mn) | M(Mc) | M(Me), /* M */ - M(Nd) | M(Nl) | M(No), /* N */ - M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ - M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ - M(Zs) | M(Zl) | M(Zp), /* Z */ - M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ -}; - -/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 - if not found */ -int unicode_general_category(CharRange *cr, const char *gc_name) -{ - int gc_idx; - uint32_t gc_mask; - - gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); - if (gc_idx < 0) - return -2; - if (gc_idx <= UNICODE_GC_Co) { - gc_mask = (uint64_t)1 << gc_idx; - } else { - gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; - } - return unicode_general_category1(cr, gc_mask); -} - - -/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 - if not found */ -int unicode_prop(CharRange *cr, const char *prop_name) -{ - int prop_idx, ret; - - prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); - if (prop_idx < 0) - return -2; - prop_idx += UNICODE_PROP_ASCII_Hex_Digit; - - ret = 0; - switch(prop_idx) { - case UNICODE_PROP_ASCII: - if (cr_add_interval(cr, 0x00, 0x7f + 1)) - return -1; - break; - case UNICODE_PROP_Any: - if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) - return -1; - break; - case UNICODE_PROP_Assigned: - ret = unicode_prop_ops(cr, - POP_GC, M(Cn), - POP_INVERT, - POP_END); - break; - case UNICODE_PROP_Math: - ret = unicode_prop_ops(cr, - POP_GC, M(Sm), - POP_PROP, UNICODE_PROP_Other_Math, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Lowercase: - ret = unicode_prop_ops(cr, - POP_GC, M(Ll), - POP_PROP, UNICODE_PROP_Other_Lowercase, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Uppercase: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu), - POP_PROP, UNICODE_PROP_Other_Uppercase, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Cased: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt), - POP_PROP, UNICODE_PROP_Other_Uppercase, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_Lowercase, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Alphabetic: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), - POP_PROP, UNICODE_PROP_Other_Uppercase, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_Lowercase, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_Alphabetic, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_Grapheme_Base: - ret = unicode_prop_ops(cr, - POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), - POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, - POP_UNION, - POP_INVERT, - POP_END); - break; - case UNICODE_PROP_Grapheme_Extend: - ret = unicode_prop_ops(cr, - POP_GC, M(Me) | M(Mn), - POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, - POP_UNION, - POP_END); - break; - case UNICODE_PROP_XID_Start: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), - POP_PROP, UNICODE_PROP_Other_ID_Start, - POP_UNION, - POP_PROP, UNICODE_PROP_Pattern_Syntax, - POP_PROP, UNICODE_PROP_Pattern_White_Space, - POP_UNION, - POP_PROP, UNICODE_PROP_XID_Start1, - POP_UNION, - POP_INVERT, - POP_INTER, - POP_END); - break; - case UNICODE_PROP_XID_Continue: - ret = unicode_prop_ops(cr, - POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | - M(Mn) | M(Mc) | M(Nd) | M(Pc), - POP_PROP, UNICODE_PROP_Other_ID_Start, - POP_UNION, - POP_PROP, UNICODE_PROP_Other_ID_Continue, - POP_UNION, - POP_PROP, UNICODE_PROP_Pattern_Syntax, - POP_PROP, UNICODE_PROP_Pattern_White_Space, - POP_UNION, - POP_PROP, UNICODE_PROP_XID_Continue1, - POP_UNION, - POP_INVERT, - POP_INTER, - POP_END); - break; - case UNICODE_PROP_Changes_When_Uppercased: - ret = unicode_case1(cr, CASE_U); - break; - case UNICODE_PROP_Changes_When_Lowercased: - ret = unicode_case1(cr, CASE_L); - break; - case UNICODE_PROP_Changes_When_Casemapped: - ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); - break; - case UNICODE_PROP_Changes_When_Titlecased: - ret = unicode_prop_ops(cr, - POP_CASE, CASE_U, - POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, - POP_XOR, - POP_END); - break; - case UNICODE_PROP_Changes_When_Casefolded: - ret = unicode_prop_ops(cr, - POP_CASE, CASE_F, - POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, - POP_XOR, - POP_END); - break; - case UNICODE_PROP_Changes_When_NFKC_Casefolded: - ret = unicode_prop_ops(cr, - POP_CASE, CASE_F, - POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, - POP_XOR, - POP_END); - break; - /* we use the existing tables */ - case UNICODE_PROP_ID_Continue: - ret = unicode_prop_ops(cr, - POP_PROP, UNICODE_PROP_ID_Start, - POP_PROP, UNICODE_PROP_ID_Continue1, - POP_XOR, - POP_END); - break; - default: - if (prop_idx >= countof(unicode_prop_table)) - return -2; - ret = unicode_prop1(cr, prop_idx); - break; - } - return ret; -} diff --git a/NativeScript/napi/quickjs/source/libunicode.h b/NativeScript/napi/quickjs/source/libunicode.h deleted file mode 100644 index 8e6f2a01d..000000000 --- a/NativeScript/napi/quickjs/source/libunicode.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Unicode utilities - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIBUNICODE_H -#define LIBUNICODE_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define LRE_CC_RES_LEN_MAX 3 - -typedef enum { - UNICODE_NFC, - UNICODE_NFD, - UNICODE_NFKC, - UNICODE_NFKD, -} UnicodeNormalizationEnum; - -int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); -int lre_canonicalize(uint32_t c, bool is_unicode); -bool lre_is_cased(uint32_t c); -bool lre_is_case_ignorable(uint32_t c); - -/* char ranges */ - -typedef struct { - int len; /* in points, always even */ - int size; - uint32_t *points; /* points sorted by increasing value */ - void *mem_opaque; - void *(*realloc_func)(void *opaque, void *ptr, size_t size); -} CharRange; - -typedef enum { - CR_OP_UNION, - CR_OP_INTER, - CR_OP_XOR, -} CharRangeOpEnum; - -void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); -void cr_free(CharRange *cr); -int cr_realloc(CharRange *cr, int size); -int cr_copy(CharRange *cr, const CharRange *cr1); - -static inline int cr_add_point(CharRange *cr, uint32_t v) -{ - if (cr->len >= cr->size) { - if (cr_realloc(cr, cr->len + 1)) - return -1; - } - cr->points[cr->len++] = v; - return 0; -} - -static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) -{ - if ((cr->len + 2) > cr->size) { - if (cr_realloc(cr, cr->len + 2)) - return -1; - } - cr->points[cr->len++] = c1; - cr->points[cr->len++] = c2; - return 0; -} - -int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); - -static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) -{ - uint32_t b_pt[2]; - b_pt[0] = c1; - b_pt[1] = c2 + 1; - return cr_union1(cr, b_pt, 2); -} - -int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, - const uint32_t *b_pt, int b_len, int op); - -int cr_invert(CharRange *cr); -int cr_regexp_canonicalize(CharRange *cr, bool is_unicode); - -bool lre_is_id_start(uint32_t c); -bool lre_is_id_continue(uint32_t c); -bool lre_is_white_space(uint32_t c); - -int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, - UnicodeNormalizationEnum n_type, - void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); - -/* Unicode character range functions */ - -int unicode_script(CharRange *cr, - const char *script_name, bool is_ext); -int unicode_general_category(CharRange *cr, const char *gc_name); -int unicode_prop(CharRange *cr, const char *prop_name); - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* LIBUNICODE_H */ diff --git a/NativeScript/napi/quickjs/source/list.h b/NativeScript/napi/quickjs/source/list.h deleted file mode 100644 index b8dd71681..000000000 --- a/NativeScript/napi/quickjs/source/list.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Linux klist like system - * - * Copyright (c) 2016-2017 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef LIST_H -#define LIST_H - -#ifndef NULL -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -struct list_head { - struct list_head *prev; - struct list_head *next; -}; - -#define LIST_HEAD_INIT(el) { &(el), &(el) } - -/* return the pointer of type 'type *' containing 'el' as field 'member' */ -#define list_entry(el, type, member) container_of(el, type, member) - -static inline void init_list_head(struct list_head *head) -{ - head->prev = head; - head->next = head; -} - -/* insert 'el' between 'prev' and 'next' */ -static inline void __list_add(struct list_head *el, - struct list_head *prev, struct list_head *next) -{ - prev->next = el; - el->prev = prev; - el->next = next; - next->prev = el; -} - -/* add 'el' at the head of the list 'head' (= after element head) */ -static inline void list_add(struct list_head *el, struct list_head *head) -{ - __list_add(el, head, head->next); -} - -/* add 'el' at the end of the list 'head' (= before element head) */ -static inline void list_add_tail(struct list_head *el, struct list_head *head) -{ - __list_add(el, head->prev, head); -} - -static inline void list_del(struct list_head *el) -{ - struct list_head *prev, *next; - prev = el->prev; - next = el->next; - prev->next = next; - next->prev = prev; - el->prev = NULL; /* fail safe */ - el->next = NULL; /* fail safe */ -} - -static inline int list_empty(struct list_head *el) -{ - return el->next == el; -} - -#define list_for_each(el, head) \ - for(el = (head)->next; el != (head); el = el->next) - -#define list_for_each_safe(el, el1, head) \ - for(el = (head)->next, el1 = el->next; el != (head); \ - el = el1, el1 = el->next) - -#define list_for_each_prev(el, head) \ - for(el = (head)->prev; el != (head); el = el->prev) - -#define list_for_each_prev_safe(el, el1, head) \ - for(el = (head)->prev, el1 = el->prev; el != (head); \ - el = el1, el1 = el->prev) - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* LIST_H */ diff --git a/NativeScript/napi/quickjs/source/qjs.c b/NativeScript/napi/quickjs/source/qjs.c deleted file mode 100644 index fb5d53a46..000000000 --- a/NativeScript/napi/quickjs/source/qjs.c +++ /dev/null @@ -1,742 +0,0 @@ -/* - * QuickJS stand alone interpreter - * - * Copyright (c) 2017-2021 Fabrice Bellard - * Copyright (c) 2017-2021 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#endif -#include -#include -#include - -#include "cutils.h" -#include "quickjs.h" -#include "quickjs-libc.h" - -#ifdef QJS_USE_MIMALLOC -#include -#endif - -extern const uint8_t qjsc_repl[]; -extern const uint32_t qjsc_repl_size; -extern const uint8_t qjsc_standalone[]; -extern const uint32_t qjsc_standalone_size; - -// Must match standalone.js -#define TRAILER_SIZE 12 -static const char trailer_magic[] = "quickjs2"; -static const int trailer_magic_size = sizeof(trailer_magic) - 1; -static const int trailer_size = TRAILER_SIZE; - -static int qjs__argc; -static char **qjs__argv; - - -static bool is_standalone(const char *exe) -{ - FILE *exe_f = fopen(exe, "rb"); - if (!exe_f) - return false; - if (fseek(exe_f, -trailer_size, SEEK_END) < 0) - goto fail; - uint8_t buf[TRAILER_SIZE]; - if (fread(buf, 1, trailer_size, exe_f) != trailer_size) - goto fail; - fclose(exe_f); - return !memcmp(buf, trailer_magic, trailer_magic_size); -fail: - fclose(exe_f); - return false; -} - -static JSValue load_standalone_module(JSContext *ctx) -{ - JSModuleDef *m; - JSValue obj, val; - obj = JS_ReadObject(ctx, qjsc_standalone, qjsc_standalone_size, JS_READ_OBJ_BYTECODE); - if (JS_IsException(obj)) - goto exception; - assert(JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE); - if (JS_ResolveModule(ctx, obj) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - if (js_module_set_import_meta(ctx, obj, false, true) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - val = JS_EvalFunction(ctx, JS_DupValue(ctx, obj)); - val = js_std_await(ctx, val); - - if (JS_IsException(val)) { - JS_FreeValue(ctx, obj); - exception: - js_std_dump_error(ctx); - exit(1); - } - JS_FreeValue(ctx, val); - - m = JS_VALUE_GET_PTR(obj); - JS_FreeValue(ctx, obj); - return JS_GetModuleNamespace(ctx, m); -} - -static int eval_buf(JSContext *ctx, const void *buf, int buf_len, - const char *filename, int eval_flags) -{ - JSValue val; - int ret; - - if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) { - /* for the modules, we compile then run to be able to set - import.meta */ - val = JS_Eval(ctx, buf, buf_len, filename, - eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); - if (!JS_IsException(val)) { - if (js_module_set_import_meta(ctx, val, true, true) < 0) { - js_std_dump_error(ctx); - ret = -1; - goto end; - } - val = JS_EvalFunction(ctx, val); - } - val = js_std_await(ctx, val); - } else { - val = JS_Eval(ctx, buf, buf_len, filename, eval_flags); - } - if (JS_IsException(val)) { - js_std_dump_error(ctx); - ret = -1; - } else { - ret = 0; - } -end: - JS_FreeValue(ctx, val); - return ret; -} - -static int eval_file(JSContext *ctx, const char *filename, int module) -{ - uint8_t *buf; - int ret, eval_flags; - size_t buf_len; - - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - perror(filename); - exit(1); - } - - if (module < 0) { - module = (js__has_suffix(filename, ".mjs") || - JS_DetectModule((const char *)buf, buf_len)); - } - if (module) - eval_flags = JS_EVAL_TYPE_MODULE; - else - eval_flags = JS_EVAL_TYPE_GLOBAL; - ret = eval_buf(ctx, buf, buf_len, filename, eval_flags); - js_free(ctx, buf); - return ret; -} - -static int64_t parse_limit(const char *arg) { - char *p; - unsigned long unit = 1024; /* default to traditional KB */ - double d = strtod(arg, &p); - - if (p == arg) { - fprintf(stderr, "Invalid limit: %s\n", arg); - return -1; - } - - if (*p) { - switch (*p++) { - case 'b': case 'B': unit = 1UL << 0; break; - case 'k': case 'K': unit = 1UL << 10; break; /* IEC kibibytes */ - case 'm': case 'M': unit = 1UL << 20; break; /* IEC mebibytes */ - case 'g': case 'G': unit = 1UL << 30; break; /* IEC gigibytes */ - default: - fprintf(stderr, "Invalid limit: %s, unrecognized suffix, only k,m,g are allowed\n", arg); - return -1; - } - if (*p) { - fprintf(stderr, "Invalid limit: %s, only one suffix allowed\n", arg); - return -1; - } - } - - return (int64_t)(d * unit); -} - -static JSValue js_gc(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JS_RunGC(JS_GetRuntime(ctx)); - return JS_UNDEFINED; -} - -static JSValue js_navigator_get_userAgent(JSContext *ctx, JSValue this_val) -{ - char version[32]; - snprintf(version, sizeof(version), "quickjs-ng/%s", JS_GetVersion()); - return JS_NewString(ctx, version); -} - -static const JSCFunctionListEntry navigator_proto_funcs[] = { - JS_CGETSET_DEF2("userAgent", js_navigator_get_userAgent, NULL, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE), - JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Navigator", JS_PROP_CONFIGURABLE), -}; - -static const JSCFunctionListEntry global_obj[] = { - JS_CFUNC_DEF("gc", 0, js_gc), -}; - -/* also used to initialize the worker context */ -static JSContext *JS_NewCustomContext(JSRuntime *rt) -{ - JSContext *ctx; - ctx = JS_NewContext(rt); - if (!ctx) - return NULL; - /* system modules */ - js_init_module_std(ctx, "qjs:std"); - js_init_module_os(ctx, "qjs:os"); - js_init_module_bjson(ctx, "qjs:bjson"); - - JSValue global = JS_GetGlobalObject(ctx); - JS_SetPropertyFunctionList(ctx, global, global_obj, countof(global_obj)); - JSValue args = JS_NewArray(ctx); - int i; - for(i = 0; i < qjs__argc; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, qjs__argv[i])); - } - JS_SetPropertyStr(ctx, global, "execArgv", args); - JS_SetPropertyStr(ctx, global, "argv0", JS_NewString(ctx, qjs__argv[0])); - JSValue navigator_proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, navigator_proto, navigator_proto_funcs, countof(navigator_proto_funcs)); - JSValue navigator = JS_NewObjectProto(ctx, navigator_proto); - JS_DefinePropertyValueStr(ctx, global, "navigator", navigator, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE); - JS_FreeValue(ctx, global); - JS_FreeValue(ctx, navigator_proto); - - return ctx; -} - -struct trace_malloc_data { - uint8_t *base; -}; - -static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr, - struct trace_malloc_data *dp) -{ - return ptr - dp->base; -} - -static void JS_PRINTF_FORMAT_ATTR(2, 3) js_trace_malloc_printf(void *opaque, JS_PRINTF_FORMAT const char *fmt, ...) -{ - va_list ap; - int c; - - va_start(ap, fmt); - while ((c = *fmt++) != '\0') { - if (c == '%') { - /* only handle %p and %zd */ - if (*fmt == 'p') { - uint8_t *ptr = va_arg(ap, void *); - if (ptr == NULL) { - printf("NULL"); - } else { - printf("H%+06lld.%zd", - js_trace_malloc_ptr_offset(ptr, opaque), - js__malloc_usable_size(ptr)); - } - fmt++; - continue; - } - if (fmt[0] == 'z' && fmt[1] == 'd') { - size_t sz = va_arg(ap, size_t); - printf("%zd", sz); - fmt += 2; - continue; - } - } - putc(c, stdout); - } - va_end(ap); -} - -static void js_trace_malloc_init(struct trace_malloc_data *s) -{ - free(s->base = malloc(8)); -} - -static void *js_trace_calloc(void *opaque, size_t count, size_t size) -{ - void *ptr; - ptr = calloc(count, size); - js_trace_malloc_printf(opaque, "C %zd %zd -> %p\n", count, size, ptr); - return ptr; -} - -static void *js_trace_malloc(void *opaque, size_t size) -{ - void *ptr; - ptr = malloc(size); - js_trace_malloc_printf(opaque, "A %zd -> %p\n", size, ptr); - return ptr; -} - -static void js_trace_free(void *opaque, void *ptr) -{ - if (!ptr) - return; - js_trace_malloc_printf(opaque, "F %p\n", ptr); - free(ptr); -} - -static void *js_trace_realloc(void *opaque, void *ptr, size_t size) -{ - js_trace_malloc_printf(opaque, "R %zd %p", size, ptr); - ptr = realloc(ptr, size); - js_trace_malloc_printf(opaque, " -> %p\n", ptr); - return ptr; -} - -static const JSMallocFunctions trace_mf = { - js_trace_calloc, - js_trace_malloc, - js_trace_free, - js_trace_realloc, - js__malloc_usable_size -}; - -#ifdef QJS_USE_MIMALLOC -static void *js_mi_calloc(void *opaque, size_t count, size_t size) -{ - return mi_calloc(count, size); -} - -static void *js_mi_malloc(void *opaque, size_t size) -{ - return mi_malloc(size); -} - -static void js_mi_free(void *opaque, void *ptr) -{ - if (!ptr) - return; - mi_free(ptr); -} - -static void *js_mi_realloc(void *opaque, void *ptr, size_t size) -{ - return mi_realloc(ptr, size); -} - -static const JSMallocFunctions mi_mf = { - js_mi_calloc, - js_mi_malloc, - js_mi_free, - js_mi_realloc, - mi_malloc_usable_size -}; -#endif - -#define PROG_NAME "qjs" - -void help(void) -{ - printf("QuickJS-ng version %s\n" - "usage: " PROG_NAME " [options] [file [args]]\n" - "-h --help list options\n" - "-e --eval EXPR evaluate EXPR\n" - "-i --interactive go to interactive mode\n" - "-m --module load as ES6 module (default=autodetect)\n" - " --script load as ES6 script (default=autodetect)\n" - "-I --include file include an additional file\n" - " --std make 'std', 'os' and 'bjson' available to script\n" - "-T --trace trace memory allocation\n" - "-d --dump dump the memory usage stats\n" - "-D --dump-flags flags for dumping debug data (see DUMP_* defines)\n" - "-c --compile FILE compile the given JS file as a standalone executable\n" - "-o --out FILE output file for standalone executables\n" - " --exe select the executable to use as the base, defaults to the current one\n" - " --memory-limit n limit the memory usage to 'n' Kbytes\n" - " --stack-size n limit the stack size to 'n' Kbytes\n" - "-q --quit just instantiate the interpreter and quit\n", JS_GetVersion()); - exit(1); -} - -int main(int argc, char **argv) -{ - JSRuntime *rt; - JSContext *ctx; - JSValue ret = JS_UNDEFINED; - struct trace_malloc_data trace_data = { NULL }; - int r = 0; - int optind = 1; - char *compile_file = NULL; - char *exe = NULL; - char *expr = NULL; - char *dump_flags_str = NULL; - char *out = NULL; - int standalone = 0; - int interactive = 0; - int dump_memory = 0; - int dump_flags = 0; - int trace_memory = 0; - int empty_run = 0; - int module = -1; - int load_std = 0; - char *include_list[32]; - int i, include_count = 0; - int64_t memory_limit = -1; - int64_t stack_size = -1; - - /* save for later */ - qjs__argc = argc; - qjs__argv = argv; - - if (is_standalone(argv[0])) { - standalone = 1; - goto start; - } - - dump_flags_str = getenv("QJS_DUMP_FLAGS"); - dump_flags = dump_flags_str ? strtol(dump_flags_str, NULL, 16) : 0; - - /* cannot use getopt because we want to pass the command line to - the script */ - while (optind < argc && *argv[optind] == '-') { - char *arg = argv[optind] + 1; - const char *longopt = ""; - char *opt_arg = NULL; - /* a single - is not an option, it also stops argument scanning */ - if (!*arg) - break; - optind++; - if (*arg == '-') { - longopt = arg + 1; - opt_arg = strchr(longopt, '='); - if (opt_arg) - *opt_arg++ = '\0'; - arg += strlen(arg); - /* -- stops argument scanning */ - if (!*longopt) - break; - } - for (; *arg || *longopt; longopt = "") { - char opt = *arg; - if (opt) { - arg++; - if (!opt_arg && *arg) - opt_arg = arg; - } - if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { - help(); - continue; - } - if (opt == 'e' || !strcmp(longopt, "eval")) { - if (!opt_arg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing expression for -e\n"); - exit(1); - } - opt_arg = argv[optind++]; - } - expr = opt_arg; - break; - } - if (opt == 'I' || !strcmp(longopt, "include")) { - if (optind >= argc) { - fprintf(stderr, "expecting filename"); - exit(1); - } - if (include_count >= countof(include_list)) { - fprintf(stderr, "too many included files"); - exit(1); - } - include_list[include_count++] = argv[optind++]; - continue; - } - if (opt == 'i' || !strcmp(longopt, "interactive")) { - interactive++; - continue; - } - if (opt == 'm' || !strcmp(longopt, "module")) { - module = 1; - continue; - } - if (!strcmp(longopt, "script")) { - module = 0; - continue; - } - if (opt == 'd' || !strcmp(longopt, "dump")) { - dump_memory++; - continue; - } - if (opt == 'D' || !strcmp(longopt, "dump-flags")) { - dump_flags = opt_arg ? strtol(opt_arg, NULL, 16) : 0; - break; - } - if (opt == 'T' || !strcmp(longopt, "trace")) { - trace_memory++; - continue; - } - if (!strcmp(longopt, "std")) { - load_std = 1; - continue; - } - if (opt == 'q' || !strcmp(longopt, "quit")) { - empty_run++; - continue; - } - if (!strcmp(longopt, "memory-limit")) { - if (!opt_arg) { - if (optind >= argc) { - fprintf(stderr, "expecting memory limit"); - exit(1); - } - opt_arg = argv[optind++]; - } - memory_limit = parse_limit(opt_arg); - break; - } - if (!strcmp(longopt, "stack-size")) { - if (!opt_arg) { - if (optind >= argc) { - fprintf(stderr, "expecting stack size"); - exit(1); - } - opt_arg = argv[optind++]; - } - stack_size = parse_limit(opt_arg); - break; - } - if (opt == 'c' || !strcmp(longopt, "compile")) { - if (!opt_arg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing file for -c\n"); - exit(1); - } - opt_arg = argv[optind++]; - } - compile_file = opt_arg; - break; - } - if (opt == 'o' || !strcmp(longopt, "out")) { - if (!opt_arg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing file for -o\n"); - exit(1); - } - opt_arg = argv[optind++]; - } - out = opt_arg; - break; - } - if (!strcmp(longopt, "exe")) { - if (!opt_arg) { - if (optind >= argc) { - fprintf(stderr, "qjs: missing file for --exe\n"); - exit(1); - } - opt_arg = argv[optind++]; - } - exe = opt_arg; - break; - } - if (opt) { - fprintf(stderr, "qjs: unknown option '-%c'\n", opt); - } else { - fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); - } - help(); - } - } - - if (compile_file && !out) - help(); - -start: - - if (trace_memory) { - js_trace_malloc_init(&trace_data); - rt = JS_NewRuntime2(&trace_mf, &trace_data); - } else { -#ifdef QJS_USE_MIMALLOC - rt = JS_NewRuntime2(&mi_mf, NULL); -#else - rt = JS_NewRuntime(); -#endif - } - if (!rt) { - fprintf(stderr, "qjs: cannot allocate JS runtime\n"); - exit(2); - } - if (memory_limit >= 0) - JS_SetMemoryLimit(rt, (size_t)memory_limit); - if (stack_size >= 0) - JS_SetMaxStackSize(rt, (size_t)stack_size); - if (dump_flags != 0) - JS_SetDumpFlags(rt, dump_flags); - js_std_set_worker_new_context_func(JS_NewCustomContext); - js_std_init_handlers(rt); - ctx = JS_NewCustomContext(rt); - if (!ctx) { - fprintf(stderr, "qjs: cannot allocate JS context\n"); - exit(2); - } - - /* loader for ES6 modules */ - JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); - - /* exit on unhandled promise rejections */ - JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker, NULL); - - if (!empty_run) { - js_std_add_helpers(ctx, argc - optind, argv + optind); - - /* make 'std' and 'os' visible to non module code */ - if (load_std) { - const char *str = - "import * as bjson from 'qjs:bjson';\n" - "import * as std from 'qjs:std';\n" - "import * as os from 'qjs:os';\n" - "globalThis.bjson = bjson;\n" - "globalThis.std = std;\n" - "globalThis.os = os;\n"; - eval_buf(ctx, str, strlen(str), "", JS_EVAL_TYPE_MODULE); - } - - for(i = 0; i < include_count; i++) { - if (eval_file(ctx, include_list[i], 0)) - goto fail; - } - - if (standalone) { - JSValue ns = load_standalone_module(ctx); - if (JS_IsException(ns)) - goto fail; - JSValue func = JS_GetPropertyStr(ctx, ns, "runStandalone"); - JS_FreeValue(ctx, ns); - if (JS_IsException(func)) - goto fail; - ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); - JS_FreeValue(ctx, func); - } else if (compile_file) { - JSValue ns = load_standalone_module(ctx); - if (JS_IsException(ns)) - goto fail; - JSValue func = JS_GetPropertyStr(ctx, ns, "compileStandalone"); - JS_FreeValue(ctx, ns); - if (JS_IsException(func)) - goto fail; - JSValue args[3]; - args[0] = JS_NewString(ctx, compile_file); - args[1] = JS_NewString(ctx, out); - args[2] = JS_NewString(ctx, exe != NULL ? exe : argv[0]); - ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); - JS_FreeValue(ctx, func); - JS_FreeValue(ctx, args[0]); - JS_FreeValue(ctx, args[1]); - JS_FreeValue(ctx, args[2]); - } else if (expr) { - if (eval_buf(ctx, expr, strlen(expr), "", 0)) - goto fail; - } else if (optind >= argc) { - /* interactive mode */ - interactive = 1; - } else { - const char *filename; - filename = argv[optind]; - if (eval_file(ctx, filename, module)) - goto fail; - } - if (interactive) { - JS_SetHostPromiseRejectionTracker(rt, NULL, NULL); - js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); - } - if (standalone || compile_file) { - if (JS_IsException(ret)) { - r = 1; - } else { - JS_FreeValue(ctx, ret); - r = js_std_loop(ctx); - } - } else { - r = js_std_loop(ctx); - } - if (r) { - js_std_dump_error(ctx); - goto fail; - } - } - - if (dump_memory) { - JSMemoryUsage stats; - JS_ComputeMemoryUsage(rt, &stats); - JS_DumpMemoryUsage(stdout, &stats, rt); - } - js_std_free_handlers(rt); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - - if (empty_run && dump_memory) { - clock_t t[5]; - double best[5] = {0}; - int i, j; - for (i = 0; i < 100; i++) { - t[0] = clock(); - rt = JS_NewRuntime(); - t[1] = clock(); - ctx = JS_NewContext(rt); - t[2] = clock(); - JS_FreeContext(ctx); - t[3] = clock(); - JS_FreeRuntime(rt); - t[4] = clock(); - for (j = 4; j > 0; j--) { - double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC; - if (i == 0 || best[j] > ms) - best[j] = ms; - } - } - printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n", - best[1] + best[2] + best[3] + best[4], - best[1], best[2], best[3], best[4]); - } - return 0; - fail: - js_std_free_handlers(rt); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - return 1; -} diff --git a/NativeScript/napi/quickjs/source/qjsc.c b/NativeScript/napi/quickjs/source/qjsc.c deleted file mode 100644 index 6e12b341d..000000000 --- a/NativeScript/napi/quickjs/source/qjsc.c +++ /dev/null @@ -1,572 +0,0 @@ -/* - * QuickJS command line compiler - * - * Copyright (c) 2018-2024 Fabrice Bellard - * Copyright (c) 2023-2025 Ben Noordhuis - * Copyright (c) 2023-2025 Saúl Ibarra Corretgé - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#if defined(_MSC_VER) -#include "getopt_compat.h" -#else -#include -#endif -#include - -#include "cutils.h" -#include "quickjs-libc.h" - -typedef enum { - OUTPUT_C, - OUTPUT_C_MAIN, - OUTPUT_RAW, -} OutputTypeEnum; - -typedef struct { - char *name; - char *short_name; - int flags; -} namelist_entry_t; - -typedef struct namelist_t { - namelist_entry_t *array; - int count; - int size; -} namelist_t; - -static namelist_t cname_list; -static namelist_t cmodule_list; -static namelist_t init_module_list; -static OutputTypeEnum output_type; -static FILE *outfile; -static const char *c_ident_prefix = "qjsc_"; -static int strip; - -void namelist_add(namelist_t *lp, const char *name, const char *short_name, - int flags) -{ - namelist_entry_t *e; - if (lp->count == lp->size) { - size_t newsize = lp->size + (lp->size >> 1) + 4; - namelist_entry_t *a = - realloc(lp->array, sizeof(lp->array[0]) * newsize); - /* XXX: check for realloc failure */ - lp->array = a; - lp->size = newsize; - } - e = &lp->array[lp->count++]; - e->name = strdup(name); - if (short_name) - e->short_name = strdup(short_name); - else - e->short_name = NULL; - e->flags = flags; -} - -void namelist_free(namelist_t *lp) -{ - while (lp->count > 0) { - namelist_entry_t *e = &lp->array[--lp->count]; - free(e->name); - free(e->short_name); - } - free(lp->array); - lp->array = NULL; - lp->size = 0; -} - -namelist_entry_t *namelist_find(namelist_t *lp, const char *name) -{ - int i; - for(i = 0; i < lp->count; i++) { - namelist_entry_t *e = &lp->array[i]; - if (!strcmp(e->name, name)) - return e; - } - return NULL; -} - -static void get_c_name(char *buf, size_t buf_size, const char *file) -{ - const char *p, *r; - size_t len, i; - int c; - char *q; - - p = strrchr(file, '/'); - if (!p) - p = file; - else - p++; - r = strrchr(p, '.'); - if (!r) - len = strlen(p); - else - len = r - p; - js__pstrcpy(buf, buf_size, c_ident_prefix); - q = buf + strlen(buf); - for(i = 0; i < len; i++) { - c = p[i]; - if (!((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z'))) { - c = '_'; - } - if ((q - buf) < buf_size - 1) - *q++ = c; - } - *q = '\0'; -} - -static void dump_hex(FILE *f, const uint8_t *buf, size_t len) -{ - size_t i, col; - col = 0; - for(i = 0; i < len; i++) { - fprintf(f, " 0x%02x,", buf[i]); - if (++col == 8) { - fprintf(f, "\n"); - col = 0; - } - } - if (col != 0) - fprintf(f, "\n"); -} - -static void output_object_code(JSContext *ctx, - FILE *fo, JSValue obj, const char *c_name, - bool load_only) -{ - uint8_t *out_buf; - size_t out_buf_len; - int flags = JS_WRITE_OBJ_BYTECODE; - - if (strip) { - flags |= JS_WRITE_OBJ_STRIP_SOURCE; - if (strip > 1) - flags |= JS_WRITE_OBJ_STRIP_DEBUG; - } - - out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags); - if (!out_buf) { - js_std_dump_error(ctx); - exit(1); - } - - namelist_add(&cname_list, c_name, NULL, load_only); - - if (output_type == OUTPUT_RAW) { - fwrite(out_buf, 1, out_buf_len, fo); - } else { - fprintf(fo, "const uint32_t %s_size = %u;\n\n", - c_name, (unsigned int)out_buf_len); - fprintf(fo, "const uint8_t %s[%u] = {\n", - c_name, (unsigned int)out_buf_len); - dump_hex(fo, out_buf, out_buf_len); - fprintf(fo, "};\n\n"); - } - - js_free(ctx, out_buf); -} - -static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m) -{ - /* should never be called when compiling JS code */ - abort(); - return -1; // pacify compiler -} - -static void find_unique_cname(char *cname, size_t cname_size) -{ - char cname1[1024]; - int suffix_num; - size_t len, max_len; - assert(cname_size >= 32); - /* find a C name not matching an existing module C name by - adding a numeric suffix */ - len = strlen(cname); - max_len = cname_size - 16; - if (len > max_len) - cname[max_len] = '\0'; - suffix_num = 1; - for(;;) { - snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num); - if (!namelist_find(&cname_list, cname1)) - break; - suffix_num++; - } - js__pstrcpy(cname, cname_size, cname1); -} - -JSModuleDef *jsc_module_loader(JSContext *ctx, - const char *module_name, void *opaque) -{ - JSModuleDef *m; - namelist_entry_t *e; - - /* check if it is a declared C or system module */ - e = namelist_find(&cmodule_list, module_name); - if (e) { - /* add in the static init module list */ - namelist_add(&init_module_list, e->name, e->short_name, 0); - /* create a dummy module */ - m = JS_NewCModule(ctx, module_name, js_module_dummy_init); - } else if (js__has_suffix(module_name, ".so")) { - JS_ThrowReferenceError(ctx, "%s: dynamically linking to shared libraries not supported", - module_name); - return NULL; - } else { - size_t buf_len; - uint8_t *buf; - JSValue func_val; - char cname[1024]; - - buf = js_load_file(ctx, &buf_len, module_name); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s'", - module_name); - return NULL; - } - - /* compile the module */ - func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - js_free(ctx, buf); - if (JS_IsException(func_val)) - return NULL; - get_c_name(cname, sizeof(cname), module_name); - if (namelist_find(&cname_list, cname)) { - find_unique_cname(cname, sizeof(cname)); - } - output_object_code(ctx, outfile, func_val, cname, true); - - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); - } - return m; -} - -static void compile_file(JSContext *ctx, FILE *fo, - const char *filename, - const char *script_name, - const char *c_name1, - int module) -{ - uint8_t *buf; - char c_name[1024]; - int eval_flags; - JSValue obj; - size_t buf_len; - - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - fprintf(stderr, "Could not load '%s'\n", filename); - exit(1); - } - eval_flags = JS_EVAL_FLAG_COMPILE_ONLY; - if (module < 0) { - module = (js__has_suffix(filename, ".mjs") || - JS_DetectModule((const char *)buf, buf_len)); - } - if (module) - eval_flags |= JS_EVAL_TYPE_MODULE; - else - eval_flags |= JS_EVAL_TYPE_GLOBAL; - obj = JS_Eval(ctx, (const char *)buf, buf_len, script_name ? script_name : filename, eval_flags); - if (JS_IsException(obj)) { - js_std_dump_error(ctx); - exit(1); - } - js_free(ctx, buf); - if (c_name1) { - js__pstrcpy(c_name, sizeof(c_name), c_name1); - } else { - get_c_name(c_name, sizeof(c_name), filename); - } - output_object_code(ctx, fo, obj, c_name, false); - JS_FreeValue(ctx, obj); -} - -static const char main_c_template1[] = - "int main(int argc, char **argv)\n" - "{\n" - " int r;\n" - " JSRuntime *rt;\n" - " JSContext *ctx;\n" - " r = 0;\n" - " rt = JS_NewRuntime();\n" - " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" - " js_std_init_handlers(rt);\n" - ; - -static const char main_c_template2[] = - " r = js_std_loop(ctx);\n" - " if (r) {\n" - " js_std_dump_error(ctx);\n" - " }\n" - " js_std_free_handlers(rt);\n" - " JS_FreeContext(ctx);\n" - " JS_FreeRuntime(rt);\n" - " return r;\n" - "}\n"; - -#define PROG_NAME "qjsc" - -void help(void) -{ - printf("QuickJS-ng Compiler version %s\n" - "usage: " PROG_NAME " [options] [files]\n" - "\n" - "options are:\n" - "-b output raw bytecode instead of C code\n" - "-e output main() and bytecode in a C file\n" - "-o output set the output filename\n" - "-n script_name set the script name (as used in stack traces)\n" - "-N cname set the C name of the generated data\n" - "-m compile as Javascript module (default=autodetect)\n" - "-D module_name compile a dynamically loaded module or worker\n" - "-M module_name[,cname] add initialization code for an external C module\n" - "-p prefix set the prefix of the generated C names\n" - "-s strip the source code, specify twice to also strip debug info\n" - "-S n set the maximum stack size to 'n' bytes (default=%d)\n", - JS_GetVersion(), - JS_DEFAULT_STACK_SIZE); - exit(1); -} - -int main(int argc, char **argv) -{ - int c, i, verbose; - const char *out_filename, *cname, *script_name; - char cfilename[1024]; - FILE *fo; - JSRuntime *rt; - JSContext *ctx; - int module; - size_t stack_size; - namelist_t dynamic_module_list; - - out_filename = NULL; - script_name = NULL; - output_type = OUTPUT_C; - cname = NULL; - module = -1; - verbose = 0; - strip = 0; - stack_size = 0; - memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); - - - /* add system modules */ - namelist_add(&cmodule_list, "qjs:std", "std", 0); - namelist_add(&cmodule_list, "qjs:os", "os", 0); - namelist_add(&cmodule_list, "qjs:bjson", "bjson", 0); - namelist_add(&cmodule_list, "std", "std", 0); - namelist_add(&cmodule_list, "os", "os", 0); - namelist_add(&cmodule_list, "bjson", "bjson", 0); - - for(;;) { - c = getopt(argc, argv, "ho:N:mn:bxesvM:p:S:D:"); - if (c == -1) - break; - switch(c) { - case 'h': - help(); - case 'b': - output_type = OUTPUT_RAW; - break; - case 'o': - out_filename = optarg; - break; - case 'e': - output_type = OUTPUT_C_MAIN; - break; - case 'n': - script_name = optarg; - break; - case 'N': - cname = optarg; - break; - case 'm': - module = 1; - break; - case 'M': - { - char *p; - char path[1024]; - char cname[1024]; - js__pstrcpy(path, sizeof(path), optarg); - p = strchr(path, ','); - if (p) { - *p = '\0'; - js__pstrcpy(cname, sizeof(cname), p + 1); - } else { - get_c_name(cname, sizeof(cname), path); - } - namelist_add(&cmodule_list, path, cname, 0); - } - break; - case 'D': - namelist_add(&dynamic_module_list, optarg, NULL, 0); - break; - case 's': - strip++; - break; - case 'v': - verbose++; - break; - case 'p': - c_ident_prefix = optarg; - break; - case 'S': - stack_size = (size_t)strtod(optarg, NULL); - break; - default: - break; - } - } - - if (optind >= argc) - help(); - - if (!out_filename) - out_filename = "out.c"; - - js__pstrcpy(cfilename, sizeof(cfilename), out_filename); - - if (output_type == OUTPUT_RAW) - fo = fopen(cfilename, "wb"); - else - fo = fopen(cfilename, "w"); - - if (!fo) { - perror(cfilename); - exit(1); - } - outfile = fo; - - rt = JS_NewRuntime(); - ctx = JS_NewContext(rt); - - /* loader for ES6 modules */ - JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL); - - if (output_type != OUTPUT_RAW) { - fprintf(fo, "/* File generated automatically by the QuickJS-ng compiler. */\n" - "\n" - ); - } - - if (output_type == OUTPUT_C_MAIN) { - fprintf(fo, "#include \"quickjs-libc.h\"\n" - "\n" - ); - } else if (output_type == OUTPUT_C) { - fprintf(fo, "#include \n" - "\n" - ); - } - - for(i = optind; i < argc; i++) { - const char *filename = argv[i]; - compile_file(ctx, fo, filename, script_name, cname, module); - cname = NULL; - } - - for(i = 0; i < dynamic_module_list.count; i++) { - if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL)) { - fprintf(stderr, "Could not load dynamic module '%s'\n", - dynamic_module_list.array[i].name); - exit(1); - } - } - - if (output_type == OUTPUT_C_MAIN) { - fprintf(fo, - "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" - "{\n" - " JSContext *ctx = JS_NewContext(rt);\n" - " if (!ctx)\n" - " return NULL;\n"); - /* add the precompiled modules (XXX: could modify the module - loader instead) */ - for(i = 0; i < init_module_list.count; i++) { - namelist_entry_t *e = &init_module_list.array[i]; - /* initialize the static C modules */ - - fprintf(fo, - " {\n" - " extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n" - " js_init_module_%s(ctx, \"%s\");\n" - " }\n", - e->short_name, e->short_name, e->name); - } - for(i = 0; i < cname_list.count; i++) { - namelist_entry_t *e = &cname_list.array[i]; - if (e->flags) { - fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", - e->name, e->name); - } - } - fprintf(fo, - " return ctx;\n" - "}\n\n"); - - fputs(main_c_template1, fo); - - if (stack_size != 0) { - fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", - (unsigned int)stack_size); - } - - /* add the module loader */ - fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); - - fprintf(fo, - " ctx = JS_NewCustomContext(rt);\n" - " js_std_add_helpers(ctx, argc, argv);\n"); - - for(i = 0; i < cname_list.count; i++) { - namelist_entry_t *e = &cname_list.array[i]; - if (!e->flags) { - fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", - e->name, e->name); - } - } - fputs(main_c_template2, fo); - } - - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - - fclose(fo); - - namelist_free(&cname_list); - namelist_free(&cmodule_list); - namelist_free(&init_module_list); - return 0; -} diff --git a/NativeScript/napi/quickjs/source/quickjs-atom.h b/NativeScript/napi/quickjs/source/quickjs-atom.h deleted file mode 100644 index 358fe2306..000000000 --- a/NativeScript/napi/quickjs/source/quickjs-atom.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * QuickJS atom definitions - * - * Copyright (c) 2017-2018 Fabrice Bellard - * Copyright (c) 2017-2018 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifdef DEF - -/* Note: first atoms are considered as keywords in the parser */ -DEF(null, "null") /* must be first */ -DEF(false, "false") -DEF(true, "true") -DEF(if, "if") -DEF(else, "else") -DEF(return, "return") -DEF(var, "var") -DEF(this, "this") -DEF(delete, "delete") -DEF(void, "void") -DEF(typeof, "typeof") -DEF(new, "new") -DEF(in, "in") -DEF(instanceof, "instanceof") -DEF(do, "do") -DEF(while, "while") -DEF(for, "for") -DEF(break, "break") -DEF(continue, "continue") -DEF(switch, "switch") -DEF(case, "case") -DEF(default, "default") -DEF(throw, "throw") -DEF(try, "try") -DEF(catch, "catch") -DEF(finally, "finally") -DEF(function, "function") -DEF(debugger, "debugger") -DEF(with, "with") -/* FutureReservedWord */ -DEF(class, "class") -DEF(const, "const") -DEF(enum, "enum") -DEF(export, "export") -DEF(extends, "extends") -DEF(import, "import") -DEF(super, "super") -/* FutureReservedWords when parsing strict mode code */ -DEF(implements, "implements") -DEF(interface, "interface") -DEF(let, "let") -DEF(package, "package") -DEF(private, "private") -DEF(protected, "protected") -DEF(public, "public") -DEF(static, "static") -DEF(yield, "yield") -DEF(await, "await") - -/* empty string */ -DEF(empty_string, "") -/* identifiers */ -DEF(keys, "keys") -DEF(size, "size") -DEF(length, "length") -DEF(message, "message") -DEF(cause, "cause") -DEF(errors, "errors") -DEF(stack, "stack") -DEF(name, "name") -DEF(toString, "toString") -DEF(toLocaleString, "toLocaleString") -DEF(valueOf, "valueOf") -DEF(eval, "eval") -DEF(prototype, "prototype") -DEF(constructor, "constructor") -DEF(configurable, "configurable") -DEF(writable, "writable") -DEF(enumerable, "enumerable") -DEF(value, "value") -DEF(get, "get") -DEF(set, "set") -DEF(of, "of") -DEF(__proto__, "__proto__") -DEF(undefined, "undefined") -DEF(number, "number") -DEF(boolean, "boolean") -DEF(string, "string") -DEF(object, "object") -DEF(symbol, "symbol") -DEF(integer, "integer") -DEF(unknown, "unknown") -DEF(arguments, "arguments") -DEF(callee, "callee") -DEF(caller, "caller") -DEF(_eval_, "") -DEF(_ret_, "") -DEF(_var_, "") -DEF(_arg_var_, "") -DEF(_with_, "") -DEF(lastIndex, "lastIndex") -DEF(target, "target") -DEF(index, "index") -DEF(input, "input") -DEF(defineProperties, "defineProperties") -DEF(apply, "apply") -DEF(join, "join") -DEF(concat, "concat") -DEF(split, "split") -DEF(construct, "construct") -DEF(getPrototypeOf, "getPrototypeOf") -DEF(setPrototypeOf, "setPrototypeOf") -DEF(isExtensible, "isExtensible") -DEF(preventExtensions, "preventExtensions") -DEF(has, "has") -DEF(deleteProperty, "deleteProperty") -DEF(defineProperty, "defineProperty") -DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") -DEF(ownKeys, "ownKeys") -DEF(add, "add") -DEF(done, "done") -DEF(next, "next") -DEF(values, "values") -DEF(source, "source") -DEF(flags, "flags") -DEF(global, "global") -DEF(unicode, "unicode") -DEF(raw, "raw") -DEF(new_target, "new.target") -DEF(this_active_func, "this.active_func") -DEF(home_object, "") -DEF(computed_field, "") -DEF(static_computed_field, "") /* must come after computed_fields */ -DEF(class_fields_init, "") -DEF(brand, "") -DEF(hash_constructor, "#constructor") -DEF(as, "as") -DEF(from, "from") -DEF(meta, "meta") -DEF(_default_, "*default*") -DEF(_star_, "*") -DEF(Module, "Module") -DEF(then, "then") -DEF(resolve, "resolve") -DEF(reject, "reject") -DEF(promise, "promise") -DEF(proxy, "proxy") -DEF(revoke, "revoke") -DEF(async, "async") -DEF(exec, "exec") -DEF(groups, "groups") -DEF(indices, "indices") -DEF(status, "status") -DEF(reason, "reason") -DEF(globalThis, "globalThis") -DEF(bigint, "bigint") -DEF(not_equal, "not-equal") -DEF(timed_out, "timed-out") -DEF(ok, "ok") -DEF(toJSON, "toJSON") -DEF(maxByteLength, "maxByteLength") -/* class names */ -DEF(Object, "Object") -DEF(Array, "Array") -DEF(Error, "Error") -DEF(Number, "Number") -DEF(String, "String") -DEF(Boolean, "Boolean") -DEF(Symbol, "Symbol") -DEF(Arguments, "Arguments") -DEF(Math, "Math") -DEF(JSON, "JSON") -DEF(Date, "Date") -DEF(Function, "Function") -DEF(GeneratorFunction, "GeneratorFunction") -DEF(ForInIterator, "ForInIterator") -DEF(RegExp, "RegExp") -DEF(ArrayBuffer, "ArrayBuffer") -DEF(SharedArrayBuffer, "SharedArrayBuffer") -/* must keep same order as class IDs for typed arrays */ -DEF(Uint8ClampedArray, "Uint8ClampedArray") -DEF(Int8Array, "Int8Array") -DEF(Uint8Array, "Uint8Array") -DEF(Int16Array, "Int16Array") -DEF(Uint16Array, "Uint16Array") -DEF(Int32Array, "Int32Array") -DEF(Uint32Array, "Uint32Array") -DEF(BigInt64Array, "BigInt64Array") -DEF(BigUint64Array, "BigUint64Array") -DEF(Float16Array, "Float16Array") -DEF(Float32Array, "Float32Array") -DEF(Float64Array, "Float64Array") -DEF(DataView, "DataView") -DEF(BigInt, "BigInt") -DEF(WeakRef, "WeakRef") -DEF(FinalizationRegistry, "FinalizationRegistry") -DEF(Map, "Map") -DEF(Set, "Set") /* Map + 1 */ -DEF(WeakMap, "WeakMap") /* Map + 2 */ -DEF(WeakSet, "WeakSet") /* Map + 3 */ -DEF(Iterator, "Iterator") -DEF(IteratorHelper, "Iterator Helper") -DEF(IteratorWrap, "Iterator Wrap") -DEF(Map_Iterator, "Map Iterator") -DEF(Set_Iterator, "Set Iterator") -DEF(Array_Iterator, "Array Iterator") -DEF(String_Iterator, "String Iterator") -DEF(RegExp_String_Iterator, "RegExp String Iterator") -DEF(Generator, "Generator") -DEF(Proxy, "Proxy") -DEF(Promise, "Promise") -DEF(PromiseResolveFunction, "PromiseResolveFunction") -DEF(PromiseRejectFunction, "PromiseRejectFunction") -DEF(AsyncFunction, "AsyncFunction") -DEF(AsyncFunctionResolve, "AsyncFunctionResolve") -DEF(AsyncFunctionReject, "AsyncFunctionReject") -DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") -DEF(AsyncGenerator, "AsyncGenerator") -DEF(EvalError, "EvalError") -DEF(RangeError, "RangeError") -DEF(ReferenceError, "ReferenceError") -DEF(SyntaxError, "SyntaxError") -DEF(TypeError, "TypeError") -DEF(URIError, "URIError") -DEF(InternalError, "InternalError") -DEF(CallSite, "CallSite") -/* private symbols */ -DEF(Private_brand, "") -/* symbols */ -DEF(Symbol_toPrimitive, "Symbol.toPrimitive") -DEF(Symbol_iterator, "Symbol.iterator") -DEF(Symbol_match, "Symbol.match") -DEF(Symbol_matchAll, "Symbol.matchAll") -DEF(Symbol_replace, "Symbol.replace") -DEF(Symbol_search, "Symbol.search") -DEF(Symbol_split, "Symbol.split") -DEF(Symbol_toStringTag, "Symbol.toStringTag") -DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") -DEF(Symbol_hasInstance, "Symbol.hasInstance") -DEF(Symbol_species, "Symbol.species") -DEF(Symbol_unscopables, "Symbol.unscopables") -DEF(Symbol_asyncIterator, "Symbol.asyncIterator") - -#endif /* DEF */ diff --git a/NativeScript/napi/quickjs/source/quickjs-c-atomics.h b/NativeScript/napi/quickjs/source/quickjs-c-atomics.h deleted file mode 100644 index 8fc6b7203..000000000 --- a/NativeScript/napi/quickjs/source/quickjs-c-atomics.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * QuickJS C atomics definitions - * - * Copyright (c) 2023 Marcin Kolny - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) - // Use GCC builtins for version < 4.9 -# if((__GNUC__ << 16) + __GNUC_MINOR__ < ((4) << 16) + 9) -# define GCC_BUILTIN_ATOMICS -# endif -#endif - -#ifdef GCC_BUILTIN_ATOMICS -#define atomic_fetch_add(obj, arg) \ - __atomic_fetch_add(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_compare_exchange_strong(obj, expected, desired) \ - __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) -#define atomic_exchange(obj, desired) \ - __atomic_exchange_n (obj, desired, __ATOMIC_SEQ_CST) -#define atomic_load(obj) \ - __atomic_load_n(obj, __ATOMIC_SEQ_CST) -#define atomic_store(obj, desired) \ - __atomic_store_n(obj, desired, __ATOMIC_SEQ_CST) -#define atomic_fetch_or(obj, arg) \ - __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_fetch_xor(obj, arg) \ - __atomic_fetch_xor(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_fetch_and(obj, arg) \ - __atomic_fetch_and(obj, arg, __ATOMIC_SEQ_CST) -#define atomic_fetch_sub(obj, arg) \ - __atomic_fetch_sub(obj, arg, __ATOMIC_SEQ_CST) -#define _Atomic -#else -#include -#endif diff --git a/NativeScript/napi/quickjs/source/quickjs-libc.c b/NativeScript/napi/quickjs/source/quickjs-libc.c deleted file mode 100644 index 0222b2e57..000000000 --- a/NativeScript/napi/quickjs/source/quickjs-libc.c +++ /dev/null @@ -1,4362 +0,0 @@ -/* - * QuickJS C library - * - * Copyright (c) 2017-2021 Fabrice Bellard - * Copyright (c) 2017-2021 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#endif -#include -#include -#if !defined(_MSC_VER) -#include -#endif -#include -#include -#include -#include -#if defined(_MSC_VER) -#include "dirent_compat.h" -#else -#include -#endif -#if defined(_WIN32) -#include -#include -#include -#include -#include -#include -#include -#define popen _popen -#define pclose _pclose -#define rmdir _rmdir -#define getcwd _getcwd -#define chdir _chdir -#else -#include -#if !defined(__wasi__) -#include -#include -#include -#include -#endif - -#if defined(__APPLE__) -typedef sig_t sighandler_t; -#include -#define environ (*_NSGetEnviron()) -#endif - -#if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) -typedef sig_t sighandler_t; -extern char **environ; -#endif - -#endif /* _WIN32 */ - -#if !defined(_WIN32) && !defined(__wasi__) -/* enable the os.Worker API. IT relies on POSIX threads */ -#define USE_WORKER -#endif - -#ifdef USE_WORKER -#include -#include "quickjs-c-atomics.h" -#endif - -#include "cutils.h" -#include "list.h" -#include "quickjs-libc.h" - -#define MAX_SAFE_INTEGER (((int64_t) 1 << 53) - 1) - -#ifndef QJS_NATIVE_MODULE_SUFFIX -#ifdef _WIN32 -#define QJS_NATIVE_MODULE_SUFFIX ".dll" -#else -#define QJS_NATIVE_MODULE_SUFFIX ".so" -#endif -#endif - -/* TODO: - - add socket calls -*/ - -typedef struct { - struct list_head link; - int fd; - JSValue rw_func[2]; -} JSOSRWHandler; - -typedef struct { - struct list_head link; - int sig_num; - JSValue func; -} JSOSSignalHandler; - -typedef struct { - struct list_head link; - int64_t timer_id; - uint8_t repeats:1; - int64_t timeout; - int64_t delay; - JSValue func; -} JSOSTimer; - -typedef struct { - struct list_head link; - uint8_t *data; - size_t data_len; - /* list of SharedArrayBuffers, necessary to free the message */ - uint8_t **sab_tab; - size_t sab_tab_len; -} JSWorkerMessage; - -typedef struct { - int ref_count; -#ifdef USE_WORKER - pthread_mutex_t mutex; -#endif - struct list_head msg_queue; /* list of JSWorkerMessage.link */ - int read_fd; - int write_fd; -} JSWorkerMessagePipe; - -typedef struct { - struct list_head link; - JSWorkerMessagePipe *recv_pipe; - JSValue on_message_func; -} JSWorkerMessageHandler; - -typedef struct JSThreadState { - struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ - struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ - struct list_head os_timers; /* list of JSOSTimer.link */ - struct list_head port_list; /* list of JSWorkerMessageHandler.link */ - int eval_script_recurse; /* only used in the main thread */ - int64_t next_timer_id; /* for setTimeout / setInterval */ - bool can_js_os_poll; - /* not used in the main thread */ - JSWorkerMessagePipe *recv_pipe, *send_pipe; - JSClassID std_file_class_id; - JSClassID worker_class_id; -} JSThreadState; - -static uint64_t os_pending_signals; - -static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) -{ - dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); -} - -static bool my_isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -static JSThreadState *js_get_thread_state(JSRuntime *rt) -{ - return (JSThreadState *)js_std_cmd(/*GetOpaque*/0, rt); -} - -static void js_set_thread_state(JSRuntime *rt, JSThreadState *ts) -{ - js_std_cmd(/*SetOpaque*/1, rt, ts); -} - -static JSValue js_printf_internal(JSContext *ctx, - int argc, JSValue *argv, FILE *fp) -{ - char fmtbuf[32]; - uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; - JSValue res; - DynBuf dbuf; - const char *fmt_str = NULL; - const uint8_t *fmt, *fmt_end; - const uint8_t *p; - char *q; - int i, c, len, mod; - size_t fmt_len; - int32_t int32_arg; - int64_t int64_arg; - double double_arg; - const char *string_arg; - int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = dbuf_printf; - - js_std_dbuf_init(ctx, &dbuf); - - if (argc > 0) { - fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); - if (!fmt_str) - goto fail; - - i = 1; - fmt = (const uint8_t *)fmt_str; - fmt_end = fmt + fmt_len; - while (fmt < fmt_end) { - for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) - continue; - dbuf_put(&dbuf, p, fmt - p); - if (fmt >= fmt_end) - break; - q = fmtbuf; - *q++ = *fmt++; /* copy '%' */ - - /* flags */ - for(;;) { - c = *fmt; - if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || - c == '\'') { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = c; - fmt++; - } else { - break; - } - } - /* width */ - if (*fmt == '*') { - if (i >= argc) - goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - fmt++; - } else { - while (my_isdigit(*fmt)) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - } - } - if (*fmt == '.') { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - if (*fmt == '*') { - if (i >= argc) - goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - fmt++; - } else { - while (my_isdigit(*fmt)) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - } - } - } - - /* we only support the "l" modifier for 64 bit numbers */ - mod = ' '; - if (*fmt == 'l') { - mod = *fmt++; - } - - /* type */ - c = *fmt++; - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = c; - *q = '\0'; - - switch (c) { - case 'c': - if (i >= argc) - goto missing; - if (JS_IsString(argv[i])) { - // TODO(chqrlie) need an API to wrap charCodeAt and codePointAt */ - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - int32_arg = utf8_decode((const uint8_t *)string_arg, &p); - JS_FreeCString(ctx, string_arg); - } else { - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - } - // XXX: throw an exception? - if ((unsigned)int32_arg > 0x10FFFF) - int32_arg = 0xFFFD; - /* ignore conversion flags, width and precision */ - len = utf8_encode(cbuf, int32_arg); - dbuf_put(&dbuf, cbuf, len); - break; - - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - case 'X': - if (i >= argc) - goto missing; - if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) - goto fail; - if (mod == 'l') { - /* 64 bit number */ -#if defined(_WIN32) - if (q >= fmtbuf + sizeof(fmtbuf) - 3) - goto invalid; - q[2] = q[-1]; - q[-1] = 'I'; - q[0] = '6'; - q[1] = '4'; - q[3] = '\0'; - dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); -#else - if (q >= fmtbuf + sizeof(fmtbuf) - 2) - goto invalid; - q[1] = q[-1]; - q[-1] = q[0] = 'l'; - q[2] = '\0'; - dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); -#endif - } else { - dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); - } - break; - - case 's': - if (i >= argc) - goto missing; - /* XXX: handle strings containing null characters */ - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - dbuf_printf_fun(&dbuf, fmtbuf, string_arg); - JS_FreeCString(ctx, string_arg); - break; - - case 'e': - case 'f': - case 'g': - case 'a': - case 'E': - case 'F': - case 'G': - case 'A': - if (i >= argc) - goto missing; - if (JS_ToFloat64(ctx, &double_arg, argv[i++])) - goto fail; - dbuf_printf_fun(&dbuf, fmtbuf, double_arg); - break; - - case '%': - dbuf_putc(&dbuf, '%'); - break; - - default: - /* XXX: should support an extension mechanism */ - invalid: - JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); - goto fail; - missing: - JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); - goto fail; - } - } - JS_FreeCString(ctx, fmt_str); - } - if (dbuf.error) { - res = JS_ThrowOutOfMemory(ctx); - } else { - if (fp) { - len = fwrite(dbuf.buf, 1, dbuf.size, fp); - res = JS_NewInt32(ctx, len); - } else { - res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); - } - } - dbuf_free(&dbuf); - return res; - -fail: - JS_FreeCString(ctx, fmt_str); - dbuf_free(&dbuf); - return JS_EXCEPTION; -} - -uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) -{ - FILE *f; - uint8_t *buf; - size_t buf_len; - long lret; - - f = fopen(filename, "rb"); - if (!f) - return NULL; - if (fseek(f, 0, SEEK_END) < 0) - goto fail; - lret = ftell(f); - if (lret < 0) - goto fail; - /* XXX: on Linux, ftell() return LONG_MAX for directories */ - if (lret == LONG_MAX) { - errno = EISDIR; - goto fail; - } - buf_len = lret; - if (fseek(f, 0, SEEK_SET) < 0) - goto fail; - if (ctx) - buf = js_malloc(ctx, buf_len + 1); - else - buf = malloc(buf_len + 1); - if (!buf) - goto fail; - if (fread(buf, 1, buf_len, f) != buf_len) { - errno = EIO; - if (ctx) - js_free(ctx, buf); - else - free(buf); - fail: - fclose(f); - return NULL; - } - buf[buf_len] = '\0'; - fclose(f); - *pbuf_len = buf_len; - return buf; -} - -/* load and evaluate a file */ -static JSValue js_loadScript(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - uint8_t *buf; - const char *filename; - JSValue ret; - size_t buf_len; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load '%s'", filename); - JS_FreeCString(ctx, filename); - return JS_EXCEPTION; - } - ret = JS_Eval(ctx, (char *)buf, buf_len, filename, - JS_EVAL_TYPE_GLOBAL); - js_free(ctx, buf); - JS_FreeCString(ctx, filename); - return ret; -} - -static int get_bool_option(JSContext *ctx, bool *pbool, - JSValue obj, - const char *option) -{ - JSValue val; - val = JS_GetPropertyStr(ctx, obj, option); - if (JS_IsException(val)) - return -1; - if (!JS_IsUndefined(val)) { - *pbool = JS_ToBool(ctx, val); - } - JS_FreeValue(ctx, val); - return 0; -} - -static void free_buf(JSRuntime *rt, void *opaque, void *ptr) { - js_free_rt(rt, ptr); -} - -/* load a file as a UTF-8 encoded string or Uint8Array */ -static JSValue js_std_loadFile(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - uint8_t *buf; - const char *filename; - JSValue ret, options_obj; - size_t buf_len; - bool binary = false; - - if (argc >= 2) { - options_obj = argv[1]; - if (get_bool_option(ctx, &binary, options_obj, - "binary")) - return JS_EXCEPTION; - } - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - buf = js_load_file(ctx, &buf_len, filename); - JS_FreeCString(ctx, filename); - if (!buf) - return JS_NULL; - if (binary) { - ret = JS_NewUint8Array(ctx, buf, buf_len, free_buf, NULL, false); - } else { - ret = JS_NewStringLen(ctx, (char *)buf, buf_len); - js_free(ctx, buf); - } - - return ret; -} - -typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, - const char *module_name); - - -#if defined(_WIN32) -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JSModuleDef *m; - HINSTANCE hd; - JSInitModuleFunc *init; - char *filename = NULL; - size_t len = strlen(module_name); - bool is_absolute = len > 2 && ((module_name[0] >= 'A' && module_name[0] <= 'Z') || - (module_name[0] >= 'a' && module_name[0] <= 'z')) && module_name[1] == ':'; - bool is_relative = len > 2 && module_name[0] == '.' && (module_name[1] == '/' || module_name[1] == '\\'); - if (is_absolute || is_relative) { - filename = (char *)module_name; - } else { - filename = js_malloc(ctx, len + 2 + 1); - if (!filename) - return NULL; - strcpy(filename, "./"); - strcpy(filename + 2, module_name); - } - hd = LoadLibraryA(filename); - if (filename != module_name) - js_free(ctx, filename); - if (hd == NULL) { - JS_ThrowReferenceError(ctx, "js_load_module '%s' error: %lu", - module_name, GetLastError()); - goto fail; - } - init = (JSInitModuleFunc *)(uintptr_t)GetProcAddress(hd, "js_init_module"); - if (!init) { - JS_ThrowReferenceError(ctx, "js_init_module '%s' not found: %lu", - module_name, GetLastError()); - goto fail; - } - m = init(ctx, module_name); - if (!m) { - JS_ThrowReferenceError(ctx, "js_call_module '%s' initialization error", - module_name); - fail: - if (hd != NULL) - FreeLibrary(hd); - return NULL; - } - return m; -} -#elif defined(__wasi__) -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); - return NULL; -} -#else -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JSModuleDef *m; - void *hd; - JSInitModuleFunc *init; - char *filename; - - if (!strchr(module_name, '/')) { - /* must add a '/' so that the DLL is not searched in the - system library paths */ - filename = js_malloc(ctx, strlen(module_name) + 2 + 1); - if (!filename) - return NULL; - strcpy(filename, "./"); - strcpy(filename + 2, module_name); - } else { - filename = (char *)module_name; - } - - /* C module */ - hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); - if (filename != module_name) - js_free(ctx, filename); - if (!hd) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library: %s", - module_name, dlerror()); - goto fail; - } - - *(void **) (&init) = dlsym(hd, "js_init_module"); - if (!init) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", - module_name); - goto fail; - } - - m = init(ctx, module_name); - if (!m) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", - module_name); - fail: - if (hd) - dlclose(hd); - return NULL; - } - return m; -} -#endif /* !_WIN32 */ - -int js_module_set_import_meta(JSContext *ctx, JSValue func_val, - bool use_realpath, bool is_main) -{ - JSModuleDef *m; - char buf[PATH_MAX + 16]; - JSValue meta_obj; - JSAtom module_name_atom; - const char *module_name; - - assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); - m = JS_VALUE_GET_PTR(func_val); - - module_name_atom = JS_GetModuleName(ctx, m); - module_name = JS_AtomToCString(ctx, module_name_atom); - JS_FreeAtom(ctx, module_name_atom); - if (!module_name) - return -1; - if (!strchr(module_name, ':')) { - strcpy(buf, "file://"); -#if !defined(_WIN32) && !defined(__wasi__) - /* realpath() cannot be used with modules compiled with qjsc - because the corresponding module source code is not - necessarily present */ - if (use_realpath) { - char *res = realpath(module_name, buf + strlen(buf)); - if (!res) { - JS_ThrowTypeError(ctx, "realpath failure"); - JS_FreeCString(ctx, module_name); - return -1; - } - } else -#endif - { - js__pstrcat(buf, sizeof(buf), module_name); - } - } else { - js__pstrcpy(buf, sizeof(buf), module_name); - } - JS_FreeCString(ctx, module_name); - - meta_obj = JS_GetImportMeta(ctx, m); - if (JS_IsException(meta_obj)) - return -1; - JS_DefinePropertyValueStr(ctx, meta_obj, "url", - JS_NewString(ctx, buf), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, meta_obj, "main", - JS_NewBool(ctx, is_main), - JS_PROP_C_W_E); - JS_FreeValue(ctx, meta_obj); - return 0; -} - -JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque) -{ - JSModuleDef *m; - - if (js__has_suffix(module_name, QJS_NATIVE_MODULE_SUFFIX)) { - m = js_module_loader_so(ctx, module_name); - } else { - size_t buf_len; - uint8_t *buf; - JSValue func_val; - - buf = js_load_file(ctx, &buf_len, module_name); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s'", - module_name); - return NULL; - } - - /* compile the module */ - func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - js_free(ctx, buf); - if (JS_IsException(func_val)) - return NULL; - if (js_module_set_import_meta(ctx, func_val, true, false) < 0) { - JS_FreeValue(ctx, func_val); - return NULL; - } - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); - } - return m; -} - -static JSValue js_std_exit(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int status; - if (JS_ToInt32(ctx, &status, argv[0])) - status = -1; - exit(status); - return JS_UNDEFINED; -} - -static JSValue js_std_getenv(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *name, *str; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - str = getenv(name); - JS_FreeCString(ctx, name); - if (!str) - return JS_UNDEFINED; - else - return JS_NewString(ctx, str); -} - -#if defined(_WIN32) -static void setenv(const char *name, const char *value, int overwrite) -{ - char *str; - size_t name_len, value_len; - name_len = strlen(name); - value_len = strlen(value); - str = malloc(name_len + 1 + value_len + 1); - memcpy(str, name, name_len); - str[name_len] = '='; - memcpy(str + name_len + 1, value, value_len); - str[name_len + 1 + value_len] = '\0'; - _putenv(str); - free(str); -} - -static void unsetenv(const char *name) -{ - setenv(name, "", true); -} -#endif /* _WIN32 */ - -static JSValue js_std_setenv(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *name, *value; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - value = JS_ToCString(ctx, argv[1]); - if (!value) { - JS_FreeCString(ctx, name); - return JS_EXCEPTION; - } - setenv(name, value, true); - JS_FreeCString(ctx, name); - JS_FreeCString(ctx, value); - return JS_UNDEFINED; -} - -static JSValue js_std_unsetenv(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *name; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - unsetenv(name); - JS_FreeCString(ctx, name); - return JS_UNDEFINED; -} - -/* return an object containing the list of the available environment - variables. */ -static JSValue js_std_getenviron(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - char **envp; - const char *name, *p, *value; - JSValue obj; - uint32_t idx; - size_t name_len; - JSAtom atom; - int ret; - - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - envp = environ; - for(idx = 0; envp[idx] != NULL; idx++) { - name = envp[idx]; - p = strchr(name, '='); - name_len = p - name; - if (!p) - continue; - value = p + 1; - atom = JS_NewAtomLen(ctx, name, name_len); - if (atom == JS_ATOM_NULL) - goto fail; - ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), - JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - if (ret < 0) - goto fail; - } - return obj; - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_std_gc(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JS_RunGC(JS_GetRuntime(ctx)); - return JS_UNDEFINED; -} - -static int interrupt_handler(JSRuntime *rt, void *opaque) -{ - return (os_pending_signals >> SIGINT) & 1; -} - -static JSValue js_evalScript(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - const char *str = NULL; - size_t len; - JSValue ret, obj; - JSValue options_obj; - bool backtrace_barrier = false; - bool eval_function = false; - bool eval_module = false; - bool compile_only = false; - bool compile_module = false; - bool is_async = false; - int flags; - - if (argc >= 2) { - options_obj = argv[1]; - if (get_bool_option(ctx, &backtrace_barrier, options_obj, - "backtrace_barrier")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &eval_function, options_obj, - "eval_function")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &eval_module, options_obj, - "eval_module")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &compile_only, options_obj, - "compile_only")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &compile_module, options_obj, - "compile_module")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &is_async, options_obj, - "async")) - return JS_EXCEPTION; - } - - if (eval_module) { - obj = argv[0]; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_MODULE) - return JS_ThrowTypeError(ctx, "not a module"); - - if (JS_ResolveModule(ctx, obj) < 0) - return JS_EXCEPTION; - - if (js_module_set_import_meta(ctx, obj, false, false) < 0) - return JS_EXCEPTION; - - return JS_EvalFunction(ctx, obj); - } - - if (!eval_function) { - str = JS_ToCStringLen(ctx, &len, argv[0]); - if (!str) - return JS_EXCEPTION; - } - if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { - /* install the interrupt handler */ - JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); - } - flags = compile_module ? JS_EVAL_TYPE_MODULE : JS_EVAL_TYPE_GLOBAL; - if (backtrace_barrier) - flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; - if (compile_only) - flags |= JS_EVAL_FLAG_COMPILE_ONLY; - if (is_async) - flags |= JS_EVAL_FLAG_ASYNC; - if (eval_function) { - obj = JS_DupValue(ctx, argv[0]); - ret = JS_EvalFunction(ctx, obj); // takes ownership of |obj| - } else { - ret = JS_Eval(ctx, str, len, "", flags); - } - JS_FreeCString(ctx, str); - if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { - /* remove the interrupt handler */ - JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); - os_pending_signals &= ~((uint64_t)1 << SIGINT); - /* convert the uncatchable "interrupted" error into a normal error - so that it can be caught by the REPL */ - if (JS_IsException(ret)) - JS_ResetUncatchableError(ctx); - } - return ret; -} - -typedef struct { - FILE *f; - bool is_popen; -} JSSTDFile; - -static bool is_stdio(FILE *f) -{ - return f == stdin || f == stdout || f == stderr; -} - -static void js_std_file_finalizer(JSRuntime *rt, JSValue val) -{ - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s = JS_GetOpaque(val, ts->std_file_class_id); - if (s) { - if (s->f && !is_stdio(s->f)) { -#if !defined(__wasi__) - if (s->is_popen) - pclose(s->f); - else -#endif - fclose(s->f); - } - js_free_rt(rt, s); - } -} - -static ssize_t js_get_errno(ssize_t ret) -{ - if (ret == -1) - ret = -errno; - return ret; -} - -static JSValue js_std_strerror(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int err; - if (JS_ToInt32(ctx, &err, argv[0])) - return JS_EXCEPTION; - return JS_NewString(ctx, strerror(err)); -} - -static JSValue js_new_std_file(JSContext *ctx, FILE *f, bool is_popen) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s; - JSValue obj; - obj = JS_NewObjectClass(ctx, ts->std_file_class_id); - if (JS_IsException(obj)) - return obj; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - s->is_popen = is_popen; - s->f = f; - JS_SetOpaque(obj, s); - return obj; -} - -static void js_set_error_object(JSContext *ctx, JSValue obj, int err) -{ - if (!JS_IsUndefined(obj)) { - JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); - } -} - -static JSValue js_std_open(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *filename, *mode = NULL; - FILE *f; - int err; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rwa+bx")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = fopen(filename, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, false); - fail: - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -#if !defined(__wasi__) -static JSValue js_std_popen(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *filename, *mode = NULL; - FILE *f; - int err; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rw")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = popen(filename, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, true); - fail: - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} -#endif // !defined(__wasi__) - -static JSValue js_std_fdopen(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *mode; - FILE *f; - int fd, err; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rwa+")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = fdopen(fd, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, false); - fail: - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -#if !defined(__wasi__) -static JSValue js_std_tmpfile(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f; - f = tmpfile(); - if (argc >= 1) - js_set_error_object(ctx, argv[0], f ? 0 : errno); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, false); -} -#endif - -static JSValue js_std_sprintf(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - return js_printf_internal(ctx, argc, argv, NULL); -} - -static JSValue js_std_printf(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - return js_printf_internal(ctx, argc, argv, stdout); -} - -static FILE *js_std_file_get(JSContext *ctx, JSValue obj) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s = JS_GetOpaque2(ctx, obj, ts->std_file_class_id); - if (!s) - return NULL; - if (!s->f) { - JS_ThrowTypeError(ctx, "invalid file handle"); - return NULL; - } - return s->f; -} - -static JSValue js_std_file_puts(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic) -{ - FILE *f; - int i; - const char *str; - size_t len; - - if (magic == 0) { - f = stdout; - } else { - f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - } - - for(i = 0; i < argc; i++) { - str = JS_ToCStringLen(ctx, &len, argv[i]); - if (!str) - return JS_EXCEPTION; - fwrite(str, 1, len, f); - JS_FreeCString(ctx, str); - } - return JS_UNDEFINED; -} - -static JSValue js_std_file_close(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSSTDFile *s = JS_GetOpaque2(ctx, this_val, ts->std_file_class_id); - int err; - if (!s) - return JS_EXCEPTION; - if (!s->f) - return JS_ThrowTypeError(ctx, "invalid file handle"); - if (is_stdio(s->f)) - return JS_ThrowTypeError(ctx, "cannot close stdio"); -#if !defined(__wasi__) - if (s->is_popen) - err = js_get_errno(pclose(s->f)); - else -#endif - err = js_get_errno(fclose(s->f)); - s->f = NULL; - return JS_NewInt32(ctx, err); -} - -static JSValue js_std_file_printf(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return js_printf_internal(ctx, argc, argv, f); -} - -static JSValue js_std_file_flush(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - fflush(f); - return JS_UNDEFINED; -} - -static JSValue js_std_file_tell(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int is_bigint) -{ - FILE *f = js_std_file_get(ctx, this_val); - int64_t pos; - if (!f) - return JS_EXCEPTION; -#if defined(__linux__) - pos = ftello(f); -#else - pos = ftell(f); -#endif - if (is_bigint) - return JS_NewBigInt64(ctx, pos); - else - return JS_NewInt64(ctx, pos); -} - -static JSValue js_std_file_seek(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int64_t pos; - int whence, ret; - if (!f) - return JS_EXCEPTION; - if (JS_ToInt64Ext(ctx, &pos, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &whence, argv[1])) - return JS_EXCEPTION; -#if defined(__linux__) - ret = fseeko(f, pos, whence); -#else - ret = fseek(f, pos, whence); -#endif - if (ret < 0) - ret = -errno; - return JS_NewInt32(ctx, ret); -} - -static JSValue js_std_file_eof(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewBool(ctx, feof(f)); -} - -static JSValue js_std_file_error(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewBool(ctx, ferror(f)); -} - -static JSValue js_std_file_clearerr(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - clearerr(f); - return JS_UNDEFINED; -} - -static JSValue js_std_file_fileno(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewInt32(ctx, fileno(f)); -} - -static JSValue js_std_file_read_write(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic) -{ - FILE *f = js_std_file_get(ctx, this_val); - uint64_t pos, len; - size_t size, ret; - uint8_t *buf; - - if (!f) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[0]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); - if (magic) - ret = fwrite(buf + pos, 1, len, f); - else - ret = fread(buf + pos, 1, len, f); - return JS_NewInt64(ctx, ret); -} - -/* XXX: could use less memory and go faster */ -static JSValue js_std_file_getline(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - DynBuf dbuf; - JSValue obj; - - if (!f) - return JS_EXCEPTION; - - js_std_dbuf_init(ctx, &dbuf); - for(;;) { - c = fgetc(f); - if (c == EOF) { - if (dbuf.size == 0) { - /* EOF */ - dbuf_free(&dbuf); - return JS_NULL; - } else { - break; - } - } - if (c == '\n') - break; - if (dbuf_putc(&dbuf, c)) { - dbuf_free(&dbuf); - return JS_ThrowOutOfMemory(ctx); - } - } - obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); - dbuf_free(&dbuf); - return obj; -} - -/* XXX: could use less memory and go faster */ -static JSValue js_std_file_readAs(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - DynBuf dbuf; - JSValue obj; - uint64_t max_size64; - size_t max_size; - JSValue max_size_val; - - if (!f) - return JS_EXCEPTION; - - if (argc >= 1) - max_size_val = argv[0]; - else - max_size_val = JS_UNDEFINED; - max_size = (size_t)-1; - if (!JS_IsUndefined(max_size_val)) { - if (JS_ToIndex(ctx, &max_size64, max_size_val)) - return JS_EXCEPTION; - if (max_size64 < max_size) - max_size = max_size64; - } - - js_std_dbuf_init(ctx, &dbuf); - while (max_size != 0) { - c = fgetc(f); - if (c == EOF) - break; - if (dbuf_putc(&dbuf, c)) { - dbuf_free(&dbuf); - return JS_EXCEPTION; - } - max_size--; - } - if (magic) { - obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); - } else { - obj = JS_NewArrayBufferCopy(ctx, dbuf.buf, dbuf.size); - } - dbuf_free(&dbuf); - return obj; -} - -static JSValue js_std_file_getByte(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewInt32(ctx, fgetc(f)); -} - -static JSValue js_std_file_putByte(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - if (!f) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &c, argv[0])) - return JS_EXCEPTION; - c = fputc(c, f); - return JS_NewInt32(ctx, c); -} - -/* urlGet */ -#if !defined(__wasi__) - -#define URL_GET_PROGRAM "curl -s -i --" -#define URL_GET_BUF_SIZE 4096 - -static int http_get_header_line(FILE *f, char *buf, size_t buf_size, - DynBuf *dbuf) -{ - int c; - char *p; - - p = buf; - for(;;) { - c = fgetc(f); - if (c < 0) - return -1; - if ((p - buf) < buf_size - 1) - *p++ = c; - if (dbuf) - dbuf_putc(dbuf, c); - if (c == '\n') - break; - } - *p = '\0'; - return 0; -} - -static int http_get_status(const char *buf) -{ - const char *p = buf; - while (*p != ' ' && *p != '\0') - p++; - if (*p != ' ') - return 0; - while (*p == ' ') - p++; - return atoi(p); -} - -static JSValue js_std_urlGet(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *url; - DynBuf cmd_buf; - DynBuf data_buf_s, *data_buf = &data_buf_s; - DynBuf header_buf_s, *header_buf = &header_buf_s; - char *buf; - size_t i, len; - int status; - JSValue response = JS_UNDEFINED, ret_obj; - JSValue options_obj; - FILE *f; - bool binary_flag, full_flag; - - url = JS_ToCString(ctx, argv[0]); - if (!url) - return JS_EXCEPTION; - - binary_flag = false; - full_flag = false; - - if (argc >= 2) { - options_obj = argv[1]; - - if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) - goto fail_obj; - - if (get_bool_option(ctx, &full_flag, options_obj, "full")) { - fail_obj: - JS_FreeCString(ctx, url); - return JS_EXCEPTION; - } - } - - js_std_dbuf_init(ctx, &cmd_buf); - dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); - for(i = 0; url[i] != '\0'; i++) { - unsigned char c = url[i]; - switch (c) { - case '\'': - /* shell single quoted string does not support \' */ - dbuf_putstr(&cmd_buf, "'\\''"); - break; - case '[': case ']': case '{': case '}': case '\\': - /* prevent interpretation by curl as range or set specification */ - dbuf_putc(&cmd_buf, '\\'); - /* FALLTHROUGH */ - default: - dbuf_putc(&cmd_buf, c); - break; - } - } - JS_FreeCString(ctx, url); - dbuf_putstr(&cmd_buf, "'"); - dbuf_putc(&cmd_buf, '\0'); - if (dbuf_error(&cmd_buf)) { - dbuf_free(&cmd_buf); - return JS_EXCEPTION; - } - // printf("%s\n", (char *)cmd_buf.buf); - f = popen((char *)cmd_buf.buf, "r"); - dbuf_free(&cmd_buf); - if (!f) { - return JS_ThrowTypeError(ctx, "could not start curl"); - } - - js_std_dbuf_init(ctx, data_buf); - js_std_dbuf_init(ctx, header_buf); - - buf = js_malloc(ctx, URL_GET_BUF_SIZE); - if (!buf) - goto fail; - - /* get the HTTP status */ - if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { - status = 0; - goto bad_header; - } - status = http_get_status(buf); - if (!full_flag && !(status >= 200 && status <= 299)) { - goto bad_header; - } - - /* wait until there is an empty line */ - for(;;) { - if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { - bad_header: - response = JS_NULL; - goto done; - } - if (!strcmp(buf, "\r\n")) - break; - } - if (dbuf_error(header_buf)) - goto fail; - header_buf->size -= 2; /* remove the trailing CRLF */ - - /* download the data */ - for(;;) { - len = fread(buf, 1, URL_GET_BUF_SIZE, f); - if (len == 0) - break; - dbuf_put(data_buf, (uint8_t *)buf, len); - } - if (dbuf_error(data_buf)) - goto fail; - if (binary_flag) { - response = JS_NewArrayBufferCopy(ctx, - data_buf->buf, data_buf->size); - } else { - response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); - } - if (JS_IsException(response)) - goto fail; - done: - js_free(ctx, buf); - buf = NULL; - pclose(f); - f = NULL; - dbuf_free(data_buf); - data_buf = NULL; - - if (full_flag) { - ret_obj = JS_NewObject(ctx); - if (JS_IsException(ret_obj)) - goto fail; - JS_DefinePropertyValueStr(ctx, ret_obj, "response", - response, - JS_PROP_C_W_E); - if (!JS_IsNull(response)) { - JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", - JS_NewStringLen(ctx, (char *)header_buf->buf, - header_buf->size), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, ret_obj, "status", - JS_NewInt32(ctx, status), - JS_PROP_C_W_E); - } - } else { - ret_obj = response; - } - dbuf_free(header_buf); - return ret_obj; - fail: - if (f) - pclose(f); - js_free(ctx, buf); - if (data_buf) - dbuf_free(data_buf); - if (header_buf) - dbuf_free(header_buf); - JS_FreeValue(ctx, response); - return JS_EXCEPTION; -} -#endif // !defined(__wasi__) - -static JSClassDef js_std_file_class = { - "FILE", - .finalizer = js_std_file_finalizer, -}; - -static const JSCFunctionListEntry js_std_error_props[] = { - /* various errno values */ -#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) - DEF(EINVAL), - DEF(EIO), - DEF(EACCES), - DEF(EEXIST), - DEF(ENOSPC), - DEF(ENOSYS), - DEF(EBUSY), - DEF(ENOENT), - DEF(EPERM), - DEF(EPIPE), - DEF(EBADF), -#undef DEF -}; - -static const JSCFunctionListEntry js_std_funcs[] = { - JS_CFUNC_DEF("exit", 1, js_std_exit ), - JS_CFUNC_DEF("gc", 0, js_std_gc ), - JS_CFUNC_DEF("evalScript", 1, js_evalScript ), - JS_CFUNC_DEF("loadScript", 1, js_loadScript ), - JS_CFUNC_DEF("getenv", 1, js_std_getenv ), - JS_CFUNC_DEF("setenv", 1, js_std_setenv ), - JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), - JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), -#if !defined(__wasi__) - JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), -#endif - JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), - JS_CFUNC_DEF("strerror", 1, js_std_strerror ), - - /* FILE I/O */ - JS_CFUNC_DEF("open", 2, js_std_open ), -#if !defined(__wasi__) - JS_CFUNC_DEF("popen", 2, js_std_popen ), - JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), -#endif - JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), - JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), - JS_CFUNC_DEF("printf", 1, js_std_printf ), - JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), - JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), - JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), - JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), - JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), -}; - -static const JSCFunctionListEntry js_std_file_proto_funcs[] = { - JS_CFUNC_DEF("close", 0, js_std_file_close ), - JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), - JS_CFUNC_DEF("printf", 1, js_std_file_printf ), - JS_CFUNC_DEF("flush", 0, js_std_file_flush ), - JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), - JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), - JS_CFUNC_DEF("seek", 2, js_std_file_seek ), - JS_CFUNC_DEF("eof", 0, js_std_file_eof ), - JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), - JS_CFUNC_DEF("error", 0, js_std_file_error ), - JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), - JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), - JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), - JS_CFUNC_DEF("getline", 0, js_std_file_getline ), - JS_CFUNC_MAGIC_DEF("readAsArrayBuffer", 0, js_std_file_readAs, 0 ), - JS_CFUNC_MAGIC_DEF("readAsString", 0, js_std_file_readAs, 1 ), - JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), - JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), - /* setvbuf, ... */ -}; - -static int js_std_init(JSContext *ctx, JSModuleDef *m) -{ - JSValue proto; - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - - /* FILE class */ - /* the class ID is created once */ - JS_NewClassID(rt, &ts->std_file_class_id); - /* the class is created once per runtime */ - JS_NewClass(rt, ts->std_file_class_id, &js_std_file_class); - proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, - countof(js_std_file_proto_funcs)); - JS_SetClassProto(ctx, ts->std_file_class_id, proto); - - JS_SetModuleExportList(ctx, m, js_std_funcs, - countof(js_std_funcs)); - JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, false)); - JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, false)); - JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, false)); - return 0; -} - -JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_std_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); - JS_AddModuleExport(ctx, m, "in"); - JS_AddModuleExport(ctx, m, "out"); - JS_AddModuleExport(ctx, m, "err"); - return m; -} - -/**********************************************************/ -/* 'os' object */ - -static JSValue js_os_open(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *filename; - int flags, mode, ret; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &flags, argv[1])) - goto fail; - if (argc >= 3 && !JS_IsUndefined(argv[2])) { - if (JS_ToInt32(ctx, &mode, argv[2])) { - fail: - JS_FreeCString(ctx, filename); - return JS_EXCEPTION; - } - } else { - mode = 0666; - } -#if defined(_WIN32) - /* force binary mode by default */ - if (!(flags & O_TEXT)) - flags |= O_BINARY; -#endif - ret = js_get_errno(open(filename, flags, mode)); - JS_FreeCString(ctx, filename); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_close(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd, ret; - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - ret = js_get_errno(close(fd)); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_seek(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd, whence; - int64_t pos, ret; - bool is_bigint; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - is_bigint = JS_IsBigInt(ctx, argv[1]); - if (JS_ToInt64Ext(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &whence, argv[2])) - return JS_EXCEPTION; - ret = lseek(fd, pos, whence); - if (ret == -1) - ret = -errno; - if (is_bigint) - return JS_NewBigInt64(ctx, ret); - else - return JS_NewInt64(ctx, ret); -} - -static JSValue js_os_read_write(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic) -{ - int fd; - uint64_t pos, len; - size_t size; - ssize_t ret; - uint8_t *buf; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &pos, argv[2])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[3])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[1]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); - if (magic) - ret = js_get_errno(write(fd, buf + pos, len)); - else - ret = js_get_errno(read(fd, buf + pos, len)); - return JS_NewInt64(ctx, ret); -} - -static JSValue js_os_isatty(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd; - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - return JS_NewBool(ctx, (isatty(fd) != 0)); -} - -#if defined(_WIN32) -static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd; - HANDLE handle; - CONSOLE_SCREEN_BUFFER_INFO info; - JSValue obj; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - handle = (HANDLE)_get_osfhandle(fd); - - if (!GetConsoleScreenBufferInfo(handle, &info)) - return JS_NULL; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); - return obj; -} - -/* Windows 10 built-in VT100 emulation */ -#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 -#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 - -static JSValue js_os_ttySetRaw(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd; - HANDLE handle; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - handle = (HANDLE)_get_osfhandle(fd); - SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); - _setmode(fd, _O_BINARY); - if (fd == 0) { - handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ - SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); - } - return JS_UNDEFINED; -} -#elif !defined(__wasi__) -static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd; - struct winsize ws; - JSValue obj; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && - ws.ws_col >= 4 && ws.ws_row >= 4) { - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); - return obj; - } else { - return JS_NULL; - } -} - -static struct termios oldtty; - -static void term_exit(void) -{ - tcsetattr(0, TCSANOW, &oldtty); -} - -/* XXX: should add a way to go back to normal mode */ -static JSValue js_os_ttySetRaw(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - struct termios tty; - int fd; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - - memset(&tty, 0, sizeof(tty)); - tcgetattr(fd, &tty); - oldtty = tty; - - tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP - |INLCR|IGNCR|ICRNL|IXON); - tty.c_oflag |= OPOST; - tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); - tty.c_cflag &= ~(CSIZE|PARENB); - tty.c_cflag |= CS8; - tty.c_cc[VMIN] = 1; - tty.c_cc[VTIME] = 0; - - tcsetattr(fd, TCSANOW, &tty); - - atexit(term_exit); - return JS_UNDEFINED; -} - -#endif /* !_WIN32 && !__wasi__ */ - -static JSValue js_os_remove(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *filename; - int ret; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; -#if defined(_WIN32) - { - struct stat st; - if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) { - ret = rmdir(filename); - } else { - ret = unlink(filename); - } - } -#else - ret = remove(filename); -#endif - ret = js_get_errno(ret); - JS_FreeCString(ctx, filename); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_rename(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *oldpath, *newpath; - int ret; - - oldpath = JS_ToCString(ctx, argv[0]); - if (!oldpath) - return JS_EXCEPTION; - newpath = JS_ToCString(ctx, argv[1]); - if (!newpath) { - JS_FreeCString(ctx, oldpath); - return JS_EXCEPTION; - } - ret = js_get_errno(rename(oldpath, newpath)); - JS_FreeCString(ctx, oldpath); - JS_FreeCString(ctx, newpath); - return JS_NewInt32(ctx, ret); -} - -static bool is_main_thread(JSRuntime *rt) -{ - JSThreadState *ts = js_get_thread_state(rt); - return !ts->recv_pipe; -} - -static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) -{ - JSOSRWHandler *rh; - struct list_head *el; - - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == fd) - return rh; - } - return NULL; -} - -static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) -{ - int i; - list_del(&rh->link); - for(i = 0; i < 2; i++) { - JS_FreeValueRT(rt, rh->rw_func[i]); - } - js_free_rt(rt, rh); -} - -static JSValue js_os_setReadHandler(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSOSRWHandler *rh; - int fd; - JSValue func; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - func = argv[1]; - if (JS_IsNull(func)) { - rh = find_rh(ts, fd); - if (rh) { - JS_FreeValue(ctx, rh->rw_func[magic]); - rh->rw_func[magic] = JS_NULL; - if (JS_IsNull(rh->rw_func[0]) && - JS_IsNull(rh->rw_func[1])) { - /* remove the entry */ - free_rw_handler(JS_GetRuntime(ctx), rh); - } - } - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - rh = find_rh(ts, fd); - if (!rh) { - rh = js_mallocz(ctx, sizeof(*rh)); - if (!rh) - return JS_EXCEPTION; - rh->fd = fd; - rh->rw_func[0] = JS_NULL; - rh->rw_func[1] = JS_NULL; - list_add_tail(&rh->link, &ts->os_rw_handlers); - } - JS_FreeValue(ctx, rh->rw_func[magic]); - rh->rw_func[magic] = JS_DupValue(ctx, func); - } - return JS_UNDEFINED; -} - -static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) -{ - JSOSSignalHandler *sh; - struct list_head *el; - list_for_each(el, &ts->os_signal_handlers) { - sh = list_entry(el, JSOSSignalHandler, link); - if (sh->sig_num == sig_num) - return sh; - } - return NULL; -} - -static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) -{ - list_del(&sh->link); - JS_FreeValueRT(rt, sh->func); - js_free_rt(rt, sh); -} - -static void os_signal_handler(int sig_num) -{ - os_pending_signals |= ((uint64_t)1 << sig_num); -} - -#if defined(_WIN32) -typedef void (*sighandler_t)(int sig_num); -#endif - -static JSValue js_os_signal(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSOSSignalHandler *sh; - uint32_t sig_num; - JSValue func; - sighandler_t handler; - - if (!is_main_thread(rt)) - return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); - - if (JS_ToUint32(ctx, &sig_num, argv[0])) - return JS_EXCEPTION; - if (sig_num >= 64) - return JS_ThrowRangeError(ctx, "invalid signal number"); - func = argv[1]; - /* func = null: SIG_DFL, func = undefined, SIG_IGN */ - if (JS_IsNull(func) || JS_IsUndefined(func)) { - sh = find_sh(ts, sig_num); - if (sh) { - free_sh(JS_GetRuntime(ctx), sh); - } - if (JS_IsNull(func)) - handler = SIG_DFL; - else - handler = SIG_IGN; - signal(sig_num, handler); - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - sh = find_sh(ts, sig_num); - if (!sh) { - sh = js_mallocz(ctx, sizeof(*sh)); - if (!sh) - return JS_EXCEPTION; - sh->sig_num = sig_num; - list_add_tail(&sh->link, &ts->os_signal_handlers); - } - JS_FreeValue(ctx, sh->func); - sh->func = JS_DupValue(ctx, func); - signal(sig_num, os_signal_handler); - } - return JS_UNDEFINED; -} - -#if !defined(_WIN32) && !defined(__wasi__) -static JSValue js_os_cputime(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - struct rusage ru; - int64_t cputime; - - cputime = 0; - if (!getrusage(RUSAGE_SELF, &ru)) - cputime = (int64_t)ru.ru_utime.tv_sec * 1000000 + ru.ru_utime.tv_usec; - - return JS_NewInt64(ctx, cputime); -} -#endif - -static JSValue js_os_now(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - return JS_NewInt64(ctx, js__hrtime_ns() / 1000); -} - -static uint64_t js__hrtime_ms(void) -{ - return js__hrtime_ns() / (1000 * 1000); -} - -static void free_timer(JSRuntime *rt, JSOSTimer *th) -{ - list_del(&th->link); - JS_FreeValueRT(rt, th->func); - js_free_rt(rt, th); -} - -// TODO(bnoordhuis) accept string as first arg and eval at timer expiry -// TODO(bnoordhuis) retain argv[2..] as args for callback if argc > 2 -static JSValue js_os_setTimeout(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int64_t delay; - JSValue func; - JSOSTimer *th; - - func = argv[0]; - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - if (JS_ToInt64(ctx, &delay, argv[1])) - return JS_EXCEPTION; - if (delay < 1) - delay = 1; - th = js_mallocz(ctx, sizeof(*th)); - if (!th) - return JS_EXCEPTION; - th->timer_id = ts->next_timer_id++; - if (ts->next_timer_id > MAX_SAFE_INTEGER) - ts->next_timer_id = 1; - th->repeats = (magic > 0); - th->timeout = js__hrtime_ms() + delay; - th->delay = delay; - th->func = JS_DupValue(ctx, func); - list_add_tail(&th->link, &ts->os_timers); - return JS_NewInt64(ctx, th->timer_id); -} - -static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) -{ - struct list_head *el; - if (timer_id <= 0) - return NULL; - list_for_each(el, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - if (th->timer_id == timer_id) - return th; - } - return NULL; -} - -static JSValue js_os_clearTimeout(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSOSTimer *th; - int64_t timer_id; - - if (JS_ToInt64(ctx, &timer_id, argv[0])) - return JS_EXCEPTION; - th = find_timer_by_id(ts, timer_id); - if (!th) - return JS_UNDEFINED; - free_timer(rt, th); - return JS_UNDEFINED; -} - -/* return a promise */ -static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int64_t delay; - JSOSTimer *th; - JSValue promise, resolving_funcs[2]; - - if (JS_ToInt64(ctx, &delay, argv[0])) - return JS_EXCEPTION; - promise = JS_NewPromiseCapability(ctx, resolving_funcs); - if (JS_IsException(promise)) - return JS_EXCEPTION; - - th = js_mallocz(ctx, sizeof(*th)); - if (!th) { - JS_FreeValue(ctx, promise); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return JS_EXCEPTION; - } - th->timer_id = -1; - th->timeout = js__hrtime_ms() + delay; - th->func = JS_DupValue(ctx, resolving_funcs[0]); - list_add_tail(&th->link, &ts->os_timers); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return promise; -} - -static int call_handler(JSContext *ctx, JSValue func) -{ - int r; - JSValue ret, func1; - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func1 = JS_DupValue(ctx, func); - ret = JS_Call(ctx, func1, JS_UNDEFINED, 0, NULL); - JS_FreeValue(ctx, func1); - r = 0; - if (JS_IsException(ret)) - r = -1; - JS_FreeValue(ctx, ret); - return r; -} - -static int js_os_run_timers(JSRuntime *rt, JSContext *ctx, JSThreadState *ts, int *min_delay) -{ - JSValue func; - JSOSTimer *th; - int64_t cur_time, delay; - struct list_head *el; - int r; - - if (list_empty(&ts->os_timers)) { - *min_delay = -1; - return 0; - } - - cur_time = js__hrtime_ms(); - *min_delay = INT32_MAX; - - list_for_each(el, &ts->os_timers) { - th = list_entry(el, JSOSTimer, link); - delay = th->timeout - cur_time; - if (delay > 0) { - *min_delay = min_int(*min_delay, delay); - } else { - *min_delay = 0; - func = JS_DupValueRT(rt, th->func); - if (th->repeats) - th->timeout = cur_time + th->delay; - else - free_timer(rt, th); - r = call_handler(ctx, func); - JS_FreeValueRT(rt, func); - return r; - } - } - - return 0; -} - -#if defined(_WIN32) - -static int js_os_poll(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int min_delay, console_fd; - JSOSRWHandler *rh; - struct list_head *el; - - /* XXX: handle signals if useful */ - - if (js_os_run_timers(rt, ctx, ts, &min_delay)) - return -1; - if (min_delay == 0) - return 0; // expired timer - if (min_delay < 0) - if (list_empty(&ts->os_rw_handlers)) - return -1; /* no more events */ - - console_fd = -1; - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { - console_fd = rh->fd; - break; - } - } - - if (console_fd >= 0) { - DWORD ti, ret; - HANDLE handle; - if (min_delay == -1) - ti = INFINITE; - else - ti = min_delay; - handle = (HANDLE)_get_osfhandle(console_fd); - ret = WaitForSingleObject(handle, ti); - if (ret == WAIT_OBJECT_0) { - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) { - return call_handler(ctx, rh->rw_func[0]); - /* must stop because the list may have been modified */ - } - } - } - } else { - Sleep(min_delay); - } - return 0; -} -#else - -#ifdef USE_WORKER - -static void js_free_message(JSWorkerMessage *msg); - -/* return 1 if a message was handled, 0 if no message */ -static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) -{ - JSWorkerMessagePipe *ps = port->recv_pipe; - int ret; - struct list_head *el; - JSWorkerMessage *msg; - JSValue obj, data_obj, func, retval; - - pthread_mutex_lock(&ps->mutex); - if (!list_empty(&ps->msg_queue)) { - el = ps->msg_queue.next; - msg = list_entry(el, JSWorkerMessage, link); - - /* remove the message from the queue */ - list_del(&msg->link); - - if (list_empty(&ps->msg_queue)) { - uint8_t buf[16]; - int ret; - for(;;) { - ret = read(ps->read_fd, buf, sizeof(buf)); - if (ret >= 0) - break; - if (errno != EAGAIN && errno != EINTR) - break; - } - } - - pthread_mutex_unlock(&ps->mutex); - - data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, - JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); - - js_free_message(msg); - - if (JS_IsException(data_obj)) - goto fail; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, data_obj); - goto fail; - } - JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); - - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func = JS_DupValue(ctx, port->on_message_func); - retval = JS_Call(ctx, func, JS_UNDEFINED, 1, &obj); - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, func); - if (JS_IsException(retval)) { - fail: - js_std_dump_error(ctx); - } else { - JS_FreeValue(ctx, retval); - } - ret = 1; - } else { - pthread_mutex_unlock(&ps->mutex); - ret = 0; - } - return ret; -} -#else -static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) -{ - return 0; -} -#endif - -static int js_os_poll(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - int ret, fd_max, min_delay; - fd_set rfds, wfds; - JSOSRWHandler *rh; - struct list_head *el; - struct timeval tv, *tvp; - - /* only check signals in the main thread */ - if (!ts->recv_pipe && - unlikely(os_pending_signals != 0)) { - JSOSSignalHandler *sh; - uint64_t mask; - - list_for_each(el, &ts->os_signal_handlers) { - sh = list_entry(el, JSOSSignalHandler, link); - mask = (uint64_t)1 << sh->sig_num; - if (os_pending_signals & mask) { - os_pending_signals &= ~mask; - return call_handler(ctx, sh->func); - } - } - } - - if (js_os_run_timers(rt, ctx, ts, &min_delay)) - return -1; - if (min_delay == 0) - return 0; // expired timer - if (min_delay < 0) - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->port_list)) - return -1; /* no more events */ - - tvp = NULL; - if (min_delay >= 0) { - tv.tv_sec = min_delay / 1000; - tv.tv_usec = (min_delay % 1000) * 1000; - tvp = &tv; - } - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - fd_max = -1; - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - fd_max = max_int(fd_max, rh->fd); - if (!JS_IsNull(rh->rw_func[0])) - FD_SET(rh->fd, &rfds); - if (!JS_IsNull(rh->rw_func[1])) - FD_SET(rh->fd, &wfds); - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - fd_max = max_int(fd_max, ps->read_fd); - FD_SET(ps->read_fd, &rfds); - } - } - - ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); - if (ret > 0) { - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (!JS_IsNull(rh->rw_func[0]) && - FD_ISSET(rh->fd, &rfds)) { - return call_handler(ctx, rh->rw_func[0]); - /* must stop because the list may have been modified */ - } - if (!JS_IsNull(rh->rw_func[1]) && - FD_ISSET(rh->fd, &wfds)) { - return call_handler(ctx, rh->rw_func[1]); - /* must stop because the list may have been modified */ - } - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (FD_ISSET(ps->read_fd, &rfds)) { - if (handle_posted_message(rt, ctx, port)) - goto done; - } - } - } - } - done: - return 0; -} -#endif /* !_WIN32 */ - -static JSValue make_obj_error(JSContext *ctx, - JSValue obj, - int err) -{ - JSValue arr; - if (JS_IsException(obj)) - return obj; - arr = JS_NewArray(ctx); - if (JS_IsException(arr)) - return JS_EXCEPTION; - JS_DefinePropertyValueUint32(ctx, arr, 0, obj, - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), - JS_PROP_C_W_E); - return arr; -} - -static JSValue make_string_error(JSContext *ctx, - const char *buf, - int err) -{ - return make_obj_error(ctx, JS_NewString(ctx, buf), err); -} - -/* return [cwd, errorcode] */ -static JSValue js_os_getcwd(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - char buf[PATH_MAX]; - int err; - - if (!getcwd(buf, sizeof(buf))) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} - -static JSValue js_os_chdir(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *target; - int err; - - target = JS_ToCString(ctx, argv[0]); - if (!target) - return JS_EXCEPTION; - err = js_get_errno(chdir(target)); - JS_FreeCString(ctx, target); - return JS_NewInt32(ctx, err); -} - -static JSValue js_os_mkdir(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int mode, ret; - const char *path; - - if (argc >= 2) { - if (JS_ToInt32(ctx, &mode, argv[1])) - return JS_EXCEPTION; - } else { - mode = 0777; - } - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - (void)mode; - ret = js_get_errno(mkdir(path)); -#else - ret = js_get_errno(mkdir(path, mode)); -#endif - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - -/* return [array, errorcode] */ -static JSValue js_os_readdir(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *path; - DIR *f; - struct dirent *d; - JSValue obj; - int err; - uint32_t len; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) { - JS_FreeCString(ctx, path); - return JS_EXCEPTION; - } - f = opendir(path); - if (!f) - err = errno; - else - err = 0; - JS_FreeCString(ctx, path); - if (!f) - goto done; - len = 0; - for(;;) { - errno = 0; - d = readdir(f); - if (!d) { - err = errno; - break; - } - JS_DefinePropertyValueUint32(ctx, obj, len++, - JS_NewString(ctx, d->d_name), - JS_PROP_C_W_E); - } - closedir(f); - done: - return make_obj_error(ctx, obj, err); -} - -#if !defined(_WIN32) -static int64_t timespec_to_ms(const struct timespec *tv) -{ - return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); -} -#endif - -/* return [obj, errcode] */ -static JSValue js_os_stat(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int is_lstat) -{ - const char *path; - int err, res; - struct stat st; - JSValue obj; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - res = stat(path, &st); -#else - if (is_lstat) - res = lstat(path, &st); - else - res = stat(path, &st); -#endif - err = (res < 0) ? errno : 0; - JS_FreeCString(ctx, path); - if (res < 0) { - obj = JS_NULL; - } else { - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - JS_DefinePropertyValueStr(ctx, obj, "dev", - JS_NewInt64(ctx, st.st_dev), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ino", - JS_NewInt64(ctx, st.st_ino), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mode", - JS_NewInt32(ctx, st.st_mode), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "nlink", - JS_NewInt64(ctx, st.st_nlink), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "uid", - JS_NewInt64(ctx, st.st_uid), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "gid", - JS_NewInt64(ctx, st.st_gid), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "rdev", - JS_NewInt64(ctx, st.st_rdev), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "size", - JS_NewInt64(ctx, st.st_size), - JS_PROP_C_W_E); -#if !defined(_WIN32) - JS_DefinePropertyValueStr(ctx, obj, "blocks", - JS_NewInt64(ctx, st.st_blocks), - JS_PROP_C_W_E); -#endif -#if defined(_WIN32) - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), - JS_PROP_C_W_E); -#elif defined(__APPLE__) - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), - JS_PROP_C_W_E); -#else - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), - JS_PROP_C_W_E); -#endif - } - return make_obj_error(ctx, obj, err); -} - -#if !defined(_WIN32) -static void ms_to_timeval(struct timeval *tv, uint64_t v) -{ - tv->tv_sec = v / 1000; - tv->tv_usec = (v % 1000) * 1000; -} -#endif - -static JSValue js_os_utimes(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *path; - int64_t atime, mtime; - int ret; - - if (JS_ToInt64(ctx, &atime, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt64(ctx, &mtime, argv[2])) - return JS_EXCEPTION; - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - { - struct _utimbuf times; - times.actime = atime / 1000; - times.modtime = mtime / 1000; - ret = js_get_errno(_utime(path, ×)); - } -#else - { - struct timeval times[2]; - ms_to_timeval(×[0], atime); - ms_to_timeval(×[1], mtime); - ret = js_get_errno(utimes(path, times)); - } -#endif - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - -/* sleep(delay_ms) */ -static JSValue js_os_sleep(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int64_t delay; - int ret; - - if (JS_ToInt64(ctx, &delay, argv[0])) - return JS_EXCEPTION; - if (delay < 0) - delay = 0; -#if defined(_WIN32) - { - if (delay > INT32_MAX) - delay = INT32_MAX; - Sleep(delay); - ret = 0; - } -#else - { - struct timespec ts; - - ts.tv_sec = delay / 1000; - ts.tv_nsec = (delay % 1000) * 1000000; - ret = js_get_errno(nanosleep(&ts, NULL)); - } -#endif - return JS_NewInt32(ctx, ret); -} - -#if defined(_WIN32) -static char *realpath(const char *path, char *buf) -{ - if (!_fullpath(buf, path, PATH_MAX)) { - errno = ENOENT; - return NULL; - } else { - return buf; - } -} -#endif - -#if !defined(__wasi__) -/* return [path, errorcode] */ -static JSValue js_os_realpath(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *path; - char buf[PATH_MAX], *res; - int err; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = realpath(path, buf); - JS_FreeCString(ctx, path); - if (!res) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} -#endif - -#if !defined(_WIN32) && !defined(__wasi__) -static JSValue js_os_symlink(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *target, *linkpath; - int err; - - target = JS_ToCString(ctx, argv[0]); - if (!target) - return JS_EXCEPTION; - linkpath = JS_ToCString(ctx, argv[1]); - if (!linkpath) { - JS_FreeCString(ctx, target); - return JS_EXCEPTION; - } - err = js_get_errno(symlink(target, linkpath)); - JS_FreeCString(ctx, target); - JS_FreeCString(ctx, linkpath); - return JS_NewInt32(ctx, err); -} - -/* return [path, errorcode] */ -static JSValue js_os_readlink(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - const char *path; - char buf[PATH_MAX]; - int err; - ssize_t res; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = readlink(path, buf, sizeof(buf) - 1); - if (res < 0) { - buf[0] = '\0'; - err = errno; - } else { - buf[res] = '\0'; - err = 0; - } - JS_FreeCString(ctx, path); - return make_string_error(ctx, buf, err); -} - -static char **build_envp(JSContext *ctx, JSValue obj) -{ - uint32_t len, i; - JSPropertyEnum *tab; - char **envp, *pair; - const char *key, *str; - JSValue val; - size_t key_len, str_len; - - if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) - return NULL; - envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); - if (!envp) - goto fail; - for(i = 0; i < len; i++) { - val = JS_GetProperty(ctx, obj, tab[i].atom); - if (JS_IsException(val)) - goto fail; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto fail; - key = JS_AtomToCString(ctx, tab[i].atom); - if (!key) { - JS_FreeCString(ctx, str); - goto fail; - } - key_len = strlen(key); - str_len = strlen(str); - pair = js_malloc(ctx, key_len + str_len + 2); - if (!pair) { - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - goto fail; - } - memcpy(pair, key, key_len); - pair[key_len] = '='; - memcpy(pair + key_len + 1, str, str_len); - pair[key_len + 1 + str_len] = '\0'; - envp[i] = pair; - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - } - done: - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tab[i].atom); - js_free(ctx, tab); - return envp; - fail: - if (envp) { - for(i = 0; i < len; i++) - js_free(ctx, envp[i]); - js_free(ctx, envp); - envp = NULL; - } - goto done; -} - -/* execvpe is not available on non GNU systems */ -static int my_execvpe(const char *filename, char **argv, char **envp) -{ - char *path, *p, *p_next, *p1; - char buf[PATH_MAX]; - size_t filename_len, path_len; - bool eacces_error; - - filename_len = strlen(filename); - if (filename_len == 0) { - errno = ENOENT; - return -1; - } - if (strchr(filename, '/')) - return execve(filename, argv, envp); - - path = getenv("PATH"); - if (!path) - path = (char *)"/bin:/usr/bin"; - eacces_error = false; - p = path; - for(p = path; p != NULL; p = p_next) { - p1 = strchr(p, ':'); - if (!p1) { - p_next = NULL; - path_len = strlen(p); - } else { - p_next = p1 + 1; - path_len = p1 - p; - } - /* path too long */ - if ((path_len + 1 + filename_len + 1) > PATH_MAX) - continue; - memcpy(buf, p, path_len); - buf[path_len] = '/'; - memcpy(buf + path_len + 1, filename, filename_len); - buf[path_len + 1 + filename_len] = '\0'; - - execve(buf, argv, envp); - - switch(errno) { - case EACCES: - eacces_error = true; - break; - case ENOENT: - case ENOTDIR: - break; - default: - return -1; - } - } - if (eacces_error) - errno = EACCES; - return -1; -} - -static void (*js_os_exec_closefrom)(int); - -#if !defined(EMSCRIPTEN) && !defined(__wasi__) - -static js_once_t js_os_exec_once = JS_ONCE_INIT; - -static void js_os_exec_once_init(void) -{ - js_os_exec_closefrom = dlsym(RTLD_DEFAULT, "closefrom"); -} - -#endif - -/* exec(args[, options]) -> exitcode */ -static JSValue js_os_exec(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSValue options, args = argv[0]; - JSValue val, ret_val; - const char **exec_argv, *file = NULL, *str, *cwd = NULL; - char **envp = environ; - uint32_t exec_argc, i; - int ret, pid, status; - bool block_flag = true, use_path = true; - static const char *std_name[3] = { "stdin", "stdout", "stderr" }; - int std_fds[3]; - uint32_t uid = -1, gid = -1; - - val = JS_GetPropertyStr(ctx, args, "length"); - if (JS_IsException(val)) - return JS_EXCEPTION; - ret = JS_ToUint32(ctx, &exec_argc, val); - JS_FreeValue(ctx, val); - if (ret) - return JS_EXCEPTION; - /* arbitrary limit to avoid overflow */ - if (exec_argc < 1 || exec_argc > 65535) { - return JS_ThrowTypeError(ctx, "invalid number of arguments"); - } - exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); - if (!exec_argv) - return JS_EXCEPTION; - for(i = 0; i < exec_argc; i++) { - val = JS_GetPropertyUint32(ctx, args, i); - if (JS_IsException(val)) - goto exception; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto exception; - exec_argv[i] = str; - } - exec_argv[exec_argc] = NULL; - - for(i = 0; i < 3; i++) - std_fds[i] = i; - - /* get the options, if any */ - if (argc >= 2) { - options = argv[1]; - - if (get_bool_option(ctx, &block_flag, options, "block")) - goto exception; - if (get_bool_option(ctx, &use_path, options, "usePath")) - goto exception; - - val = JS_GetPropertyStr(ctx, options, "file"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - file = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!file) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "cwd"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - cwd = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!cwd) - goto exception; - } - - /* stdin/stdout/stderr handles */ - for(i = 0; i < 3; i++) { - val = JS_GetPropertyStr(ctx, options, std_name[i]); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - int fd; - ret = JS_ToInt32(ctx, &fd, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - std_fds[i] = fd; - } - } - - val = JS_GetPropertyStr(ctx, options, "env"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - envp = build_envp(ctx, val); - JS_FreeValue(ctx, val); - if (!envp) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "uid"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - ret = JS_ToUint32(ctx, &uid, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "gid"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - ret = JS_ToUint32(ctx, &gid, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - } - } - -#if !defined(EMSCRIPTEN) && !defined(__wasi__) - // should happen pre-fork because it calls dlsym() - // and that's not an async-signal-safe function - js_once(&js_os_exec_once, js_os_exec_once_init); -#endif - - pid = fork(); - if (pid < 0) { - JS_ThrowTypeError(ctx, "fork error"); - goto exception; - } - if (pid == 0) { - /* child */ - /* remap the stdin/stdout/stderr handles if necessary */ - for(i = 0; i < 3; i++) { - if (std_fds[i] != i) { - if (dup2(std_fds[i], i) < 0) - _exit(127); - } - } - - if (js_os_exec_closefrom) { - js_os_exec_closefrom(3); - } else { - int fd_max = sysconf(_SC_OPEN_MAX); - for(i = 3; i < fd_max; i++) - close(i); - } - - if (cwd) { - if (chdir(cwd) < 0) - _exit(127); - } - if (uid != -1) { - if (setuid(uid) < 0) - _exit(127); - } - if (gid != -1) { - if (setgid(gid) < 0) - _exit(127); - } - - if (!file) - file = exec_argv[0]; - if (use_path) - ret = my_execvpe(file, (char **)exec_argv, envp); - else - ret = execve(file, (char **)exec_argv, envp); - _exit(127); - } - /* parent */ - if (block_flag) { - for(;;) { - ret = waitpid(pid, &status, 0); - if (ret == pid) { - if (WIFEXITED(status)) { - ret = WEXITSTATUS(status); - break; - } else if (WIFSIGNALED(status)) { - ret = -WTERMSIG(status); - break; - } - } - } - } else { - ret = pid; - } - ret_val = JS_NewInt32(ctx, ret); - done: - JS_FreeCString(ctx, file); - JS_FreeCString(ctx, cwd); - for(i = 0; i < exec_argc; i++) - JS_FreeCString(ctx, exec_argv[i]); - js_free(ctx, exec_argv); - if (envp != environ) { - char **p; - p = envp; - while (*p != NULL) { - js_free(ctx, *p); - p++; - } - js_free(ctx, envp); - } - return ret_val; - exception: - ret_val = JS_EXCEPTION; - goto done; -} - -/* getpid() -> pid */ -static JSValue js_os_getpid(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - return JS_NewInt32(ctx, getpid()); -} - -/* waitpid(pid, block) -> [pid, status] */ -static JSValue js_os_waitpid(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int pid, status, options, ret; - JSValue obj; - - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &options, argv[1])) - return JS_EXCEPTION; - - ret = waitpid(pid, &status, options); - if (ret < 0) { - ret = -errno; - status = 0; - } - - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), - JS_PROP_C_W_E); - return obj; -} - -/* pipe() -> [read_fd, write_fd] or null if error */ -static JSValue js_os_pipe(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int pipe_fds[2], ret; - JSValue obj; - - ret = pipe(pipe_fds); - if (ret < 0) - return JS_NULL; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), - JS_PROP_C_W_E); - return obj; -} - -/* kill(pid, sig) */ -static JSValue js_os_kill(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int pid, sig, ret; - - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &sig, argv[1])) - return JS_EXCEPTION; - ret = js_get_errno(kill(pid, sig)); - return JS_NewInt32(ctx, ret); -} - -/* dup(fd) */ -static JSValue js_os_dup(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd, ret; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - ret = js_get_errno(dup(fd)); - return JS_NewInt32(ctx, ret); -} - -/* dup2(fd) */ -static JSValue js_os_dup2(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - int fd, fd2, ret; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &fd2, argv[1])) - return JS_EXCEPTION; - ret = js_get_errno(dup2(fd, fd2)); - return JS_NewInt32(ctx, ret); -} - -#endif /* !_WIN32 */ - -#ifdef USE_WORKER - -/* Worker */ - -typedef struct { - JSWorkerMessagePipe *recv_pipe; - JSWorkerMessagePipe *send_pipe; - JSWorkerMessageHandler *msg_handler; -} JSWorkerData; - -typedef struct { - char *filename; /* module filename */ - char *basename; /* module base name */ - JSWorkerMessagePipe *recv_pipe, *send_pipe; -} WorkerFuncArgs; - -typedef struct { - int ref_count; - uint64_t buf[]; -} JSSABHeader; - -static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); - -static int atomic_add_int(int *ptr, int v) -{ - return atomic_fetch_add((_Atomic uint32_t*)ptr, v) + v; -} - -/* shared array buffer allocator */ -static void *js_sab_alloc(void *opaque, size_t size) -{ - JSSABHeader *sab; - sab = malloc(sizeof(JSSABHeader) + size); - if (!sab) - return NULL; - sab->ref_count = 1; - return sab->buf; -} - -static void js_sab_free(void *opaque, void *ptr) -{ - JSSABHeader *sab; - int ref_count; - sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); - ref_count = atomic_add_int(&sab->ref_count, -1); - assert(ref_count >= 0); - if (ref_count == 0) { - free(sab); - } -} - -static void js_sab_dup(void *opaque, void *ptr) -{ - JSSABHeader *sab; - sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); - atomic_add_int(&sab->ref_count, 1); -} - -static JSWorkerMessagePipe *js_new_message_pipe(void) -{ - JSWorkerMessagePipe *ps; - int pipe_fds[2]; - - if (pipe(pipe_fds) < 0) - return NULL; - - ps = malloc(sizeof(*ps)); - if (!ps) { - close(pipe_fds[0]); - close(pipe_fds[1]); - return NULL; - } - ps->ref_count = 1; - init_list_head(&ps->msg_queue); - pthread_mutex_init(&ps->mutex, NULL); - ps->read_fd = pipe_fds[0]; - ps->write_fd = pipe_fds[1]; - return ps; -} - -static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) -{ - atomic_add_int(&ps->ref_count, 1); - return ps; -} - -static void js_free_message(JSWorkerMessage *msg) -{ - size_t i; - /* free the SAB */ - for(i = 0; i < msg->sab_tab_len; i++) { - js_sab_free(NULL, msg->sab_tab[i]); - } - free(msg->sab_tab); - free(msg->data); - free(msg); -} - -static void js_free_message_pipe(JSWorkerMessagePipe *ps) -{ - struct list_head *el, *el1; - JSWorkerMessage *msg; - int ref_count; - - if (!ps) - return; - - ref_count = atomic_add_int(&ps->ref_count, -1); - assert(ref_count >= 0); - if (ref_count == 0) { - list_for_each_safe(el, el1, &ps->msg_queue) { - msg = list_entry(el, JSWorkerMessage, link); - js_free_message(msg); - } - pthread_mutex_destroy(&ps->mutex); - close(ps->read_fd); - close(ps->write_fd); - free(ps); - } -} - -static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) -{ - if (port) { - js_free_message_pipe(port->recv_pipe); - JS_FreeValueRT(rt, port->on_message_func); - list_del(&port->link); - js_free_rt(rt, port); - } -} - -static void js_worker_finalizer(JSRuntime *rt, JSValue val) -{ - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque(val, ts->worker_class_id); - if (worker) { - js_free_message_pipe(worker->recv_pipe); - js_free_message_pipe(worker->send_pipe); - js_free_port(rt, worker->msg_handler); - js_free_rt(rt, worker); - } -} - -static JSClassDef js_worker_class = { - "Worker", - .finalizer = js_worker_finalizer, -}; - -static void *worker_func(void *opaque) -{ - WorkerFuncArgs *args = opaque; - JSRuntime *rt; - JSThreadState *ts; - JSContext *ctx; - JSValue val; - - rt = JS_NewRuntime(); - if (rt == NULL) { - fprintf(stderr, "JS_NewRuntime failure"); - exit(1); - } - js_std_init_handlers(rt); - - JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL); - - /* set the pipe to communicate with the parent */ - ts = js_get_thread_state(rt); - ts->recv_pipe = args->recv_pipe; - ts->send_pipe = args->send_pipe; - - /* function pointer to avoid linking the whole JS_NewContext() if - not needed */ - ctx = js_worker_new_context_func(rt); - if (ctx == NULL) { - fprintf(stderr, "JS_NewContext failure"); - } - - JS_SetCanBlock(rt, true); - - js_std_add_helpers(ctx, -1, NULL); - - val = JS_LoadModule(ctx, args->basename, args->filename); - free(args->filename); - free(args->basename); - free(args); - val = js_std_await(ctx, val); - if (JS_IsException(val)) - js_std_dump_error(ctx); - JS_FreeValue(ctx, val); - - js_std_loop(ctx); - - js_std_free_handlers(rt); - JS_FreeContext(ctx); - JS_FreeRuntime(rt); - return NULL; -} - -static JSValue js_worker_ctor_internal(JSContext *ctx, JSValue new_target, - JSWorkerMessagePipe *recv_pipe, - JSWorkerMessagePipe *send_pipe) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSValue obj = JS_UNDEFINED, proto; - JSWorkerData *s; - - /* create the object */ - if (JS_IsUndefined(new_target)) { - proto = JS_GetClassProto(ctx, ts->worker_class_id); - } else { - proto = JS_GetPropertyStr(ctx, new_target, "prototype"); - if (JS_IsException(proto)) - goto fail; - } - obj = JS_NewObjectProtoClass(ctx, proto, ts->worker_class_id); - JS_FreeValue(ctx, proto); - if (JS_IsException(obj)) - goto fail; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - goto fail; - s->recv_pipe = js_dup_message_pipe(recv_pipe); - s->send_pipe = js_dup_message_pipe(send_pipe); - - JS_SetOpaque(obj, s); - return obj; - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_worker_ctor(JSContext *ctx, JSValue new_target, - int argc, JSValue *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - WorkerFuncArgs *args = NULL; - pthread_t tid; - pthread_attr_t attr; - JSValue obj = JS_UNDEFINED; - int ret; - const char *filename = NULL, *basename; - JSAtom basename_atom; - - /* XXX: in order to avoid problems with resource liberation, we - don't support creating workers inside workers */ - if (!is_main_thread(rt)) - return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); - - /* base name, assuming the calling function is a normal JS - function */ - basename_atom = JS_GetScriptOrModuleName(ctx, 1); - if (basename_atom == JS_ATOM_NULL) { - return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); - } - basename = JS_AtomToCString(ctx, basename_atom); - JS_FreeAtom(ctx, basename_atom); - if (!basename) - goto fail; - - /* module name */ - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - - args = malloc(sizeof(*args)); - if (!args) - goto oom_fail; - memset(args, 0, sizeof(*args)); - args->filename = strdup(filename); - args->basename = strdup(basename); - - /* ports */ - args->recv_pipe = js_new_message_pipe(); - if (!args->recv_pipe) - goto oom_fail; - args->send_pipe = js_new_message_pipe(); - if (!args->send_pipe) - goto oom_fail; - - obj = js_worker_ctor_internal(ctx, new_target, - args->send_pipe, args->recv_pipe); - if (JS_IsException(obj)) - goto fail; - - pthread_attr_init(&attr); - /* no join at the end */ - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - // musl libc gives threads 80 kb stacks, much smaller than - // JS_DEFAULT_STACK_SIZE (1 MB) - pthread_attr_setstacksize(&attr, 2 << 20); // 2 MB, glibc default - ret = pthread_create(&tid, &attr, worker_func, args); - pthread_attr_destroy(&attr); - if (ret != 0) { - JS_ThrowTypeError(ctx, "could not create worker"); - goto fail; - } - JS_FreeCString(ctx, basename); - JS_FreeCString(ctx, filename); - return obj; - oom_fail: - JS_ThrowOutOfMemory(ctx); - fail: - JS_FreeCString(ctx, basename); - JS_FreeCString(ctx, filename); - if (args) { - free(args->filename); - free(args->basename); - js_free_message_pipe(args->recv_pipe); - js_free_message_pipe(args->send_pipe); - free(args); - } - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_worker_postMessage(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); - JSWorkerMessagePipe *ps; - size_t data_len, i; - uint8_t *data; - JSWorkerMessage *msg; - JSSABTab sab_tab; - - if (!worker) - return JS_EXCEPTION; - - data = JS_WriteObject2(ctx, &data_len, argv[0], - JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, - &sab_tab); - if (!data) - return JS_EXCEPTION; - - msg = malloc(sizeof(*msg)); - if (!msg) - goto fail; - msg->data = NULL; - msg->sab_tab = NULL; - - /* must reallocate because the allocator may be different */ - msg->data = malloc(data_len); - if (!msg->data) - goto fail; - memcpy(msg->data, data, data_len); - msg->data_len = data_len; - - if (sab_tab.len > 0) { - msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab.len); - if (!msg->sab_tab) - goto fail; - memcpy(msg->sab_tab, sab_tab.tab, sizeof(msg->sab_tab[0]) * sab_tab.len); - } - msg->sab_tab_len = sab_tab.len; - - js_free(ctx, data); - js_free(ctx, sab_tab.tab); - - /* increment the SAB reference counts */ - for(i = 0; i < msg->sab_tab_len; i++) { - js_sab_dup(NULL, msg->sab_tab[i]); - } - - ps = worker->send_pipe; - pthread_mutex_lock(&ps->mutex); - /* indicate that data is present */ - if (list_empty(&ps->msg_queue)) { - uint8_t ch = '\0'; - int ret; - for(;;) { - ret = write(ps->write_fd, &ch, 1); - if (ret == 1) - break; - if (ret < 0 && (errno != EAGAIN || errno != EINTR)) - break; - } - } - list_add_tail(&msg->link, &ps->msg_queue); - pthread_mutex_unlock(&ps->mutex); - return JS_UNDEFINED; - fail: - if (msg) { - free(msg->data); - free(msg->sab_tab); - free(msg); - } - js_free(ctx, data); - js_free(ctx, sab_tab.tab); - return JS_EXCEPTION; - -} - -static JSValue js_worker_set_onmessage(JSContext *ctx, JSValue this_val, - JSValue func) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); - JSWorkerMessageHandler *port; - - if (!worker) - return JS_EXCEPTION; - - port = worker->msg_handler; - if (JS_IsNull(func)) { - if (port) { - js_free_port(rt, port); - worker->msg_handler = NULL; - } - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - if (!port) { - port = js_mallocz(ctx, sizeof(*port)); - if (!port) - return JS_EXCEPTION; - port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); - port->on_message_func = JS_NULL; - list_add_tail(&port->link, &ts->port_list); - worker->msg_handler = port; - } - JS_FreeValue(ctx, port->on_message_func); - port->on_message_func = JS_DupValue(ctx, func); - } - return JS_UNDEFINED; -} - -static JSValue js_worker_get_onmessage(JSContext *ctx, JSValue this_val) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, ts->worker_class_id); - JSWorkerMessageHandler *port; - if (!worker) - return JS_EXCEPTION; - port = worker->msg_handler; - if (port) { - return JS_DupValue(ctx, port->on_message_func); - } else { - return JS_NULL; - } -} - -static const JSCFunctionListEntry js_worker_proto_funcs[] = { - JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), - JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), -}; - -#endif /* USE_WORKER */ - -void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) -{ -#ifdef USE_WORKER - js_worker_new_context_func = func; -#endif -} - -#if defined(_WIN32) -#define OS_PLATFORM "win32" -#elif defined(__APPLE__) -#define OS_PLATFORM "darwin" -#elif defined(EMSCRIPTEN) -#define OS_PLATFORM "js" -#elif defined(__CYGWIN__) -#define OS_PLATFORM "cygwin" -#elif defined(__linux__) -#define OS_PLATFORM "linux" -#elif defined(__OpenBSD__) -#define OS_PLATFORM "openbsd" -#elif defined(__NetBSD__) -#define OS_PLATFORM "netbsd" -#elif defined(__FreeBSD__) -#define OS_PLATFORM "freebsd" -#elif defined(__wasi__) -#define OS_PLATFORM "wasi" -#else -#define OS_PLATFORM "unknown" -#endif - -#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) - -static const JSCFunctionListEntry js_os_funcs[] = { - JS_CFUNC_DEF("open", 2, js_os_open ), - OS_FLAG(O_RDONLY), - OS_FLAG(O_WRONLY), - OS_FLAG(O_RDWR), - OS_FLAG(O_APPEND), - OS_FLAG(O_CREAT), - OS_FLAG(O_EXCL), - OS_FLAG(O_TRUNC), -#if defined(_WIN32) - OS_FLAG(O_BINARY), - OS_FLAG(O_TEXT), -#endif - JS_CFUNC_DEF("close", 1, js_os_close ), - JS_CFUNC_DEF("seek", 3, js_os_seek ), - JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), - JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), - JS_CFUNC_DEF("isatty", 1, js_os_isatty ), -#if !defined(__wasi__) - JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), - JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), -#endif - JS_CFUNC_DEF("remove", 1, js_os_remove ), - JS_CFUNC_DEF("rename", 2, js_os_rename ), - JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), - JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), - JS_CFUNC_DEF("signal", 2, js_os_signal ), - OS_FLAG(SIGINT), - OS_FLAG(SIGABRT), - OS_FLAG(SIGFPE), - OS_FLAG(SIGILL), - OS_FLAG(SIGSEGV), - OS_FLAG(SIGTERM), -#if !defined(_WIN32) && !defined(__wasi__) - OS_FLAG(SIGQUIT), - OS_FLAG(SIGPIPE), - OS_FLAG(SIGALRM), - OS_FLAG(SIGUSR1), - OS_FLAG(SIGUSR2), - OS_FLAG(SIGCHLD), - OS_FLAG(SIGCONT), - OS_FLAG(SIGSTOP), - OS_FLAG(SIGTSTP), - OS_FLAG(SIGTTIN), - OS_FLAG(SIGTTOU), - JS_CFUNC_DEF("cputime", 0, js_os_cputime ), -#endif - JS_CFUNC_DEF("now", 0, js_os_now ), - JS_CFUNC_MAGIC_DEF("setTimeout", 2, js_os_setTimeout, 0 ), - JS_CFUNC_MAGIC_DEF("setInterval", 2, js_os_setTimeout, 1 ), - // per spec: both functions can cancel timeouts and intervals - JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), - JS_CFUNC_DEF("clearInterval", 1, js_os_clearTimeout ), - JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), - JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), - JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), - JS_CFUNC_DEF("chdir", 0, js_os_chdir ), - JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), - JS_CFUNC_DEF("readdir", 1, js_os_readdir ), - /* st_mode constants */ - OS_FLAG(S_IFMT), - OS_FLAG(S_IFIFO), - OS_FLAG(S_IFCHR), - OS_FLAG(S_IFDIR), - OS_FLAG(S_IFBLK), - OS_FLAG(S_IFREG), -#if !defined(_WIN32) - OS_FLAG(S_IFSOCK), - OS_FLAG(S_IFLNK), - OS_FLAG(S_ISGID), - OS_FLAG(S_ISUID), -#endif - JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), - JS_CFUNC_DEF("utimes", 3, js_os_utimes ), - JS_CFUNC_DEF("sleep", 1, js_os_sleep ), -#if !defined(__wasi__) - JS_CFUNC_DEF("realpath", 1, js_os_realpath ), -#endif -#if !defined(_WIN32) && !defined(__wasi__) - JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), - JS_CFUNC_DEF("symlink", 2, js_os_symlink ), - JS_CFUNC_DEF("readlink", 1, js_os_readlink ), - JS_CFUNC_DEF("exec", 1, js_os_exec ), - JS_CFUNC_DEF("getpid", 0, js_os_getpid ), - JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), - OS_FLAG(WNOHANG), - JS_CFUNC_DEF("pipe", 0, js_os_pipe ), - JS_CFUNC_DEF("kill", 2, js_os_kill ), - JS_CFUNC_DEF("dup", 1, js_os_dup ), - JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), -#endif -}; - -static int js_os_init(JSContext *ctx, JSModuleDef *m) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - - ts->can_js_os_poll = true; - -#ifdef USE_WORKER - { - JSValue proto, obj; - /* Worker class */ - JS_NewClassID(rt, &ts->worker_class_id); - JS_NewClass(rt, ts->worker_class_id, &js_worker_class); - proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); - - obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, - JS_CFUNC_constructor, 0); - JS_SetConstructor(ctx, obj, proto); - - JS_SetClassProto(ctx, ts->worker_class_id, proto); - - /* set 'Worker.parent' if necessary */ - if (ts->recv_pipe && ts->send_pipe) { - JS_DefinePropertyValueStr(ctx, obj, "parent", - js_worker_ctor_internal(ctx, JS_UNDEFINED, ts->recv_pipe, ts->send_pipe), - JS_PROP_C_W_E); - } - - JS_SetModuleExport(ctx, m, "Worker", obj); - } -#endif /* USE_WORKER */ - - return JS_SetModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); -} - -JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_os_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); -#ifdef USE_WORKER - JS_AddModuleExport(ctx, m, "Worker"); -#endif - return m; -} - -/**********************************************************/ - -static JSValue js_print(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ -#ifdef _WIN32 - HANDLE handle; - DWORD mode; -#endif - const char *s; - JSValue v; - DynBuf b; - int i; - - dbuf_init(&b); - for(i = 0; i < argc; i++) { - v = argv[i]; - s = JS_ToCString(ctx, v); - if (!s && JS_IsObject(v)) { - JS_FreeValue(ctx, JS_GetException(ctx)); - v = JS_ToObjectString(ctx, v); - s = JS_ToCString(ctx, v); - JS_FreeValue(ctx, v); - } - if (s) { - dbuf_printf(&b, "%s%s", &" "[!i], s); - JS_FreeCString(ctx, s); - } else { - dbuf_printf(&b, "%s", &" "[!i]); - JS_FreeValue(ctx, JS_GetException(ctx)); - } - } - dbuf_putc(&b, '\n'); -#ifdef _WIN32 - // use WriteConsoleA with CP_UTF8 for better Unicode handling vis-a-vis - // the mangling that happens when going through msvcrt's stdio layer, - // *except* when stdout is redirected to something that is not a console - handle = (HANDLE)_get_osfhandle(/*STDOUT_FILENO*/1); // don't CloseHandle - if (GetFileType(handle) != FILE_TYPE_CHAR) - goto fallback; - if (!GetConsoleMode(handle, &mode)) - goto fallback; - handle = GetStdHandle(STD_OUTPUT_HANDLE); - if (handle == INVALID_HANDLE_VALUE) - goto fallback; - mode = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); - WriteConsoleA(handle, b.buf, b.size, NULL, NULL); - SetConsoleOutputCP(mode); - FlushFileBuffers(handle); - goto done; -fallback: -#endif - fwrite(b.buf, 1, b.size, stdout); - fflush(stdout); - goto done; // avoid unused label warning -done: - dbuf_free(&b); - return JS_UNDEFINED; -} - -void js_std_add_helpers(JSContext *ctx, int argc, char **argv) -{ - JSValue global_obj, console, args; - int i; - - /* XXX: should these global definitions be enumerable? */ - global_obj = JS_GetGlobalObject(ctx); - - console = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, console, "log", - JS_NewCFunction(ctx, js_print, "log", 1)); - JS_SetPropertyStr(ctx, global_obj, "console", console); - - /* same methods as the mozilla JS shell */ - if (argc >= 0) { - args = JS_NewArray(ctx); - for(i = 0; i < argc; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); - } - JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); - } - - JS_SetPropertyStr(ctx, global_obj, "print", - JS_NewCFunction(ctx, js_print, "print", 1)); - - JS_FreeValue(ctx, global_obj); -} - -static void js_std_finalize(JSRuntime *rt, void *arg) -{ - JSThreadState *ts = arg; - js_set_thread_state(rt, NULL); - js_free_rt(rt, ts); -} - -void js_std_init_handlers(JSRuntime *rt) -{ - JSThreadState *ts; - - ts = js_mallocz_rt(rt, sizeof(*ts)); - if (!ts) { - fprintf(stderr, "Could not allocate memory for the worker"); - exit(1); - } - init_list_head(&ts->os_rw_handlers); - init_list_head(&ts->os_signal_handlers); - init_list_head(&ts->os_timers); - init_list_head(&ts->port_list); - - ts->next_timer_id = 1; - - js_set_thread_state(rt, ts); - JS_AddRuntimeFinalizer(rt, js_std_finalize, ts); - -#ifdef USE_WORKER - /* set the SharedArrayBuffer memory handlers */ - { - JSSharedArrayBufferFunctions sf; - memset(&sf, 0, sizeof(sf)); - sf.sab_alloc = js_sab_alloc; - sf.sab_free = js_sab_free; - sf.sab_dup = js_sab_dup; - JS_SetSharedArrayBufferFunctions(rt, &sf); - } -#endif -} - -void js_std_free_handlers(JSRuntime *rt) -{ - JSThreadState *ts = js_get_thread_state(rt); - struct list_head *el, *el1; - - list_for_each_safe(el, el1, &ts->os_rw_handlers) { - JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); - free_rw_handler(rt, rh); - } - - list_for_each_safe(el, el1, &ts->os_signal_handlers) { - JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); - free_sh(rt, sh); - } - - list_for_each_safe(el, el1, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - free_timer(rt, th); - } - -#ifdef USE_WORKER - /* XXX: free port_list ? */ - js_free_message_pipe(ts->recv_pipe); - js_free_message_pipe(ts->send_pipe); -#endif -} - -static void js_dump_obj(JSContext *ctx, FILE *f, JSValue val) -{ - const char *str; - - str = JS_ToCString(ctx, val); - if (str) { - fprintf(f, "%s\n", str); - JS_FreeCString(ctx, str); - } else { - fprintf(f, "[exception]\n"); - } -} - -static void js_std_dump_error1(JSContext *ctx, JSValue exception_val) -{ - JSValue val; - bool is_error; - - is_error = JS_IsError(ctx, exception_val); - js_dump_obj(ctx, stderr, exception_val); - if (is_error) { - val = JS_GetPropertyStr(ctx, exception_val, "stack"); - } else { - js_std_cmd(/*ErrorBackTrace*/2, ctx, &val); - } - if (!JS_IsUndefined(val)) { - js_dump_obj(ctx, stderr, val); - JS_FreeValue(ctx, val); - } -} - -void js_std_dump_error(JSContext *ctx) -{ - JSValue exception_val; - - exception_val = JS_GetException(ctx); - js_std_dump_error1(ctx, exception_val); - JS_FreeValue(ctx, exception_val); -} - -void js_std_promise_rejection_tracker(JSContext *ctx, JSValue promise, - JSValue reason, - bool is_handled, void *opaque) -{ - if (!is_handled) { - fprintf(stderr, "Possibly unhandled promise rejection: "); - js_std_dump_error1(ctx, reason); - fflush(stderr); - exit(1); - } -} - -/* main loop which calls the user JS callbacks */ -int js_std_loop(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSContext *ctx1; - int err; - - for(;;) { - /* execute the pending jobs */ - for(;;) { - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); - if (err <= 0) { - if (err < 0) - goto done; - break; - } - } - - if (!ts->can_js_os_poll || js_os_poll(ctx)) - break; - } -done: - return JS_HasException(ctx); -} - -/* Wait for a promise and execute pending jobs while waiting for - it. Return the promise result or JS_EXCEPTION in case of promise - rejection. */ -JSValue js_std_await(JSContext *ctx, JSValue obj) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSValue ret; - int state; - - for(;;) { - state = JS_PromiseState(ctx, obj); - if (state == JS_PROMISE_FULFILLED) { - ret = JS_PromiseResult(ctx, obj); - JS_FreeValue(ctx, obj); - break; - } else if (state == JS_PROMISE_REJECTED) { - ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); - JS_FreeValue(ctx, obj); - break; - } else if (state == JS_PROMISE_PENDING) { - JSContext *ctx1; - int err; - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); - if (err < 0) { - js_std_dump_error(ctx1); - } - if (ts->can_js_os_poll) - js_os_poll(ctx); - } else { - /* not a promise */ - ret = obj; - break; - } - } - return ret; -} - -void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, - int load_only) -{ - JSValue obj, val; - obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); - if (JS_IsException(obj)) - goto exception; - if (load_only) { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { - if (js_module_set_import_meta(ctx, obj, false, false) < 0) - goto exception; - } - } else { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { - if (JS_ResolveModule(ctx, obj) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - if (js_module_set_import_meta(ctx, obj, false, true) < 0) - goto exception; - val = JS_EvalFunction(ctx, obj); - val = js_std_await(ctx, val); - } else { - val = JS_EvalFunction(ctx, obj); - } - if (JS_IsException(val)) { - exception: - js_std_dump_error(ctx); - exit(1); - } - JS_FreeValue(ctx, val); - } -} - -static JSValue js_bjson_read(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - uint8_t *buf; - uint64_t pos, len; - JSValue obj; - size_t size; - int flags; - - if (JS_ToIndex(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &flags, argv[3])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[0]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "array buffer overflow"); - obj = JS_ReadObject(ctx, buf + pos, len, flags); - return obj; -} - -static JSValue js_bjson_write(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - size_t len; - uint8_t *buf; - JSValue array; - int flags; - - if (JS_ToInt32(ctx, &flags, argv[1])) - return JS_EXCEPTION; - buf = JS_WriteObject(ctx, &len, argv[0], flags); - if (!buf) - return JS_EXCEPTION; - array = JS_NewArrayBufferCopy(ctx, buf, len); - js_free(ctx, buf); - return array; -} - - -static const JSCFunctionListEntry js_bjson_funcs[] = { - JS_CFUNC_DEF("read", 4, js_bjson_read ), - JS_CFUNC_DEF("write", 2, js_bjson_write ), -#define DEF(x) JS_PROP_INT32_DEF(#x, JS_##x, JS_PROP_CONFIGURABLE) - DEF(READ_OBJ_BYTECODE), - DEF(READ_OBJ_REFERENCE), - DEF(READ_OBJ_SAB), - DEF(WRITE_OBJ_BYTECODE), - DEF(WRITE_OBJ_REFERENCE), - DEF(WRITE_OBJ_SAB), - DEF(WRITE_OBJ_STRIP_DEBUG), - DEF(WRITE_OBJ_STRIP_SOURCE), -#undef DEF -}; - -static int js_bjson_init(JSContext *ctx, JSModuleDef *m) -{ - return JS_SetModuleExportList(ctx, m, js_bjson_funcs, - countof(js_bjson_funcs)); -} - -JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_bjson_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_bjson_funcs, countof(js_bjson_funcs)); - return m; -} diff --git a/NativeScript/napi/quickjs/source/quickjs-libc.h b/NativeScript/napi/quickjs/source/quickjs-libc.h deleted file mode 100644 index 6b49363f7..000000000 --- a/NativeScript/napi/quickjs/source/quickjs-libc.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * QuickJS C library - * - * Copyright (c) 2017-2018 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#ifndef QUICKJS_LIBC_H -#define QUICKJS_LIBC_H - -#include -#include -#include - -#include "quickjs.h" - -#ifdef __cplusplus -extern "C" { -#endif - -JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); -JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); -JSModuleDef *js_init_module_bjson(JSContext *ctx, const char *module_name); -void js_std_add_helpers(JSContext *ctx, int argc, char **argv); -int js_std_loop(JSContext *ctx); -JSValue js_std_await(JSContext *ctx, JSValue obj); -void js_std_init_handlers(JSRuntime *rt); -void js_std_free_handlers(JSRuntime *rt); -void js_std_dump_error(JSContext *ctx); -uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); -int js_module_set_import_meta(JSContext *ctx, JSValue func_val, - bool use_realpath, bool is_main); -JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque); -void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, - int flags); -void js_std_promise_rejection_tracker(JSContext *ctx, JSValue promise, - JSValue reason, - bool is_handled, void *opaque); -void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* QUICKJS_LIBC_H */ diff --git a/NativeScript/napi/quickjs/source/quickjs-opcode.h b/NativeScript/napi/quickjs/source/quickjs-opcode.h deleted file mode 100644 index fb5394c58..000000000 --- a/NativeScript/napi/quickjs/source/quickjs-opcode.h +++ /dev/null @@ -1,375 +0,0 @@ -/* - * QuickJS opcode definitions - * - * Copyright (c) 2017-2018 Fabrice Bellard - * Copyright (c) 2017-2018 Charlie Gordon - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifdef FMT -FMT(none) -FMT(none_int) -FMT(none_loc) -FMT(none_arg) -FMT(none_var_ref) -FMT(u8) -FMT(i8) -FMT(loc8) -FMT(const8) -FMT(label8) -FMT(u16) -FMT(i16) -FMT(label16) -FMT(npop) -FMT(npopx) -FMT(npop_u16) -FMT(loc) -FMT(arg) -FMT(var_ref) -FMT(u32) -FMT(u32x2) -FMT(i32) -FMT(const) -FMT(label) -FMT(atom) -FMT(atom_u8) -FMT(atom_u16) -FMT(atom_label_u8) -FMT(atom_label_u16) -FMT(label_u16) -#undef FMT -#endif /* FMT */ - -#ifdef DEF - -#ifndef def -#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) -#endif - -DEF(invalid, 1, 0, 0, none) /* never emitted */ - -/* push values */ -DEF( push_i32, 5, 0, 1, i32) -DEF( push_const, 5, 0, 1, const) -DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ -DEF(push_atom_value, 5, 0, 1, atom) -DEF( private_symbol, 5, 0, 1, atom) -DEF( undefined, 1, 0, 1, none) -DEF( null, 1, 0, 1, none) -DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ -DEF( push_false, 1, 0, 1, none) -DEF( push_true, 1, 0, 1, none) -DEF( object, 1, 0, 1, none) -DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ -DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ - -DEF( drop, 1, 1, 0, none) /* a -> */ -DEF( nip, 1, 2, 1, none) /* a b -> b */ -DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ -DEF( dup, 1, 1, 2, none) /* a -> a a */ -DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ -DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ -DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ -DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ -DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ -DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ -DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ -DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ -DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ -DEF( swap, 1, 2, 2, none) /* a b -> b a */ -DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ -DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ -DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ -DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ -DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ - -DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ -DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ -DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ -DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ -DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ -DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ -DEF( apply, 3, 3, 1, u16) -DEF( return, 1, 1, 0, none) -DEF( return_undef, 1, 0, 0, none) -DEF(check_ctor_return, 1, 1, 2, none) -DEF( check_ctor, 1, 0, 0, none) -DEF( init_ctor, 1, 0, 1, none) -DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ -DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ -DEF( return_async, 1, 1, 0, none) -DEF( throw, 1, 1, 0, none) -DEF( throw_error, 6, 0, 0, atom_u8) -DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ -DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ -DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a - bytecode string */ -DEF( get_super, 1, 1, 1, none) -DEF( import, 1, 1, 1, none) /* dynamic module import */ - -DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */ -DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ -DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ -DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ -DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ -DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ - -DEF( get_ref_value, 1, 2, 3, none) -DEF( put_ref_value, 1, 3, 0, none) - -DEF( define_var, 6, 0, 0, atom_u8) -DEF(check_define_var, 6, 0, 0, atom_u8) -DEF( define_func, 6, 1, 0, atom_u8) - -// order matters, see IC counterparts -DEF( get_field, 5, 1, 1, atom) -DEF( get_field2, 5, 1, 2, atom) -DEF( put_field, 5, 2, 0, atom) - -DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ -DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ -DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ -DEF( get_array_el, 1, 2, 1, none) -DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ -DEF( put_array_el, 1, 3, 0, none) -DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ -DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ -DEF( define_field, 5, 2, 1, atom) -DEF( set_name, 5, 1, 1, atom) -DEF(set_name_computed, 1, 2, 2, none) -DEF( set_proto, 1, 2, 1, none) -DEF(set_home_object, 1, 2, 2, none) -DEF(define_array_el, 1, 3, 2, none) -DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ -DEF(copy_data_properties, 2, 3, 3, u8) -DEF( define_method, 6, 2, 1, atom_u8) -DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ -DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ -DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ - -DEF( get_loc, 3, 0, 1, loc) -DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ -DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ -DEF( get_arg, 3, 0, 1, arg) -DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ -DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */ -DEF( get_var_ref, 3, 0, 1, var_ref) -DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ -DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ -DEF(set_loc_uninitialized, 3, 0, 0, loc) -DEF( get_loc_check, 3, 0, 1, loc) -DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ -DEF( put_loc_check_init, 3, 1, 0, loc) -DEF(get_var_ref_check, 3, 0, 1, var_ref) -DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ -DEF(put_var_ref_check_init, 3, 1, 0, var_ref) -DEF( close_loc, 3, 0, 0, loc) -DEF( if_false, 5, 1, 0, label) -DEF( if_true, 5, 1, 0, label) /* must come after if_false */ -DEF( goto, 5, 0, 0, label) /* must come after if_true */ -DEF( catch, 5, 0, 1, label) -DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ -DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ -DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ - -DEF( to_object, 1, 1, 1, none) -//DEF( to_string, 1, 1, 1, none) -DEF( to_propkey, 1, 1, 1, none) -DEF( to_propkey2, 1, 2, 2, none) - -DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ -DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) - -DEF( make_loc_ref, 7, 0, 2, atom_u16) -DEF( make_arg_ref, 7, 0, 2, atom_u16) -DEF(make_var_ref_ref, 7, 0, 2, atom_u16) -DEF( make_var_ref, 5, 0, 2, atom) - -DEF( for_in_start, 1, 1, 1, none) -DEF( for_of_start, 1, 1, 3, none) -DEF(for_await_of_start, 1, 1, 3, none) -DEF( for_in_next, 1, 1, 3, none) -DEF( for_of_next, 2, 3, 5, u8) -DEF(iterator_check_object, 1, 1, 1, none) -DEF(iterator_get_value_done, 1, 1, 2, none) -DEF( iterator_close, 1, 3, 0, none) -DEF( iterator_next, 1, 4, 4, none) -DEF( iterator_call, 2, 4, 5, u8) -DEF( initial_yield, 1, 0, 0, none) -DEF( yield, 1, 1, 2, none) -DEF( yield_star, 1, 1, 2, none) -DEF(async_yield_star, 1, 1, 2, none) -DEF( await, 1, 1, 1, none) - -/* arithmetic/logic operations */ -DEF( neg, 1, 1, 1, none) -DEF( plus, 1, 1, 1, none) -DEF( dec, 1, 1, 1, none) -DEF( inc, 1, 1, 1, none) -DEF( post_dec, 1, 1, 2, none) -DEF( post_inc, 1, 1, 2, none) -DEF( dec_loc, 2, 0, 0, loc8) -DEF( inc_loc, 2, 0, 0, loc8) -DEF( add_loc, 2, 1, 0, loc8) -DEF( not, 1, 1, 1, none) -DEF( lnot, 1, 1, 1, none) -DEF( typeof, 1, 1, 1, none) -DEF( delete, 1, 2, 1, none) -DEF( delete_var, 5, 0, 1, atom) - -/* warning: order matters (see js_parse_assign_expr) */ -DEF( mul, 1, 2, 1, none) -DEF( div, 1, 2, 1, none) -DEF( mod, 1, 2, 1, none) -DEF( add, 1, 2, 1, none) -DEF( sub, 1, 2, 1, none) -DEF( shl, 1, 2, 1, none) -DEF( sar, 1, 2, 1, none) -DEF( shr, 1, 2, 1, none) -DEF( and, 1, 2, 1, none) -DEF( xor, 1, 2, 1, none) -DEF( or, 1, 2, 1, none) -DEF( pow, 1, 2, 1, none) - -DEF( lt, 1, 2, 1, none) -DEF( lte, 1, 2, 1, none) -DEF( gt, 1, 2, 1, none) -DEF( gte, 1, 2, 1, none) -DEF( instanceof, 1, 2, 1, none) -DEF( in, 1, 2, 1, none) -DEF( eq, 1, 2, 1, none) -DEF( neq, 1, 2, 1, none) -DEF( strict_eq, 1, 2, 1, none) -DEF( strict_neq, 1, 2, 1, none) -DEF(is_undefined_or_null, 1, 1, 1, none) -DEF( private_in, 1, 2, 1, none) -/* must be the last non short and non temporary opcode */ -DEF( nop, 1, 0, 0, none) - -/* temporary opcodes: never emitted in the final bytecode */ - -def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ -def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ - -def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ - -def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ -def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ -def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ -def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ -def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ -def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ -def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ -def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ -def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ - -def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */ - -DEF( push_minus1, 1, 0, 1, none_int) -DEF( push_0, 1, 0, 1, none_int) -DEF( push_1, 1, 0, 1, none_int) -DEF( push_2, 1, 0, 1, none_int) -DEF( push_3, 1, 0, 1, none_int) -DEF( push_4, 1, 0, 1, none_int) -DEF( push_5, 1, 0, 1, none_int) -DEF( push_6, 1, 0, 1, none_int) -DEF( push_7, 1, 0, 1, none_int) -DEF( push_i8, 2, 0, 1, i8) -DEF( push_i16, 3, 0, 1, i16) -DEF( push_const8, 2, 0, 1, const8) -DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ -DEF(push_empty_string, 1, 0, 1, none) - -DEF( get_loc8, 2, 0, 1, loc8) -DEF( put_loc8, 2, 1, 0, loc8) -DEF( set_loc8, 2, 1, 1, loc8) - -DEF( get_loc0_loc1, 1, 0, 2, none_loc) -DEF( get_loc0, 1, 0, 1, none_loc) -DEF( get_loc1, 1, 0, 1, none_loc) -DEF( get_loc2, 1, 0, 1, none_loc) -DEF( get_loc3, 1, 0, 1, none_loc) -DEF( put_loc0, 1, 1, 0, none_loc) -DEF( put_loc1, 1, 1, 0, none_loc) -DEF( put_loc2, 1, 1, 0, none_loc) -DEF( put_loc3, 1, 1, 0, none_loc) -DEF( set_loc0, 1, 1, 1, none_loc) -DEF( set_loc1, 1, 1, 1, none_loc) -DEF( set_loc2, 1, 1, 1, none_loc) -DEF( set_loc3, 1, 1, 1, none_loc) -DEF( get_arg0, 1, 0, 1, none_arg) -DEF( get_arg1, 1, 0, 1, none_arg) -DEF( get_arg2, 1, 0, 1, none_arg) -DEF( get_arg3, 1, 0, 1, none_arg) -DEF( put_arg0, 1, 1, 0, none_arg) -DEF( put_arg1, 1, 1, 0, none_arg) -DEF( put_arg2, 1, 1, 0, none_arg) -DEF( put_arg3, 1, 1, 0, none_arg) -DEF( set_arg0, 1, 1, 1, none_arg) -DEF( set_arg1, 1, 1, 1, none_arg) -DEF( set_arg2, 1, 1, 1, none_arg) -DEF( set_arg3, 1, 1, 1, none_arg) -DEF( get_var_ref0, 1, 0, 1, none_var_ref) -DEF( get_var_ref1, 1, 0, 1, none_var_ref) -DEF( get_var_ref2, 1, 0, 1, none_var_ref) -DEF( get_var_ref3, 1, 0, 1, none_var_ref) -DEF( put_var_ref0, 1, 1, 0, none_var_ref) -DEF( put_var_ref1, 1, 1, 0, none_var_ref) -DEF( put_var_ref2, 1, 1, 0, none_var_ref) -DEF( put_var_ref3, 1, 1, 0, none_var_ref) -DEF( set_var_ref0, 1, 1, 1, none_var_ref) -DEF( set_var_ref1, 1, 1, 1, none_var_ref) -DEF( set_var_ref2, 1, 1, 1, none_var_ref) -DEF( set_var_ref3, 1, 1, 1, none_var_ref) - -DEF( get_length, 1, 1, 1, none) - -DEF( if_false8, 2, 1, 0, label8) -DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ -DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ -DEF( goto16, 3, 0, 0, label16) - -DEF( call0, 1, 1, 1, npopx) -DEF( call1, 1, 1, 1, npopx) -DEF( call2, 1, 1, 1, npopx) -DEF( call3, 1, 1, 1, npopx) - -DEF( is_undefined, 1, 1, 1, none) -DEF( is_null, 1, 1, 1, none) -DEF(typeof_is_undefined, 1, 1, 1, none) -DEF( typeof_is_function, 1, 1, 1, none) - -// order matters, see non-IC counterparts -DEF( get_field_ic, 5, 1, 1, none) -DEF( get_field2_ic, 5, 1, 2, none) -DEF( put_field_ic, 5, 2, 0, none) - -#undef DEF -#undef def -#endif /* DEF */ diff --git a/NativeScript/napi/quickjs/source/quickjs.c b/NativeScript/napi/quickjs/source/quickjs.c deleted file mode 100644 index 774d80bef..000000000 --- a/NativeScript/napi/quickjs/source/quickjs.c +++ /dev/null @@ -1,56487 +0,0 @@ -/* - * QuickJS Javascript Engine - * - * Copyright (c) 2017-2024 Fabrice Bellard - * Copyright (c) 2017-2024 Charlie Gordon - * Copyright (c) 2023-2025 Ben Noordhuis - * Copyright (c) 2023-2025 Saúl Ibarra Corretgé - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -#include -#include -#include -#include -#include -#include -#if !defined(_MSC_VER) -#include -#if defined(_WIN32) -#include -#include -#endif -#endif -#include -#include -#include - -#include "cutils.h" -#include "list.h" -#include "quickjs.h" -#include "libregexp.h" -#include "libbf.h" - -#if defined(EMSCRIPTEN) || defined(_MSC_VER) -#define DIRECT_DISPATCH 0 -#else -#define DIRECT_DISPATCH 1 -#endif - -#if defined(__APPLE__) -#define MALLOC_OVERHEAD 0 -#else -#define MALLOC_OVERHEAD 8 -#endif - -#if defined(__NEWLIB__) -#define NO_TM_GMTOFF -#endif - -#ifdef ANDROID -#include -#define LOG_TAG "QuickJS" -#define printf(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) -#endif - -// atomic_store etc. are completely busted in recent versions of tcc; -// somehow the compiler forgets to load |ptr| into %rdi when calling -// the __atomic_*() helpers in its lib/stdatomic.c and lib/atomic.S -#if !defined(__TINYC__) && !defined(EMSCRIPTEN) && !defined(__wasi__) && !__STDC_NO_ATOMICS__ -#include "quickjs-c-atomics.h" -#define CONFIG_ATOMICS -#endif - -#ifndef __GNUC__ -#define __extension__ -#endif - -#ifndef NDEBUG -#define ENABLE_DUMPS -#endif - -//#define FORCE_GC_AT_MALLOC /* test the GC by forcing it before each object allocation */ - -#define check_dump_flag(rt, flag) ((rt->dump_flags & (flag +0)) == (flag +0)) - -#define STRINGIFY_(x) #x -#define STRINGIFY(x) STRINGIFY_(x) - -#define QJS_VERSION_STRING \ - STRINGIFY(QJS_VERSION_MAJOR) "." STRINGIFY(QJS_VERSION_MINOR) "." STRINGIFY(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX - -const char* JS_GetVersion(void) { - return QJS_VERSION_STRING; -} - -#undef STRINFIGY_ -#undef STRINGIFY - -enum { - /* classid tag */ /* union usage | properties */ - JS_CLASS_OBJECT = 1, /* must be first */ - JS_CLASS_ARRAY, /* u.array | length */ - JS_CLASS_ERROR, - JS_CLASS_NUMBER, /* u.object_data */ - JS_CLASS_STRING, /* u.object_data */ - JS_CLASS_BOOLEAN, /* u.object_data */ - JS_CLASS_SYMBOL, /* u.object_data */ - JS_CLASS_ARGUMENTS, /* u.array | length */ - JS_CLASS_MAPPED_ARGUMENTS, /* | length */ - JS_CLASS_DATE, /* u.object_data */ - JS_CLASS_MODULE_NS, - JS_CLASS_C_FUNCTION, /* u.cfunc */ - JS_CLASS_BYTECODE_FUNCTION, /* u.func */ - JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ - JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ - JS_CLASS_GENERATOR_FUNCTION, /* u.func */ - JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ - JS_CLASS_REGEXP, /* u.regexp */ - JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ - JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ - JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ - JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ - JS_CLASS_DATAVIEW, /* u.typed_array */ - JS_CLASS_BIG_INT, /* u.object_data */ - JS_CLASS_MAP, /* u.map_state */ - JS_CLASS_SET, /* u.map_state */ - JS_CLASS_WEAKMAP, /* u.map_state */ - JS_CLASS_WEAKSET, /* u.map_state */ - JS_CLASS_ITERATOR, - JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ - JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ - JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ - JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ - JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ - JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ - JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ - JS_CLASS_GENERATOR, /* u.generator_data */ - JS_CLASS_PROXY, /* u.proxy_data */ - JS_CLASS_PROMISE, /* u.promise_data */ - JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ - JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ - JS_CLASS_ASYNC_FUNCTION, /* u.func */ - JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ - JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ - JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ - JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ - JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ - JS_CLASS_WEAK_REF, - JS_CLASS_FINALIZATION_REGISTRY, - JS_CLASS_CALL_SITE, - - JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ -}; - -/* number of typed array types */ -#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) -static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; -#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) - -typedef enum JSErrorEnum { - JS_EVAL_ERROR, - JS_RANGE_ERROR, - JS_REFERENCE_ERROR, - JS_SYNTAX_ERROR, - JS_TYPE_ERROR, - JS_URI_ERROR, - JS_INTERNAL_ERROR, - JS_AGGREGATE_ERROR, - - JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ - JS_PLAIN_ERROR = JS_NATIVE_ERROR_COUNT -} JSErrorEnum; - -#define JS_MAX_LOCAL_VARS 65535 -#define JS_STACK_SIZE_MAX 65534 -#define JS_STRING_LEN_MAX ((1 << 30) - 1) - -#define __exception __attribute__((warn_unused_result)) - -typedef struct JSShape JSShape; -typedef struct JSString JSString; -typedef struct JSString JSAtomStruct; - -#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) - -typedef enum { - JS_GC_PHASE_NONE, - JS_GC_PHASE_DECREF, - JS_GC_PHASE_REMOVE_CYCLES, -} JSGCPhaseEnum; - -typedef struct JSMallocState { - size_t malloc_count; - size_t malloc_size; - size_t malloc_limit; - void *opaque; /* user opaque */ -} JSMallocState; - -typedef struct JSRuntimeFinalizerState { - struct JSRuntimeFinalizerState *next; - JSRuntimeFinalizer *finalizer; - void *arg; -} JSRuntimeFinalizerState; - -struct JSRuntime { - JSMallocFunctions mf; - JSMallocState malloc_state; - const char *rt_info; - - int atom_hash_size; /* power of two */ - int atom_count; - int atom_size; - int atom_count_resize; /* resize hash table at this count */ - uint32_t *atom_hash; - JSAtomStruct **atom_array; - int atom_free_index; /* 0 = none */ - - JSClassID js_class_id_alloc; /* counter for user defined classes */ - int class_count; /* size of class_array */ - JSClass *class_array; - - struct list_head context_list; /* list of JSContext.link */ - /* list of JSGCObjectHeader.link. List of allocated GC objects (used - by the garbage collector) */ - struct list_head gc_obj_list; - /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ - struct list_head gc_zero_ref_count_list; - struct list_head tmp_obj_list; /* used during GC */ - JSGCPhaseEnum gc_phase : 8; - size_t malloc_gc_threshold; - int (*gc_before_callback)(JSRuntime*); /* Callback before the gc event takes place */ - void (*gc_after_callback)(JSRuntime*); /* Callback after the gc event takes place */ -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - struct list_head string_list; /* list of JSString.link */ -#endif - /* stack limitation */ - uintptr_t stack_size; /* in bytes, 0 if no limit */ - uintptr_t stack_top; - uintptr_t stack_limit; /* lower stack limit */ - - JSValue current_exception; - /* true if inside an out of memory error, to avoid recursing */ - bool in_out_of_memory; - /* and likewise if inside Error.prepareStackTrace() */ - bool in_prepare_stack_trace; - /* true if inside JS_FreeRuntime */ - bool in_free; - - struct JSStackFrame *current_stack_frame; - - JSInterruptHandler *interrupt_handler; - void *interrupt_opaque; - - JSHostPromiseRejectionTracker *host_promise_rejection_tracker; - void *host_promise_rejection_tracker_opaque; - - struct list_head job_list; /* list of JSJobEntry.link */ - struct list_head weakref_keepalive_list; /* strong refs kept until the current host job completes */ - - JSModuleNormalizeFunc *module_normalize_func; - JSModuleLoaderFunc *module_loader_func; - void *module_loader_opaque; - /* timestamp for internal use in module evaluation */ - int64_t module_async_evaluation_next_timestamp; - - /* used to allocate, free and clone SharedArrayBuffers */ - JSSharedArrayBufferFunctions sab_funcs; - - bool can_block; /* true if Atomics.wait can block */ - uint32_t dump_flags : 24; - - /* Shape hash table */ - int shape_hash_bits; - int shape_hash_size; - int shape_hash_count; /* number of hashed shapes */ - JSShape **shape_hash; - bf_context_t bf_ctx; - void *user_opaque; - void *libc_opaque; - JSRuntimeFinalizerState *finalizers; -}; - -struct JSClass { - uint32_t class_id; /* 0 means free entry */ - JSAtom class_name; - JSClassFinalizer *finalizer; - JSClassGCMark *gc_mark; - JSClassCall *call; - /* pointers for exotic behavior, can be NULL if none are present */ - const JSClassExoticMethods *exotic; -}; - -typedef struct JSStackFrame { - struct JSStackFrame *prev_frame; /* NULL if first stack frame */ - JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ - JSValue *arg_buf; /* arguments */ - JSValue *var_buf; /* variables */ - struct list_head var_ref_list; /* list of JSVarRef.link */ - uint8_t *cur_pc; /* only used in bytecode functions : PC of the - instruction after the call */ - uint32_t arg_count : 31; - uint32_t is_strict_mode : 1; - /* only used in generators. Current stack pointer value. NULL if - the function is running. */ - JSValue *cur_sp; -} JSStackFrame; - -typedef struct JSWeakRefKeepAliveEntry { - struct list_head link; - JSValue value; -} JSWeakRefKeepAliveEntry; - -typedef enum { - JS_GC_OBJ_TYPE_JS_OBJECT, - JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, - JS_GC_OBJ_TYPE_SHAPE, - JS_GC_OBJ_TYPE_VAR_REF, - JS_GC_OBJ_TYPE_ASYNC_FUNCTION, - JS_GC_OBJ_TYPE_JS_CONTEXT, -} JSGCObjectTypeEnum; - -/* header for GC objects. GC objects are C data structures with a - reference count that can reference other GC objects. JS Objects are - a particular type of GC object. */ -struct JSGCObjectHeader { - int ref_count; /* must come first, 32-bit */ - JSGCObjectTypeEnum gc_obj_type : 4; - uint8_t mark : 4; /* used by the GC */ - uint8_t dummy1; /* not used by the GC */ - uint16_t dummy2; /* not used by the GC */ - struct list_head link; -}; - -typedef struct JSVarRef { - union { - JSGCObjectHeader header; /* must come first */ - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - - /* 0 : the JSVarRef is on the stack. header.link is an element - of JSStackFrame.var_ref_list. - 1 : the JSVarRef is detached. header.link has the normal meanning - */ - uint8_t is_detached : 1; - uint8_t is_arg : 1; - uint16_t var_idx; /* index of the corresponding function variable on - the stack */ - }; - }; - JSValue *pvalue; /* pointer to the value, either on the stack or - to 'value' */ - JSValue value; /* used when the variable is no longer on the stack */ -} JSVarRef; - -// typedef struct JSRefCountHeader { -// int ref_count; -// } JSRefCountHeader; - -/* the same structure is used for big integers. - Big integers are never infinite or NaNs */ -typedef struct JSBigInt { - JSRefCountHeader header; /* must come first, 32-bit */ - bf_t num; -} JSBigInt; - -typedef enum { - JS_AUTOINIT_ID_PROTOTYPE, - JS_AUTOINIT_ID_MODULE_NS, - JS_AUTOINIT_ID_PROP, -} JSAutoInitIDEnum; - -/* must be large enough to have a negligible runtime cost and small - enough to call the interrupt callback often. */ -#define JS_INTERRUPT_COUNTER_INIT 10000 - -struct JSContext { - JSGCObjectHeader header; /* must come first */ - JSRuntime *rt; - struct list_head link; - - uint16_t binary_object_count; - int binary_object_size; - - JSShape *array_shape; /* initial shape for Array objects */ - - JSValue *class_proto; - JSValue function_proto; - JSValue function_ctor; - JSValue array_ctor; - JSValue regexp_ctor; - JSValue promise_ctor; - JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; - JSValue error_ctor; - JSValue error_back_trace; - JSValue error_prepare_stack; - int error_stack_trace_limit; - JSValue iterator_ctor; - JSValue iterator_proto; - JSValue async_iterator_proto; - JSValue array_proto_values; - JSValue throw_type_error; - JSValue eval_obj; - - JSValue global_obj; /* global object */ - JSValue global_var_obj; /* contains the global let/const definitions */ - - double time_origin; - - uint64_t random_state; - bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */ - /* when the counter reaches zero, JSRutime.interrupt_handler is called */ - int interrupt_counter; - - struct list_head loaded_modules; /* list of JSModuleDef.link */ - - /* if NULL, RegExp compilation is not supported */ - JSValue (*compile_regexp)(JSContext *ctx, JSValue pattern, - JSValue flags); - /* if NULL, eval is not supported */ - JSValue (*eval_internal)(JSContext *ctx, JSValue this_obj, - const char *input, size_t input_len, - const char *filename, int flags, int scope_idx); - - JSValue regexp_left_ctx; // RegExp.leftContext - JSValue regexp_right_ctx; // RegExp.rightContext - JSValue regexp_last_match_str; - int64_t regexp_last_match_start; - int64_t regexp_last_match_end; - - void *user_opaque; -}; - -typedef union JSFloat64Union { - double d; - uint64_t u64; - uint32_t u32[2]; -} JSFloat64Union; - -typedef enum { - JS_WEAK_REF_KIND_MAP, - JS_WEAK_REF_KIND_WEAK_REF, - JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY, -} JSWeakRefKindEnum; - -typedef struct JSWeakRefRecord { - JSWeakRefKindEnum kind; - struct JSWeakRefRecord *next_weak_ref; - union { - struct JSMapRecord *map_record; - struct JSWeakRefData *weak_ref_data; - struct JSFinRecEntry *fin_rec_entry; - } u; -} JSWeakRefRecord; - -enum { - JS_ATOM_TYPE_STRING = 1, - JS_ATOM_TYPE_GLOBAL_SYMBOL, - JS_ATOM_TYPE_SYMBOL, - JS_ATOM_TYPE_PRIVATE, -}; - -enum { - JS_ATOM_HASH_SYMBOL, - JS_ATOM_HASH_PRIVATE, -}; - -typedef enum { - JS_ATOM_KIND_STRING, - JS_ATOM_KIND_SYMBOL, - JS_ATOM_KIND_PRIVATE, -} JSAtomKindEnum; - -#define JS_ATOM_HASH_MASK ((1 << 30) - 1) - -struct JSString { - JSRefCountHeader header; /* must come first, 32-bit */ - uint32_t len : 31; - uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ - /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, - for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 - XXX: could change encoding to have one more bit in hash */ - uint32_t hash : 30; - uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ - uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ - JSWeakRefRecord *first_weak_ref; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - struct list_head link; /* string list */ -#endif - union { - __extension__ uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ - __extension__ uint16_t str16[0]; - } u; -}; - -typedef struct JSClosureVar { - uint8_t is_local : 1; - uint8_t is_arg : 1; - uint8_t is_const : 1; - uint8_t is_lexical : 1; - uint8_t var_kind : 4; /* see JSVarKindEnum */ - /* 8 bits available */ - uint16_t var_idx; /* is_local = true: index to a normal variable of the - parent function. otherwise: index to a closure - variable of the parent function */ - JSAtom var_name; -} JSClosureVar; - -#define ARG_SCOPE_INDEX 1 -#define ARG_SCOPE_END (-2) - -typedef struct JSVarScope { - int parent; /* index into fd->scopes of the enclosing scope */ - int first; /* index into fd->vars of the last variable in this scope */ -} JSVarScope; - -typedef enum { - /* XXX: add more variable kinds here instead of using bit fields */ - JS_VAR_NORMAL, - JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ - JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator - function declaration */ - JS_VAR_CATCH, - JS_VAR_FUNCTION_NAME, /* function expression name */ - JS_VAR_PRIVATE_FIELD, - JS_VAR_PRIVATE_METHOD, - JS_VAR_PRIVATE_GETTER, - JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ - JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ -} JSVarKindEnum; - -/* XXX: could use a different structure in bytecode functions to save - memory */ -typedef struct JSVarDef { - JSAtom var_name; - /* index into fd->scopes of this variable lexical scope */ - int scope_level; - /* during compilation: - - if scope_level = 0: scope in which the variable is defined - - if scope_level != 0: index into fd->vars of the next - variable in the same or enclosing lexical scope - in a bytecode function: - index into fd->vars of the next - variable in the same or enclosing lexical scope - */ - int scope_next; - uint8_t is_const : 1; - uint8_t is_lexical : 1; - uint8_t is_captured : 1; - uint8_t is_static_private : 1; /* only used during private class field parsing */ - uint8_t var_kind : 4; /* see JSVarKindEnum */ - /* only used during compilation: function pool index for lexical - variables with var_kind = - JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of - the definition of the 'var' variables (they have scope_level = - 0) */ - int func_pool_idx : 24; /* only used during compilation : index in - the constant pool for hoisted function - definition */ -} JSVarDef; - -/* for the encoding of the pc2line table */ -#define PC2LINE_BASE (-1) -#define PC2LINE_RANGE 5 -#define PC2LINE_OP_FIRST 1 -#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) - -typedef enum JSFunctionKindEnum { - JS_FUNC_NORMAL = 0, - JS_FUNC_GENERATOR = (1 << 0), - JS_FUNC_ASYNC = (1 << 1), - JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), -} JSFunctionKindEnum; - -#define IC_CACHE_ITEM_CAPACITY 4 - -typedef struct JSInlineCacheWatchpoint { - struct list_head link; - struct JSInlineCacheRingSlot *ref; - uint32_t offset; - JSAtom atom; -} JSInlineCacheWatchpoint; - -typedef struct JSInlineCacheRingSlot { - /* SoA for space optimization: 120 bytes */ - JSShape* shape[IC_CACHE_ITEM_CAPACITY]; - uint32_t prop_offset[IC_CACHE_ITEM_CAPACITY]; - JSObject* proto[IC_CACHE_ITEM_CAPACITY]; - JSInlineCacheWatchpoint* watchpoint_ref[IC_CACHE_ITEM_CAPACITY]; - JSAtom atom; - uint8_t index; -} JSInlineCacheRingSlot; - -typedef struct JSInlineCacheHashSlot { - JSAtom atom; - uint32_t index; - struct JSInlineCacheHashSlot *next; -} JSInlineCacheHashSlot; - -typedef struct JSInlineCache { - uint32_t count; - uint32_t capacity; - uint32_t hash_bits; - JSInlineCacheHashSlot **hash; - JSInlineCacheRingSlot *cache; -} JSInlineCache; - -#define INLINE_CACHE_MISS ((uint32_t)-1) // sentinel - -// This is a struct so we don't tie up two argument registers in calls to -// JS_GetPropertyInternal2 and JS_SetPropertyInternal2 in the common case -// where there is no IC and therefore no offset to update. -typedef struct JSInlineCacheUpdate { - JSInlineCache *ic; - uint32_t offset; -} JSInlineCacheUpdate; - -static JSInlineCache *init_ic(JSContext *ctx); -static int rebuild_ic(JSContext *ctx, JSInlineCache *ic); -static int resize_ic_hash(JSContext *ctx, JSInlineCache *ic); -static int free_ic(JSRuntime *rt, JSInlineCache *ic); -static int ic_delete_shape_proto_watchpoints(JSRuntime *rt, JSShape *shape, JSAtom atom, bool force); -static int ic_watchpoint_delete_handler(JSRuntime* rt, JSInlineCacheRingSlot *ref, uint8_t offset, JSAtom atom, JSInlineCacheRingSlot *target); -static int js_shape_delete_watchpoints(JSRuntime *rt, JSShape *shape, JSInlineCacheRingSlot *target); -static JSInlineCacheWatchpoint *js_shape_create_watchpoint(JSRuntime *rt, JSShape *shape, JSInlineCacheRingSlot *ptr, uint8_t offset, JSAtom atom); -static void add_ic_slot(JSContext *ctx, JSInlineCacheUpdate *icu, - JSAtom atom, JSObject *object, uint32_t prop_offset, JSObject* prototype); - -static uint32_t get_ic_prop_offset(const JSInlineCacheUpdate *icu, - JSShape *shape, JSObject **prototype) -{ - uint32_t i, cache_offset = icu->offset; - JSInlineCache *ic = icu->ic; - JSInlineCacheRingSlot *cr; - JSShape *shape_slot; - assert(cache_offset < ic->capacity); - cr = ic->cache + cache_offset; - i = cr->index; - for (;;) { - shape_slot = *(cr->shape + i); - if (likely(shape_slot == shape)) { - cr->index = i; - *prototype = cr->proto[i]; - return cr->prop_offset[i]; - } - - i = (i + 1) % countof(cr->shape); - if (unlikely(i == cr->index)) { - break; - } - } - - *prototype = NULL; - return INLINE_CACHE_MISS; -} - -static force_inline JSAtom get_ic_atom(JSInlineCache *ic, uint32_t cache_offset) -{ - assert(cache_offset < ic->capacity); - return ic->cache[cache_offset].atom; -} - -typedef struct JSFunctionBytecode { - JSGCObjectHeader header; /* must come first */ - uint8_t is_strict_mode : 1; - uint8_t has_prototype : 1; /* true if a prototype field is necessary */ - uint8_t has_simple_parameter_list : 1; - uint8_t is_derived_class_constructor : 1; - /* true if home_object needs to be initialized */ - uint8_t need_home_object : 1; - uint8_t func_kind : 2; - uint8_t new_target_allowed : 1; - uint8_t super_call_allowed : 1; - uint8_t super_allowed : 1; - uint8_t arguments_allowed : 1; - uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ - /* XXX: 5 bits available */ - uint8_t *byte_code_buf; /* (self pointer) */ - int byte_code_len; - JSAtom func_name; - JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ - JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ - uint16_t arg_count; - uint16_t var_count; - uint16_t defined_arg_count; /* for length function property */ - uint16_t stack_size; /* maximum stack size */ - JSContext *realm; /* function realm */ - JSValue *cpool; /* constant pool (self pointer) */ - int cpool_count; - int closure_var_count; - JSInlineCache *ic; - JSAtom filename; - int line_num; - int col_num; - int source_len; - int pc2line_len; - uint8_t *pc2line_buf; - char *source; -} JSFunctionBytecode; - -typedef struct JSBoundFunction { - JSValue func_obj; - JSValue this_val; - int argc; - JSValue argv[]; -} JSBoundFunction; - -typedef enum JSIteratorKindEnum { - JS_ITERATOR_KIND_KEY, - JS_ITERATOR_KIND_VALUE, - JS_ITERATOR_KIND_KEY_AND_VALUE, -} JSIteratorKindEnum; - -typedef enum JSIteratorHelperKindEnum { - JS_ITERATOR_HELPER_KIND_DROP, - JS_ITERATOR_HELPER_KIND_EVERY, - JS_ITERATOR_HELPER_KIND_FILTER, - JS_ITERATOR_HELPER_KIND_FIND, - JS_ITERATOR_HELPER_KIND_FLAT_MAP, - JS_ITERATOR_HELPER_KIND_FOR_EACH, - JS_ITERATOR_HELPER_KIND_MAP, - JS_ITERATOR_HELPER_KIND_SOME, - JS_ITERATOR_HELPER_KIND_TAKE, -} JSIteratorHelperKindEnum; - -typedef struct JSForInIterator { - JSValue obj; - bool is_array; - uint32_t array_length; - uint32_t idx; -} JSForInIterator; - -typedef struct JSRegExp { - JSString *pattern; - JSString *bytecode; /* also contains the flags */ -} JSRegExp; - -typedef struct JSProxyData { - JSValue target; - JSValue handler; - uint8_t is_func; - uint8_t is_revoked; -} JSProxyData; - -typedef struct JSArrayBuffer { - int byte_length; /* 0 if detached */ - int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ - uint8_t detached; - uint8_t shared; /* if shared, the array buffer cannot be detached */ - uint8_t *data; /* NULL if detached */ - struct list_head array_list; - void *opaque; - JSFreeArrayBufferDataFunc *free_func; -} JSArrayBuffer; - -typedef struct JSTypedArray { - struct list_head link; /* link to arraybuffer */ - JSObject *obj; /* back pointer to the TypedArray/DataView object */ - JSObject *buffer; /* based array buffer */ - uint32_t offset; /* byte offset in the array buffer */ - uint32_t length; /* byte length in the array buffer */ - bool track_rab; /* auto-track length of backing array buffer */ -} JSTypedArray; - -typedef struct JSAsyncFunctionState { - JSValue this_val; /* 'this' generator argument */ - int argc; /* number of function arguments */ - bool throw_flag; /* used to throw an exception in JS_CallInternal() */ - JSStackFrame frame; -} JSAsyncFunctionState; - -/* XXX: could use an object instead to avoid the - JS_TAG_ASYNC_FUNCTION tag for the GC */ -typedef struct JSAsyncFunctionData { - JSGCObjectHeader header; /* must come first */ - JSValue resolving_funcs[2]; - bool is_active; /* true if the async function state is valid */ - JSAsyncFunctionState func_state; -} JSAsyncFunctionData; - -typedef struct JSReqModuleEntry { - JSAtom module_name; - JSModuleDef *module; /* used using resolution */ -} JSReqModuleEntry; - -typedef enum JSExportTypeEnum { - JS_EXPORT_TYPE_LOCAL, - JS_EXPORT_TYPE_INDIRECT, -} JSExportTypeEnum; - -typedef struct JSExportEntry { - union { - struct { - int var_idx; /* closure variable index */ - JSVarRef *var_ref; /* if != NULL, reference to the variable */ - } local; /* for local export */ - int req_module_idx; /* module for indirect export */ - } u; - JSExportTypeEnum export_type; - JSAtom local_name; /* '*' if export ns from. not used for local - export after compilation */ - JSAtom export_name; /* exported variable name */ -} JSExportEntry; - -typedef struct JSStarExportEntry { - int req_module_idx; /* in req_module_entries */ -} JSStarExportEntry; - -typedef struct JSImportEntry { - int var_idx; /* closure variable index */ - JSAtom import_name; - int req_module_idx; /* in req_module_entries */ -} JSImportEntry; - -typedef enum { - JS_MODULE_STATUS_UNLINKED, - JS_MODULE_STATUS_LINKING, - JS_MODULE_STATUS_LINKED, - JS_MODULE_STATUS_EVALUATING, - JS_MODULE_STATUS_EVALUATING_ASYNC, - JS_MODULE_STATUS_EVALUATED, -} JSModuleStatus; - -struct JSModuleDef { - JSRefCountHeader header; /* must come first, 32-bit */ - JSAtom module_name; - struct list_head link; - - JSReqModuleEntry *req_module_entries; - int req_module_entries_count; - int req_module_entries_size; - - JSExportEntry *export_entries; - int export_entries_count; - int export_entries_size; - - JSStarExportEntry *star_export_entries; - int star_export_entries_count; - int star_export_entries_size; - - JSImportEntry *import_entries; - int import_entries_count; - int import_entries_size; - - JSValue module_ns; - JSValue func_obj; /* only used for JS modules */ - JSModuleInitFunc *init_func; /* only used for C modules */ - bool has_tla; /* true if func_obj contains await */ - bool resolved; - bool func_created; - JSModuleStatus status : 8; - /* temp use during js_module_link() & js_module_evaluate() */ - int dfs_index, dfs_ancestor_index; - JSModuleDef *stack_prev; - /* temp use during js_module_evaluate() */ - JSModuleDef **async_parent_modules; - int async_parent_modules_count; - int async_parent_modules_size; - int pending_async_dependencies; - bool async_evaluation; - int64_t async_evaluation_timestamp; - JSModuleDef *cycle_root; - JSValue promise; /* corresponds to spec field: capability */ - JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ - /* true if evaluation yielded an exception. It is saved in - eval_exception */ - bool eval_has_exception; - JSValue eval_exception; - JSValue meta_obj; /* for import.meta */ -}; - -typedef struct JSJobEntry { - struct list_head link; - JSContext *ctx; - JSJobFunc *job_func; - int argc; - JSValue argv[]; -} JSJobEntry; - -typedef struct JSProperty { - union { - JSValue value; /* JS_PROP_NORMAL */ - struct { /* JS_PROP_GETSET */ - JSObject *getter; /* NULL if undefined */ - JSObject *setter; /* NULL if undefined */ - } getset; - JSVarRef *var_ref; /* JS_PROP_VARREF */ - struct { /* JS_PROP_AUTOINIT */ - /* in order to use only 2 pointers, we compress the realm - and the init function pointer */ - uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) - in the 2 low bits */ - void *opaque; - } init; - } u; -} JSProperty; - -#define JS_PROP_INITIAL_SIZE 2 -#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ -#define JS_ARRAY_INITIAL_SIZE 2 - -typedef struct JSShapeProperty { - uint32_t hash_next : 26; /* 0 if last in list */ - uint32_t flags : 6; /* JS_PROP_XXX */ - JSAtom atom; /* JS_ATOM_NULL = free property entry */ -} JSShapeProperty; - -struct JSShape { - /* hash table of size hash_mask + 1 before the start of the - structure (see prop_hash_end()). */ - JSGCObjectHeader header; - /* true if the shape is inserted in the shape hash table. If not, - JSShape.hash is not valid */ - uint8_t is_hashed; - /* If true, the shape may have small array index properties 'n' with 0 - <= n <= 2^31-1. If false, the shape is guaranteed not to have - small array index properties */ - uint8_t has_small_array_index; - uint32_t hash; /* current hash value */ - uint32_t prop_hash_mask; - int prop_size; /* allocated properties */ - int prop_count; /* include deleted properties */ - int deleted_prop_count; - JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ - JSObject *proto; - struct list_head *watchpoint; /* watchpoint for prototype inline cache */ - JSShapeProperty prop[]; /* prop_size elements */ -}; - -struct JSObject { - union { - JSGCObjectHeader header; - struct { - int __gc_ref_count; /* corresponds to header.ref_count */ - uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ - - uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* true if object has exotic property handlers */ - uint8_t fast_array : 1; /* true if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ - uint8_t is_constructor : 1; /* true if object is a constructor function */ - uint8_t is_uncatchable_error : 1; /* if true, error is not catchable */ - uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ - uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ - uint16_t class_id; /* see JS_CLASS_x */ - }; - }; - /* byte offsets: 16/24 */ - JSShape *shape; /* prototype and property names + flag */ - JSProperty *prop; /* array of properties */ - /* byte offsets: 24/40 */ - JSWeakRefRecord *first_weak_ref; - /* byte offsets: 28/48 */ - union { - void *opaque; - struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ - struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ - struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ - struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ - struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ - struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ - struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ - struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ - struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ - struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ - struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ - struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ - struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ - struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ - struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ - struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ - struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ - struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ - struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ - /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ - struct JSFunctionBytecode *function_bytecode; - JSVarRef **var_refs; - JSObject *home_object; /* for 'super' access */ - } func; - struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ - JSContext *realm; - JSCFunctionType c_function; - uint8_t length; - uint8_t cproto; - int16_t magic; - } cfunc; - /* array part for fast arrays and typed arrays */ - struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - union { - uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ - struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - } u1; - union { - JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ - void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ - int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ - uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ - int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ - uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ - int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ - uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ - int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ - uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ - uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ - float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ - double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ - } u; - uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ - } array; /* 12/20 bytes */ - JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ - JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ - } u; - /* byte sizes: 40/48/72 */ -}; - -typedef struct JSCallSiteData { - JSValue filename; - JSValue func; - JSValue func_name; - bool native; - int line_num; - int col_num; -} JSCallSiteData; - -enum { - __JS_ATOM_NULL = JS_ATOM_NULL, -#define DEF(name, str) JS_ATOM_ ## name, -#include "quickjs-atom.h" -#undef DEF - JS_ATOM_END, -}; -#define JS_ATOM_LAST_KEYWORD JS_ATOM_super -#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield - -static const char js_atom_init[] = -#define DEF(name, str) str "\0" -#include "quickjs-atom.h" -#undef DEF -; - -typedef enum OPCodeFormat { -#define FMT(f) OP_FMT_ ## f, -#define DEF(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef DEF -#undef FMT -} OPCodeFormat; - -typedef enum OPCodeEnum { -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) OP_ ## id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT - OP_COUNT, /* excluding temporary opcodes */ - /* temporary opcodes : overlap with the short opcodes */ - OP_TEMP_START = OP_nop + 1, - OP___dummy = OP_TEMP_START - 1, -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) -#define def(id, size, n_pop, n_push, f) OP_ ## id, -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT - OP_TEMP_END, -} OPCodeEnum; - -static int JS_InitAtoms(JSRuntime *rt); -static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, - int atom_type); -static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); -static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); -static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, int flags); -static JSValue js_call_bound_function(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, int flags); -static JSValue JS_CallInternal(JSContext *ctx, JSValue func_obj, - JSValue this_obj, JSValue new_target, - int argc, JSValue *argv, int flags); -static JSValue JS_CallConstructorInternal(JSContext *ctx, - JSValue func_obj, - JSValue new_target, - int argc, JSValue *argv, int flags); -static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValue this_obj, - int argc, JSValue *argv); -static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, - int argc, JSValue *argv); -static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, - JSValue val, bool is_array_ctor); -static JSValue JS_EvalObject(JSContext *ctx, JSValue this_obj, - JSValue val, int flags, int scope_idx); -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowInternalError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...); - -static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); -static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); -static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); -static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); -static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val); -static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); -static __maybe_unused void JS_DumpShapes(JSRuntime *rt); - -static JSValue js_function_apply(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic); -static void js_array_finalizer(JSRuntime *rt, JSValue val); -static void js_array_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_object_data_finalizer(JSRuntime *rt, JSValue val); -static void js_object_data_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_c_function_finalizer(JSRuntime *rt, JSValue val); -static void js_c_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val); -static void js_bytecode_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_bound_function_finalizer(JSRuntime *rt, JSValue val); -static void js_bound_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val); -static void js_for_in_iterator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_regexp_finalizer(JSRuntime *rt, JSValue val); -static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val); -static void js_typed_array_finalizer(JSRuntime *rt, JSValue val); -static void js_typed_array_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_proxy_finalizer(JSRuntime *rt, JSValue val); -static void js_proxy_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_map_finalizer(JSRuntime *rt, JSValue val); -static void js_map_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val); -static void js_map_iterator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val); -static void js_array_iterator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val); -static void js_iterator_helper_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValue val); -static void js_iterator_wrap_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val); -static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_generator_finalizer(JSRuntime *rt, JSValue obj); -static void js_generator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_promise_finalizer(JSRuntime *rt, JSValue val); -static void js_promise_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); -static void js_promise_resolve_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); - -#define HINT_STRING 0 -#define HINT_NUMBER 1 -#define HINT_NONE 2 -#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive -static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); -static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); -static int JS_ToBoolFree(JSContext *ctx, JSValue val); -static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); -static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); -static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); -static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); -static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, - JSValue flags); -static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValue ctor, - JSValue pattern, JSValue bc); -static void gc_decref(JSRuntime *rt); -static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, - const JSClassDef *class_def, JSAtom name); -static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int unshift); - -typedef enum JSStrictEqModeEnum { - JS_EQ_STRICT, - JS_EQ_SAME_VALUE, - JS_EQ_SAME_VALUE_ZERO, -} JSStrictEqModeEnum; - -static bool js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, - JSStrictEqModeEnum eq_mode); -static bool js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2); -static bool js_same_value(JSContext *ctx, JSValue op1, JSValue op2); -static bool js_same_value_zero(JSContext *ctx, JSValue op1, JSValue op2); -static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); -static JSProperty *add_property(JSContext *ctx, - JSObject *p, JSAtom prop, int prop_flags); -static JSValue JS_NewBigInt(JSContext *ctx); -static inline bf_t *JS_GetBigInt(JSValue val) -{ - JSBigInt *p = JS_VALUE_GET_PTR(val); - return &p->num; -} -static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val); -static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val); -static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); -static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValue val); -static bf_t *JS_ToBigInt1(JSContext *ctx, bf_t *buf, JSValue val); -static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf); -JSValue JS_ThrowOutOfMemory(JSContext *ctx); -static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); -static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValue obj); -static int js_proxy_setPrototypeOf(JSContext *ctx, JSValue obj, - JSValue proto_val, bool throw_flag); -static int js_proxy_isExtensible(JSContext *ctx, JSValue obj); -static int js_proxy_preventExtensions(JSContext *ctx, JSValue obj); -static int js_proxy_isArray(JSContext *ctx, JSValue obj); -static int JS_CreateProperty(JSContext *ctx, JSObject *p, - JSAtom prop, JSValue val, - JSValue getter, JSValue setter, - int flags); -static int js_string_memcmp(const JSString *p1, const JSString *p2, int len); -static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref); -static bool is_valid_weakref_target(JSValue val); -static void insert_weakref_record(JSValue target, struct JSWeakRefRecord *wr); -static JSValue js_array_buffer_constructor3(JSContext *ctx, - JSValue new_target, - uint64_t len, uint64_t *max_len, - JSClassID class_id, - uint8_t *buf, - JSFreeArrayBufferDataFunc *free_func, - void *opaque, bool alloc_flag); -static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); -static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValue obj); -static bool array_buffer_is_resizable(const JSArrayBuffer *abuf); -static JSValue js_typed_array_constructor(JSContext *ctx, - JSValue this_val, - int argc, JSValue *argv, - int classid); -static JSValue js_typed_array_constructor_ta(JSContext *ctx, - JSValue new_target, - JSValue src_obj, - int classid, uint32_t len); -static bool is_typed_array(JSClassID class_id); -static bool typed_array_is_oob(JSObject *p); -static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); -static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); -static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); -static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, - bool is_arg); -static JSValue js_generator_function_call(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, - int flags); -static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val); -static void js_async_function_resolve_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static JSValue JS_EvalInternal(JSContext *ctx, JSValue this_obj, - const char *input, size_t input_len, - const char *filename, int flags, int scope_idx); -static void js_free_module_def(JSContext *ctx, JSModuleDef *m); -static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, - JS_MarkFunc *mark_func); -static JSValue js_import_meta(JSContext *ctx); -static JSValue js_dynamic_import(JSContext *ctx, JSValue specifier); -static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); -static JSValue js_new_promise_capability(JSContext *ctx, - JSValue *resolving_funcs, - JSValue ctor); -static __exception int perform_promise_then(JSContext *ctx, - JSValue promise, - JSValue *resolve_reject, - JSValue *cap_resolving_funcs); -static JSValue js_promise_resolve(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic); -static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv); -static bool js_string_eq(const JSString *p1, const JSString *p2); -static int js_string_compare(const JSString *p1, const JSString *p2); -static int JS_SetPropertyValue(JSContext *ctx, JSValue this_obj, - JSValue prop, JSValue val, int flags); -static int JS_NumberIsInteger(JSContext *ctx, JSValue val); -static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValue val); -static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); -static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, - JSObject *p, JSAtom prop); -static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); -static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, - JS_MarkFunc *mark_func); -static void JS_AddIntrinsicBasicObjects(JSContext *ctx); -static void js_free_shape(JSRuntime *rt, JSShape *sh); -static void js_free_shape_null(JSRuntime *rt, JSShape *sh); -static int js_shape_prepare_update(JSContext *ctx, JSObject *p, - JSShapeProperty **pprs); -static int init_shape_hash(JSRuntime *rt); -static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, - JSValue obj); -static __exception int js_get_length64(JSContext *ctx, int64_t *pres, - JSValue obj); -static __exception int js_set_length64(JSContext *ctx, JSValue obj, - int64_t len); -static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); -static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, - JSValue array_arg); -static bool js_get_fast_array(JSContext *ctx, JSValue obj, - JSValue **arrpp, uint32_t *countp); -static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, - JSValue sync_iter); -static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val); -static void js_c_function_data_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func); -static JSValue js_c_function_data_call(JSContext *ctx, JSValue func_obj, - JSValue this_val, - int argc, JSValue *argv, int flags); -static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val); -static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, - JSGCObjectTypeEnum type); -static void remove_gc_object(JSGCObjectHeader *h); -static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); -static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); -static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, - void *opaque); -static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, - JSAtom atom, void *opaque); - -static void js_set_uncatchable_error(JSContext *ctx, JSValue val, bool flag); - -static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd); -static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf); -static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num); -static void _JS_AddIntrinsicCallSite(JSContext *ctx); - -static void JS_SetOpaqueInternal(JSValue obj, void *opaque); - -static const JSClassExoticMethods js_arguments_exotic_methods; -static const JSClassExoticMethods js_string_exotic_methods; -static const JSClassExoticMethods js_proxy_exotic_methods; -static const JSClassExoticMethods js_module_ns_exotic_methods; - -static inline bool double_is_int32(double d) -{ - uint64_t u, e; - JSFloat64Union t; - - t.d = d; - u = t.u64; - - e = ((u >> 52) & 0x7FF) - 1023; - if (e > 30) { - // accept 0, INT32_MIN, reject too large, too small, nan, inf, -0 - return !u || (u == 0xc1e0000000000000); - } else { - // shift out sign, exponent and whole part bits - // value is fractional if remaining low bits are non-zero - return !(u << 12 << e); - } -} - -static JSValue js_float64(double d) -{ - return __JS_NewFloat64(d); -} - -static int compare_u32(uint32_t a, uint32_t b) -{ - return -(a < b) + (b < a); // -1, 0 or 1 -} - -static JSValue js_int32(int32_t v) -{ - return JS_MKVAL(JS_TAG_INT, v); -} - -static JSValue js_uint32(uint32_t v) -{ - if (v <= INT32_MAX) - return js_int32(v); - else - return js_float64(v); -} - -static JSValue js_int64(int64_t v) -{ - if (v >= INT32_MIN && v <= INT32_MAX) - return js_int32(v); - else - return js_float64(v); -} - -#define JS_NewInt64(ctx, val) js_int64(val) - -static JSValue js_number(double d) -{ - if (double_is_int32(d)) - return js_int32((int32_t)d); - else - return js_float64(d); -} - -JSValue JS_NewNumber(JSContext *ctx, double d) -{ - return js_number(d); -} - -static JSValue js_bool(bool v) -{ - return JS_MKVAL(JS_TAG_BOOL, (v != 0)); -} - -static JSValue js_dup(JSValue v) -{ - if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); - p->ref_count++; - } - return v; -} - -JSValue JS_DupValue(JSContext *ctx, JSValue v) -{ - return js_dup(v); -} - -JSValue JS_DupValueRT(JSRuntime *rt, JSValue v) -{ - return js_dup(v); -} - -static void js_trigger_gc(JSRuntime *rt, size_t size) -{ - bool force_gc; -#ifdef FORCE_GC_AT_MALLOC - force_gc = true; -#else - force_gc = ((rt->malloc_state.malloc_size + size) > - rt->malloc_gc_threshold); -#endif - if (force_gc) { -#ifdef ENABLE_DUMPS // JS_DUMP_GC - if (check_dump_flag(rt, JS_DUMP_GC)) { - printf("GC: size=%zd\n", rt->malloc_state.malloc_size); - } -#endif - if((rt->gc_before_callback == NULL) || rt->gc_before_callback(rt)){ - JS_RunGC(rt); - if(rt->gc_after_callback != NULL)rt->gc_after_callback(rt); - } - rt->malloc_gc_threshold = rt->malloc_state.malloc_size + - (rt->malloc_state.malloc_size >> 1); - } -} - -static size_t js_malloc_usable_size_unknown(const void *ptr) -{ - return 0; -} - -void *js_calloc_rt(JSRuntime *rt, size_t count, size_t size) -{ - void *ptr; - JSMallocState *s; - - /* Do not allocate zero bytes: behavior is platform dependent */ - assert(count != 0 && size != 0); - - if (size > 0) - if (unlikely(count != (count * size) / size)) - return NULL; - - s = &rt->malloc_state; - /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ - if (unlikely(s->malloc_size + (count * size) > s->malloc_limit - 1)) - return NULL; - - ptr = rt->mf.js_calloc(s->opaque, count, size); - if (!ptr) - return NULL; - - s->malloc_count++; - s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; - return ptr; -} - -void *js_malloc_rt(JSRuntime *rt, size_t size) -{ - void *ptr; - JSMallocState *s; - - /* Do not allocate zero bytes: behavior is platform dependent */ - assert(size != 0); - - s = &rt->malloc_state; - /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ - if (unlikely(s->malloc_size + size > s->malloc_limit - 1)) - return NULL; - - ptr = rt->mf.js_malloc(s->opaque, size); - if (!ptr) - return NULL; - - s->malloc_count++; - s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; - return ptr; -} - -void js_free_rt(JSRuntime *rt, void *ptr) -{ - JSMallocState *s; - - if (!ptr) - return; - - s = &rt->malloc_state; - s->malloc_count--; - s->malloc_size -= rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; - rt->mf.js_free(s->opaque, ptr); -} - -void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) -{ - size_t old_size; - JSMallocState *s; - - if (!ptr) { - if (size == 0) - return NULL; - return js_malloc_rt(rt, size); - } - if (unlikely(size == 0)) { - js_free_rt(rt, ptr); - return NULL; - } - old_size = rt->mf.js_malloc_usable_size(ptr); - s = &rt->malloc_state; - /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ - if (s->malloc_size + size - old_size > s->malloc_limit - 1) - return NULL; - - ptr = rt->mf.js_realloc(s->opaque, ptr, size); - if (!ptr) - return NULL; - - s->malloc_size += rt->mf.js_malloc_usable_size(ptr) - old_size; - return ptr; -} - -size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) -{ - return rt->mf.js_malloc_usable_size(ptr); -} - -/** - * This used to be implemented as malloc + memset, but using calloc - * yields better performance in initial, bursty allocations, something useful - * for QuickJS. - * - * More information: https://github.com/quickjs-ng/quickjs/pull/519 - */ -void *js_mallocz_rt(JSRuntime *rt, size_t size) -{ - return js_calloc_rt(rt, 1, size); -} - -/* called by libbf */ -static void *js_bf_realloc(void *opaque, void *ptr, size_t size) -{ - JSRuntime *rt = opaque; - return js_realloc_rt(rt, ptr, size); -} - -/* Throw out of memory in case of error */ -void *js_calloc(JSContext *ctx, size_t count, size_t size) -{ - void *ptr; - ptr = js_calloc_rt(ctx->rt, count, size); - if (unlikely(!ptr)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ptr; -} - -/* Throw out of memory in case of error */ -void *js_malloc(JSContext *ctx, size_t size) -{ - void *ptr; - ptr = js_malloc_rt(ctx->rt, size); - if (unlikely(!ptr)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ptr; -} - -/* Throw out of memory in case of error */ -void *js_mallocz(JSContext *ctx, size_t size) -{ - void *ptr; - ptr = js_mallocz_rt(ctx->rt, size); - if (unlikely(!ptr)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ptr; -} - -void js_free(JSContext *ctx, void *ptr) -{ - js_free_rt(ctx->rt, ptr); -} - -/* Throw out of memory in case of error */ -void *js_realloc(JSContext *ctx, void *ptr, size_t size) -{ - void *ret; - ret = js_realloc_rt(ctx->rt, ptr, size); - if (unlikely(!ret && size != 0)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return ret; -} - -/* store extra allocated size in *pslack if successful */ -void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) -{ - void *ret; - ret = js_realloc_rt(ctx->rt, ptr, size); - if (unlikely(!ret && size != 0)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - if (pslack) { - size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); - *pslack = (new_size > size) ? new_size - size : 0; - } - return ret; -} - -size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) -{ - return js_malloc_usable_size_rt(ctx->rt, ptr); -} - -/* Throw out of memory exception in case of error */ -char *js_strndup(JSContext *ctx, const char *s, size_t n) -{ - char *ptr; - ptr = js_malloc(ctx, n + 1); - if (ptr) { - memcpy(ptr, s, n); - ptr[n] = '\0'; - } - return ptr; -} - -char *js_strdup(JSContext *ctx, const char *str) -{ - return js_strndup(ctx, str, strlen(str)); -} - -static no_inline int js_realloc_array(JSContext *ctx, void **parray, - int elem_size, int *psize, int req_size) -{ - int new_size; - size_t slack; - void *new_array; - /* XXX: potential arithmetic overflow */ - new_size = max_int(req_size, *psize * 3 / 2); - new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); - if (!new_array) - return -1; - new_size += slack / elem_size; - *psize = new_size; - *parray = new_array; - return 0; -} - -/* resize the array and update its size if req_size > *psize */ -static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, - int *psize, int req_size) -{ - if (unlikely(req_size > *psize)) - return js_realloc_array(ctx, parray, elem_size, psize, req_size); - else - return 0; -} - -static void *js_dbuf_realloc(void *ctx, void *ptr, size_t size) -{ - return js_realloc(ctx, ptr, size); -} - -static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) -{ - dbuf_init2(s, ctx, js_dbuf_realloc); -} - -static inline int is_digit(int c) { - return c >= '0' && c <= '9'; -} - -static inline int string_get(const JSString *p, int idx) { - return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx]; -} - -typedef struct JSClassShortDef { - JSAtom class_name; - JSClassFinalizer *finalizer; - JSClassGCMark *gc_mark; -} JSClassShortDef; - -static JSClassShortDef const js_std_class_def[] = { - { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ - { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ - { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ - { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ - { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ - { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ - { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ - { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ - { JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */ - { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ - { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ - { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ - { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ - { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ - { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ - { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ - { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ - { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ - { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ - { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ - { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ - { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ - { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ - { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ - { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ - { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ - { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ - { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ - { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ - { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ - { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ - { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ - { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ - { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ - { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ - { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ - { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */ - { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */ - { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ - { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ - { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ - { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ - { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ - { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ - { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ - { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ - { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ -}; - -static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, - int start, int count) -{ - JSClassDef cm_s, *cm = &cm_s; - int i, class_id; - - for(i = 0; i < count; i++) { - class_id = i + start; - memset(cm, 0, sizeof(*cm)); - cm->finalizer = tab[i].finalizer; - cm->gc_mark = tab[i].gc_mark; - if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) - return -1; - } - return 0; -} - -/* Uses code from LLVM project. */ -static inline uintptr_t js_get_stack_pointer(void) -{ -#if defined(__clang__) || defined(__GNUC__) - return (uintptr_t)__builtin_frame_address(0); -#elif defined(_MSC_VER) - return (uintptr_t)_AddressOfReturnAddress(); -#else - char CharOnStack = 0; - // The volatile store here is intended to escape the local variable, to - // prevent the compiler from optimizing CharOnStack into anything other - // than a char on the stack. - // - // Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19. - char *volatile Ptr = &CharOnStack; - return (uintptr_t) Ptr; -#endif -} - -static inline bool js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) -{ - uintptr_t sp; - sp = js_get_stack_pointer() - alloca_size; - return unlikely(sp < rt->stack_limit); -} - -JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) -{ - JSRuntime *rt; - JSMallocState ms; - - memset(&ms, 0, sizeof(ms)); - ms.opaque = opaque; - ms.malloc_limit = 0; - - rt = mf->js_calloc(opaque, 1, sizeof(JSRuntime)); - if (!rt) - return NULL; - rt->mf = *mf; - if (!rt->mf.js_malloc_usable_size) { - /* use dummy function if none provided */ - rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; - } - /* Inline what js_malloc_rt does since we cannot use it here. */ - ms.malloc_count++; - ms.malloc_size += rt->mf.js_malloc_usable_size(rt) + MALLOC_OVERHEAD; - rt->malloc_state = ms; - rt->malloc_gc_threshold = 256 * 1024; - - bf_context_init(&rt->bf_ctx, js_bf_realloc, rt); - - init_list_head(&rt->context_list); - init_list_head(&rt->gc_obj_list); - init_list_head(&rt->gc_zero_ref_count_list); - rt->gc_phase = JS_GC_PHASE_NONE; - -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - init_list_head(&rt->string_list); -#endif - init_list_head(&rt->job_list); - init_list_head(&rt->weakref_keepalive_list); - - if (JS_InitAtoms(rt)) - goto fail; - - /* create the object, array and function classes */ - if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, - countof(js_std_class_def)) < 0) - goto fail; - rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; - rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; - rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; - - rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; - rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call; - rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; - rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call; - if (init_shape_hash(rt)) - goto fail; - - rt->js_class_id_alloc = JS_CLASS_INIT_COUNT; - - rt->stack_size = JS_DEFAULT_STACK_SIZE; -#ifdef __wasi__ - rt->stack_size = 0; -#endif - - JS_UpdateStackTop(rt); - - rt->current_exception = JS_UNINITIALIZED; - - return rt; - fail: - JS_FreeRuntime(rt); - return NULL; -} - -void *JS_GetRuntimeOpaque(JSRuntime *rt) -{ - return rt->user_opaque; -} - -void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) -{ - rt->user_opaque = opaque; -} - -int JS_AddRuntimeFinalizer(JSRuntime *rt, JSRuntimeFinalizer *finalizer, - void *arg) -{ - JSRuntimeFinalizerState *fs = js_malloc_rt(rt, sizeof(*fs)); - if (!fs) - return -1; - fs->next = rt->finalizers; - fs->finalizer = finalizer; - fs->arg = arg; - rt->finalizers = fs; - return 0; -} - -static void *js_def_calloc(void *opaque, size_t count, size_t size) -{ - return calloc(count, size); -} - -static void *js_def_malloc(void *opaque, size_t size) -{ - return malloc(size); -} - -static void js_def_free(void *opaque, void *ptr) -{ - free(ptr); -} - -static void *js_def_realloc(void *opaque, void *ptr, size_t size) -{ - return realloc(ptr, size); -} - -static const JSMallocFunctions def_malloc_funcs = { - js_def_calloc, - js_def_malloc, - js_def_free, - js_def_realloc, - js__malloc_usable_size -}; - -JSRuntime *JS_NewRuntime(void) -{ - return JS_NewRuntime2(&def_malloc_funcs, NULL); -} - -void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) -{ - rt->malloc_state.malloc_limit = limit; -} - -void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags) -{ -#ifdef ENABLE_DUMPS - rt->dump_flags = flags; -#endif -} - -uint64_t JS_GetDumpFlags(JSRuntime *rt) -{ -#ifdef ENABLE_DUMPS - return rt->dump_flags; -#else - return 0; -#endif -} - -size_t JS_GetGCThreshold(JSRuntime *rt) { - return rt->malloc_gc_threshold; -} - -/* use -1 to disable automatic GC */ -void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) -{ - rt->malloc_gc_threshold = gc_threshold; -} - -#define malloc(s) malloc_is_forbidden(s) -#define free(p) free_is_forbidden(p) -#define realloc(p,s) realloc_is_forbidden(p,s) - -void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) -{ - rt->interrupt_handler = cb; - rt->interrupt_opaque = opaque; -} - -void JS_SetCanBlock(JSRuntime *rt, bool can_block) -{ - rt->can_block = can_block; -} - -void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, - const JSSharedArrayBufferFunctions *sf) -{ - rt->sab_funcs = *sf; -} - -/* return 0 if OK, < 0 if exception */ -int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, - int argc, JSValue *argv) -{ - JSRuntime *rt = ctx->rt; - JSJobEntry *e; - int i; - - assert(!rt->in_free); - - e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); - if (!e) - return -1; - e->ctx = ctx; - e->job_func = job_func; - e->argc = argc; - for(i = 0; i < argc; i++) { - e->argv[i] = js_dup(argv[i]); - } - list_add_tail(&e->link, &rt->job_list); - return 0; -} - -bool JS_IsJobPending(JSRuntime *rt) -{ - return !list_empty(&rt->job_list); -} - -/* return < 0 if exception, 0 if no job pending, 1 if a job was - executed successfully. the context of the job is stored in '*pctx' */ -int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) -{ - JSContext *ctx; - JSJobEntry *e; - JSValue res; - int i, ret; - - if (list_empty(&rt->job_list)) { - *pctx = NULL; - return 0; - } - - /* get the first pending job and execute it */ - e = list_entry(rt->job_list.next, JSJobEntry, link); - list_del(&e->link); - ctx = e->ctx; - res = e->job_func(e->ctx, e->argc, e->argv); - for(i = 0; i < e->argc; i++) - JS_FreeValue(ctx, e->argv[i]); - if (JS_IsException(res)) - ret = -1; - else - ret = 1; - JS_FreeValue(ctx, res); - js_free(ctx, e); - *pctx = ctx; - return ret; -} - -static inline uint32_t atom_get_free(const JSAtomStruct *p) -{ - return (uintptr_t)p >> 1; -} - -static inline bool atom_is_free(const JSAtomStruct *p) -{ - return (uintptr_t)p & 1; -} - -static inline JSAtomStruct *atom_set_free(uint32_t v) -{ - return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); -} - -/* Note: the string contents are uninitialized */ -static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) -{ - JSString *str; - str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); - if (unlikely(!str)) - return NULL; - str->header.ref_count = 1; - str->is_wide_char = is_wide_char; - str->len = max_len; - str->atom_type = 0; - str->hash = 0; /* optional but costless */ - str->hash_next = 0; /* optional */ -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&str->link, &rt->string_list); -#endif - return str; -} - -static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) -{ - JSString *p; - p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); - if (unlikely(!p)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return p; -} - -/* same as JS_FreeValueRT() but faster */ -static inline void js_free_string(JSRuntime *rt, JSString *str) -{ - if (--str->header.ref_count <= 0) { - if (str->atom_type) { - JS_FreeAtomStruct(rt, str); - } else { -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&str->link); -#endif - js_free_rt(rt, str); - } - } -} - -void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) -{ - if (rt) - rt->rt_info = s; -} - -void JS_FreeRuntime(JSRuntime *rt) -{ - struct list_head *el, *el1; - int i; - - rt->in_free = true; - JS_FreeValueRT(rt, rt->current_exception); - - list_for_each_safe(el, el1, &rt->job_list) { - JSJobEntry *e = list_entry(el, JSJobEntry, link); - for(i = 0; i < e->argc; i++) - JS_FreeValueRT(rt, e->argv[i]); - js_free_rt(rt, e); - } - init_list_head(&rt->job_list); - JS_ClearWeakRefKeepAlives(rt); - - JS_RunGC(rt); - -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - /* leaking objects */ - if (check_dump_flag(rt, JS_DUMP_LEAKS)) { - bool header_done; - JSGCObjectHeader *p; - int count; - - /* remove the internal refcounts to display only the object - referenced externally */ - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - p->mark = 0; - } - gc_decref(rt); - - header_done = false; - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count != 0) { - if (!header_done) { - printf("Object leaks:\n"); - JS_DumpObjectHeader(rt); - header_done = true; - } - JS_DumpGCObject(rt, p); - } - } - - count = 0; - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - if (p->ref_count == 0) { - count++; - } - } - if (count != 0) - printf("Secondary object leaks: %d\n", count); - } -#endif - -// assert(list_empty(&rt->gc_obj_list)); - - /* free the classes */ - for(i = 0; i < rt->class_count; i++) { - JSClass *cl = &rt->class_array[i]; - if (cl->class_id != 0) { - JS_FreeAtomRT(rt, cl->class_name); - } - } - js_free_rt(rt, rt->class_array); - - bf_context_end(&rt->bf_ctx); - -#ifdef ENABLE_DUMPS // JS_DUMP_ATOM_LEAKS - /* only the atoms defined in JS_InitAtoms() should be left */ - if (check_dump_flag(rt, JS_DUMP_ATOM_LEAKS)) { - bool header_done = false; - - for(i = 0; i < rt->atom_size; i++) { - JSAtomStruct *p = rt->atom_array[i]; - if (!atom_is_free(p) /* && p->str*/) { - if (i >= JS_ATOM_END || p->header.ref_count != 1) { - if (!header_done) { - header_done = true; - if (rt->rt_info) { - printf("%s:1: atom leakage:", rt->rt_info); - } else { - printf("Atom leaks:\n" - " %6s %6s %s\n", - "ID", "REFCNT", "NAME"); - } - } - if (rt->rt_info) { - printf(" "); - } else { - printf(" %6u %6u ", i, p->header.ref_count); - } - switch (p->atom_type) { - case JS_ATOM_TYPE_STRING: - JS_DumpString(rt, p); - break; - case JS_ATOM_TYPE_GLOBAL_SYMBOL: - printf("Symbol.for("); - JS_DumpString(rt, p); - printf(")"); - break; - case JS_ATOM_TYPE_SYMBOL: - if (p->hash == JS_ATOM_HASH_SYMBOL) { - printf("Symbol("); - JS_DumpString(rt, p); - printf(")"); - } else { - printf("Private("); - JS_DumpString(rt, p); - printf(")"); - } - break; - } - if (rt->rt_info) { - printf(":%u", p->header.ref_count); - } else { - printf("\n"); - } - } - } - } - if (rt->rt_info && header_done) - printf("\n"); - } -#endif - - /* free the atoms */ - for(i = 0; i < rt->atom_size; i++) { - JSAtomStruct *p = rt->atom_array[i]; - if (!atom_is_free(p)) { -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&p->link); -#endif - js_free_rt(rt, p); - } - } - js_free_rt(rt, rt->atom_array); - js_free_rt(rt, rt->atom_hash); - js_free_rt(rt, rt->shape_hash); -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - if (check_dump_flag(rt, JS_DUMP_LEAKS) && !list_empty(&rt->string_list)) { - if (rt->rt_info) { - printf("%s:1: string leakage:", rt->rt_info); - } else { - printf("String leaks:\n" - " %6s %s\n", - "REFCNT", "VALUE"); - } - list_for_each_safe(el, el1, &rt->string_list) { - JSString *str = list_entry(el, JSString, link); - if (rt->rt_info) { - printf(" "); - } else { - printf(" %6u ", str->header.ref_count); - } - JS_DumpString(rt, str); - if (rt->rt_info) { - printf(":%u", str->header.ref_count); - } else { - printf("\n"); - } - list_del(&str->link); - js_free_rt(rt, str); - } - if (rt->rt_info) - printf("\n"); - } -#endif - - while (rt->finalizers) { - JSRuntimeFinalizerState *fs = rt->finalizers; - rt->finalizers = fs->next; - fs->finalizer(rt, fs->arg); - js_free_rt(rt, fs); - } - -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - if (check_dump_flag(rt, JS_DUMP_LEAKS)) { - JSMallocState *s = &rt->malloc_state; - if (s->malloc_count > 1) { - if (rt->rt_info) - printf("%s:1: ", rt->rt_info); - printf("Memory leak: %zd bytes lost in %zd block%s\n", - s->malloc_size - sizeof(JSRuntime), - s->malloc_count - 1, &"s"[s->malloc_count == 2]); - } - } -#endif - - { - JSMallocState *ms = &rt->malloc_state; - rt->mf.js_free(ms->opaque, rt); - } -} - -JSContext *JS_NewContextRaw(JSRuntime *rt) -{ - JSContext *ctx; - int i; - - ctx = js_mallocz_rt(rt, sizeof(JSContext)); - if (!ctx) - return NULL; - ctx->header.ref_count = 1; - add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); - - ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * - rt->class_count); - if (!ctx->class_proto) { - js_free_rt(rt, ctx); - return NULL; - } - ctx->rt = rt; - list_add_tail(&ctx->link, &rt->context_list); - ctx->bf_ctx = &rt->bf_ctx; - for(i = 0; i < rt->class_count; i++) - ctx->class_proto[i] = JS_NULL; - ctx->array_ctor = JS_NULL; - ctx->iterator_ctor = JS_NULL; - ctx->regexp_ctor = JS_NULL; - ctx->promise_ctor = JS_NULL; - ctx->error_ctor = JS_NULL; - ctx->error_back_trace = JS_UNDEFINED; - ctx->error_prepare_stack = JS_UNDEFINED; - ctx->regexp_left_ctx = JS_UNDEFINED; - ctx->regexp_right_ctx = JS_UNDEFINED; - ctx->regexp_last_match_str = JS_UNDEFINED; - ctx->regexp_last_match_start = -1; - ctx->regexp_last_match_end = -1; - - ctx->error_stack_trace_limit = 10; - init_list_head(&ctx->loaded_modules); - - JS_AddIntrinsicBasicObjects(ctx); - return ctx; -} - -JSContext *JS_NewContext(JSRuntime *rt) -{ - JSContext *ctx; - - ctx = JS_NewContextRaw(rt); - if (!ctx) - return NULL; - - JS_AddIntrinsicBaseObjects(ctx); - JS_AddIntrinsicDate(ctx); - JS_AddIntrinsicEval(ctx); - JS_AddIntrinsicRegExp(ctx); - JS_AddIntrinsicJSON(ctx); - JS_AddIntrinsicProxy(ctx); - JS_AddIntrinsicMapSet(ctx); - JS_AddIntrinsicTypedArrays(ctx); - JS_AddIntrinsicPromise(ctx); - JS_AddIntrinsicBigInt(ctx); - JS_AddIntrinsicWeakRef(ctx); - - JS_AddPerformance(ctx); - - return ctx; -} - -void *JS_GetContextOpaque(JSContext *ctx) -{ - return ctx->user_opaque; -} - -void JS_SetContextOpaque(JSContext *ctx, void *opaque) -{ - ctx->user_opaque = opaque; -} - -/* set the new value and free the old value after (freeing the value - can reallocate the object data) */ -static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) -{ - JSValue old_val; - old_val = *pval; - *pval = new_val; - JS_FreeValue(ctx, old_val); -} - -void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) -{ - assert(class_id < ctx->rt->class_count); - set_value(ctx, &ctx->class_proto[class_id], obj); -} - -JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) -{ - assert(class_id < ctx->rt->class_count); - return js_dup(ctx->class_proto[class_id]); -} - -JSValue JS_GetFunctionProto(JSContext *ctx) -{ - return js_dup(ctx->function_proto); -} - -typedef enum JSFreeModuleEnum { - JS_FREE_MODULE_ALL, - JS_FREE_MODULE_NOT_RESOLVED, -} JSFreeModuleEnum; - -/* XXX: would be more efficient with separate module lists */ -static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) -{ - struct list_head *el, *el1; - list_for_each_safe(el, el1, &ctx->loaded_modules) { - JSModuleDef *m = list_entry(el, JSModuleDef, link); - if (flag == JS_FREE_MODULE_ALL || - (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { - js_free_module_def(ctx, m); - } - } -} - -JSContext *JS_DupContext(JSContext *ctx) -{ - ctx->header.ref_count++; - return ctx; -} - -/* used by the GC */ -static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, - JS_MarkFunc *mark_func) -{ - int i; - struct list_head *el; - - /* modules are not seen by the GC, so we directly mark the objects - referenced by each module */ - list_for_each(el, &ctx->loaded_modules) { - JSModuleDef *m = list_entry(el, JSModuleDef, link); - js_mark_module_def(rt, m, mark_func); - } - - JS_MarkValue(rt, ctx->global_obj, mark_func); - JS_MarkValue(rt, ctx->global_var_obj, mark_func); - - JS_MarkValue(rt, ctx->throw_type_error, mark_func); - JS_MarkValue(rt, ctx->eval_obj, mark_func); - - JS_MarkValue(rt, ctx->array_proto_values, mark_func); - for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); - } - JS_MarkValue(rt, ctx->error_ctor, mark_func); - JS_MarkValue(rt, ctx->error_back_trace, mark_func); - JS_MarkValue(rt, ctx->error_prepare_stack, mark_func); - for(i = 0; i < rt->class_count; i++) { - JS_MarkValue(rt, ctx->class_proto[i], mark_func); - } - JS_MarkValue(rt, ctx->iterator_ctor, mark_func); - JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); - JS_MarkValue(rt, ctx->promise_ctor, mark_func); - JS_MarkValue(rt, ctx->array_ctor, mark_func); - JS_MarkValue(rt, ctx->regexp_ctor, mark_func); - JS_MarkValue(rt, ctx->function_ctor, mark_func); - JS_MarkValue(rt, ctx->function_proto, mark_func); - JS_MarkValue(rt, ctx->regexp_last_match_str, mark_func); - JS_MarkValue(rt, ctx->regexp_right_ctx, mark_func); - JS_MarkValue(rt, ctx->regexp_left_ctx, mark_func); - - if (ctx->array_shape) - mark_func(rt, &ctx->array_shape->header); -} - -void JS_FreeContext(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - int i; - - if (--ctx->header.ref_count > 0) - return; - assert(ctx->header.ref_count == 0); - -#ifdef ENABLE_DUMPS // JS_DUMP_ATOMS - if (check_dump_flag(rt, JS_DUMP_ATOMS)) - JS_DumpAtoms(ctx->rt); -#endif -#ifdef ENABLE_DUMPS // JS_DUMP_SHAPES - if (check_dump_flag(rt, JS_DUMP_SHAPES)) - JS_DumpShapes(ctx->rt); -#endif -#ifdef ENABLE_DUMPS // JS_DUMP_OBJECTS - if (check_dump_flag(rt, JS_DUMP_OBJECTS)) { - struct list_head *el; - JSGCObjectHeader *p; - printf("JSObjects: {\n"); - JS_DumpObjectHeader(ctx->rt); - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - JS_DumpGCObject(rt, p); - } - printf("}\n"); - } -#endif -#ifdef ENABLE_DUMPS // JS_DUMP_MEM - if (check_dump_flag(rt, JS_DUMP_MEM)) { - JSMemoryUsage stats; - JS_ComputeMemoryUsage(rt, &stats); - JS_DumpMemoryUsage(stdout, &stats, rt); - } -#endif - - js_free_modules(ctx, JS_FREE_MODULE_ALL); - - JS_FreeValue(ctx, ctx->global_obj); - JS_FreeValue(ctx, ctx->global_var_obj); - - JS_FreeValue(ctx, ctx->throw_type_error); - JS_FreeValue(ctx, ctx->eval_obj); - - JS_FreeValue(ctx, ctx->array_proto_values); - for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - JS_FreeValue(ctx, ctx->native_error_proto[i]); - } - JS_FreeValue(ctx, ctx->error_ctor); - JS_FreeValue(ctx, ctx->error_back_trace); - JS_FreeValue(ctx, ctx->error_prepare_stack); - for(i = 0; i < rt->class_count; i++) { - JS_FreeValue(ctx, ctx->class_proto[i]); - } - js_free_rt(rt, ctx->class_proto); - JS_FreeValue(ctx, ctx->iterator_ctor); - JS_FreeValue(ctx, ctx->async_iterator_proto); - JS_FreeValue(ctx, ctx->promise_ctor); - JS_FreeValue(ctx, ctx->array_ctor); - JS_FreeValue(ctx, ctx->regexp_ctor); - JS_FreeValue(ctx, ctx->function_ctor); - JS_FreeValue(ctx, ctx->function_proto); - - JS_FreeValue(ctx, ctx->regexp_left_ctx); - JS_FreeValue(ctx, ctx->regexp_right_ctx); - JS_FreeValue(ctx, ctx->regexp_last_match_str); - - js_free_shape_null(ctx->rt, ctx->array_shape); - - list_del(&ctx->link); - remove_gc_object(&ctx->header); - js_free_rt(ctx->rt, ctx); -} - -JSRuntime *JS_GetRuntime(JSContext *ctx) -{ - return ctx->rt; -} - -static void update_stack_limit(JSRuntime *rt) -{ -#if defined(__wasi__) - rt->stack_limit = 0; /* no limit */ -#else - if (rt->stack_size == 0) { - rt->stack_limit = 0; /* no limit */ - } else { - rt->stack_limit = rt->stack_top - rt->stack_size; - } -#endif -} - -void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) -{ - rt->stack_size = stack_size; - update_stack_limit(rt); -} - -void JS_UpdateStackTop(JSRuntime *rt) -{ - rt->stack_top = js_get_stack_pointer(); - update_stack_limit(rt); -} - -static inline bool is_strict_mode(JSContext *ctx) -{ - JSStackFrame *sf = ctx->rt->current_stack_frame; - return sf && sf->is_strict_mode; -} - -/* JSAtom support */ - -#define JS_ATOM_TAG_INT (1U << 31) -#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) -#define JS_ATOM_MAX ((1U << 30) - 1) - -/* return the max count from the hash size */ -#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) - -static inline bool __JS_AtomIsConst(JSAtom v) -{ - return (int32_t)v < JS_ATOM_END; -} - -static inline bool __JS_AtomIsTaggedInt(JSAtom v) -{ - return (v & JS_ATOM_TAG_INT) != 0; -} - -static inline JSAtom __JS_AtomFromUInt32(uint32_t v) -{ - return v | JS_ATOM_TAG_INT; -} - -static inline uint32_t __JS_AtomToUInt32(JSAtom atom) -{ - return atom & ~JS_ATOM_TAG_INT; -} - -static inline int is_num(int c) -{ - return c >= '0' && c <= '9'; -} - -/* return true if the string is a number n with 0 <= n <= 2^32-1 */ -static inline bool is_num_string(uint32_t *pval, const JSString *p) -{ - uint32_t n; - uint64_t n64; - int c, i, len; - - len = p->len; - if (len == 0 || len > 10) - return false; - c = string_get(p, 0); - if (is_num(c)) { - if (c == '0') { - if (len != 1) - return false; - n = 0; - } else { - n = c - '0'; - for(i = 1; i < len; i++) { - c = string_get(p, i); - if (!is_num(c)) - return false; - n64 = (uint64_t)n * 10 + (c - '0'); - if ((n64 >> 32) != 0) - return false; - n = n64; - } - } - *pval = n; - return true; - } else { - return false; - } -} - -/* XXX: could use faster version ? */ -static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) -{ - size_t i; - - for(i = 0; i < len; i++) - h = h * 263 + str[i]; - return h; -} - -static inline uint32_t hash_string16(const uint16_t *str, - size_t len, uint32_t h) -{ - size_t i; - - for(i = 0; i < len; i++) - h = h * 263 + str[i]; - return h; -} - -static uint32_t hash_string(const JSString *str, uint32_t h) -{ - if (str->is_wide_char) - h = hash_string16(str->u.str16, str->len, h); - else - h = hash_string8(str->u.str8, str->len, h); - return h; -} - -static __maybe_unused void JS_DumpString(JSRuntime *rt, - const JSString *p) -{ - int i, c, sep; - - if (p == NULL) { - printf(""); - return; - } - if (p->header.ref_count != 1) - printf("%d", p->header.ref_count); - if (p->is_wide_char) - putchar('L'); - sep = '\"'; - putchar(sep); - for(i = 0; i < p->len; i++) { - c = string_get(p, i); - if (c == sep || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar(sep); -} - -static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) -{ - JSAtomStruct *p; - int h, i; - /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ - printf("JSAtom count=%d size=%d hash_size=%d:\n", - rt->atom_count, rt->atom_size, rt->atom_hash_size); - printf("JSAtom hash table: {\n"); - for(i = 0; i < rt->atom_hash_size; i++) { - h = rt->atom_hash[i]; - if (h) { - printf(" %d:", i); - while (h) { - p = rt->atom_array[h]; - printf(" "); - JS_DumpString(rt, p); - h = p->hash_next; - } - printf("\n"); - } - } - printf("}\n"); - printf("JSAtom table: {\n"); - for(i = 0; i < rt->atom_size; i++) { - p = rt->atom_array[i]; - if (!atom_is_free(p)) { - printf(" %d: { %d %08x ", i, p->atom_type, p->hash); - if (!(p->len == 0 && p->is_wide_char != 0)) - JS_DumpString(rt, p); - printf(" %d }\n", p->hash_next); - } - } - printf("}\n"); -} - -static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) -{ - JSAtomStruct *p; - uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; - - assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ - new_hash_mask = new_hash_size - 1; - new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); - if (!new_hash) - return -1; - for(i = 0; i < rt->atom_hash_size; i++) { - h = rt->atom_hash[i]; - while (h != 0) { - p = rt->atom_array[h]; - hash_next1 = p->hash_next; - /* add in new hash table */ - j = p->hash & new_hash_mask; - p->hash_next = new_hash[j]; - new_hash[j] = h; - h = hash_next1; - } - } - js_free_rt(rt, rt->atom_hash); - rt->atom_hash = new_hash; - rt->atom_hash_size = new_hash_size; - rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); - // JS_DumpAtoms(rt); - return 0; -} - -static int JS_InitAtoms(JSRuntime *rt) -{ - int i, len, atom_type; - const char *p; - - rt->atom_hash_size = 0; - rt->atom_hash = NULL; - rt->atom_count = 0; - rt->atom_size = 0; - rt->atom_free_index = 0; - if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ - return -1; - - p = js_atom_init; - for(i = 1; i < JS_ATOM_END; i++) { - if (i == JS_ATOM_Private_brand) - atom_type = JS_ATOM_TYPE_PRIVATE; - else if (i >= JS_ATOM_Symbol_toPrimitive) - atom_type = JS_ATOM_TYPE_SYMBOL; - else - atom_type = JS_ATOM_TYPE_STRING; - len = strlen(p); - if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) - return -1; - p = p + len + 1; - } - return 0; -} - -static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) -{ - JSAtomStruct *p; - - if (!__JS_AtomIsConst(v)) { - p = rt->atom_array[v]; - p->header.ref_count++; - } - return v; -} - -JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) -{ - JSRuntime *rt; - JSAtomStruct *p; - - if (!__JS_AtomIsConst(v)) { - rt = ctx->rt; - p = rt->atom_array[v]; - p->header.ref_count++; - } - return v; -} - -static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) -{ - JSRuntime *rt; - JSAtomStruct *p; - - rt = ctx->rt; - if (__JS_AtomIsTaggedInt(v)) - return JS_ATOM_KIND_STRING; - p = rt->atom_array[v]; - switch(p->atom_type) { - case JS_ATOM_TYPE_STRING: - return JS_ATOM_KIND_STRING; - case JS_ATOM_TYPE_GLOBAL_SYMBOL: - return JS_ATOM_KIND_SYMBOL; - case JS_ATOM_TYPE_SYMBOL: - switch(p->hash) { - case JS_ATOM_HASH_SYMBOL: - return JS_ATOM_KIND_SYMBOL; - case JS_ATOM_HASH_PRIVATE: - return JS_ATOM_KIND_PRIVATE; - default: - abort(); - } - default: - abort(); - } - return (JSAtomKindEnum){-1}; // pacify compiler -} - -static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) -{ - uint32_t i = p->hash_next; /* atom_index */ - if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { - JSAtomStruct *p1; - - i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; - p1 = rt->atom_array[i]; - while (p1 != p) { - assert(i != 0); - i = p1->hash_next; - p1 = rt->atom_array[i]; - } - } - return i; -} - -/* string case (internal). Return JS_ATOM_NULL if error. 'str' is - freed. */ -static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) -{ - uint32_t h, h1, i; - JSAtomStruct *p; - int len; - - if (atom_type < JS_ATOM_TYPE_SYMBOL) { - /* str is not NULL */ - if (str->atom_type == atom_type) { - /* str is the atom, return its index */ - i = js_get_atom_index(rt, str); - /* reduce string refcount and increase atom's unless constant */ - if (__JS_AtomIsConst(i)) - str->header.ref_count--; - return i; - } - /* try and locate an already registered atom */ - len = str->len; - h = hash_string(str, atom_type); - h &= JS_ATOM_HASH_MASK; - h1 = h & (rt->atom_hash_size - 1); - i = rt->atom_hash[h1]; - while (i != 0) { - p = rt->atom_array[i]; - if (p->hash == h && - p->atom_type == atom_type && - p->len == len && - js_string_memcmp(p, str, len) == 0) { - if (!__JS_AtomIsConst(i)) - p->header.ref_count++; - goto done; - } - i = p->hash_next; - } - } else { - h1 = 0; /* avoid warning */ - if (atom_type == JS_ATOM_TYPE_SYMBOL) { - h = JS_ATOM_HASH_SYMBOL; - } else { - h = JS_ATOM_HASH_PRIVATE; - atom_type = JS_ATOM_TYPE_SYMBOL; - } - } - - if (rt->atom_free_index == 0) { - /* allow new atom entries */ - uint32_t new_size, start; - JSAtomStruct **new_array; - - /* alloc new with size progression 3/2: - 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 - preallocating space for predefined atoms (at least 195). - */ - new_size = max_int(211, rt->atom_size * 3 / 2); - if (new_size > JS_ATOM_MAX) - goto fail; - /* XXX: should use realloc2 to use slack space */ - new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); - if (!new_array) - goto fail; - /* Note: the atom 0 is not used */ - start = rt->atom_size; - if (start == 0) { - /* JS_ATOM_NULL entry */ - p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); - if (!p) { - js_free_rt(rt, new_array); - goto fail; - } - p->header.ref_count = 1; /* not refcounted */ - p->atom_type = JS_ATOM_TYPE_SYMBOL; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&p->link, &rt->string_list); -#endif - new_array[0] = p; - rt->atom_count++; - start = 1; - } - rt->atom_size = new_size; - rt->atom_array = new_array; - rt->atom_free_index = start; - for(i = start; i < new_size; i++) { - uint32_t next; - if (i == (new_size - 1)) - next = 0; - else - next = i + 1; - rt->atom_array[i] = atom_set_free(next); - } - } - - if (str) { - if (str->atom_type == 0) { - p = str; - p->atom_type = atom_type; - } else { - p = js_malloc_rt(rt, sizeof(JSString) + - (str->len << str->is_wide_char) + - 1 - str->is_wide_char); - if (unlikely(!p)) - goto fail; - p->header.ref_count = 1; - p->is_wide_char = str->is_wide_char; - p->len = str->len; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&p->link, &rt->string_list); -#endif - memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) + - 1 - str->is_wide_char); - js_free_string(rt, str); - } - } else { - p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ - if (!p) - return JS_ATOM_NULL; - p->header.ref_count = 1; - p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ - p->len = 0; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&p->link, &rt->string_list); -#endif - } - - /* use an already free entry */ - i = rt->atom_free_index; - rt->atom_free_index = atom_get_free(rt->atom_array[i]); - rt->atom_array[i] = p; - - p->hash = h; - p->hash_next = i; /* atom_index */ - p->atom_type = atom_type; - p->first_weak_ref = NULL; - - rt->atom_count++; - - if (atom_type != JS_ATOM_TYPE_SYMBOL) { - p->hash_next = rt->atom_hash[h1]; - rt->atom_hash[h1] = i; - if (unlikely(rt->atom_count >= rt->atom_count_resize)) - JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); - } - - // JS_DumpAtoms(rt); - return i; - - fail: - i = JS_ATOM_NULL; - done: - if (str) - js_free_string(rt, str); - return i; -} - -// XXX: `str` must be pure ASCII. No UTF-8 encoded strings -// XXX: `str` must not be the string representation of a small integer -static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, - int atom_type) -{ - JSString *p; - p = js_alloc_string_rt(rt, len, 0); - if (!p) - return JS_ATOM_NULL; - memcpy(p->u.str8, str, len); - p->u.str8[len] = '\0'; - return __JS_NewAtom(rt, p, atom_type); -} - -// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings -static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, - int atom_type) -{ - uint32_t h, h1, i; - JSAtomStruct *p; - - h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); - h &= JS_ATOM_HASH_MASK; - h1 = h & (rt->atom_hash_size - 1); - i = rt->atom_hash[h1]; - while (i != 0) { - p = rt->atom_array[i]; - if (p->hash == h && - p->atom_type == JS_ATOM_TYPE_STRING && - p->len == len && - p->is_wide_char == 0 && - memcmp(p->u.str8, str, len) == 0) { - if (!__JS_AtomIsConst(i)) - p->header.ref_count++; - return i; - } - i = p->hash_next; - } - return JS_ATOM_NULL; -} - -static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) -{ - uint32_t i = p->hash_next; /* atom_index */ - if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { - JSAtomStruct *p0, *p1; - uint32_t h0; - - h0 = p->hash & (rt->atom_hash_size - 1); - i = rt->atom_hash[h0]; - p1 = rt->atom_array[i]; - if (p1 == p) { - rt->atom_hash[h0] = p1->hash_next; - } else { - for(;;) { - assert(i != 0); - p0 = p1; - i = p1->hash_next; - p1 = rt->atom_array[i]; - if (p1 == p) { - p0->hash_next = p1->hash_next; - break; - } - } - } - } - /* insert in free atom list */ - rt->atom_array[i] = atom_set_free(rt->atom_free_index); - rt->atom_free_index = i; - if (unlikely(p->first_weak_ref)) { - reset_weak_ref(rt, &p->first_weak_ref); - } - /* free the string structure */ -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&p->link); -#endif - js_free_rt(rt, p); - rt->atom_count--; - assert(rt->atom_count >= 0); -} - -static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) -{ - JSAtomStruct *p; - - p = rt->atom_array[i]; - if (--p->header.ref_count > 0) - return; - JS_FreeAtomStruct(rt, p); -} - -/* Warning: 'p' is freed */ -static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) -{ - JSRuntime *rt = ctx->rt; - uint32_t n; - if (is_num_string(&n, p)) { - if (n <= JS_ATOM_MAX_INT) { - js_free_string(rt, p); - return __JS_AtomFromUInt32(n); - } - } - /* XXX: should generate an exception */ - return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); -} - -/* `str` may be pure ASCII or UTF-8 encoded */ -JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) -{ - JSValue val; - - if (len == 0 || !is_digit(*str)) { - // TODO(chqrlie): this does not work if `str` has UTF-8 encoded contents - // bug example: `({ "\u00c3\u00a9": 1 }).\u00e9` evaluates to `1`. - JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); - if (atom) - return atom; - } - val = JS_NewStringLen(ctx, str, len); - if (JS_IsException(val)) - return JS_ATOM_NULL; - return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); -} - -/* `str` may be pure ASCII or UTF-8 encoded */ -JSAtom JS_NewAtom(JSContext *ctx, const char *str) -{ - return JS_NewAtomLen(ctx, str, strlen(str)); -} - -JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) -{ - if (n <= JS_ATOM_MAX_INT) { - return __JS_AtomFromUInt32(n); - } else { - char buf[16]; - size_t len = u32toa(buf, n); - JSValue val = js_new_string8_len(ctx, buf, len); - if (JS_IsException(val)) - return JS_ATOM_NULL; - return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), - JS_ATOM_TYPE_STRING); - } -} - -static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) -{ - if ((uint64_t)n <= JS_ATOM_MAX_INT) { - return __JS_AtomFromUInt32((uint32_t)n); - } else { - char buf[24]; - size_t len = i64toa(buf, n); - JSValue val = js_new_string8_len(ctx, buf, len); - if (JS_IsException(val)) - return JS_ATOM_NULL; - return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), - JS_ATOM_TYPE_STRING); - } -} - -/* 'p' is freed */ -static JSValue JS_NewSymbolInternal(JSContext *ctx, JSString *p, int atom_type) -{ - JSRuntime *rt = ctx->rt; - JSAtom atom; - atom = __JS_NewAtom(rt, p, atom_type); - if (atom == JS_ATOM_NULL) - return JS_ThrowOutOfMemory(ctx); - return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); -} - -/* descr must be a non-numeric string atom */ -static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, - int atom_type) -{ - JSRuntime *rt = ctx->rt; - JSString *p; - - assert(!__JS_AtomIsTaggedInt(descr)); - assert(descr < rt->atom_size); - p = rt->atom_array[descr]; - js_dup(JS_MKPTR(JS_TAG_STRING, p)); - return JS_NewSymbolInternal(ctx, p, atom_type); -} - -/* `description` may be pure ASCII or UTF-8 encoded */ -JSValue JS_NewSymbol(JSContext *ctx, const char *description, bool is_global) -{ - JSAtom atom = JS_NewAtom(ctx, description); - if (atom == JS_ATOM_NULL) - return JS_EXCEPTION; - return JS_NewSymbolFromAtom(ctx, atom, is_global ? JS_ATOM_TYPE_GLOBAL_SYMBOL : JS_ATOM_TYPE_SYMBOL); -} - -#define ATOM_GET_STR_BUF_SIZE 64 - -/* Should only be used for debug. */ -static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, - JSAtom atom) -{ - if (__JS_AtomIsTaggedInt(atom)) { - snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); - } else if (atom == JS_ATOM_NULL) { - snprintf(buf, buf_size, ""); - } else if (atom >= rt->atom_size) { - assert(atom < rt->atom_size); - snprintf(buf, buf_size, "", atom); - } else { - JSAtomStruct *p = rt->atom_array[atom]; - *buf = '\0'; - if (atom_is_free(p)) { - assert(!atom_is_free(p)); - snprintf(buf, buf_size, "", atom); - } else if (p != NULL) { - JSString *str = p; - if (str->is_wide_char) { - /* encode surrogates correctly */ - utf8_encode_buf16(buf, buf_size, str->u.str16, str->len); - } else { - /* special case ASCII strings */ - int i, c = 0; - for(i = 0; i < str->len; i++) { - c |= str->u.str8[i]; - } - if (c < 0x80) - return (const char *)str->u.str8; - utf8_encode_buf8(buf, buf_size, str->u.str8, str->len); - } - } - } - return buf; -} - -static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) -{ - return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); -} - -static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, bool force_string) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - - if (__JS_AtomIsTaggedInt(atom)) { - size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); - return js_new_string8_len(ctx, buf, len); - } else { - JSRuntime *rt = ctx->rt; - JSAtomStruct *p; - assert(atom < rt->atom_size); - p = rt->atom_array[atom]; - if (p->atom_type == JS_ATOM_TYPE_STRING) { - goto ret_string; - } else if (force_string) { - if (p->len == 0 && p->is_wide_char != 0) { - /* no description string */ - p = rt->atom_array[JS_ATOM_empty_string]; - } - ret_string: - return js_dup(JS_MKPTR(JS_TAG_STRING, p)); - } else { - return js_dup(JS_MKPTR(JS_TAG_SYMBOL, p)); - } - } -} - -JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) -{ - return __JS_AtomToValue(ctx, atom, false); -} - -JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) -{ - return __JS_AtomToValue(ctx, atom, true); -} - -/* return true if the atom is an array index (i.e. 0 <= index <= - 2^32-2 and return its value */ -static bool JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) -{ - if (__JS_AtomIsTaggedInt(atom)) { - *pval = __JS_AtomToUInt32(atom); - return true; - } else { - JSRuntime *rt = ctx->rt; - JSAtomStruct *p; - uint32_t val; - - assert(atom < rt->atom_size); - p = rt->atom_array[atom]; - if (p->atom_type == JS_ATOM_TYPE_STRING && - is_num_string(&val, p) && val != -1) { - *pval = val; - return true; - } else { - *pval = 0; - return false; - } - } -} - -/* This test must be fast if atom is not a numeric index (e.g. a - method name). Return JS_UNDEFINED if not a numeric - index. JS_EXCEPTION can also be returned. */ -static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) -{ - JSRuntime *rt = ctx->rt; - JSAtomStruct *p1; - JSString *p; - int c, len, ret; - JSValue num, str; - - if (__JS_AtomIsTaggedInt(atom)) - return js_int32(__JS_AtomToUInt32(atom)); - assert(atom < rt->atom_size); - p1 = rt->atom_array[atom]; - if (p1->atom_type != JS_ATOM_TYPE_STRING) - return JS_UNDEFINED; - p = p1; - len = p->len; - if (p->is_wide_char) { - const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len; - if (r >= r_end) - return JS_UNDEFINED; - c = *r; - if (c == '-') { - if (r >= r_end) - return JS_UNDEFINED; - r++; - c = *r; - /* -0 case is specific */ - if (c == '0' && len == 2) - goto minus_zero; - } - /* XXX: should test NaN, but the tests do not check it */ - if (!is_num(c)) { - /* XXX: String should be normalized, therefore 8-bit only */ - const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; - if (!(c =='I' && (r_end - r) == 8 && - !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) - return JS_UNDEFINED; - } - } else { - const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len; - if (r >= r_end) - return JS_UNDEFINED; - c = *r; - if (c == '-') { - if (r >= r_end) - return JS_UNDEFINED; - r++; - c = *r; - /* -0 case is specific */ - if (c == '0' && len == 2) { - minus_zero: - return js_float64(-0.0); - } - } - if (!is_num(c)) { - if (!(c =='I' && (r_end - r) == 8 && - !memcmp(r + 1, "nfinity", 7))) - return JS_UNDEFINED; - } - } - /* this is ECMA CanonicalNumericIndexString primitive */ - num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); - if (JS_IsException(num)) - return num; - str = JS_ToString(ctx, num); - if (JS_IsException(str)) { - JS_FreeValue(ctx, num); - return str; - } - ret = js_string_eq(p, JS_VALUE_GET_STRING(str)); - JS_FreeValue(ctx, str); - if (ret) { - return num; - } else { - JS_FreeValue(ctx, num); - return JS_UNDEFINED; - } -} - -/* return -1 if exception or true/false */ -static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) -{ - JSValue num; - num = JS_AtomIsNumericIndex1(ctx, atom); - if (likely(JS_IsUndefined(num))) - return false; - if (JS_IsException(num)) - return -1; - JS_FreeValue(ctx, num); - return true; -} - -void JS_FreeAtom(JSContext *ctx, JSAtom v) -{ - if (!__JS_AtomIsConst(v)) - __JS_FreeAtom(ctx->rt, v); -} - -void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) -{ - if (!__JS_AtomIsConst(v)) - __JS_FreeAtom(rt, v); -} - -/* return true if 'v' is a symbol with a string description */ -static bool JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) -{ - JSRuntime *rt; - JSAtomStruct *p; - - rt = ctx->rt; - if (__JS_AtomIsTaggedInt(v)) - return false; - p = rt->atom_array[v]; - return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && - p->hash == JS_ATOM_HASH_SYMBOL) || - p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && - !(p->len == 0 && p->is_wide_char != 0)); -} - -static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - const char *p; - int i; - - /* XXX: should handle embedded null characters */ - /* XXX: should move encoding code to JS_AtomGetStr */ - p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - for (i = 0; p[i]; i++) { - int c = (unsigned char)p[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) - break; - } - if (i > 0 && p[i] == '\0') { - printf("%s", p); - } else { - putchar('"'); - printf("%.*s", i, p); - for (; p[i]; i++) { - int c = (unsigned char)p[i]; - if (c == '\"' || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar('\"'); - } -} - -/* free with JS_FreeCString() */ -const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) -{ - JSValue str; - const char *cstr; - - str = JS_AtomToString(ctx, atom); - if (JS_IsException(str)) - return NULL; - cstr = JS_ToCString(ctx, str); - JS_FreeValue(ctx, str); - return cstr; -} - -/* return a string atom containing name concatenated with str1 */ -/* `str1` may be pure ASCII or UTF-8 encoded */ -// TODO(chqrlie): use string concatenation instead of UTF-8 conversion -static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) -{ - JSValue str; - JSAtom atom; - const char *cstr; - char *cstr2; - size_t len, len1; - - str = JS_AtomToString(ctx, name); - if (JS_IsException(str)) - return JS_ATOM_NULL; - cstr = JS_ToCStringLen(ctx, &len, str); - if (!cstr) - goto fail; - len1 = strlen(str1); - cstr2 = js_malloc(ctx, len + len1 + 1); - if (!cstr2) - goto fail; - memcpy(cstr2, cstr, len); - memcpy(cstr2 + len, str1, len1); - cstr2[len + len1] = '\0'; - atom = JS_NewAtomLen(ctx, cstr2, len + len1); - js_free(ctx, cstr2); - JS_FreeCString(ctx, cstr); - JS_FreeValue(ctx, str); - return atom; - fail: - JS_FreeCString(ctx, cstr); - JS_FreeValue(ctx, str); - return JS_ATOM_NULL; -} - -static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) -{ - char buf[16]; - u32toa(buf, n); - return js_atom_concat_str(ctx, name, buf); -} - -static inline bool JS_IsEmptyString(JSValue v) -{ - return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; -} - -/* JSClass support */ - -/* a new class ID is allocated if *pclass_id == 0, otherwise *pclass_id is left unchanged */ -JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id) -{ - JSClassID class_id = *pclass_id; - if (class_id == 0) { - class_id = rt->js_class_id_alloc++; - *pclass_id = class_id; - } - return class_id; -} - -JSClassID JS_GetClassID(JSValue v) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) - return JS_INVALID_CLASS_ID; - p = JS_VALUE_GET_OBJ(v); - return p->class_id; -} - -bool JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) -{ - return (class_id < rt->class_count && - rt->class_array[class_id].class_id != 0); -} - -/* create a new object internal class. Return -1 if error, 0 if - OK. The finalizer can be NULL if none is needed. */ -static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, - const JSClassDef *class_def, JSAtom name) -{ - int new_size, i; - JSClass *cl, *new_class_array; - struct list_head *el; - - if (class_id >= (1 << 16)) - return -1; - if (class_id < rt->class_count && - rt->class_array[class_id].class_id != 0) - return -1; - - if (class_id >= rt->class_count) { - new_size = max_int(JS_CLASS_INIT_COUNT, - max_int(class_id + 1, rt->class_count * 3 / 2)); - - /* reallocate the context class prototype array, if any */ - list_for_each(el, &rt->context_list) { - JSContext *ctx = list_entry(el, JSContext, link); - JSValue *new_tab; - new_tab = js_realloc_rt(rt, ctx->class_proto, - sizeof(ctx->class_proto[0]) * new_size); - if (!new_tab) - return -1; - for(i = rt->class_count; i < new_size; i++) - new_tab[i] = JS_NULL; - ctx->class_proto = new_tab; - } - /* reallocate the class array */ - new_class_array = js_realloc_rt(rt, rt->class_array, - sizeof(JSClass) * new_size); - if (!new_class_array) - return -1; - memset(new_class_array + rt->class_count, 0, - (new_size - rt->class_count) * sizeof(JSClass)); - rt->class_array = new_class_array; - rt->class_count = new_size; - } - cl = &rt->class_array[class_id]; - cl->class_id = class_id; - cl->class_name = JS_DupAtomRT(rt, name); - cl->finalizer = class_def->finalizer; - cl->gc_mark = class_def->gc_mark; - cl->call = class_def->call; - cl->exotic = class_def->exotic; - return 0; -} - -int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) -{ - int ret, len; - JSAtom name; - - // XXX: class_def->class_name must be raw 8-bit contents. No UTF-8 encoded strings - len = strlen(class_def->class_name); - name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); - if (name == JS_ATOM_NULL) { - name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); - if (name == JS_ATOM_NULL) - return -1; - } - ret = JS_NewClass1(rt, class_id, class_def, name); - JS_FreeAtomRT(rt, name); - return ret; -} - -// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed -// XXX: no special case for len == 0 -static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) -{ - JSString *str; - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - memcpy(str->u.str8, buf, len); - str->u.str8[len] = '\0'; - return JS_MKPTR(JS_TAG_STRING, str); -} - -// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed -// XXX: no special case for the empty string -static inline JSValue js_new_string8(JSContext *ctx, const char *str) -{ - return js_new_string8_len(ctx, str, strlen(str)); -} - -static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) -{ - JSString *str; - str = js_alloc_string(ctx, len, 1); - if (!str) - return JS_EXCEPTION; - memcpy(str->u.str16, buf, len * 2); - return JS_MKPTR(JS_TAG_STRING, str); -} - -static JSValue js_new_string_char(JSContext *ctx, uint16_t c) -{ - if (c < 0x100) { - char ch8 = c; - return js_new_string8_len(ctx, &ch8, 1); - } else { - uint16_t ch16 = c; - return js_new_string16_len(ctx, &ch16, 1); - } -} - -static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) -{ - int len = end - start; - if (start == 0 && end == p->len) { - return js_dup(JS_MKPTR(JS_TAG_STRING, p)); - } - if (len <= 0) { - return JS_AtomToString(ctx, JS_ATOM_empty_string); - } - if (p->is_wide_char) { - JSString *str; - int i; - uint16_t c = 0; - for (i = start; i < end; i++) { - c |= p->u.str16[i]; - } - if (c > 0xFF) - return js_new_string16_len(ctx, p->u.str16 + start, len); - - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - for (i = 0; i < len; i++) { - str->u.str8[i] = p->u.str16[start + i]; - } - str->u.str8[len] = '\0'; - return JS_MKPTR(JS_TAG_STRING, str); - } else { - return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len); - } -} - -typedef struct StringBuffer { - JSContext *ctx; - JSString *str; - int len; - int size; - int is_wide_char; - int error_status; -} StringBuffer; - -/* It is valid to call string_buffer_end() and all string_buffer functions even - if string_buffer_init() or another string_buffer function returns an error. - If the error_status is set, string_buffer_end() returns JS_EXCEPTION. - */ -static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, - int is_wide) -{ - s->ctx = ctx; - s->size = size; - s->len = 0; - s->is_wide_char = is_wide; - s->error_status = 0; - s->str = js_alloc_string(ctx, size, is_wide); - if (unlikely(!s->str)) { - s->size = 0; - return s->error_status = -1; - } -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - /* the StringBuffer may reallocate the JSString, only link it at the end */ - list_del(&s->str->link); -#endif - return 0; -} - -static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) -{ - return string_buffer_init2(ctx, s, size, 0); -} - -static void string_buffer_free(StringBuffer *s) -{ - js_free(s->ctx, s->str); - s->str = NULL; -} - -static int string_buffer_set_error(StringBuffer *s) -{ - js_free(s->ctx, s->str); - s->str = NULL; - s->size = 0; - s->len = 0; - return s->error_status = -1; -} - -static no_inline int string_buffer_widen(StringBuffer *s, int size) -{ - JSString *str; - size_t slack; - int i; - - if (s->error_status) - return -1; - - str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); - if (!str) - return string_buffer_set_error(s); - size += slack >> 1; - for(i = s->len; i-- > 0;) { - str->u.str16[i] = str->u.str8[i]; - } - s->is_wide_char = 1; - s->size = size; - s->str = str; - return 0; -} - -static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) -{ - JSString *new_str; - int new_size; - size_t new_size_bytes, slack; - - if (s->error_status) - return -1; - - if (new_len > JS_STRING_LEN_MAX) { - JS_ThrowRangeError(s->ctx, "invalid string length"); - return string_buffer_set_error(s); - } - new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); - if (!s->is_wide_char && c >= 0x100) { - return string_buffer_widen(s, new_size); - } - new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; - new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); - if (!new_str) - return string_buffer_set_error(s); - new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); - s->size = new_size; - s->str = new_str; - return 0; -} - -static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) -{ - if (unlikely(s->len >= s->size)) { - if (string_buffer_realloc(s, s->len + 1, c)) - return -1; - } - if (s->is_wide_char) { - s->str->u.str16[s->len++] = c; - } else if (c < 0x100) { - s->str->u.str8[s->len++] = c; - } else { - if (string_buffer_widen(s, s->size)) - return -1; - s->str->u.str16[s->len++] = c; - } - return 0; -} - -/* 0 <= c <= 0xff */ -static int string_buffer_putc8(StringBuffer *s, uint32_t c) -{ - if (unlikely(s->len >= s->size)) { - if (string_buffer_realloc(s, s->len + 1, c)) - return -1; - } - if (s->is_wide_char) { - s->str->u.str16[s->len++] = c; - } else { - s->str->u.str8[s->len++] = c; - } - return 0; -} - -/* 0 <= c <= 0xffff */ -static int string_buffer_putc16(StringBuffer *s, uint32_t c) -{ - if (likely(s->len < s->size)) { - if (s->is_wide_char) { - s->str->u.str16[s->len++] = c; - return 0; - } else if (c < 0x100) { - s->str->u.str8[s->len++] = c; - return 0; - } - } - return string_buffer_putc_slow(s, c); -} - -/* 0 <= c <= 0x10ffff */ -static int string_buffer_putc(StringBuffer *s, uint32_t c) -{ - if (unlikely(c >= 0x10000)) { - /* surrogate pair */ - if (string_buffer_putc16(s, get_hi_surrogate(c))) - return -1; - c = get_lo_surrogate(c); - } - return string_buffer_putc16(s, c); -} - -static int string_getc(const JSString *p, int *pidx) -{ - int idx, c, c1; - idx = *pidx; - if (p->is_wide_char) { - c = p->u.str16[idx++]; - if (is_hi_surrogate(c) && idx < p->len) { - c1 = p->u.str16[idx]; - if (is_lo_surrogate(c1)) { - c = from_surrogate(c, c1); - idx++; - } - } - } else { - c = p->u.str8[idx++]; - } - *pidx = idx; - return c; -} - -static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) -{ - int i; - - if (s->len + len > s->size) { - if (string_buffer_realloc(s, s->len + len, 0)) - return -1; - } - if (s->is_wide_char) { - for (i = 0; i < len; i++) { - s->str->u.str16[s->len + i] = p[i]; - } - s->len += len; - } else { - memcpy(&s->str->u.str8[s->len], p, len); - s->len += len; - } - return 0; -} - -static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) -{ - int c = 0, i; - - for (i = 0; i < len; i++) { - c |= p[i]; - } - if (s->len + len > s->size) { - if (string_buffer_realloc(s, s->len + len, c)) - return -1; - } else if (!s->is_wide_char && c >= 0x100) { - if (string_buffer_widen(s, s->size)) - return -1; - } - if (s->is_wide_char) { - memcpy(&s->str->u.str16[s->len], p, len << 1); - s->len += len; - } else { - for (i = 0; i < len; i++) { - s->str->u.str8[s->len + i] = p[i]; - } - s->len += len; - } - return 0; -} - -/* appending an ASCII string */ -static int string_buffer_puts8(StringBuffer *s, const char *str) -{ - return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); -} - -static int string_buffer_concat(StringBuffer *s, const JSString *p, - uint32_t from, uint32_t to) -{ - if (to <= from) - return 0; - if (p->is_wide_char) - return string_buffer_write16(s, p->u.str16 + from, to - from); - else - return string_buffer_write8(s, p->u.str8 + from, to - from); -} - -static int string_buffer_concat_value(StringBuffer *s, JSValue v) -{ - JSString *p; - JSValue v1; - int res; - - if (s->error_status) { - /* prevent exception overload */ - return -1; - } - if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { - v1 = JS_ToString(s->ctx, v); - if (JS_IsException(v1)) - return string_buffer_set_error(s); - p = JS_VALUE_GET_STRING(v1); - res = string_buffer_concat(s, p, 0, p->len); - JS_FreeValue(s->ctx, v1); - return res; - } - p = JS_VALUE_GET_STRING(v); - return string_buffer_concat(s, p, 0, p->len); -} - -static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) -{ - JSString *p; - int res; - - if (s->error_status) { - /* prevent exception overload */ - JS_FreeValue(s->ctx, v); - return -1; - } - if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { - v = JS_ToStringFree(s->ctx, v); - if (JS_IsException(v)) - return string_buffer_set_error(s); - } - p = JS_VALUE_GET_STRING(v); - res = string_buffer_concat(s, p, 0, p->len); - JS_FreeValue(s->ctx, v); - return res; -} - -static int string_buffer_fill(StringBuffer *s, int c, int count) -{ - /* XXX: optimize */ - if (s->len + count > s->size) { - if (string_buffer_realloc(s, s->len + count, c)) - return -1; - } - while (count-- > 0) { - if (string_buffer_putc16(s, c)) - return -1; - } - return 0; -} - -static JSValue string_buffer_end(StringBuffer *s) -{ - JSString *str; - str = s->str; - if (s->error_status) - return JS_EXCEPTION; - if (s->len == 0) { - js_free(s->ctx, str); - s->str = NULL; - return JS_AtomToString(s->ctx, JS_ATOM_empty_string); - } - if (s->len < s->size) { - /* smaller size so js_realloc should not fail, but OK if it does */ - /* XXX: should add some slack to avoid unnecessary calls */ - /* XXX: might need to use malloc+free to ensure smaller size */ - str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + - (s->len << s->is_wide_char) + 1 - s->is_wide_char); - if (str == NULL) - str = s->str; - s->str = str; - } - if (!s->is_wide_char) - str->u.str8[s->len] = 0; -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_add_tail(&str->link, &s->ctx->rt->string_list); -#endif - str->is_wide_char = s->is_wide_char; - str->len = s->len; - s->str = NULL; - return JS_MKPTR(JS_TAG_STRING, str); -} - -/* create a string from a UTF-8 buffer */ -JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) -{ - JSString *str; - size_t len; - int kind; - - if (buf_len <= 0) { - return JS_AtomToString(ctx, JS_ATOM_empty_string); - } - /* Compute string kind and length: 7-bit, 8-bit, 16-bit, 16-bit UTF-16 */ - kind = utf8_scan(buf, buf_len, &len); - if (len > JS_STRING_LEN_MAX) - return JS_ThrowRangeError(ctx, "invalid string length"); - - switch (kind) { - case UTF8_PLAIN_ASCII: - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - memcpy(str->u.str8, buf, len); - str->u.str8[len] = '\0'; - break; - case UTF8_NON_ASCII: - /* buf contains non-ASCII code-points, but limited to 8-bit values */ - str = js_alloc_string(ctx, len, 0); - if (!str) - return JS_EXCEPTION; - utf8_decode_buf8(str->u.str8, len + 1, buf, buf_len); - break; - default: - // This causes a potential problem in JS_ThrowError if message is invalid - //if (kind & UTF8_HAS_ERRORS) - // return JS_ThrowRangeError(ctx, "invalid UTF-8 sequence"); - str = js_alloc_string(ctx, len, 1); - if (!str) - return JS_EXCEPTION; - utf8_decode_buf16(str->u.str16, len, buf, buf_len); - break; - } - return JS_MKPTR(JS_TAG_STRING, str); -} - -static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, - JSValue str2, const char *str3) -{ - StringBuffer b_s, *b = &b_s; - int len1, len3; - JSString *p; - - if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { - str2 = JS_ToStringFree(ctx, str2); - if (JS_IsException(str2)) - goto fail; - } - p = JS_VALUE_GET_STRING(str2); - len1 = strlen(str1); - len3 = strlen(str3); - - if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) - goto fail; - - string_buffer_write8(b, (const uint8_t *)str1, len1); - string_buffer_concat(b, p, 0, p->len); - string_buffer_write8(b, (const uint8_t *)str3, len3); - - JS_FreeValue(ctx, str2); - return string_buffer_end(b); - - fail: - JS_FreeValue(ctx, str2); - return JS_EXCEPTION; -} - -/* `str` may be pure ASCII or UTF-8 encoded */ -JSValue JS_NewAtomString(JSContext *ctx, const char *str) -{ - JSAtom atom = JS_NewAtom(ctx, str); - if (atom == JS_ATOM_NULL) - return JS_EXCEPTION; - JSValue val = JS_AtomToString(ctx, atom); - JS_FreeAtom(ctx, atom); - return val; -} - -/* return (NULL, 0) if exception. */ -/* return pointer into a JSString with a live ref_count */ -/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ -const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValue val1, bool cesu8) -{ - JSValue val; - JSString *str, *str_new; - int pos, len, c, c1; - JSObject *p; - uint8_t *q; - - if (JS_VALUE_GET_TAG(val1) == JS_TAG_STRING) { - val = js_dup(val1); - goto go; - } - - val = JS_ToString(ctx, val1); - if (!JS_IsException(val)) - goto go; - - // Stringification can fail when there is an exception pending, - // e.g. a stack overflow InternalError. Special-case exception - // objects to make debugging easier, look up the .message property - // and stringify that. - if (JS_VALUE_GET_TAG(val1) != JS_TAG_OBJECT) - goto fail; - - p = JS_VALUE_GET_OBJ(val1); - if (p->class_id != JS_CLASS_ERROR) - goto fail; - - val = JS_GetProperty(ctx, val1, JS_ATOM_message); - if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) { - JS_FreeValue(ctx, val); - goto fail; - } - -go: - str = JS_VALUE_GET_STRING(val); - len = str->len; - if (!str->is_wide_char) { - const uint8_t *src = str->u.str8; - int count; - - /* count the number of non-ASCII characters */ - /* Scanning the whole string is required for ASCII strings, - and computing the number of non-ASCII bytes is less expensive - than testing each byte, hence this method is faster for ASCII - strings, which is the most common case. - */ - count = 0; - for (pos = 0; pos < len; pos++) { - count += src[pos] >> 7; - } - if (count == 0) { - if (plen) - *plen = len; - return (const char *)src; - } - str_new = js_alloc_string(ctx, len + count, 0); - if (!str_new) - goto fail; - q = str_new->u.str8; - for (pos = 0; pos < len; pos++) { - c = src[pos]; - if (c < 0x80) { - *q++ = c; - } else { - *q++ = (c >> 6) | 0xc0; - *q++ = (c & 0x3f) | 0x80; - } - } - } else { - const uint16_t *src = str->u.str16; - /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may - produce 4 bytes but use 2 code points. - */ - str_new = js_alloc_string(ctx, len * 3, 0); - if (!str_new) - goto fail; - q = str_new->u.str8; - pos = 0; - while (pos < len) { - c = src[pos++]; - if (c < 0x80) { - *q++ = c; - } else { - if (is_hi_surrogate(c)) { - if (pos < len && !cesu8) { - c1 = src[pos]; - if (is_lo_surrogate(c1)) { - pos++; - c = from_surrogate(c, c1); - } else { - /* Keep unmatched surrogate code points */ - /* c = 0xfffd; */ /* error */ - } - } else { - /* Keep unmatched surrogate code points */ - /* c = 0xfffd; */ /* error */ - } - } - q += utf8_encode(q, c); - } - } - } - - *q = '\0'; - str_new->len = q - str_new->u.str8; - JS_FreeValue(ctx, val); - if (plen) - *plen = str_new->len; - return (const char *)str_new->u.str8; - fail: - if (plen) - *plen = 0; - return NULL; -} - -void JS_FreeCString(JSContext *ctx, const char *ptr) -{ - JSString *p; - if (!ptr) - return; - /* purposely removing constness */ - p = container_of(ptr, JSString, u); - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); -} - -static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) -{ - int c, i; - for(i = 0; i < len; i++) { - c = src1[i] - src2[i]; - if (c != 0) - return c; - } - return 0; -} - -static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) -{ - int c, i; - for(i = 0; i < len; i++) { - c = src1[i] - src2[i]; - if (c != 0) - return c; - } - return 0; -} - -static int js_string_memcmp(const JSString *p1, const JSString *p2, int len) -{ - int res; - - if (likely(!p1->is_wide_char)) { - if (likely(!p2->is_wide_char)) - res = memcmp(p1->u.str8, p2->u.str8, len); - else - res = -memcmp16_8(p2->u.str16, p1->u.str8, len); - } else { - if (!p2->is_wide_char) - res = memcmp16_8(p1->u.str16, p2->u.str8, len); - else - res = memcmp16(p1->u.str16, p2->u.str16, len); - } - return res; -} - -static bool js_string_eq(const JSString *p1, const JSString *p2) { - if (p1->len != p2->len) - return false; - return js_string_memcmp(p1, p2, p1->len) == 0; -} - -/* return < 0, 0 or > 0 */ -static int js_string_compare(const JSString *p1, const JSString *p2) -{ - int res, len; - len = min_int(p1->len, p2->len); - res = js_string_memcmp(p1, p2, len); - if (res == 0) - res = compare_u32(p1->len, p2->len); - return res; -} - -static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len) -{ - if (p->is_wide_char) { - memcpy(dst, p->u.str16 + offset, len * 2); - } else { - const uint8_t *src1 = p->u.str8 + offset; - int i; - - for(i = 0; i < len; i++) - dst[i] = src1[i]; - } -} - -static JSValue JS_ConcatString1(JSContext *ctx, - const JSString *p1, const JSString *p2) -{ - JSString *p; - uint32_t len; - int is_wide_char; - - len = p1->len + p2->len; - if (len > JS_STRING_LEN_MAX) - return JS_ThrowRangeError(ctx, "invalid string length"); - is_wide_char = p1->is_wide_char | p2->is_wide_char; - p = js_alloc_string(ctx, len, is_wide_char); - if (!p) - return JS_EXCEPTION; - if (!is_wide_char) { - memcpy(p->u.str8, p1->u.str8, p1->len); - memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len); - p->u.str8[len] = '\0'; - } else { - copy_str16(p->u.str16, p1, 0, p1->len); - copy_str16(p->u.str16 + p1->len, p2, 0, p2->len); - } - return JS_MKPTR(JS_TAG_STRING, p); -} - -/* op1 and op2 are converted to strings. For convience, op1 or op2 = - JS_EXCEPTION are accepted and return JS_EXCEPTION. */ -static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) -{ - JSValue ret; - JSString *p1, *p2; - - if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { - op1 = JS_ToStringFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - return JS_EXCEPTION; - } - } - if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) { - op2 = JS_ToStringFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - return JS_EXCEPTION; - } - } - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - - /* XXX: could also check if p1 is empty */ - if (p2->len == 0) { - goto ret_op1; - } - if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char - && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { - /* Concatenate in place in available space at the end of p1 */ - if (p1->is_wide_char) { - memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); - p1->len += p2->len; - } else { - memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); - p1->len += p2->len; - p1->u.str8[p1->len] = '\0'; - } - ret_op1: - JS_FreeValue(ctx, op2); - return op1; - } - ret = JS_ConcatString1(ctx, p1, p2); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return ret; -} - -/* Shape support */ - -static inline size_t get_shape_size(size_t hash_size, size_t prop_size) -{ - return hash_size * sizeof(uint32_t) + sizeof(JSShape) + - prop_size * sizeof(JSShapeProperty); -} - -static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) -{ - return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); -} - -static inline uint32_t *prop_hash_end(JSShape *sh) -{ - return (uint32_t *)sh; -} - -static inline void *get_alloc_from_shape(JSShape *sh) -{ - return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); -} - -static inline JSShapeProperty *get_shape_prop(JSShape *sh) -{ - return sh->prop; -} - -static int init_shape_hash(JSRuntime *rt) -{ - rt->shape_hash_bits = 4; /* 16 shapes */ - rt->shape_hash_size = 1 << rt->shape_hash_bits; - rt->shape_hash_count = 0; - rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * - rt->shape_hash_size); - if (!rt->shape_hash) - return -1; - return 0; -} - -/* same magic hash multiplier as the Linux kernel */ -static uint32_t shape_hash(uint32_t h, uint32_t val) -{ - return (h + val) * 0x9e370001; -} - -/* truncate the shape hash to 'hash_bits' bits */ -static uint32_t get_shape_hash(uint32_t h, int hash_bits) -{ - return h >> (32 - hash_bits); -} - -static uint32_t shape_initial_hash(JSObject *proto) -{ - uint32_t h; - h = shape_hash(1, (uintptr_t)proto); - if (sizeof(proto) > 4) - h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); - return h; -} - -static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) -{ - int new_shape_hash_size, i; - uint32_t h; - JSShape **new_shape_hash, *sh, *sh_next; - - new_shape_hash_size = 1 << new_shape_hash_bits; - new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * - new_shape_hash_size); - if (!new_shape_hash) - return -1; - for(i = 0; i < rt->shape_hash_size; i++) { - for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { - sh_next = sh->shape_hash_next; - h = get_shape_hash(sh->hash, new_shape_hash_bits); - sh->shape_hash_next = new_shape_hash[h]; - new_shape_hash[h] = sh; - } - } - js_free_rt(rt, rt->shape_hash); - rt->shape_hash_bits = new_shape_hash_bits; - rt->shape_hash_size = new_shape_hash_size; - rt->shape_hash = new_shape_hash; - return 0; -} - -static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) -{ - uint32_t h; - h = get_shape_hash(sh->hash, rt->shape_hash_bits); - sh->shape_hash_next = rt->shape_hash[h]; - rt->shape_hash[h] = sh; - rt->shape_hash_count++; -} - -static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) -{ - uint32_t h; - JSShape **psh; - - h = get_shape_hash(sh->hash, rt->shape_hash_bits); - psh = &rt->shape_hash[h]; - while (*psh != sh) - psh = &(*psh)->shape_hash_next; - *psh = sh->shape_hash_next; - rt->shape_hash_count--; -} - -/* create a new empty shape with prototype 'proto' */ -static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, - int hash_size, int prop_size) -{ - JSRuntime *rt = ctx->rt; - void *sh_alloc; - JSShape *sh; - - /* resize the shape hash table if necessary */ - if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { - resize_shape_hash(rt, rt->shape_hash_bits + 1); - } - - sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); - if (!sh_alloc) - return NULL; - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; - add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); - if (proto) - js_dup(JS_MKPTR(JS_TAG_OBJECT, proto)); - sh->proto = proto; - memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * - hash_size); - sh->prop_hash_mask = hash_size - 1; - sh->prop_size = prop_size; - sh->prop_count = 0; - sh->deleted_prop_count = 0; - - /* insert in the hash table */ - sh->hash = shape_initial_hash(proto); - sh->is_hashed = true; - sh->has_small_array_index = false; - sh->watchpoint = NULL; - js_shape_hash_link(ctx->rt, sh); - return sh; -} - -static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) -{ - return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, - JS_PROP_INITIAL_SIZE); -} - -/* The shape is cloned. The new shape is not inserted in the shape - hash table */ -static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) -{ - JSShape *sh; - void *sh_alloc, *sh_alloc1; - size_t size; - JSShapeProperty *pr; - uint32_t i, hash_size; - - hash_size = sh1->prop_hash_mask + 1; - size = get_shape_size(hash_size, sh1->prop_size); - sh_alloc = js_malloc(ctx, size); - if (!sh_alloc) - return NULL; - sh_alloc1 = get_alloc_from_shape(sh1); - memcpy(sh_alloc, sh_alloc1, size); - sh = get_shape_from_alloc(sh_alloc, hash_size); - sh->header.ref_count = 1; - add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); - sh->is_hashed = false; - sh->watchpoint = NULL; - if (sh->proto) { - js_dup(JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - } - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { - JS_DupAtom(ctx, pr->atom); - } - return sh; -} - -static JSShape *js_dup_shape(JSShape *sh) -{ - sh->header.ref_count++; - return sh; -} - -static void js_free_shape0(JSRuntime *rt, JSShape *sh) -{ - uint32_t i; - JSShapeProperty *pr; - - assert(sh->header.ref_count == 0); - if (sh->is_hashed) - js_shape_hash_unlink(rt, sh); - if (sh->proto != NULL) { - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - } - js_shape_delete_watchpoints(rt, sh, NULL); - pr = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - JS_FreeAtomRT(rt, pr->atom); - pr++; - } - remove_gc_object(&sh->header); - js_free_rt(rt, get_alloc_from_shape(sh)); -} - -static void js_free_shape(JSRuntime *rt, JSShape *sh) -{ - if (unlikely(--sh->header.ref_count <= 0)) { - js_free_shape0(rt, sh); - } -} - -static void js_free_shape_null(JSRuntime *rt, JSShape *sh) -{ - if (sh) - js_free_shape(rt, sh); -} - -/* make space to hold at least 'count' properties */ -static no_inline int resize_properties(JSContext *ctx, JSShape **psh, - JSObject *p, uint32_t count) -{ - JSShape *sh; - uint32_t new_size, new_hash_size, new_hash_mask, i; - JSShapeProperty *pr; - void *sh_alloc; - intptr_t h; - - sh = *psh; - new_size = max_int(count, sh->prop_size * 3 / 2); - /* Reallocate prop array first to avoid crash or size inconsistency - in case of memory allocation failure */ - if (p) { - JSProperty *new_prop; - new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); - if (unlikely(!new_prop)) - return -1; - p->prop = new_prop; - } - new_hash_size = sh->prop_hash_mask + 1; - while (new_hash_size < new_size) - new_hash_size = 2 * new_hash_size; - if (new_hash_size != (sh->prop_hash_mask + 1)) { - JSShape *old_sh; - /* resize the hash table and the properties */ - old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) - return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - /* copy all the fields and the properties */ - memcpy(sh, old_sh, - sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - new_hash_mask = new_hash_size - 1; - sh->prop_hash_mask = new_hash_mask; - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); - for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { - if (pr->atom != JS_ATOM_NULL) { - h = ((uintptr_t)pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = i + 1; - } - } - js_free(ctx, get_alloc_from_shape(old_sh)); - } else { - /* only resize the properties */ - list_del(&sh->header.link); - sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), - get_shape_size(new_hash_size, new_size)); - if (unlikely(!sh_alloc)) { - /* insert again in the GC list */ - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - return -1; - } - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - } - *psh = sh; - sh->prop_size = new_size; - return 0; -} - -/* remove the deleted properties. */ -static int compact_properties(JSContext *ctx, JSObject *p) -{ - JSShape *sh, *old_sh; - void *sh_alloc; - intptr_t h; - uint32_t new_hash_size, i, j, new_hash_mask, new_size; - JSShapeProperty *old_pr, *pr; - JSProperty *prop, *new_prop; - - sh = p->shape; - assert(!sh->is_hashed); - - new_size = max_int(JS_PROP_INITIAL_SIZE, - sh->prop_count - sh->deleted_prop_count); - assert(new_size <= sh->prop_size); - - new_hash_size = sh->prop_hash_mask + 1; - while ((new_hash_size / 2) >= new_size) - new_hash_size = new_hash_size / 2; - new_hash_mask = new_hash_size - 1; - - /* resize the hash table and the properties */ - old_sh = sh; - sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); - if (!sh_alloc) - return -1; - sh = get_shape_from_alloc(sh_alloc, new_hash_size); - list_del(&old_sh->header.link); - memcpy(sh, old_sh, sizeof(JSShape)); - list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); - - memset(prop_hash_end(sh) - new_hash_size, 0, - sizeof(prop_hash_end(sh)[0]) * new_hash_size); - - j = 0; - old_pr = old_sh->prop; - pr = sh->prop; - prop = p->prop; - for(i = 0; i < sh->prop_count; i++) { - if (old_pr->atom != JS_ATOM_NULL) { - pr->atom = old_pr->atom; - pr->flags = old_pr->flags; - h = ((uintptr_t)old_pr->atom & new_hash_mask); - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = j + 1; - prop[j] = prop[i]; - j++; - pr++; - } - old_pr++; - } - assert(j == (sh->prop_count - sh->deleted_prop_count)); - sh->prop_hash_mask = new_hash_mask; - sh->prop_size = new_size; - sh->deleted_prop_count = 0; - sh->prop_count = j; - - p->shape = sh; - js_free(ctx, get_alloc_from_shape(old_sh)); - - /* reduce the size of the object properties */ - new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); - if (new_prop) - p->prop = new_prop; - return 0; -} - -static int add_shape_property(JSContext *ctx, JSShape **psh, - JSObject *p, JSAtom atom, int prop_flags) -{ - JSRuntime *rt = ctx->rt; - JSShape *sh = *psh; - JSShapeProperty *pr, *prop; - uint32_t hash_mask, new_shape_hash = 0; - intptr_t h; - - /* update the shape hash */ - if (sh->is_hashed) { - js_shape_hash_unlink(rt, sh); - new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); - } - - if (unlikely(sh->prop_count >= sh->prop_size)) { - if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { - /* in case of error, reinsert in the hash table. - sh is still valid if resize_properties() failed */ - if (sh->is_hashed) - js_shape_hash_link(rt, sh); - return -1; - } - sh = *psh; - } - if (sh->is_hashed) { - sh->hash = new_shape_hash; - js_shape_hash_link(rt, sh); - } - /* Initialize the new shape property. - The object property at p->prop[sh->prop_count] is uninitialized */ - prop = get_shape_prop(sh); - pr = &prop[sh->prop_count++]; - pr->atom = JS_DupAtom(ctx, atom); - pr->flags = prop_flags; - sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); - /* add in hash table */ - hash_mask = sh->prop_hash_mask; - h = atom & hash_mask; - pr->hash_next = prop_hash_end(sh)[-h - 1]; - prop_hash_end(sh)[-h - 1] = sh->prop_count; - return 0; -} - -/* find a hashed empty shape matching the prototype. Return NULL if - not found */ -static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) -{ - JSShape *sh1; - uint32_t h, h1; - - h = shape_initial_hash(proto); - h1 = get_shape_hash(h, rt->shape_hash_bits); - for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { - if (sh1->hash == h && - sh1->proto == proto && - sh1->prop_count == 0) { - return sh1; - } - } - return NULL; -} - -/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if - not found */ -static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, - JSAtom atom, int prop_flags) -{ - JSShape *sh1; - uint32_t h, h1, i, n; - - h = sh->hash; - h = shape_hash(h, atom); - h = shape_hash(h, prop_flags); - h1 = get_shape_hash(h, rt->shape_hash_bits); - for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { - /* we test the hash first so that the rest is done only if the - shapes really match */ - if (sh1->hash == h && - sh1->proto == sh->proto && - sh1->prop_count == ((n = sh->prop_count) + 1)) { - for(i = 0; i < n; i++) { - if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || - unlikely(sh1->prop[i].flags != sh->prop[i].flags)) - goto next; - } - if (unlikely(sh1->prop[n].atom != atom) || - unlikely(sh1->prop[n].flags != prop_flags)) - goto next; - return sh1; - } - next: ; - } - return NULL; -} - -static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) -{ - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - int j; - - /* XXX: should output readable class prototype */ - printf("%5d %3d%c %14p %5d %5d", i, - sh->header.ref_count, " *"[sh->is_hashed], - (void *)sh->proto, sh->prop_size, sh->prop_count); - for(j = 0; j < sh->prop_count; j++) { - printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), - sh->prop[j].atom)); - } - printf("\n"); -} - -static __maybe_unused void JS_DumpShapes(JSRuntime *rt) -{ - int i; - JSShape *sh; - struct list_head *el; - JSObject *p; - JSGCObjectHeader *gp; - - printf("JSShapes: {\n"); - printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); - for(i = 0; i < rt->shape_hash_size; i++) { - for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { - JS_DumpShape(rt, i, sh); - assert(sh->is_hashed); - } - } - /* dump non-hashed shapes */ - list_for_each(el, &rt->gc_obj_list) { - gp = list_entry(el, JSGCObjectHeader, link); - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - p = (JSObject *)gp; - if (!p->shape->is_hashed) { - JS_DumpShape(rt, -1, p->shape); - } - } - } - printf("}\n"); -} - -static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id) -{ - JSObject *p; - - js_trigger_gc(ctx->rt, sizeof(JSObject)); - p = js_malloc(ctx, sizeof(JSObject)); - if (unlikely(!p)) - goto fail; - p->class_id = class_id; - p->extensible = true; - p->free_mark = 0; - p->is_exotic = 0; - p->fast_array = 0; - p->is_constructor = 0; - p->is_uncatchable_error = 0; - p->tmp_mark = 0; - p->is_HTMLDDA = 0; - p->first_weak_ref = NULL; - p->u.opaque = NULL; - p->shape = sh; - p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); - if (unlikely(!p->prop)) { - js_free(ctx, p); - fail: - js_free_shape(ctx->rt, sh); - return JS_EXCEPTION; - } - - switch(class_id) { - case JS_CLASS_OBJECT: - break; - case JS_CLASS_ARRAY: - { - JSProperty *pr; - p->is_exotic = 1; - p->fast_array = 1; - p->u.array.u.values = NULL; - p->u.array.count = 0; - p->u.array.u1.size = 0; - /* the length property is always the first one */ - if (likely(sh == ctx->array_shape)) { - pr = &p->prop[0]; - } else { - /* only used for the first array */ - /* cannot fail */ - pr = add_property(ctx, p, JS_ATOM_length, - JS_PROP_WRITABLE | JS_PROP_LENGTH); - } - pr->u.value = js_int32(0); - } - break; - case JS_CLASS_C_FUNCTION: - p->prop[0].u.value = JS_UNDEFINED; - break; - case JS_CLASS_ARGUMENTS: - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - case JS_CLASS_FLOAT16_ARRAY: - case JS_CLASS_FLOAT32_ARRAY: - case JS_CLASS_FLOAT64_ARRAY: - p->is_exotic = 1; - p->fast_array = 1; - p->u.array.u.ptr = NULL; - p->u.array.count = 0; - break; - case JS_CLASS_DATAVIEW: - p->u.array.u.ptr = NULL; - p->u.array.count = 0; - break; - case JS_CLASS_NUMBER: - case JS_CLASS_STRING: - case JS_CLASS_BOOLEAN: - case JS_CLASS_SYMBOL: - case JS_CLASS_DATE: - case JS_CLASS_BIG_INT: - p->u.object_data = JS_UNDEFINED; - goto set_exotic; - case JS_CLASS_REGEXP: - p->u.regexp.pattern = NULL; - p->u.regexp.bytecode = NULL; - goto set_exotic; - default: - set_exotic: - if (ctx->rt->class_array[class_id].exotic) { - p->is_exotic = 1; - } - break; - } - p->header.ref_count = 1; - add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); - return JS_MKPTR(JS_TAG_OBJECT, p); -} - -static JSObject *get_proto_obj(JSValue proto_val) -{ - if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) - return NULL; - else - return JS_VALUE_GET_OBJ(proto_val); -} - -/* WARNING: proto must be an object or JS_NULL */ -JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto_val, - JSClassID class_id) -{ - JSShape *sh; - JSObject *proto; - - proto = get_proto_obj(proto_val); - sh = find_hashed_shape_proto(ctx->rt, proto); - if (likely(sh)) { - sh = js_dup_shape(sh); - } else { - sh = js_new_shape(ctx, proto); - if (!sh) - return JS_EXCEPTION; - } - return JS_NewObjectFromShape(ctx, sh, class_id); -} - -static int JS_SetObjectData(JSContext *ctx, JSValue obj, JSValue val) -{ - JSObject *p; - - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(obj); - switch(p->class_id) { - case JS_CLASS_NUMBER: - case JS_CLASS_STRING: - case JS_CLASS_BOOLEAN: - case JS_CLASS_SYMBOL: - case JS_CLASS_DATE: - case JS_CLASS_BIG_INT: - JS_FreeValue(ctx, p->u.object_data); - p->u.object_data = val; - return 0; - } - } - JS_FreeValue(ctx, val); - if (!JS_IsException(obj)) - JS_ThrowTypeError(ctx, "invalid object type"); - return -1; -} - -JSValue JS_NewObjectClass(JSContext *ctx, int class_id) -{ - return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); -} - -JSValue JS_NewObjectProto(JSContext *ctx, JSValue proto) -{ - return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); -} - -JSValue JS_NewArray(JSContext *ctx) -{ - return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), - JS_CLASS_ARRAY); -} - -JSValue JS_NewObject(JSContext *ctx) -{ - /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ - return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); -} - -static void js_function_set_properties(JSContext *ctx, JSValue func_obj, - JSAtom name, int len) -{ - /* ES6 feature non compatible with ES5.1: length is configurable */ - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, js_int32(len), - JS_PROP_CONFIGURABLE); - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, - JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); -} - -static bool js_class_has_bytecode(JSClassID class_id) -{ - return (class_id == JS_CLASS_BYTECODE_FUNCTION || - class_id == JS_CLASS_GENERATOR_FUNCTION || - class_id == JS_CLASS_ASYNC_FUNCTION || - class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); -} - -/* return NULL without exception if not a function or no bytecode */ -static JSFunctionBytecode *JS_GetFunctionBytecode(JSValue val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return NULL; - p = JS_VALUE_GET_OBJ(val); - if (!js_class_has_bytecode(p->class_id)) - return NULL; - return p->u.func.function_bytecode; -} - -static void js_method_set_home_object(JSContext *ctx, JSValue func_obj, - JSValue home_obj) -{ - JSObject *p, *p1; - JSFunctionBytecode *b; - - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(func_obj); - if (!js_class_has_bytecode(p->class_id)) - return; - b = p->u.func.function_bytecode; - if (b->need_home_object) { - p1 = p->u.func.home_object; - if (p1) { - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); - } - if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) - p1 = JS_VALUE_GET_OBJ(js_dup(home_obj)); - else - p1 = NULL; - p->u.func.home_object = p1; - } -} - -static JSValue js_get_function_name(JSContext *ctx, JSAtom name) -{ - JSValue name_str; - - name_str = JS_AtomToString(ctx, name); - if (JS_AtomSymbolHasDescription(ctx, name)) { - name_str = JS_ConcatString3(ctx, "[", name_str, "]"); - } - return name_str; -} - -/* Modify the name of a method according to the atom and - 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and - JS_PROP_HAS_SET. Also set the home object of the method. - Return < 0 if exception. */ -static int js_method_set_properties(JSContext *ctx, JSValue func_obj, - JSAtom name, int flags, JSValue home_obj) -{ - JSValue name_str; - - name_str = js_get_function_name(ctx, name); - if (flags & JS_PROP_HAS_GET) { - name_str = JS_ConcatString3(ctx, "get ", name_str, ""); - } else if (flags & JS_PROP_HAS_SET) { - name_str = JS_ConcatString3(ctx, "set ", name_str, ""); - } - if (JS_IsException(name_str)) - return -1; - if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, - JS_PROP_CONFIGURABLE) < 0) - return -1; - js_method_set_home_object(ctx, func_obj, home_obj); - return 0; -} - -/* Note: at least 'length' arguments will be readable in 'argv' */ -/* `name` may be NULL, pure ASCII or UTF-8 encoded */ -static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, - const char *name, - int length, JSCFunctionEnum cproto, int magic, - JSValue proto_val) -{ - JSValue func_obj; - JSObject *p; - JSAtom name_atom; - - func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); - if (JS_IsException(func_obj)) - return func_obj; - p = JS_VALUE_GET_OBJ(func_obj); - p->u.cfunc.realm = JS_DupContext(ctx); - p->u.cfunc.c_function.generic = func; - p->u.cfunc.length = length; - p->u.cfunc.cproto = cproto; - p->u.cfunc.magic = magic; - p->is_constructor = (cproto == JS_CFUNC_constructor || - cproto == JS_CFUNC_constructor_magic || - cproto == JS_CFUNC_constructor_or_func || - cproto == JS_CFUNC_constructor_or_func_magic); - if (!name) - name = ""; - name_atom = JS_NewAtom(ctx, name); - js_function_set_properties(ctx, func_obj, name_atom, length); - JS_FreeAtom(ctx, name_atom); - return func_obj; -} - -/* Note: at least 'length' arguments will be readable in 'argv' */ -JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, - const char *name, - int length, JSCFunctionEnum cproto, int magic) -{ - return JS_NewCFunction3(ctx, func, name, length, cproto, magic, - ctx->function_proto); -} - -typedef struct JSCFunctionDataRecord { - JSCFunctionData *func; - uint8_t length; - uint8_t data_len; - uint16_t magic; - JSValue data[]; -} JSCFunctionDataRecord; - -static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val) -{ - JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); - int i; - - if (s) { - for(i = 0; i < s->data_len; i++) { - JS_FreeValueRT(rt, s->data[i]); - } - js_free_rt(rt, s); - } -} - -static void js_c_function_data_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); - int i; - - if (s) { - for(i = 0; i < s->data_len; i++) { - JS_MarkValue(rt, s->data[i], mark_func); - } - } -} - -static JSValue js_c_function_data_call(JSContext *ctx, JSValue func_obj, - JSValue this_val, - int argc, JSValue *argv, int flags) -{ - JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); - JSValue *arg_buf; - int i; - - /* XXX: could add the function on the stack for debug */ - if (unlikely(argc < s->length)) { - arg_buf = alloca(sizeof(arg_buf[0]) * s->length); - for(i = 0; i < argc; i++) - arg_buf[i] = argv[i]; - for(i = argc; i < s->length; i++) - arg_buf[i] = JS_UNDEFINED; - } else { - arg_buf = argv; - } - - return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data); -} - -JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, - int length, int magic, int data_len, - JSValue *data) -{ - JSCFunctionDataRecord *s; - JSValue func_obj; - int i; - - func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, - JS_CLASS_C_FUNCTION_DATA); - if (JS_IsException(func_obj)) - return func_obj; - s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); - if (!s) { - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; - } - s->func = func; - s->length = length; - s->data_len = data_len; - s->magic = magic; - for(i = 0; i < data_len; i++) - s->data[i] = js_dup(data[i]); - JS_SetOpaqueInternal(func_obj, s); - js_function_set_properties(ctx, func_obj, - JS_ATOM_empty_string, length); - return func_obj; -} - -static JSContext *js_autoinit_get_realm(JSProperty *pr) -{ - return (JSContext *)(pr->u.init.realm_and_id & ~3); -} - -static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) -{ - return pr->u.init.realm_and_id & 3; -} - -static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) -{ - JS_FreeContext(js_autoinit_get_realm(pr)); -} - -static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, - JS_MarkFunc *mark_func) -{ - mark_func(rt, &js_autoinit_get_realm(pr)->header); -} - -static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) -{ - if (unlikely(prop_flags & JS_PROP_TMASK)) { - if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - if (pr->u.getset.getter) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (pr->u.getset.setter) - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - free_var_ref(rt, pr->u.var_ref); - } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - js_autoinit_free(rt, pr); - } - } else { - JS_FreeValueRT(rt, pr->u.value); - } -} - -static force_inline JSShapeProperty *find_own_property1(JSObject *p, - JSAtom atom) -{ - JSShape *sh; - JSShapeProperty *pr, *prop; - intptr_t h; - sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; - prop = get_shape_prop(sh); - while (h) { - pr = &prop[h - 1]; - if (likely(pr->atom == atom)) { - return pr; - } - h = pr->hash_next; - } - return NULL; -} - -static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, - JSObject *p, - JSAtom atom) -{ - JSShape *sh; - JSShapeProperty *pr, *prop; - intptr_t h; - sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; - prop = get_shape_prop(sh); - while (h) { - pr = &prop[h - 1]; - if (likely(pr->atom == atom)) { - *ppr = &p->prop[h - 1]; - /* the compiler should be able to assume that pr != NULL here */ - return pr; - } - h = pr->hash_next; - } - *ppr = NULL; - return NULL; -} - -static force_inline JSShapeProperty* find_own_property_ic(JSProperty** ppr, JSObject* p, - JSAtom atom, uint32_t* offset) -{ - JSShape* sh; - JSShapeProperty *pr, *prop; - intptr_t h, i; - sh = p->shape; - h = (uintptr_t)atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h - 1]; - prop = get_shape_prop(sh); - while (h) { - i = h - 1; - pr = &prop[i]; - if (likely(pr->atom == atom)) { - *ppr = &p->prop[i]; - *offset = i; - /* the compiler should be able to assume that pr != NULL here */ - return pr; - } - h = pr->hash_next; - } - *ppr = NULL; - return NULL; -} - -/* indicate that the object may be part of a function prototype cycle */ -static void set_cycle_flag(JSContext *ctx, JSValue obj) -{ -} - -static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) -{ - if (var_ref) { - assert(var_ref->header.ref_count > 0); - if (--var_ref->header.ref_count == 0) { - if (var_ref->is_detached) { - JS_FreeValueRT(rt, var_ref->value); - remove_gc_object(&var_ref->header); - } else { - list_del(&var_ref->header.link); /* still on the stack */ - } - js_free_rt(rt, var_ref); - } - } -} - -static void js_array_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - int i; - - for(i = 0; i < p->u.array.count; i++) { - JS_FreeValueRT(rt, p->u.array.u.values[i]); - } - js_free_rt(rt, p->u.array.u.values); -} - -static void js_array_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - int i; - - for(i = 0; i < p->u.array.count; i++) { - JS_MarkValue(rt, p->u.array.u.values[i], mark_func); - } -} - -static void js_object_data_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JS_FreeValueRT(rt, p->u.object_data); - p->u.object_data = JS_UNDEFINED; -} - -static void js_object_data_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JS_MarkValue(rt, p->u.object_data, mark_func); -} - -static void js_c_function_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - - if (p->u.cfunc.realm) - JS_FreeContext(p->u.cfunc.realm); -} - -static void js_c_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - - if (p->u.cfunc.realm) - mark_func(rt, &p->u.cfunc.realm->header); -} - -static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p1, *p = JS_VALUE_GET_OBJ(val); - JSFunctionBytecode *b; - JSVarRef **var_refs; - int i; - - p1 = p->u.func.home_object; - if (p1) { - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); - } - b = p->u.func.function_bytecode; - if (b) { - var_refs = p->u.func.var_refs; - if (var_refs) { - for(i = 0; i < b->closure_var_count; i++) - free_var_ref(rt, var_refs[i]); - js_free_rt(rt, var_refs); - } - JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); - } -} - -static void js_bytecode_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSVarRef **var_refs = p->u.func.var_refs; - JSFunctionBytecode *b = p->u.func.function_bytecode; - int i; - - if (p->u.func.home_object) { - JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), - mark_func); - } - if (b) { - if (var_refs) { - for(i = 0; i < b->closure_var_count; i++) { - JSVarRef *var_ref = var_refs[i]; - if (var_ref && var_ref->is_detached) { - mark_func(rt, &var_ref->header); - } - } - } - /* must mark the function bytecode because template objects may be - part of a cycle */ - JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); - } -} - -static void js_bound_function_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSBoundFunction *bf = p->u.bound_function; - int i; - - JS_FreeValueRT(rt, bf->func_obj); - JS_FreeValueRT(rt, bf->this_val); - for(i = 0; i < bf->argc; i++) { - JS_FreeValueRT(rt, bf->argv[i]); - } - js_free_rt(rt, bf); -} - -static void js_bound_function_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSBoundFunction *bf = p->u.bound_function; - int i; - - JS_MarkValue(rt, bf->func_obj, mark_func); - JS_MarkValue(rt, bf->this_val, mark_func); - for(i = 0; i < bf->argc; i++) - JS_MarkValue(rt, bf->argv[i], mark_func); -} - -static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSForInIterator *it = p->u.for_in_iterator; - JS_FreeValueRT(rt, it->obj); - js_free_rt(rt, it); -} - -static void js_for_in_iterator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSForInIterator *it = p->u.for_in_iterator; - JS_MarkValue(rt, it->obj, mark_func); -} - -static void free_object(JSRuntime *rt, JSObject *p) -{ - int i; - JSClassFinalizer *finalizer; - JSShape *sh; - JSShapeProperty *pr; - - p->free_mark = 1; /* used to tell the object is invalid when - freeing cycles */ - /* free all the fields */ - sh = p->shape; - pr = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - free_property(rt, &p->prop[i], pr->flags); - pr++; - } - js_free_rt(rt, p->prop); - /* as an optimization we destroy the shape immediately without - putting it in gc_zero_ref_count_list */ - js_free_shape(rt, sh); - - /* fail safe */ - p->shape = NULL; - p->prop = NULL; - - if (unlikely(p->first_weak_ref)) { - reset_weak_ref(rt, &p->first_weak_ref); - } - - finalizer = rt->class_array[p->class_id].finalizer; - if (finalizer) - (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); - - /* fail safe */ - p->class_id = 0; - p->u.opaque = NULL; - p->u.func.var_refs = NULL; - p->u.func.home_object = NULL; - - remove_gc_object(&p->header); - if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { - list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); - } else { - js_free_rt(rt, p); - } -} - -static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) -{ - switch(gp->gc_obj_type) { - case JS_GC_OBJ_TYPE_JS_OBJECT: - free_object(rt, (JSObject *)gp); - break; - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - free_function_bytecode(rt, (JSFunctionBytecode *)gp); - break; - default: - abort(); - } -} - -static void free_zero_refcount(JSRuntime *rt) -{ - struct list_head *el; - JSGCObjectHeader *p; - - rt->gc_phase = JS_GC_PHASE_DECREF; - for(;;) { - el = rt->gc_zero_ref_count_list.next; - if (el == &rt->gc_zero_ref_count_list) - break; - p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count == 0); - free_gc_object(rt, p); - } - rt->gc_phase = JS_GC_PHASE_NONE; -} - -/* called with the ref_count of 'v' reaches zero. */ -static void js_free_value_rt(JSRuntime *rt, JSValue v) -{ - uint32_t tag = JS_VALUE_GET_TAG(v); - -#ifdef ENABLE_DUMPS // JS_DUMP_FREE - if (check_dump_flag(rt, JS_DUMP_FREE)) { - /* Prevent invalid object access during GC */ - if ((rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) - || (tag != JS_TAG_OBJECT && tag != JS_TAG_FUNCTION_BYTECODE)) { - printf("Freeing "); - if (tag == JS_TAG_OBJECT) { - JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); - } else { - JS_DumpValue(rt, v); - printf("\n"); - } - } - } -#endif - - switch(tag) { - case JS_TAG_STRING: - { - JSString *p = JS_VALUE_GET_STRING(v); - if (p->atom_type) { - JS_FreeAtomStruct(rt, p); - } else { -#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS - list_del(&p->link); -#endif - js_free_rt(rt, p); - } - } - break; - case JS_TAG_OBJECT: - case JS_TAG_FUNCTION_BYTECODE: - { - JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); - if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { - list_del(&p->link); - list_add(&p->link, &rt->gc_zero_ref_count_list); - if (rt->gc_phase == JS_GC_PHASE_NONE) { - free_zero_refcount(rt); - } - } - } - break; - case JS_TAG_MODULE: - abort(); /* never freed here */ - break; - case JS_TAG_BIG_INT: - { - JSBigInt *bf = JS_VALUE_GET_PTR(v); - bf_delete(&bf->num); - js_free_rt(rt, bf); - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p = JS_VALUE_GET_PTR(v); - JS_FreeAtomStruct(rt, p); - } - break; - default: - printf("js_free_value_rt: unknown tag=%d\n", tag); - abort(); - } -} - -void JS_FreeValueRT(JSRuntime *rt, JSValue v) -{ - if (JS_VALUE_HAS_REF_COUNT(v)) { - JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); - if (--p->ref_count <= 0) { - js_free_value_rt(rt, v); - } - } -} - -void JS_FreeValue(JSContext *ctx, JSValue v) -{ - JS_FreeValueRT(ctx->rt, v); -} - -/* garbage collection */ - -static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, - JSGCObjectTypeEnum type) -{ - h->mark = 0; - h->gc_obj_type = type; - list_add_tail(&h->link, &rt->gc_obj_list); -} - -static void remove_gc_object(JSGCObjectHeader *h) -{ - list_del(&h->link); -} - -void JS_MarkValue(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) -{ - if (JS_VALUE_HAS_REF_COUNT(val)) { - switch(JS_VALUE_GET_TAG(val)) { - case JS_TAG_OBJECT: - case JS_TAG_FUNCTION_BYTECODE: - mark_func(rt, JS_VALUE_GET_PTR(val)); - break; - default: - break; - } - } -} - -static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, - JS_MarkFunc *mark_func) -{ - switch(gp->gc_obj_type) { - case JS_GC_OBJ_TYPE_JS_OBJECT: - { - JSObject *p = (JSObject *)gp; - JSShapeProperty *prs; - JSShape *sh; - int i; - sh = p->shape; - mark_func(rt, &sh->header); - /* mark all the fields */ - prs = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - JSProperty *pr = &p->prop[i]; - if (prs->atom != JS_ATOM_NULL) { - if (prs->flags & JS_PROP_TMASK) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - if (pr->u.getset.getter) - mark_func(rt, &pr->u.getset.getter->header); - if (pr->u.getset.setter) - mark_func(rt, &pr->u.getset.setter->header); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - if (pr->u.var_ref->is_detached) { - /* Note: the tag does not matter - provided it is a GC object */ - mark_func(rt, &pr->u.var_ref->header); - } - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - js_autoinit_mark(rt, pr, mark_func); - } - } else { - JS_MarkValue(rt, pr->u.value, mark_func); - } - } - prs++; - } - - if (p->class_id != JS_CLASS_OBJECT) { - JSClassGCMark *gc_mark; - gc_mark = rt->class_array[p->class_id].gc_mark; - if (gc_mark) - gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); - } - } - break; - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - /* the template objects can be part of a cycle */ - { - JSShape **shape, *(*shapes)[IC_CACHE_ITEM_CAPACITY]; - JSObject **proto, *(*protos)[IC_CACHE_ITEM_CAPACITY]; - JSFunctionBytecode *b = (JSFunctionBytecode *)gp; - int i; - for(i = 0; i < b->cpool_count; i++) { - JS_MarkValue(rt, b->cpool[i], mark_func); - } - if (b->realm) - mark_func(rt, &b->realm->header); - if (b->ic) { - for (i = 0; i < b->ic->count; i++) { - shapes = &b->ic->cache[i].shape; - for (shape = *shapes; shape != endof(*shapes); shape++) - if (*shape) - mark_func(rt, &(*shape)->header); - protos = &b->ic->cache[i].proto; - for (proto = *protos; proto != endof(*protos); proto++) - if (*proto) - mark_func(rt, &(*proto)->header); - } - } - } - break; - case JS_GC_OBJ_TYPE_VAR_REF: - { - JSVarRef *var_ref = (JSVarRef *)gp; - /* only detached variable referenced are taken into account */ - assert(var_ref->is_detached); - JS_MarkValue(rt, *var_ref->pvalue, mark_func); - } - break; - case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: - { - JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; - if (s->is_active) - async_func_mark(rt, &s->func_state, mark_func); - JS_MarkValue(rt, s->resolving_funcs[0], mark_func); - JS_MarkValue(rt, s->resolving_funcs[1], mark_func); - } - break; - case JS_GC_OBJ_TYPE_SHAPE: - { - JSShape *sh = (JSShape *)gp; - if (sh->proto != NULL) { - mark_func(rt, &sh->proto->header); - } - } - break; - case JS_GC_OBJ_TYPE_JS_CONTEXT: - { - JSContext *ctx = (JSContext *)gp; - JS_MarkContext(rt, ctx, mark_func); - } - break; - default: - abort(); - } -} - -static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) -{ - assert(p->ref_count > 0); - p->ref_count--; - if (p->ref_count == 0 && p->mark == 1) { - list_del(&p->link); - list_add_tail(&p->link, &rt->tmp_obj_list); - } -} - -static void gc_decref(JSRuntime *rt) -{ - struct list_head *el, *el1; - JSGCObjectHeader *p; - - init_list_head(&rt->tmp_obj_list); - - /* decrement the refcount of all the children of all the GC - objects and move the GC objects with zero refcount to - tmp_obj_list */ - list_for_each_safe(el, el1, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - assert(p->mark == 0); - mark_children(rt, p, gc_decref_child); - p->mark = 1; - if (p->ref_count == 0) { - list_del(&p->link); - list_add_tail(&p->link, &rt->tmp_obj_list); - } - } -} - -static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) -{ - p->ref_count++; - if (p->ref_count == 1) { - /* ref_count was 0: remove from tmp_obj_list and add at the - end of gc_obj_list */ - list_del(&p->link); - list_add_tail(&p->link, &rt->gc_obj_list); - p->mark = 0; /* reset the mark for the next GC call */ - } -} - -static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) -{ - p->ref_count++; -} - -static void gc_scan(JSRuntime *rt) -{ - struct list_head *el; - JSGCObjectHeader *p; - - /* keep the objects with a refcount > 0 and their children. */ - list_for_each(el, &rt->gc_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - assert(p->ref_count > 0); - p->mark = 0; /* reset the mark for the next GC call */ - mark_children(rt, p, gc_scan_incref_child); - } - - /* restore the refcount of the objects to be deleted. */ - list_for_each(el, &rt->tmp_obj_list) { - p = list_entry(el, JSGCObjectHeader, link); - mark_children(rt, p, gc_scan_incref_child2); - } -} - -static void gc_free_cycles(JSRuntime *rt) -{ - struct list_head *el, *el1; - JSGCObjectHeader *p; -#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE - bool header_done = false; -#endif - - rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; - - for(;;) { - el = rt->tmp_obj_list.next; - if (el == &rt->tmp_obj_list) - break; - p = list_entry(el, JSGCObjectHeader, link); - /* Only need to free the GC object associated with JS - values. The rest will be automatically removed because they - must be referenced by them. */ - switch(p->gc_obj_type) { - case JS_GC_OBJ_TYPE_JS_OBJECT: - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: -#ifdef ENABLE_DUMPS // JS_DUMP_GC_FREE - if (check_dump_flag(rt, JS_DUMP_GC_FREE)) { - if (!header_done) { - printf("Freeing cycles:\n"); - JS_DumpObjectHeader(rt); - header_done = true; - } - JS_DumpGCObject(rt, p); - } -#endif - free_gc_object(rt, p); - break; - default: - list_del(&p->link); - list_add_tail(&p->link, &rt->gc_zero_ref_count_list); - break; - } - } - rt->gc_phase = JS_GC_PHASE_NONE; - - list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { - p = list_entry(el, JSGCObjectHeader, link); - assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || - p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); - js_free_rt(rt, p); - } - - init_list_head(&rt->gc_zero_ref_count_list); -} - -void JS_RunGC(JSRuntime *rt) -{ - /* decrement the reference of the children of each object. mark = - 1 after this pass. */ - gc_decref(rt); - - /* keep the GC objects with a non zero refcount and their childs */ - gc_scan(rt); - - /* free the GC objects in a cycle */ - gc_free_cycles(rt); -} - -/* Return false if not an object or if the object has already been - freed (zombie objects are visible in finalizers when freeing - cycles). */ -bool JS_IsLiveObject(JSRuntime *rt, JSValue obj) -{ - JSObject *p; - if (!JS_IsObject(obj)) - return false; - p = JS_VALUE_GET_OBJ(obj); - return !p->free_mark; -} - -/* Compute memory used by various object types */ -/* XXX: poor man's approach to handling multiply referenced objects */ -typedef struct JSMemoryUsage_helper { - double memory_used_count; - double str_count; - double str_size; - int64_t js_func_count; - double js_func_size; - int64_t js_func_code_size; - int64_t js_func_pc2line_count; - int64_t js_func_pc2line_size; -} JSMemoryUsage_helper; - -static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp); - -static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) -{ - if (!str->atom_type) { /* atoms are handled separately */ - double s_ref_count = str->header.ref_count; - hp->str_count += 1 / s_ref_count; - hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + - 1 - str->is_wide_char) / s_ref_count); - } -} - -static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) -{ - int memory_used_count, js_func_size, i; - - memory_used_count = 0; - js_func_size = sizeof(*b); - if (b->vardefs) { - js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); - } - if (b->cpool) { - js_func_size += b->cpool_count * sizeof(*b->cpool); - for (i = 0; i < b->cpool_count; i++) { - JSValue val = b->cpool[i]; - compute_value_size(val, hp); - } - } - if (b->closure_var) { - js_func_size += b->closure_var_count * sizeof(*b->closure_var); - } - if (b->byte_code_buf) { - hp->js_func_code_size += b->byte_code_len; - } - memory_used_count++; - js_func_size += b->source_len + 1; - if (b->pc2line_len) { - memory_used_count++; - hp->js_func_pc2line_count += 1; - hp->js_func_pc2line_size += b->pc2line_len; - } - hp->js_func_size += js_func_size; - hp->js_func_count += 1; - hp->memory_used_count += memory_used_count; -} - -static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp) -{ - switch(JS_VALUE_GET_TAG(val)) { - case JS_TAG_STRING: - compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); - break; - case JS_TAG_BIG_INT: - /* should track JSBigInt usage */ - break; - } -} - -void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) -{ - struct list_head *el, *el1; - int i; - JSMemoryUsage_helper mem = { 0 }, *hp = &mem; - - memset(s, 0, sizeof(*s)); - s->malloc_count = rt->malloc_state.malloc_count; - s->malloc_size = rt->malloc_state.malloc_size; - s->malloc_limit = rt->malloc_state.malloc_limit; - - s->memory_used_count = 2; /* rt + rt->class_array */ - s->memory_used_size = sizeof(JSRuntime) + sizeof(JSClass) * rt->class_count; - - list_for_each(el, &rt->context_list) { - JSContext *ctx = list_entry(el, JSContext, link); - JSShape *sh = ctx->array_shape; - s->memory_used_count += 2; /* ctx + ctx->class_proto */ - s->memory_used_size += sizeof(JSContext) + - sizeof(JSValue) * rt->class_count; - s->binary_object_count += ctx->binary_object_count; - s->binary_object_size += ctx->binary_object_size; - - /* the hashed shapes are counted separately */ - if (sh && !sh->is_hashed) { - int hash_size = sh->prop_hash_mask + 1; - s->shape_count++; - s->shape_size += get_shape_size(hash_size, sh->prop_size); - } - list_for_each(el1, &ctx->loaded_modules) { - JSModuleDef *m = list_entry(el1, JSModuleDef, link); - s->memory_used_count += 1; - s->memory_used_size += sizeof(*m); - if (m->req_module_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); - } - if (m->export_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); - for (i = 0; i < m->export_entries_count; i++) { - JSExportEntry *me = &m->export_entries[i]; - if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { - /* potential multiple count */ - s->memory_used_count += 1; - compute_value_size(me->u.local.var_ref->value, hp); - } - } - } - if (m->star_export_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); - } - if (m->import_entries) { - s->memory_used_count += 1; - s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); - } - compute_value_size(m->module_ns, hp); - compute_value_size(m->func_obj, hp); - } - } - - list_for_each(el, &rt->gc_obj_list) { - JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); - JSObject *p; - JSShape *sh; - JSShapeProperty *prs; - - /* XXX: could count the other GC object types too */ - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { - compute_bytecode_size((JSFunctionBytecode *)gp, hp); - continue; - } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { - continue; - } - p = (JSObject *)gp; - sh = p->shape; - s->obj_count++; - if (p->prop) { - s->memory_used_count++; - s->prop_size += sh->prop_size * sizeof(*p->prop); - s->prop_count += sh->prop_count; - prs = get_shape_prop(sh); - for(i = 0; i < sh->prop_count; i++) { - JSProperty *pr = &p->prop[i]; - if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { - compute_value_size(pr->u.value, hp); - } - prs++; - } - } - /* the hashed shapes are counted separately */ - if (!sh->is_hashed) { - int hash_size = sh->prop_hash_mask + 1; - s->shape_count++; - s->shape_size += get_shape_size(hash_size, sh->prop_size); - } - - switch(p->class_id) { - case JS_CLASS_ARRAY: /* u.array | length */ - case JS_CLASS_ARGUMENTS: /* u.array | length */ - s->array_count++; - if (p->fast_array) { - s->fast_array_count++; - if (p->u.array.u.values) { - s->memory_used_count++; - s->memory_used_size += p->u.array.count * - sizeof(*p->u.array.u.values); - s->fast_array_elements += p->u.array.count; - for (i = 0; i < p->u.array.count; i++) { - compute_value_size(p->u.array.u.values[i], hp); - } - } - } - break; - case JS_CLASS_NUMBER: /* u.object_data */ - case JS_CLASS_STRING: /* u.object_data */ - case JS_CLASS_BOOLEAN: /* u.object_data */ - case JS_CLASS_SYMBOL: /* u.object_data */ - case JS_CLASS_DATE: /* u.object_data */ - case JS_CLASS_BIG_INT: /* u.object_data */ - compute_value_size(p->u.object_data, hp); - break; - case JS_CLASS_C_FUNCTION: /* u.cfunc */ - s->c_func_count++; - break; - case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ - { - JSFunctionBytecode *b = p->u.func.function_bytecode; - JSVarRef **var_refs = p->u.func.var_refs; - /* home_object: object will be accounted for in list scan */ - if (var_refs) { - s->memory_used_count++; - s->js_func_size += b->closure_var_count * sizeof(*var_refs); - for (i = 0; i < b->closure_var_count; i++) { - if (var_refs[i]) { - double ref_count = var_refs[i]->header.ref_count; - s->memory_used_count += 1 / ref_count; - s->js_func_size += sizeof(*var_refs[i]) / ref_count; - /* handle non object closed values */ - if (var_refs[i]->pvalue == &var_refs[i]->value) { - /* potential multiple count */ - compute_value_size(var_refs[i]->value, hp); - } - } - } - } - } - break; - case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ - { - JSBoundFunction *bf = p->u.bound_function; - /* func_obj and this_val are objects */ - for (i = 0; i < bf->argc; i++) { - compute_value_size(bf->argv[i], hp); - } - s->memory_used_count += 1; - s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); - } - break; - case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ - { - JSCFunctionDataRecord *fd = p->u.c_function_data_record; - if (fd) { - for (i = 0; i < fd->data_len; i++) { - compute_value_size(fd->data[i], hp); - } - s->memory_used_count += 1; - s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); - } - } - break; - case JS_CLASS_REGEXP: /* u.regexp */ - compute_jsstring_size(p->u.regexp.pattern, hp); - compute_jsstring_size(p->u.regexp.bytecode, hp); - break; - - case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ - { - JSForInIterator *it = p->u.for_in_iterator; - if (it) { - compute_value_size(it->obj, hp); - s->memory_used_count += 1; - s->memory_used_size += sizeof(*it); - } - } - break; - case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ - case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ - { - JSArrayBuffer *abuf = p->u.array_buffer; - if (abuf) { - s->memory_used_count += 1; - s->memory_used_size += sizeof(*abuf); - if (abuf->data) { - s->memory_used_count += 1; - s->memory_used_size += abuf->byte_length; - } - } - } - break; - case JS_CLASS_GENERATOR: /* u.generator_data */ - case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ - case JS_CLASS_DATAVIEW: /* u.typed_array */ - case JS_CLASS_MAP: /* u.map_state */ - case JS_CLASS_SET: /* u.map_state */ - case JS_CLASS_WEAKMAP: /* u.map_state */ - case JS_CLASS_WEAKSET: /* u.map_state */ - case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ - case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ - case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ - case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ - case JS_CLASS_PROXY: /* u.proxy_data */ - case JS_CLASS_PROMISE: /* u.promise_data */ - case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ - case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ - case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ - case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ - case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ - case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ - /* TODO */ - default: - /* XXX: class definition should have an opaque block size */ - if (p->u.opaque) { - s->memory_used_count += 1; - } - break; - } - } - s->obj_size += s->obj_count * sizeof(JSObject); - - /* hashed shapes */ - s->memory_used_count++; /* rt->shape_hash */ - s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; - for(i = 0; i < rt->shape_hash_size; i++) { - JSShape *sh; - for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { - int hash_size = sh->prop_hash_mask + 1; - s->shape_count++; - s->shape_size += get_shape_size(hash_size, sh->prop_size); - } - } - - /* atoms */ - s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ - s->atom_count = rt->atom_count; - s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + - sizeof(rt->atom_hash[0]) * rt->atom_hash_size; - for(i = 0; i < rt->atom_size; i++) { - JSAtomStruct *p = rt->atom_array[i]; - if (!atom_is_free(p)) { - s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + - 1 - p->is_wide_char); - } - } - s->str_count = round(mem.str_count); - s->str_size = round(mem.str_size); - s->js_func_count = mem.js_func_count; - s->js_func_size = round(mem.js_func_size); - s->js_func_code_size = mem.js_func_code_size; - s->js_func_pc2line_count = mem.js_func_pc2line_count; - s->js_func_pc2line_size = mem.js_func_pc2line_size; - s->memory_used_count += round(mem.memory_used_count) + - s->atom_count + s->str_count + - s->obj_count + s->shape_count + - s->js_func_count + s->js_func_pc2line_count; - s->memory_used_size += s->atom_size + s->str_size + - s->obj_size + s->prop_size + s->shape_size + - s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; -} - -void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) -{ - fprintf(fp, "QuickJS-ng memory usage -- %s version, %d-bit, %s Endian, malloc limit: %"PRId64"\n\n", - JS_GetVersion(), (int)sizeof(void *) * 8, is_be() ? "Big" : "Little", s->malloc_limit); - if (rt) { - static const struct { - const char *name; - size_t size; - } object_types[] = { - { "JSRuntime", sizeof(JSRuntime) }, - { "JSContext", sizeof(JSContext) }, - { "JSObject", sizeof(JSObject) }, - { "JSString", sizeof(JSString) }, - { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, - }; - int i, usage_size_ok = 0; - for(i = 0; i < countof(object_types); i++) { - unsigned int size = object_types[i].size; - void *p = js_malloc_rt(rt, size); - if (p) { - unsigned int size1 = js_malloc_usable_size_rt(rt, p); - if (size1 >= size) { - usage_size_ok = 1; - fprintf(fp, " %3u + %-2u %s\n", - size, size1 - size, object_types[i].name); - } - js_free_rt(rt, p); - } - } - if (!usage_size_ok) { - fprintf(fp, " malloc_usable_size unavailable\n"); - } - { - int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; - int class_id; - struct list_head *el; - list_for_each(el, &rt->gc_obj_list) { - JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); - JSObject *p; - if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - p = (JSObject *)gp; - obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; - } - } - fprintf(fp, "\n" "JSObject classes\n"); - if (obj_classes[0]) - fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); - for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { - if (obj_classes[class_id]) { - char buf[ATOM_GET_STR_BUF_SIZE]; - fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, - JS_AtomGetStrRT(rt, buf, sizeof(buf), js_std_class_def[class_id - 1].class_name)); - } - } - if (obj_classes[JS_CLASS_INIT_COUNT]) - fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); - } - fprintf(fp, "\n"); - } - fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); - - if (s->malloc_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", - "memory allocated", s->malloc_count, s->malloc_size, - (double)s->malloc_size / s->malloc_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", - "memory used", s->memory_used_count, s->memory_used_size, - MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / - s->memory_used_count)); - } - if (s->atom_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", - "atoms", s->atom_count, s->atom_size, - (double)s->atom_size / s->atom_count); - } - if (s->str_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", - "strings", s->str_count, s->str_size, - (double)s->str_size / s->str_count); - } - if (s->obj_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", - "objects", s->obj_count, s->obj_size, - (double)s->obj_size / s->obj_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", - " properties", s->prop_count, s->prop_size, - (double)s->prop_count / s->obj_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", - " shapes", s->shape_count, s->shape_size, - (double)s->shape_size / s->shape_count); - } - if (s->js_func_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", - "bytecode functions", s->js_func_count, s->js_func_size); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", - " bytecode", s->js_func_count, s->js_func_code_size, - (double)s->js_func_code_size / s->js_func_count); - if (s->js_func_pc2line_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", - " pc2line", s->js_func_pc2line_count, - s->js_func_pc2line_size, - (double)s->js_func_pc2line_size / s->js_func_pc2line_count); - } - } - if (s->c_func_count) { - fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); - } - if (s->array_count) { - fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); - if (s->fast_array_count) { - fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); - fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", - " elements", s->fast_array_elements, - s->fast_array_elements * (int)sizeof(JSValue), - (double)s->fast_array_elements / s->fast_array_count); - } - } - if (s->binary_object_count) { - fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", - "binary objects", s->binary_object_count, s->binary_object_size); - } -} - -JSValue JS_GetGlobalObject(JSContext *ctx) -{ - return js_dup(ctx->global_obj); -} - -/* WARNING: obj is freed */ -JSValue JS_Throw(JSContext *ctx, JSValue obj) -{ - JSRuntime *rt = ctx->rt; - JS_FreeValue(ctx, rt->current_exception); - rt->current_exception = obj; - return JS_EXCEPTION; -} - -/* return the pending exception (cannot be called twice). */ -JSValue JS_GetException(JSContext *ctx) -{ - JSValue val; - JSRuntime *rt = ctx->rt; - val = rt->current_exception; - rt->current_exception = JS_UNINITIALIZED; - return val; -} - -bool JS_HasException(JSContext *ctx) -{ - return !JS_IsUninitialized(ctx->rt->current_exception); -} - -static void dbuf_put_leb128(DynBuf *s, uint32_t v) -{ - uint32_t a; - for(;;) { - a = v & 0x7f; - v >>= 7; - if (v != 0) { - dbuf_putc(s, a | 0x80); - } else { - dbuf_putc(s, a); - break; - } - } -} - -static void dbuf_put_sleb128(DynBuf *s, int32_t v1) -{ - uint32_t v = v1; - dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); -} - -static int get_leb128(uint32_t *pval, const uint8_t *buf, - const uint8_t *buf_end) -{ - const uint8_t *ptr = buf; - uint32_t v, a, i; - v = 0; - for(i = 0; i < 5; i++) { - if (unlikely(ptr >= buf_end)) - break; - a = *ptr++; - v |= (a & 0x7f) << (i * 7); - if (!(a & 0x80)) { - *pval = v; - return ptr - buf; - } - } - *pval = 0; - return -1; -} - -static int get_sleb128(int32_t *pval, const uint8_t *buf, - const uint8_t *buf_end) -{ - int ret; - uint32_t val; - ret = get_leb128(&val, buf, buf_end); - if (ret < 0) { - *pval = 0; - return -1; - } - *pval = (val >> 1) ^ -(val & 1); - return ret; -} - -static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, - uint32_t pc_value, int *col) -{ - const uint8_t *p_end, *p; - int new_line_num, new_col_num, line_num, col_num, pc, v, ret; - unsigned int op; - - *col = 1; - p = b->pc2line_buf; - if (!p) - goto fail; - p_end = p + b->pc2line_len; - pc = 0; - line_num = b->line_num; - col_num = b->col_num; - while (p < p_end) { - op = *p++; - if (op == 0) { - uint32_t val; - ret = get_leb128(&val, p, p_end); - if (ret < 0) - goto fail; - pc += val; - p += ret; - ret = get_sleb128(&v, p, p_end); - if (ret < 0) - goto fail; - p += ret; - new_line_num = line_num + v; - } else { - op -= PC2LINE_OP_FIRST; - pc += (op / PC2LINE_RANGE); - new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; - } - ret = get_sleb128(&v, p, p_end); - if (ret < 0) - goto fail; - p += ret; - new_col_num = col_num + v; - if (pc_value < pc) - break; - line_num = new_line_num; - col_num = new_col_num; - } - *col = col_num; - return line_num; -fail: - /* should never happen */ - return b->line_num; -} - -/* in order to avoid executing arbitrary code during the stack trace - generation, we only look at simple 'name' properties containing a - string. */ -static const char *get_func_name(JSContext *ctx, JSValue func) -{ - JSProperty *pr; - JSShapeProperty *prs; - JSValue val; - - if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) - return NULL; - prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); - if (!prs) - return NULL; - if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) - return NULL; - val = pr->u.value; - if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) - return NULL; - return JS_ToCString(ctx, val); -} - -/* Note: it is important that no exception is returned by this function */ -static bool can_add_backtrace(JSValue obj) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (p->class_id != JS_CLASS_ERROR) - return false; - if (find_own_property1(p, JS_ATOM_stack)) - return false; - return true; -} - -#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) -/* only taken into account if filename is provided */ -#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) -#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2) - -/* if filename != NULL, an additional level is added with the filename - and line number information (used for parse error). */ -static void build_backtrace(JSContext *ctx, JSValue error_val, JSValue filter_func, - const char *filename, int line_num, int col_num, - int backtrace_flags) -{ - JSStackFrame *sf, *sf_start; - JSValue stack, prepare, saved_exception; - DynBuf dbuf; - const char *func_name_str; - const char *str1; - JSObject *p; - JSFunctionBytecode *b; - bool backtrace_barrier, has_prepare, has_filter_func; - JSRuntime *rt; - JSCallSiteData csd[64]; - uint32_t i; - int stack_trace_limit; - - saved_exception = JS_UNINITIALIZED; - stack_trace_limit = ctx->error_stack_trace_limit; - stack_trace_limit = min_int(stack_trace_limit, countof(csd)); - stack_trace_limit = max_int(stack_trace_limit, 0); - rt = ctx->rt; - has_prepare = false; - has_filter_func = backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC; - i = 0; - - if (!rt->in_prepare_stack_trace && !JS_IsNull(ctx->error_ctor)) { - prepare = js_dup(ctx->error_prepare_stack); - has_prepare = JS_IsFunction(ctx, prepare); - rt->in_prepare_stack_trace = true; - } - - if (has_prepare) { - saved_exception = rt->current_exception; - rt->current_exception = JS_UNINITIALIZED; - if (stack_trace_limit == 0) - goto done; - if (filename) - js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num); - } else { - js_dbuf_init(ctx, &dbuf); - if (stack_trace_limit == 0) - goto done; - if (filename) { - i++; - dbuf_printf(&dbuf, " at %s", filename); - if (line_num != -1) - dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); - dbuf_putc(&dbuf, '\n'); - } - } - - if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)) - goto done; - - sf_start = rt->current_stack_frame; - - /* Find the frame we want to start from. Note that when a filter is used the filter - function will be the first, but we also specify we want to skip the first one. */ - if (has_filter_func) { - for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { - if (js_same_value(ctx, sf->cur_func, filter_func)) { - sf_start = sf; - break; - } - } - } - - for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { - if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { - backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; - continue; - } - - p = JS_VALUE_GET_OBJ(sf->cur_func); - b = NULL; - backtrace_barrier = false; - - if (js_class_has_bytecode(p->class_id)) { - b = p->u.func.function_bytecode; - backtrace_barrier = b->backtrace_barrier; - } - - if (has_prepare) { - js_new_callsite_data(ctx, &csd[i], sf); - } else { - /* func_name_str is UTF-8 encoded if needed */ - func_name_str = get_func_name(ctx, sf->cur_func); - if (!func_name_str || func_name_str[0] == '\0') - str1 = ""; - else - str1 = func_name_str; - dbuf_printf(&dbuf, " at %s", str1); - JS_FreeCString(ctx, func_name_str); - - if (b && sf->cur_pc) { - const char *atom_str; - int line_num1, col_num1; - uint32_t pc; - - pc = sf->cur_pc - b->byte_code_buf - 1; - line_num1 = find_line_num(ctx, b, pc, &col_num1); - atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL; - dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : ""); - JS_FreeCString(ctx, atom_str); - if (line_num1 != -1) - dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); - dbuf_putc(&dbuf, ')'); - } else if (b) { - // FIXME(bnoordhuis) Missing `sf->cur_pc = pc` in bytecode - // handler in JS_CallInternal. Almost never user observable - // except with intercepting JS proxies that throw exceptions. - dbuf_printf(&dbuf, " (missing)"); - } else { - dbuf_printf(&dbuf, " (native)"); - } - dbuf_putc(&dbuf, '\n'); - } - i++; - - /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ - if (backtrace_barrier) - break; - } - done: - if (has_prepare) { - int j = 0, k; - stack = JS_NewArray(ctx); - if (JS_IsException(stack)) { - stack = JS_NULL; - } else { - for (; j < i; j++) { - JSValue v = js_new_callsite(ctx, &csd[j]); - if (JS_IsException(v)) - break; - if (JS_DefinePropertyValueUint32(ctx, stack, j, v, JS_PROP_C_W_E) < 0) { - JS_FreeValue(ctx, v); - break; - } - } - } - // Clear the csd's we didn't use in case of error. - for (k = j; k < i; k++) { - JS_FreeValue(ctx, csd[k].filename); - JS_FreeValue(ctx, csd[k].func); - JS_FreeValue(ctx, csd[k].func_name); - } - JSValue args[] = { - error_val, - stack, - }; - JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args); - JS_FreeValue(ctx, stack); - if (JS_IsException(stack2)) - stack = JS_NULL; - else - stack = stack2; - JS_FreeValue(ctx, prepare); - JS_FreeValue(ctx, rt->current_exception); - rt->current_exception = saved_exception; - } else { - if (dbuf_error(&dbuf)) - stack = JS_NULL; - else - stack = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); - dbuf_free(&dbuf); - } - - rt->in_prepare_stack_trace = false; - if (JS_IsUndefined(ctx->error_back_trace)) - ctx->error_back_trace = js_dup(stack); - if (has_filter_func || can_add_backtrace(error_val)) { - JS_DefinePropertyValue(ctx, error_val, JS_ATOM_stack, stack, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - } else { - JS_FreeValue(ctx, stack); - } -} - -JSValue JS_NewError(JSContext *ctx) -{ - JSValue obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); - if (JS_IsException(obj)) - return JS_EXCEPTION; - build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); - return obj; -} - -static JSValue JS_MakeError(JSContext *ctx, JSErrorEnum error_num, - const char *message, bool add_backtrace) -{ - JSValue obj, msg; - - if (error_num == JS_PLAIN_ERROR) { - obj = JS_NewObjectClass(ctx, JS_CLASS_ERROR); - } else { - obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], - JS_CLASS_ERROR); - } - if (JS_IsException(obj)) - return JS_EXCEPTION; - msg = JS_NewString(ctx, message); - if (JS_IsException(msg)) - msg = JS_NewString(ctx, "Invalid error message"); - if (!JS_IsException(msg)) { - JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - } - if (add_backtrace) - build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); - return obj; -} - -/* fmt and arguments may be pure ASCII or UTF-8 encoded contents */ -static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, - const char *fmt, va_list ap, bool add_backtrace) -{ - char buf[256]; - JSValue obj; - - vsnprintf(buf, sizeof(buf), fmt, ap); - obj = JS_MakeError(ctx, error_num, buf, add_backtrace); - if (unlikely(JS_IsException(obj))) { - /* out of memory: throw JS_NULL to avoid recursing */ - obj = JS_NULL; - } - return JS_Throw(ctx, obj); -} - -static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, - const char *fmt, va_list ap) -{ - JSRuntime *rt = ctx->rt; - JSStackFrame *sf; - bool add_backtrace; - - /* the backtrace is added later if called from a bytecode function */ - sf = rt->current_stack_frame; - add_backtrace = !rt->in_out_of_memory && - (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); - return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace); -} - -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowPlainError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSValue val; - va_list ap; - - va_start(ap, fmt); - val = JS_ThrowError(ctx, JS_PLAIN_ERROR, fmt, ap); - va_end(ap); - return val; -} - -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowSyntaxError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSValue val; - va_list ap; - - va_start(ap, fmt); - val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap); - va_end(ap); - return val; -} - -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowTypeError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSValue val; - va_list ap; - - va_start(ap, fmt); - val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); - va_end(ap); - return val; -} - -static int JS_PRINTF_FORMAT_ATTR(3, 4) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, JS_PRINTF_FORMAT const char *fmt, ...) -{ - va_list ap; - - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - va_start(ap, fmt); - JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); - va_end(ap); - return -1; - } else { - return false; - } -} - -/* never use it directly */ -static JSValue JS_PRINTF_FORMAT_ATTR(3, 4) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, JS_PRINTF_FORMAT const char *fmt, ...) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowTypeError(ctx, fmt, - JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); -} - -/* never use it directly */ -static JSValue JS_PRINTF_FORMAT_ATTR(3, 4) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, JS_PRINTF_FORMAT const char *fmt, ...) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowSyntaxError(ctx, fmt, - JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); -} - -/* %s is replaced by 'atom'. The macro is used so that gcc can check - the format string. */ -#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") -#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") - -static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) -{ - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); - return -1; - } else { - return false; - } -} - -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowReferenceError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSValue val; - va_list ap; - - va_start(ap, fmt); - val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap); - va_end(ap); - return val; -} - -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowRangeError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSValue val; - va_list ap; - - va_start(ap, fmt); - val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap); - va_end(ap); - return val; -} - -JSValue JS_PRINTF_FORMAT_ATTR(2, 3) JS_ThrowInternalError(JSContext *ctx, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSValue val; - va_list ap; - - va_start(ap, fmt); - val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap); - va_end(ap); - return val; -} - -JSValue JS_ThrowOutOfMemory(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - if (!rt->in_out_of_memory) { - rt->in_out_of_memory = true; - JS_ThrowInternalError(ctx, "out of memory"); - rt->in_out_of_memory = false; - } - return JS_EXCEPTION; -} - -static JSValue JS_ThrowStackOverflow(JSContext *ctx) -{ - return JS_ThrowRangeError(ctx, "Maximum call stack size exceeded"); -} - -static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "not an object"); -} - -static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) -{ - return JS_ThrowTypeError(ctx, "not a symbol"); -} - -static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not defined", - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); -} - -static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not initialized", - name == JS_ATOM_NULL ? "lexical variable" : - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); -} - -static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, - JSFunctionBytecode *b, - int idx, bool is_ref) -{ - JSAtom atom = JS_ATOM_NULL; - if (is_ref) { - atom = b->closure_var[idx].var_name; - } else { - /* not present if the function is stripped and contains no eval() */ - if (b->vardefs) - atom = b->vardefs[b->arg_count + idx].var_name; - } - return JS_ThrowReferenceErrorUninitialized(ctx, atom); -} - -static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) -{ - JSRuntime *rt = ctx->rt; - JSAtom name; - name = rt->class_array[class_id].class_name; - return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); -} - -static no_inline __exception int __js_poll_interrupts(JSContext *ctx) -{ - JSRuntime *rt = ctx->rt; - ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; - if (rt->interrupt_handler) { - if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { - /* XXX: should set a specific flag to avoid catching */ - JS_ThrowInternalError(ctx, "interrupted"); - js_set_uncatchable_error(ctx, ctx->rt->current_exception, true); - return -1; - } - } - return 0; -} - -static inline __exception int js_poll_interrupts(JSContext *ctx) -{ - if (unlikely(--ctx->interrupt_counter <= 0)) { - return __js_poll_interrupts(ctx); - } else { - return 0; - } -} - -/* return -1 (exception) or true/false */ -static int JS_SetPrototypeInternal(JSContext *ctx, JSValue obj, - JSValue proto_val, - bool throw_flag) -{ - JSObject *proto, *p, *p1; - JSShape *sh; - - if (throw_flag) { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || - JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) - goto not_obj; - } else { - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - goto not_obj; - } - p = JS_VALUE_GET_OBJ(obj); - if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { - if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { - not_obj: - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - proto = NULL; - } else { - proto = JS_VALUE_GET_OBJ(proto_val); - } - - if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return true; - - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); - sh = p->shape; - if (sh->proto == proto) - return true; - if (p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "'Immutable prototype object \'Object.prototype\' cannot have their prototype set'"); - return -1; - } - return false; - } - if (!p->extensible) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "object is not extensible"); - return -1; - } else { - return false; - } - } - if (proto) { - /* check if there is a cycle */ - p1 = proto; - do { - if (p1 == p) { - if (throw_flag) { - JS_ThrowTypeError(ctx, "circular prototype chain"); - return -1; - } else { - return false; - } - } - /* Note: for Proxy objects, proto is NULL */ - p1 = p1->shape->proto; - } while (p1 != NULL); - js_dup(proto_val); - } - - if (js_shape_prepare_update(ctx, p, NULL)) - return -1; - sh = p->shape; - ic_delete_shape_proto_watchpoints(ctx->rt, sh, JS_ATOM_NULL, true); - if (sh->proto) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); - sh->proto = proto; - return true; -} - -/* return -1 (exception) or true/false */ -int JS_SetPrototype(JSContext *ctx, JSValue obj, JSValue proto_val) -{ - return JS_SetPrototypeInternal(ctx, obj, proto_val, true); -} - -/* Only works for primitive types, otherwise return JS_NULL. */ -static JSValue JS_GetPrototypePrimitive(JSContext *ctx, JSValue val) -{ - switch(JS_VALUE_GET_NORM_TAG(val)) { - case JS_TAG_BIG_INT: - val = ctx->class_proto[JS_CLASS_BIG_INT]; - break; - case JS_TAG_INT: - case JS_TAG_FLOAT64: - val = ctx->class_proto[JS_CLASS_NUMBER]; - break; - case JS_TAG_BOOL: - val = ctx->class_proto[JS_CLASS_BOOLEAN]; - break; - case JS_TAG_STRING: - val = ctx->class_proto[JS_CLASS_STRING]; - break; - case JS_TAG_SYMBOL: - val = ctx->class_proto[JS_CLASS_SYMBOL]; - break; - case JS_TAG_OBJECT: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - default: - val = JS_NULL; - break; - } - return val; -} - -/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ -JSValue JS_GetPrototype(JSContext *ctx, JSValue obj) -{ - JSValue val; - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p; - p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) { - val = js_proxy_getPrototypeOf(ctx, obj); - } else { - p = p->shape->proto; - if (!p) - val = JS_NULL; - else - val = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - } - } else { - val = js_dup(JS_GetPrototypePrimitive(ctx, obj)); - } - return val; -} - -static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) -{ - JSValue obj1; - obj1 = JS_GetPrototype(ctx, obj); - JS_FreeValue(ctx, obj); - return obj1; -} - -int JS_GetLength(JSContext *ctx, JSValue obj, int64_t *pres) { - return js_get_length64(ctx, pres, obj); -} - -int JS_SetLength(JSContext *ctx, JSValue obj, int64_t len) { - return js_set_length64(ctx, obj, len); -} - -/* return true, false or (-1) in case of exception */ -static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValue val, - JSValue obj) -{ - JSValue obj_proto; - JSObject *proto; - const JSObject *p, *proto1; - int ret; - - if (!JS_IsFunction(ctx, obj)) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_BOUND_FUNCTION) { - JSBoundFunction *s = p->u.bound_function; - return JS_IsInstanceOf(ctx, val, s->func_obj); - } - - /* Only explicitly boxed values are instances of constructors */ - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); - if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { - if (!JS_IsException(obj_proto)) - JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); - ret = -1; - goto done; - } - proto = JS_VALUE_GET_OBJ(obj_proto); - p = JS_VALUE_GET_OBJ(val); - for(;;) { - proto1 = p->shape->proto; - if (!proto1) { - /* slow case if proxy in the prototype chain */ - if (unlikely(p->class_id == JS_CLASS_PROXY)) { - JSValue obj1; - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); - for(;;) { - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsException(obj1)) { - ret = -1; - break; - } - if (JS_IsNull(obj1)) { - ret = false; - break; - } - if (proto == JS_VALUE_GET_OBJ(obj1)) { - JS_FreeValue(ctx, obj1); - ret = true; - break; - } - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - ret = -1; - break; - } - } - } else { - ret = false; - } - break; - } - p = proto1; - if (proto == p) { - ret = true; - break; - } - } -done: - JS_FreeValue(ctx, obj_proto); - return ret; -} - -/* return true, false or (-1) in case of exception */ -int JS_IsInstanceOf(JSContext *ctx, JSValue val, JSValue obj) -{ - JSValue method; - - if (!JS_IsObject(obj)) - goto fail; - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); - if (JS_IsException(method)) - return -1; - if (!JS_IsNull(method) && !JS_IsUndefined(method)) { - JSValue ret; - ret = JS_CallFree(ctx, method, obj, 1, &val); - return JS_ToBoolFree(ctx, ret); - } - - /* legacy case */ - if (!JS_IsFunction(ctx, obj)) { - fail: - JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); - return -1; - } - return JS_OrdinaryIsInstanceOf(ctx, val, obj); -} - -/* return the value associated to the autoinit property or an exception */ -typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); - -static JSAutoInitFunc *js_autoinit_func_table[] = { - js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ - js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ - JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ -}; - -/* warning: 'prs' is reallocated after it */ -static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, - JSProperty *pr, JSShapeProperty *prs) -{ - JSValue val; - JSContext *realm; - JSAutoInitFunc *func; - - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - - realm = js_autoinit_get_realm(pr); - func = js_autoinit_func_table[js_autoinit_get_id(pr)]; - /* 'func' shall not modify the object properties 'pr' */ - val = func(realm, p, prop, pr->u.init.opaque); - js_autoinit_free(ctx->rt, pr); - prs->flags &= ~JS_PROP_TMASK; - pr->u.value = JS_UNDEFINED; - if (JS_IsException(val)) - return -1; - pr->u.value = val; - return 0; -} - -static JSValue JS_GetPropertyInternal2(JSContext *ctx, JSValue obj, - JSAtom prop, JSValue this_obj, - JSInlineCacheUpdate *icu, - bool throw_ref_error) -{ - JSObject *p, *p1; - JSProperty *pr; - JSShapeProperty *prs; - uint32_t tag, offset, proto_depth; - - offset = proto_depth = 0; - tag = JS_VALUE_GET_TAG(obj); - if (unlikely(tag != JS_TAG_OBJECT)) { - switch(tag) { - case JS_TAG_NULL: - return JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of null (reading '%s')", prop); - case JS_TAG_UNDEFINED: - return JS_ThrowTypeErrorAtom(ctx, "Cannot read properties of undefined (reading '%s')", prop); - case JS_TAG_EXCEPTION: - return JS_EXCEPTION; - case JS_TAG_STRING: - { - JSString *p1 = JS_VALUE_GET_STRING(obj); - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx, ch; - idx = __JS_AtomToUInt32(prop); - if (idx < p1->len) { - ch = string_get(p1, idx); - return js_new_string_char(ctx, ch); - } - } else if (prop == JS_ATOM_length) { - return js_int32(p1->len); - } - } - break; - default: - break; - } - /* cannot raise an exception */ - p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); - if (!p) - return JS_UNDEFINED; - } else { - p = JS_VALUE_GET_OBJ(obj); - } - - p1 = p; - for(;;) { - prs = find_own_property_ic(&pr, p, prop, &offset); - if (prs) { - /* found */ - if (unlikely(prs->flags & JS_PROP_TMASK)) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - if (unlikely(!pr->u.getset.getter)) { - return JS_UNDEFINED; - } else { - JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); - /* Note: the field could be removed in the getter */ - func = js_dup(func); - return JS_CallFree(ctx, func, this_obj, 0, NULL); - } - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - JSValue val = *pr->u.var_ref->pvalue; - if (unlikely(JS_IsUninitialized(val))) - return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return js_dup(val); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - return JS_EXCEPTION; - continue; - } - } else { - add_ic_slot(ctx, icu, prop, p1, offset, proto_depth > 0 ? p : NULL); - return js_dup(pr->u.value); - } - } - if (unlikely(p->is_exotic)) { - /* exotic behaviors */ - if (p->fast_array) { - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx < p->u.array.count) { - /* we avoid duplicating the code */ - return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); - } else if (is_typed_array(p->class_id)) { - return JS_UNDEFINED; - } - } else if (is_typed_array(p->class_id)) { - int ret; - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - return JS_EXCEPTION; - return JS_UNDEFINED; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em) { - if (em->get_property) { - JSValue obj1, retval; - /* XXX: should pass throw_ref_error */ - /* Note: if 'p' is a prototype, it can be - freed in the called function */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - retval = em->get_property(ctx, obj1, prop, this_obj); - JS_FreeValue(ctx, obj1); - return retval; - } - if (em->get_own_property) { - JSPropertyDescriptor desc; - int ret; - JSValue obj1; - - /* Note: if 'p' is a prototype, it can be - freed in the called function */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - ret = em->get_own_property(ctx, &desc, obj1, prop); - JS_FreeValue(ctx, obj1); - if (ret < 0) - return JS_EXCEPTION; - if (ret) { - if (desc.flags & JS_PROP_GETSET) { - JS_FreeValue(ctx, desc.setter); - return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); - } else { - return desc.value; - } - } - } - } - } - } - proto_depth++; - p = p->shape->proto; - if (!p) - break; - } - if (unlikely(throw_ref_error)) { - return JS_ThrowReferenceErrorNotDefined(ctx, prop); - } else { - return JS_UNDEFINED; - } -} - -static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj, - JSAtom prop, JSValue this_obj, - bool throw_ref_error) -{ - return JS_GetPropertyInternal2(ctx, obj, prop, this_obj, NULL, throw_ref_error); -} - -JSValue JS_GetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop) -{ - return JS_GetPropertyInternal2(ctx, this_obj, prop, this_obj, NULL, false); -} - -static inline JSValue JS_GetPropertyInternalWithIC(JSContext *ctx, JSValue obj, - JSAtom prop, JSValue this_obj, - JSInlineCacheUpdate *icu, - bool throw_ref_error) -{ - uint32_t tag, offset; - JSObject *p, *proto; - tag = JS_VALUE_GET_TAG(obj); - if (unlikely(tag != JS_TAG_OBJECT)) - goto slow_path; - p = JS_VALUE_GET_OBJ(obj); - offset = get_ic_prop_offset(icu, p->shape, &proto); - if (likely(offset != INLINE_CACHE_MISS)) { - if (proto) p = proto; - return js_dup(p->prop[offset].u.value); - } -slow_path: - return JS_GetPropertyInternal2(ctx, obj, prop, this_obj, icu, throw_ref_error); -} - -static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) -{ - return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", - atom); -} - -/* Private fields can be added even on non extensible objects or - Proxies */ -static int JS_DefinePrivateField(JSContext *ctx, JSValue obj, - JSValue name, JSValue val) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSAtom prop; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); - goto fail; - } - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { - JS_ThrowTypeErrorNotASymbol(ctx); - goto fail; - } - prop = js_symbol_to_atom(ctx, name); - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", - prop); - goto fail; - } - pr = add_property(ctx, p, prop, JS_PROP_C_W_E); - if (unlikely(!pr)) { - fail: - JS_FreeValue(ctx, val); - return -1; - } - pr->u.value = val; - return 0; -} - -static JSValue JS_GetPrivateField(JSContext *ctx, JSValue obj, - JSValue name) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSAtom prop; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return JS_ThrowTypeErrorNotAnObject(ctx); - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) - return JS_ThrowTypeErrorNotASymbol(ctx); - prop = js_symbol_to_atom(ctx, name); - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, prop); - if (!prs) { - JS_ThrowTypeErrorPrivateNotFound(ctx, prop); - return JS_EXCEPTION; - } - return js_dup(pr->u.value); -} - -static int JS_SetPrivateField(JSContext *ctx, JSValue obj, - JSValue name, JSValue val) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSAtom prop; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); - goto fail; - } - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { - JS_ThrowTypeErrorNotASymbol(ctx); - goto fail; - } - prop = js_symbol_to_atom(ctx, name); - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, prop); - if (!prs) { - JS_ThrowTypeErrorPrivateNotFound(ctx, prop); - fail: - JS_FreeValue(ctx, val); - return -1; - } - set_value(ctx, &pr->u.value, val); - return 0; -} - -/* add a private brand field to 'home_obj' if not already present and - if obj is != null add a private brand to it */ -static int JS_AddBrand(JSContext *ctx, JSValue obj, JSValue home_obj) -{ - JSObject *p, *p1; - JSShapeProperty *prs; - JSProperty *pr; - JSValue brand; - JSAtom brand_atom; - - if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - p = JS_VALUE_GET_OBJ(home_obj); - prs = find_own_property(&pr, p, JS_ATOM_Private_brand); - if (!prs) { - /* if the brand is not present, add it */ - brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); - if (JS_IsException(brand)) - return -1; - pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); - if (!pr) { - JS_FreeValue(ctx, brand); - return -1; - } - pr->u.value = js_dup(brand); - } else { - brand = js_dup(pr->u.value); - } - brand_atom = js_symbol_to_atom(ctx, brand); - - if (JS_IsObject(obj)) { - p1 = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p1, brand_atom); - if (unlikely(prs)) { - JS_FreeAtom(ctx, brand_atom); - JS_ThrowTypeError(ctx, "private method is already present"); - return -1; - } - pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); - JS_FreeAtom(ctx, brand_atom); - if (!pr) - return -1; - pr->u.value = JS_UNDEFINED; - } else { - JS_FreeAtom(ctx, brand_atom); - } - - return 0; -} - -/* return a boolean telling if the brand of the home object of 'func' - is present on 'obj' or -1 in case of exception */ -static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func) -{ - JSObject *p, *p1, *home_obj; - JSShapeProperty *prs; - JSProperty *pr; - JSValue brand; - - /* get the home object of 'func' */ - if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) - goto not_obj; - p1 = JS_VALUE_GET_OBJ(func); - if (!js_class_has_bytecode(p1->class_id)) - goto not_obj; - home_obj = p1->u.func.home_object; - if (!home_obj) - goto not_obj; - prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); - if (!prs) { - JS_ThrowTypeError(ctx, "expecting private field"); - return -1; - } - brand = pr->u.value; - /* safety check */ - if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) - goto not_obj; - - /* get the brand array of 'obj' */ - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { - not_obj: - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - p = JS_VALUE_GET_OBJ(obj); - prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); - return (prs != NULL); -} - -static uint32_t js_string_obj_get_length(JSContext *ctx, - JSValue obj) -{ - JSObject *p; - JSString *p1; - uint32_t len = 0; - - /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ - p = JS_VALUE_GET_OBJ(obj); - if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { - p1 = JS_VALUE_GET_STRING(p->u.object_data); - len = p1->len; - } - return len; -} - -static int num_keys_cmp(const void *p1, const void *p2, void *opaque) -{ - JSContext *ctx = opaque; - JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; - JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; - uint32_t v1, v2; - bool atom1_is_integer, atom2_is_integer; - - atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); - atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); - assert(atom1_is_integer && atom2_is_integer); - if (v1 < v2) - return -1; - else if (v1 == v2) - return 0; - else - return 1; -} - -static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) -{ - uint32_t i; - if (tab) { - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tab[i].atom); - js_free(ctx, tab); - } -} - -/* return < 0 in case if exception, 0 if OK. ptab and its atoms must - be freed by the user. */ -static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, - JSPropertyEnum **ptab, - uint32_t *plen, - JSObject *p, int flags) -{ - int i, j; - JSShape *sh; - JSShapeProperty *prs; - JSPropertyEnum *tab_atom, *tab_exotic; - JSAtom atom; - uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; - uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; - bool is_enumerable, num_sorted; - uint32_t num_key; - JSAtomKindEnum kind; - - /* clear pointer for consistency in case of failure */ - *ptab = NULL; - *plen = 0; - - /* compute the number of returned properties */ - num_keys_count = 0; - str_keys_count = 0; - sym_keys_count = 0; - exotic_keys_count = 0; - exotic_count = 0; - tab_exotic = NULL; - sh = p->shape; - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - atom = prs->atom; - if (atom != JS_ATOM_NULL) { - is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); - kind = JS_AtomGetKind(ctx, atom); - if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && - ((flags >> kind) & 1) != 0) { - /* need to raise an exception in case of the module - name space (implicit GetOwnProperty) */ - if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && - (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { - JSVarRef *var_ref = p->prop[i].u.var_ref; - if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - } - if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { - num_keys_count++; - } else if (kind == JS_ATOM_KIND_STRING) { - str_keys_count++; - } else { - sym_keys_count++; - } - } - } - } - - if (p->is_exotic) { - if (p->fast_array) { - if (flags & JS_GPN_STRING_MASK) { - num_keys_count += p->u.array.count; - } - } else if (p->class_id == JS_CLASS_STRING) { - if (flags & JS_GPN_STRING_MASK) { - num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->get_own_property_names) { - if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, - JS_MKPTR(JS_TAG_OBJECT, p))) - return -1; - for(i = 0; i < exotic_count; i++) { - atom = tab_exotic[i].atom; - kind = JS_AtomGetKind(ctx, atom); - if (((flags >> kind) & 1) != 0) { - is_enumerable = false; - if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { - JSPropertyDescriptor desc; - int res; - /* set the "is_enumerable" field if necessary */ - res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); - if (res < 0) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); - return -1; - } - if (res) { - is_enumerable = - ((desc.flags & JS_PROP_ENUMERABLE) != 0); - js_free_desc(ctx, &desc); - } - tab_exotic[i].is_enumerable = is_enumerable; - } - if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { - exotic_keys_count++; - } - } - } - } - } - } - - /* fill them */ - - atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; - /* avoid allocating 0 bytes */ - tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); - if (!tab_atom) { - js_free_prop_enum(ctx, tab_exotic, exotic_count); - return -1; - } - - num_index = 0; - str_index = num_keys_count; - sym_index = str_index + str_keys_count; - - num_sorted = true; - sh = p->shape; - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - atom = prs->atom; - if (atom != JS_ATOM_NULL) { - is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); - kind = JS_AtomGetKind(ctx, atom); - if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && - ((flags >> kind) & 1) != 0) { - if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { - j = num_index++; - num_sorted = false; - } else if (kind == JS_ATOM_KIND_STRING) { - j = str_index++; - } else { - j = sym_index++; - } - tab_atom[j].atom = JS_DupAtom(ctx, atom); - tab_atom[j].is_enumerable = is_enumerable; - } - } - } - - if (p->is_exotic) { - int len; - if (p->fast_array) { - if (flags & JS_GPN_STRING_MASK) { - len = p->u.array.count; - goto add_array_keys; - } - } else if (p->class_id == JS_CLASS_STRING) { - if (flags & JS_GPN_STRING_MASK) { - len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - add_array_keys: - for(i = 0; i < len; i++) { - tab_atom[num_index].atom = __JS_AtomFromUInt32(i); - if (tab_atom[num_index].atom == JS_ATOM_NULL) { - js_free_prop_enum(ctx, tab_atom, num_index); - return -1; - } - tab_atom[num_index].is_enumerable = true; - num_index++; - } - } - } else { - /* Note: exotic keys are not reordered and comes after the object own properties. */ - for(i = 0; i < exotic_count; i++) { - atom = tab_exotic[i].atom; - is_enumerable = tab_exotic[i].is_enumerable; - kind = JS_AtomGetKind(ctx, atom); - if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && - ((flags >> kind) & 1) != 0) { - tab_atom[sym_index].atom = atom; - tab_atom[sym_index].is_enumerable = is_enumerable; - sym_index++; - } else { - JS_FreeAtom(ctx, atom); - } - } - js_free(ctx, tab_exotic); - } - } - - assert(num_index == num_keys_count); - assert(str_index == num_keys_count + str_keys_count); - assert(sym_index == atom_count); - - if (num_keys_count != 0 && !num_sorted) { - rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, - ctx); - } - *ptab = tab_atom; - *plen = atom_count; - return 0; -} - -int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, - uint32_t *plen, JSValue obj, int flags) -{ - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, - JS_VALUE_GET_OBJ(obj), flags); -} - -/* Return -1 if exception, - false if the property does not exist, true if it exists. If true is - returned, the property descriptor 'desc' is filled present. */ -static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, - JSObject *p, JSAtom prop) -{ - JSShapeProperty *prs; - JSProperty *pr; - -retry: - prs = find_own_property(&pr, p, prop); - if (prs) { - if (desc) { - desc->flags = prs->flags & JS_PROP_C_W_E; - desc->getter = JS_UNDEFINED; - desc->setter = JS_UNDEFINED; - desc->value = JS_UNDEFINED; - if (unlikely(prs->flags & JS_PROP_TMASK)) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - desc->flags |= JS_PROP_GETSET; - if (pr->u.getset.getter) - desc->getter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (pr->u.getset.setter) - desc->setter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - JSValue val = *pr->u.var_ref->pvalue; - if (unlikely(JS_IsUninitialized(val))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - desc->value = js_dup(val); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - return -1; - goto retry; - } - } else { - desc->value = js_dup(pr->u.value); - } - } else { - /* for consistency, send the exception even if desc is NULL */ - if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { - if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* nothing to do: delay instantiation until actual value and/or attributes are read */ - } - } - return true; - } - if (p->is_exotic) { - if (p->fast_array) { - /* specific case for fast arrays */ - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx; - idx = __JS_AtomToUInt32(prop); - if (idx < p->u.array.count) { - if (desc) { - desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | - JS_PROP_CONFIGURABLE; - desc->getter = JS_UNDEFINED; - desc->setter = JS_UNDEFINED; - desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); - } - return true; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->get_own_property) { - return em->get_own_property(ctx, desc, - JS_MKPTR(JS_TAG_OBJECT, p), prop); - } - } - } - return false; -} - -int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, - JSValue obj, JSAtom prop) -{ - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); -} - -void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, - uint32_t len) -{ - js_free_prop_enum(ctx, tab, len); -} - -/* return -1 if exception (Proxy object only) or true/false */ -int JS_IsExtensible(JSContext *ctx, JSValue obj) -{ - JSObject *p; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_isExtensible(ctx, obj); - else - return p->extensible; -} - -/* return -1 if exception (Proxy object only) or true/false */ -int JS_PreventExtensions(JSContext *ctx, JSValue obj) -{ - JSObject *p; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return false; - p = JS_VALUE_GET_OBJ(obj); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_preventExtensions(ctx, obj); - p->extensible = false; - return true; -} - -/* return -1 if exception otherwise true or false */ -int JS_HasProperty(JSContext *ctx, JSValue obj, JSAtom prop) -{ - JSObject *p; - int ret; - JSValue obj1; - - if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) - return false; - p = JS_VALUE_GET_OBJ(obj); - for(;;) { - if (p->is_exotic) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->has_property) { - /* has_property can free the prototype */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - ret = em->has_property(ctx, obj1, prop); - JS_FreeValue(ctx, obj1); - return ret; - } - } - /* JS_GetOwnPropertyInternal can free the prototype */ - js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); - ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - if (ret != 0) - return ret; - if (is_typed_array(p->class_id)) { - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - return -1; - return false; - } - } - p = p->shape->proto; - if (!p) - break; - } - return false; -} - -/* val must be a symbol */ -static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val) -{ - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - return js_get_atom_index(ctx->rt, p); -} - -/* return JS_ATOM_NULL in case of exception */ -JSAtom JS_ValueToAtom(JSContext *ctx, JSValue val) -{ - JSAtom atom; - uint32_t tag; - tag = JS_VALUE_GET_TAG(val); - if (tag == JS_TAG_INT && - (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { - /* fast path for integer values */ - atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); - } else if (tag == JS_TAG_SYMBOL) { - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); - } else { - JSValue str; - str = JS_ToPropertyKey(ctx, val); - if (JS_IsException(str)) - return JS_ATOM_NULL; - if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { - atom = js_symbol_to_atom(ctx, str); - } else { - atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); - } - } - return atom; -} - -static bool js_get_fast_array_element(JSContext *ctx, JSObject *p, - uint32_t idx, JSValue *pval) -{ - switch(p->class_id) { - case JS_CLASS_ARRAY: - case JS_CLASS_ARGUMENTS: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_dup(p->u.array.u.values[idx]); - return true; - case JS_CLASS_INT8_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.int8_ptr[idx]); - return true; - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_UINT8_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.uint8_ptr[idx]); - return true; - case JS_CLASS_INT16_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.int16_ptr[idx]); - return true; - case JS_CLASS_UINT16_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.uint16_ptr[idx]); - return true; - case JS_CLASS_INT32_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_int32(p->u.array.u.int32_ptr[idx]); - return true; - case JS_CLASS_UINT32_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_uint32(p->u.array.u.uint32_ptr[idx]); - return true; - case JS_CLASS_BIG_INT64_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); - return true; - case JS_CLASS_BIG_UINT64_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); - return true; - case JS_CLASS_FLOAT16_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_float64(fromfp16(p->u.array.u.fp16_ptr[idx])); - return true; - case JS_CLASS_FLOAT32_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_float64(p->u.array.u.float_ptr[idx]); - return true; - case JS_CLASS_FLOAT64_ARRAY: - if (unlikely(idx >= p->u.array.count)) return false; - *pval = js_float64(p->u.array.u.double_ptr[idx]); - return true; - default: - return false; - } -} - -static JSValue JS_GetPropertyValue(JSContext *ctx, JSValue this_obj, - JSValue prop) -{ - JSAtom atom; - JSValue ret; - uint32_t tag; - - tag = JS_VALUE_GET_TAG(this_obj); - if (likely(tag == JS_TAG_OBJECT)) { - if (JS_VALUE_GET_TAG(prop) == JS_TAG_INT) { - JSObject *p = JS_VALUE_GET_OBJ(this_obj); - uint32_t idx = JS_VALUE_GET_INT(prop); - JSValue val; - /* fast path for array and typed array access */ - if (js_get_fast_array_element(ctx, p, idx, &val)) - return val; - } - } else { - switch(tag) { - case JS_TAG_NULL: - JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "Cannot read properties of null"); - case JS_TAG_UNDEFINED: - JS_FreeValue(ctx, prop); - return JS_ThrowTypeError(ctx, "Cannot read properties of undefined"); - } - } - atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) - return JS_EXCEPTION; - ret = JS_GetProperty(ctx, this_obj, atom); - JS_FreeAtom(ctx, atom); - return ret; -} - -JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue this_obj, - uint32_t idx) -{ - return JS_GetPropertyInt64(ctx, this_obj, idx); -} - -/* Check if an object has a generalized numeric property. Return value: - -1 for exception, *pval set to JS_EXCEPTION - true if property exists, stored into *pval, - false if property does not exist. *pval set to JS_UNDEFINED. - */ -static int JS_TryGetPropertyInt64(JSContext *ctx, JSValue obj, int64_t idx, JSValue *pval) -{ - JSValue val; - JSAtom prop; - int present; - - if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && - (uint64_t)idx <= INT32_MAX)) { - /* fast path for array and typed array access */ - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (js_get_fast_array_element(ctx, p, idx, pval)) - return true; - } - val = JS_EXCEPTION; - present = -1; - prop = JS_NewAtomInt64(ctx, idx); - if (likely(prop != JS_ATOM_NULL)) { - present = JS_HasProperty(ctx, obj, prop); - if (present > 0) { - val = JS_GetProperty(ctx, obj, prop); - if (unlikely(JS_IsException(val))) - present = -1; - } else if (present == false) { - val = JS_UNDEFINED; - } - JS_FreeAtom(ctx, prop); - } - *pval = val; - return present; -} - -JSValue JS_GetPropertyInt64(JSContext *ctx, JSValue obj, int64_t idx) -{ - JSAtom prop; - JSValue val; - - if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && - (uint64_t)idx <= INT32_MAX)) { - /* fast path for array and typed array access */ - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (js_get_fast_array_element(ctx, p, idx, &val)) - return val; - } - prop = JS_NewAtomInt64(ctx, idx); - if (prop == JS_ATOM_NULL) - return JS_EXCEPTION; - - val = JS_GetProperty(ctx, obj, prop); - JS_FreeAtom(ctx, prop); - return val; -} - -/* `prop` may be pure ASCII or UTF-8 encoded */ -JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, - const char *prop) -{ - JSAtom atom; - JSValue ret; - atom = JS_NewAtom(ctx, prop); - ret = JS_GetProperty(ctx, this_obj, atom); - JS_FreeAtom(ctx, atom); - return ret; -} - -/* Note: the property value is not initialized. Return NULL if memory - error. */ -static JSProperty *add_property(JSContext *ctx, - JSObject *p, JSAtom prop, int prop_flags) -{ - JSShape *sh, *new_sh; - - sh = p->shape; - if (sh->is_hashed) { - /* try to find an existing shape */ - new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); - if (new_sh) { - /* matching shape found: use it */ - /* the property array may need to be resized */ - if (new_sh->prop_size != sh->prop_size) { - JSProperty *new_prop; - new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * - new_sh->prop_size); - if (!new_prop) - return NULL; - p->prop = new_prop; - } - p->shape = js_dup_shape(new_sh); - js_free_shape(ctx->rt, sh); - return &p->prop[new_sh->prop_count - 1]; - } else if (sh->header.ref_count != 1) { - /* if the shape is shared, clone it */ - new_sh = js_clone_shape(ctx, sh); - if (!new_sh) - return NULL; - /* hash the cloned shape */ - new_sh->is_hashed = true; - js_shape_hash_link(ctx->rt, new_sh); - js_free_shape(ctx->rt, p->shape); - p->shape = new_sh; - } - } - assert(p->shape->header.ref_count == 1); - if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) - return NULL; - return &p->prop[p->shape->prop_count - 1]; -} - -/* can be called on Array or Arguments objects. return < 0 if - memory alloc error. */ -static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, - JSObject *p) -{ - JSProperty *pr; - JSShape *sh; - JSValue *tab; - uint32_t i, len, new_count; - - if (js_shape_prepare_update(ctx, p, NULL)) - return -1; - len = p->u.array.count; - /* resize the properties once to simplify the error handling */ - sh = p->shape; - new_count = sh->prop_count + len; - if (new_count > sh->prop_size) { - if (resize_properties(ctx, &p->shape, p, new_count)) - return -1; - } - - tab = p->u.array.u.values; - for(i = 0; i < len; i++) { - /* add_property cannot fail here but - __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ - pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); - pr->u.value = *tab++; - } - js_free(ctx, p->u.array.u.values); - p->u.array.count = 0; - p->u.array.u.values = NULL; /* fail safe */ - p->u.array.u1.size = 0; - p->fast_array = 0; - return 0; -} - -static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) -{ - JSShape *sh; - JSShapeProperty *pr, *lpr, *prop; - JSProperty *pr1; - uint32_t lpr_idx; - intptr_t h, h1; - - redo: - sh = p->shape; - h1 = atom & sh->prop_hash_mask; - h = prop_hash_end(sh)[-h1 - 1]; - prop = get_shape_prop(sh); - lpr = NULL; - lpr_idx = 0; /* prevent warning */ - while (h != 0) { - pr = &prop[h - 1]; - if (likely(pr->atom == atom)) { - /* found ! */ - if (!(pr->flags & JS_PROP_CONFIGURABLE)) - return false; - /* realloc the shape if needed */ - if (lpr) - lpr_idx = lpr - get_shape_prop(sh); - if (js_shape_prepare_update(ctx, p, &pr)) - return -1; - sh = p->shape; - /* remove property */ - if (lpr) { - lpr = get_shape_prop(sh) + lpr_idx; - lpr->hash_next = pr->hash_next; - } else { - prop_hash_end(sh)[-h1 - 1] = pr->hash_next; - } - sh->deleted_prop_count++; - /* free the entry */ - pr1 = &p->prop[h - 1]; - free_property(ctx->rt, pr1, pr->flags); - JS_FreeAtom(ctx, pr->atom); - /* put default values */ - pr->flags = 0; - pr->atom = JS_ATOM_NULL; - pr1->u.value = JS_UNDEFINED; - ic_delete_shape_proto_watchpoints(ctx->rt, sh, atom, false); - /* compact the properties if too many deleted properties */ - if (sh->deleted_prop_count >= 8 && - sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { - compact_properties(ctx, p); - } - return true; - } - lpr = pr; - h = pr->hash_next; - } - - if (p->is_exotic) { - if (p->fast_array) { - uint32_t idx; - if (JS_AtomIsArrayIndex(ctx, &idx, atom) && - idx < p->u.array.count) { - if (p->class_id == JS_CLASS_ARRAY || - p->class_id == JS_CLASS_ARGUMENTS) { - /* Special case deleting the last element of a fast Array */ - if (idx == p->u.array.count - 1) { - JS_FreeValue(ctx, p->u.array.u.values[idx]); - p->u.array.count = idx; - return true; - } - if (convert_fast_array_to_array(ctx, p)) - return -1; - goto redo; - } else { - return false; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em && em->delete_property) { - return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); - } - } - } - /* not found */ - return true; -} - -static int call_setter(JSContext *ctx, JSObject *setter, - JSValue this_obj, JSValue val, int flags) -{ - JSValue ret, func; - if (likely(setter)) { - func = JS_MKPTR(JS_TAG_OBJECT, setter); - /* Note: the field could be removed in the setter */ - func = js_dup(func); - ret = JS_CallFree(ctx, func, this_obj, 1, &val); - JS_FreeValue(ctx, val); - if (JS_IsException(ret)) - return -1; - JS_FreeValue(ctx, ret); - return true; - } else { - JS_FreeValue(ctx, val); - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeError(ctx, "no setter for property"); - return -1; - } - return false; - } -} - -/* set the array length and remove the array elements if necessary. */ -static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, - int flags) -{ - uint32_t len, idx, cur_len; - int i, ret; - - /* Note: this call can reallocate the properties of 'p' */ - ret = JS_ToArrayLengthFree(ctx, &len, val, false); - if (ret) - return -1; - /* JS_ToArrayLengthFree() must be done before the read-only test */ - if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) - return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); - - if (likely(p->fast_array)) { - uint32_t old_len = p->u.array.count; - if (len < old_len) { - for(i = len; i < old_len; i++) { - JS_FreeValue(ctx, p->u.array.u.values[i]); - } - p->u.array.count = len; - } - p->prop[0].u.value = js_uint32(len); - } else { - /* Note: length is always a uint32 because the object is an - array */ - JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); - if (len < cur_len) { - uint32_t d; - JSShape *sh; - JSShapeProperty *pr; - - d = cur_len - len; - sh = p->shape; - if (d <= sh->prop_count) { - JSAtom atom; - - /* faster to iterate */ - while (cur_len > len) { - atom = JS_NewAtomUInt32(ctx, cur_len - 1); - ret = delete_property(ctx, p, atom); - JS_FreeAtom(ctx, atom); - if (unlikely(!ret)) { - /* unlikely case: property is not - configurable */ - break; - } - cur_len--; - } - } else { - /* faster to iterate thru all the properties. Need two - passes in case one of the property is not - configurable */ - cur_len = len; - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; - i++, pr++) { - if (pr->atom != JS_ATOM_NULL && - JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { - if (idx >= cur_len && - !(pr->flags & JS_PROP_CONFIGURABLE)) { - cur_len = idx + 1; - } - } - } - - for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; - i++, pr++) { - if (pr->atom != JS_ATOM_NULL && - JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { - if (idx >= cur_len) { - /* remove the property */ - delete_property(ctx, p, pr->atom); - /* WARNING: the shape may have been modified */ - sh = p->shape; - pr = get_shape_prop(sh) + i; - } - } - } - } - } else { - cur_len = len; - } - set_value(ctx, &p->prop[0].u.value, js_uint32(cur_len)); - if (unlikely(cur_len > len)) { - return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); - } - } - return true; -} - -/* return -1 if exception */ -static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) -{ - uint32_t new_size; - size_t slack; - JSValue *new_array_prop; - /* XXX: potential arithmetic overflow */ - new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); - new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); - if (!new_array_prop) - return -1; - new_size += slack / sizeof(*new_array_prop); - p->u.array.u.values = new_array_prop; - p->u.array.u1.size = new_size; - return 0; -} - -/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = - true and p->extensible = true */ -static int add_fast_array_element(JSContext *ctx, JSObject *p, - JSValue val, int flags) -{ - uint32_t new_len, array_len; - /* extend the array by one */ - /* XXX: convert to slow array if new_len > 2^31-1 elements */ - new_len = p->u.array.count + 1; - /* update the length if necessary. We assume that if the length is - not an integer, then if it >= 2^31. */ - if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { - array_len = JS_VALUE_GET_INT(p->prop[0].u.value); - if (new_len > array_len) { - if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); - } - p->prop[0].u.value = js_int32(new_len); - } - } - if (unlikely(new_len > p->u.array.u1.size)) { - if (expand_fast_array(ctx, p, new_len)) { - JS_FreeValue(ctx, val); - return -1; - } - } - p->u.array.u.values[new_len - 1] = val; - p->u.array.count = new_len; - return true; -} - -static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) -{ - JS_FreeValue(ctx, desc->getter); - JS_FreeValue(ctx, desc->setter); - JS_FreeValue(ctx, desc->value); -} - -/* return -1 in case of exception or true or false. Warning: 'val' is - freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, - JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, - the new property is not added and an error is raised. - 'obj' must be an object when obj != this_obj. - */ -static int JS_SetPropertyInternal2(JSContext *ctx, JSValue obj, JSAtom prop, - JSValue val, JSValue this_obj, int flags, - JSInlineCacheUpdate *icu) -{ - JSObject *p, *p1; - JSShapeProperty *prs; - JSProperty *pr; - JSPropertyDescriptor desc; - int ret; - uint32_t offset = 0; - - switch(JS_VALUE_GET_TAG(this_obj)) { - case JS_TAG_NULL: - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); - goto fail; - case JS_TAG_UNDEFINED: - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); - goto fail; - case JS_TAG_OBJECT: - p = JS_VALUE_GET_OBJ(this_obj); - p1 = JS_VALUE_GET_OBJ(obj); - if (p == p1) - break; - goto retry2; - default: - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - obj = JS_GetPrototypePrimitive(ctx, obj); - p = NULL; - p1 = JS_VALUE_GET_OBJ(obj); - goto prototype_lookup; - } - -retry: - prs = find_own_property_ic(&pr, p1, prop, &offset); - if (prs) { - if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | - JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { - /* fast case */ - add_ic_slot(ctx, icu, prop, p, offset, NULL); - set_value(ctx, &pr->u.value, val); - return true; - } else if (prs->flags & JS_PROP_LENGTH) { - assert(p->class_id == JS_CLASS_ARRAY); - assert(prop == JS_ATOM_length); - return set_array_length(ctx, p, val, flags); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - /* JS_PROP_WRITABLE is always true for variable - references, but they are write protected in module name - spaces. */ - if (p->class_id == JS_CLASS_MODULE_NS) - goto read_only_prop; - set_value(ctx, pr->u.var_ref->pvalue, val); - return true; - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry (potentially useless) */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - goto fail; - goto retry; - } else { - goto read_only_prop; - } - } - - for(;;) { - if (p1->is_exotic) { - if (p1->fast_array) { - if (__JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx < p1->u.array.count) { - if (unlikely(p == p1)) - return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, flags); - else - break; - } else if (is_typed_array(p1->class_id)) { - goto typed_array_oob; - } - } else if (is_typed_array(p1->class_id)) { - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - goto fail; - typed_array_oob: - // per spec: evaluate value for side effects - if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || - p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { - int64_t v; - if (JS_ToBigInt64Free(ctx, &v, val)) - return -1; - } else { - val = JS_ToNumberFree(ctx, val); - JS_FreeValue(ctx, val); - if (JS_IsException(val)) - return -1; - } - return true; - } - } - } else { - const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; - if (em) { - JSValue obj1; - if (em->set_property) { - /* set_property can free the prototype */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); - ret = em->set_property(ctx, obj1, prop, - val, this_obj, flags); - JS_FreeValue(ctx, obj1); - JS_FreeValue(ctx, val); - return ret; - } - if (em->get_own_property) { - /* get_own_property can free the prototype */ - obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); - ret = em->get_own_property(ctx, &desc, - obj1, prop); - JS_FreeValue(ctx, obj1); - if (ret < 0) - goto fail; - if (ret) { - if (desc.flags & JS_PROP_GETSET) { - JSObject *setter; - if (JS_IsUndefined(desc.setter)) - setter = NULL; - else - setter = JS_VALUE_GET_OBJ(desc.setter); - ret = call_setter(ctx, setter, this_obj, val, flags); - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - return ret; - } else { - JS_FreeValue(ctx, desc.value); - if (!(desc.flags & JS_PROP_WRITABLE)) - goto read_only_prop; - if (likely(p == p1)) { - ret = JS_DefineProperty(ctx, this_obj, prop, val, - JS_UNDEFINED, JS_UNDEFINED, - JS_PROP_HAS_VALUE); - JS_FreeValue(ctx, val); - return ret; - } else { - break; - } - } - } - } - } - } - } - p1 = p1->shape->proto; - prototype_lookup: - if (!p1) - break; - - retry2: - prs = find_own_property(&pr, p1, prop); - if (prs) { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry (potentially useless) */ - if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) - return -1; - goto retry2; - } else if (!(prs->flags & JS_PROP_WRITABLE)) { - goto read_only_prop; - } - } - } - - if (unlikely(flags & JS_PROP_NO_ADD)) { - JS_ThrowReferenceErrorNotDefined(ctx, prop); - goto fail; - } - - if (unlikely(!p)) { - ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); - goto done; - } - - if (unlikely(!p->extensible)) { - ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); - goto done; - } - - if (p == JS_VALUE_GET_OBJ(obj)) { - if (p->is_exotic) { - if (p->class_id == JS_CLASS_ARRAY && p->fast_array && - __JS_AtomIsTaggedInt(prop)) { - uint32_t idx = __JS_AtomToUInt32(prop); - if (idx == p->u.array.count) { - /* fast case */ - return add_fast_array_element(ctx, p, val, flags); - } - } - goto generic_create_prop; - } else { - ic_delete_shape_proto_watchpoints(ctx->rt, p->shape, prop, false); - pr = add_property(ctx, p, prop, JS_PROP_C_W_E); - if (unlikely(!pr)) - goto fail; - pr->u.value = val; - /* fast case */ - add_ic_slot(ctx, icu, prop, p, p->shape->prop_count - 1, NULL); - return true; - } - } - - // TODO(bnoordhuis) return JSProperty slot and update in place - // when plain property (not is_exotic/setter/etc.) to avoid - // calling find_own_property() thrice? - ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); - if (ret < 0) - goto fail; - - if (ret) { - JS_FreeValue(ctx, desc.value); - if (desc.flags & JS_PROP_GETSET) { - JS_FreeValue(ctx, desc.getter); - JS_FreeValue(ctx, desc.setter); - ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); - goto done; - } else if (!(desc.flags & JS_PROP_WRITABLE) || - p->class_id == JS_CLASS_MODULE_NS) { - read_only_prop: - ret = JS_ThrowTypeErrorReadOnly(ctx, flags, prop); - goto done; - } - ret = JS_DefineProperty(ctx, this_obj, prop, val, - JS_UNDEFINED, JS_UNDEFINED, - JS_PROP_HAS_VALUE); - } else { - generic_create_prop: - ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, - flags | - JS_PROP_HAS_VALUE | - JS_PROP_HAS_ENUMERABLE | - JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_CONFIGURABLE | - JS_PROP_C_W_E); - } - -done: - JS_FreeValue(ctx, val); - return ret; -fail: - JS_FreeValue(ctx, val); - return -1; -} - -static int JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj, - JSAtom prop, JSValue val, int flags) -{ - return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, - flags, NULL); -} - -int JS_SetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop, JSValue val) -{ - return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, JS_PROP_THROW, NULL); -} - -// XXX(bnoordhuis) only used by OP_put_field_ic, maybe inline at call site -static inline int JS_SetPropertyInternalWithIC(JSContext *ctx, JSValue this_obj, - JSAtom prop, JSValue val, int flags, - JSInlineCacheUpdate *icu) { - uint32_t tag, offset; - JSObject *p, *proto; - tag = JS_VALUE_GET_TAG(this_obj); - if (unlikely(tag != JS_TAG_OBJECT)) - goto slow_path; - p = JS_VALUE_GET_OBJ(this_obj); - offset = get_ic_prop_offset(icu, p->shape, &proto); - if (likely(offset != INLINE_CACHE_MISS)) { - if (proto) goto slow_path; - set_value(ctx, &p->prop[offset].u.value, val); - return true; - } -slow_path: - return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, - flags, icu); -} - -/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ -static int JS_SetPropertyValue(JSContext *ctx, JSValue this_obj, - JSValue prop, JSValue val, int flags) -{ - if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && - JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { - JSObject *p; - uint32_t idx; - double d; - int32_t v; - - /* fast path for array access */ - p = JS_VALUE_GET_OBJ(this_obj); - idx = JS_VALUE_GET_INT(prop); - switch(p->class_id) { - case JS_CLASS_ARRAY: - if (unlikely(idx >= (uint32_t)p->u.array.count)) { - JSObject *p1; - JSShape *sh1; - - /* fast path to add an element to the array */ - if (idx != (uint32_t)p->u.array.count || - !p->fast_array || !p->extensible) - goto slow_path; - /* check if prototype chain has a numeric property */ - p1 = p->shape->proto; - while (p1 != NULL) { - sh1 = p1->shape; - if (p1->class_id == JS_CLASS_ARRAY) { - if (unlikely(!p1->fast_array)) - goto slow_path; - } else if (p1->class_id == JS_CLASS_OBJECT) { - if (unlikely(sh1->has_small_array_index)) - goto slow_path; - } else { - goto slow_path; - } - p1 = sh1->proto; - } - /* add element */ - return add_fast_array_element(ctx, p, val, flags); - } - set_value(ctx, &p->u.array.u.values[idx], val); - break; - case JS_CLASS_ARGUMENTS: - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto slow_path; - set_value(ctx, &p->u.array.u.values[idx], val); - break; - case JS_CLASS_UINT8C_ARRAY: - if (JS_ToUint8ClampFree(ctx, &v, val)) - goto ta_cvt_fail; - /* Note: the conversion can detach the typed array, so the - array bound check must be done after */ - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint8_ptr[idx] = v; - break; - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - if (JS_ToInt32Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint8_ptr[idx] = v; - break; - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - if (JS_ToInt32Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint16_ptr[idx] = v; - break; - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - if (JS_ToInt32Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint32_ptr[idx] = v; - break; - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - /* XXX: need specific conversion function */ - { - int64_t v; - if (JS_ToBigInt64Free(ctx, &v, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.uint64_ptr[idx] = v; - } - break; - case JS_CLASS_FLOAT16_ARRAY: - if (JS_ToFloat64Free(ctx, &d, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.fp16_ptr[idx] = tofp16(d); - break; - case JS_CLASS_FLOAT32_ARRAY: - if (JS_ToFloat64Free(ctx, &d, val)) - goto ta_cvt_fail; - if (unlikely(idx >= (uint32_t)p->u.array.count)) - goto ta_out_of_bound; - p->u.array.u.float_ptr[idx] = d; - break; - case JS_CLASS_FLOAT64_ARRAY: - if (JS_ToFloat64Free(ctx, &d, val)) { - ta_cvt_fail: - if (flags & JS_PROP_REFLECT_DEFINE_PROPERTY) { - JS_FreeValue(ctx, JS_GetException(ctx)); - return false; - } - return -1; - } - if (unlikely(idx >= (uint32_t)p->u.array.count)) { - ta_out_of_bound: - if (typed_array_is_oob(p)) - if (flags & JS_PROP_DEFINE_PROPERTY) - return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); - return true; // per spec: no OOB exception - } - p->u.array.u.double_ptr[idx] = d; - break; - default: - goto slow_path; - } - return true; - } else { - JSAtom atom; - int ret; - slow_path: - atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) { - JS_FreeValue(ctx, val); - return -1; - } - ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags); - JS_FreeAtom(ctx, atom); - return ret; - } -} - -int JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, - uint32_t idx, JSValue val) -{ - return JS_SetPropertyValue(ctx, this_obj, js_uint32(idx), val, - JS_PROP_THROW); -} - -int JS_SetPropertyInt64(JSContext *ctx, JSValue this_obj, - int64_t idx, JSValue val) -{ - JSAtom prop; - int res; - - if ((uint64_t)idx <= INT32_MAX) { - /* fast path for fast arrays */ - return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, - JS_PROP_THROW); - } - prop = JS_NewAtomInt64(ctx, idx); - if (prop == JS_ATOM_NULL) { - JS_FreeValue(ctx, val); - return -1; - } - res = JS_SetProperty(ctx, this_obj, prop, val); - JS_FreeAtom(ctx, prop); - return res; -} - -/* `prop` may be pure ASCII or UTF-8 encoded */ -int JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, - const char *prop, JSValue val) -{ - JSAtom atom; - int ret; - atom = JS_NewAtom(ctx, prop); - ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW); - JS_FreeAtom(ctx, atom); - return ret; -} - -/* compute the property flags. For each flag: (JS_PROP_HAS_x forces - it, otherwise def_flags is used) - Note: makes assumption about the bit pattern of the flags -*/ -static int get_prop_flags(int flags, int def_flags) -{ - int mask; - mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; - return (flags & mask) | (def_flags & ~mask); -} - -static int JS_CreateProperty(JSContext *ctx, JSObject *p, - JSAtom prop, JSValue val, - JSValue getter, JSValue setter, - int flags) -{ - JSProperty *pr; - int ret, prop_flags; - - /* add a new property or modify an existing exotic one */ - if (p->is_exotic) { - if (p->class_id == JS_CLASS_ARRAY) { - uint32_t idx, len; - - if (p->fast_array) { - if (__JS_AtomIsTaggedInt(prop)) { - idx = __JS_AtomToUInt32(prop); - if (idx == p->u.array.count) { - if (!p->extensible) - goto not_extensible; - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) - goto convert_to_array; - prop_flags = get_prop_flags(flags, 0); - if (prop_flags != JS_PROP_C_W_E) - goto convert_to_array; - return add_fast_array_element(ctx, p, - js_dup(val), flags); - } else { - goto convert_to_array; - } - } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { - /* convert the fast array to normal array */ - convert_to_array: - if (convert_fast_array_to_array(ctx, p)) - return -1; - goto generic_array; - } - } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { - JSProperty *plen; - JSShapeProperty *pslen; - generic_array: - /* update the length field */ - plen = &p->prop[0]; - JS_ToUint32(ctx, &len, plen->u.value); - if ((idx + 1) > len) { - pslen = get_shape_prop(p->shape); - if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) - return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); - /* XXX: should update the length after defining - the property */ - len = idx + 1; - set_value(ctx, &plen->u.value, js_uint32(len)); - } - } - } else if (is_typed_array(p->class_id)) { - ret = JS_AtomIsNumericIndex(ctx, prop); - if (ret != 0) { - if (ret < 0) - return -1; - return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); - } - } else if (!(flags & JS_PROP_NO_EXOTIC)) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - if (em) { - if (em->define_own_property) { - return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), - prop, val, getter, setter, flags); - } - ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); - if (ret < 0) - return -1; - if (!ret) - goto not_extensible; - } - } - } - - if (!p->extensible) { - not_extensible: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); - } - - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | - JS_PROP_GETSET; - } else { - prop_flags = flags & JS_PROP_C_W_E; - } - pr = add_property(ctx, p, prop, prop_flags); - if (unlikely(!pr)) - return -1; - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - pr->u.getset.getter = NULL; - if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { - pr->u.getset.getter = - JS_VALUE_GET_OBJ(js_dup(getter)); - } - pr->u.getset.setter = NULL; - if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { - pr->u.getset.setter = - JS_VALUE_GET_OBJ(js_dup(setter)); - } - } else { - if (flags & JS_PROP_HAS_VALUE) { - pr->u.value = js_dup(val); - } else { - pr->u.value = JS_UNDEFINED; - } - } - return true; -} - -/* return false if not OK */ -static bool check_define_prop_flags(int prop_flags, int flags) -{ - bool has_accessor, is_getset; - - if (!(prop_flags & JS_PROP_CONFIGURABLE)) { - if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == - (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { - return false; - } - if ((flags & JS_PROP_HAS_ENUMERABLE) && - (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) - return false; - } - if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - if (!(prop_flags & JS_PROP_CONFIGURABLE)) { - has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); - is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); - if (has_accessor != is_getset) - return false; - if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { - /* not writable: cannot set the writable bit */ - if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == - (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) - return false; - } - } - } - return true; -} - -/* ensure that the shape can be safely modified */ -static int js_shape_prepare_update(JSContext *ctx, JSObject *p, - JSShapeProperty **pprs) -{ - JSShape *sh; - uint32_t idx = 0; /* prevent warning */ - - sh = p->shape; - if (sh->is_hashed) { - if (sh->header.ref_count != 1) { - if (pprs) - idx = *pprs - get_shape_prop(sh); - /* clone the shape (the resulting one is no longer hashed) */ - sh = js_clone_shape(ctx, sh); - if (!sh) - return -1; - js_free_shape(ctx->rt, p->shape); - p->shape = sh; - if (pprs) - *pprs = get_shape_prop(sh) + idx; - } else { - js_shape_hash_unlink(ctx->rt, sh); - sh->is_hashed = false; - } - } - return 0; -} - -int js_shape_delete_watchpoints(JSRuntime *rt, JSShape *shape, JSInlineCacheRingSlot *target) { - struct list_head *el, *el1; - if (unlikely(!shape || !shape->watchpoint)) - goto end; - list_for_each_safe(el, el1, shape->watchpoint) { - JSInlineCacheWatchpoint *o = list_entry(el, JSInlineCacheWatchpoint, link); - if (!ic_watchpoint_delete_handler(rt, o->ref, o->offset, o->atom, target)) { - list_del(el); - js_free_rt(rt, o); - break; - } - } - if (!target) { - list_empty(shape->watchpoint); - js_free_rt(rt, shape->watchpoint); - } -end: - return 0; -} - -JSInlineCacheWatchpoint *js_shape_create_watchpoint(JSRuntime *rt, JSShape *shape, JSInlineCacheRingSlot *ptr, uint8_t offset, JSAtom atom) -{ - JSInlineCacheWatchpoint *o; - o = js_malloc_rt(rt, sizeof(*o)); - if (unlikely(!o)) - return NULL; - o->ref = ptr; - o->atom = atom; - o->offset = offset; - if (!shape->watchpoint) { - shape->watchpoint = js_malloc_rt(rt, sizeof(*shape->watchpoint)); - init_list_head(shape->watchpoint); - } - init_list_head(&o->link); - list_add_tail(&o->link, shape->watchpoint); - return o; -} - -static int js_update_property_flags(JSContext *ctx, JSObject *p, - JSShapeProperty **pprs, int flags) -{ - if (flags != (*pprs)->flags) { - if (js_shape_prepare_update(ctx, p, pprs)) - return -1; - (*pprs)->flags = flags; - } - return 0; -} - -/* allowed flags: - JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE - JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, - JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, - JS_PROP_THROW, JS_PROP_NO_EXOTIC. - If JS_PROP_THROW is set, return an exception instead of false. - if JS_PROP_NO_EXOTIC is set, do not call the exotic - define_own_property callback. - return -1 (exception), false or true. -*/ -int JS_DefineProperty(JSContext *ctx, JSValue this_obj, - JSAtom prop, JSValue val, - JSValue getter, JSValue setter, int flags) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - int mask, res; - - if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - return -1; - } - p = JS_VALUE_GET_OBJ(this_obj); - - redo_prop_update: - prs = find_own_property(&pr, p, prop); - if (prs) { - /* the range of the Array length property is always tested before */ - if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { - uint32_t array_length; - if (JS_ToArrayLengthFree(ctx, &array_length, - js_dup(val), false)) { - return -1; - } - /* this code relies on the fact that Uint32 are never allocated */ - val = js_uint32(array_length); - /* prs may have been modified */ - prs = find_own_property(&pr, p, prop); - assert(prs != NULL); - } - /* property already exists */ - if (!check_define_prop_flags(prs->flags, flags)) { - not_configurable: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); - } - - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - /* Instantiate property and retry */ - if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) - return -1; - goto redo_prop_update; - } - - if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | - JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - JSObject *new_getter, *new_setter; - - if (JS_IsFunction(ctx, getter)) { - new_getter = JS_VALUE_GET_OBJ(getter); - } else { - new_getter = NULL; - } - if (JS_IsFunction(ctx, setter)) { - new_setter = JS_VALUE_GET_OBJ(setter); - } else { - new_setter = NULL; - } - - if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - /* convert to getset */ - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - free_var_ref(ctx->rt, pr->u.var_ref); - } else { - JS_FreeValue(ctx, pr->u.value); - } - prs->flags = (prs->flags & - (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | - JS_PROP_GETSET; - pr->u.getset.getter = NULL; - pr->u.getset.setter = NULL; - } else { - if (!(prs->flags & JS_PROP_CONFIGURABLE)) { - if ((flags & JS_PROP_HAS_GET) && - new_getter != pr->u.getset.getter) { - goto not_configurable; - } - if ((flags & JS_PROP_HAS_SET) && - new_setter != pr->u.getset.setter) { - goto not_configurable; - } - } - } - if (flags & JS_PROP_HAS_GET) { - if (pr->u.getset.getter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (new_getter) - js_dup(getter); - pr->u.getset.getter = new_getter; - } - if (flags & JS_PROP_HAS_SET) { - if (pr->u.getset.setter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - if (new_setter) - js_dup(setter); - pr->u.getset.setter = new_setter; - } - } else { - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - /* convert to data descriptor */ - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - if (pr->u.getset.getter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); - if (pr->u.getset.setter) - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); - prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); - pr->u.value = JS_UNDEFINED; - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - /* Note: JS_PROP_VARREF is always writable */ - } else { - if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && - (flags & JS_PROP_HAS_VALUE)) { - if (!js_same_value(ctx, val, pr->u.value)) { - goto not_configurable; - } else { - return true; - } - } - } - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - if (flags & JS_PROP_HAS_VALUE) { - if (p->class_id == JS_CLASS_MODULE_NS) { - /* JS_PROP_WRITABLE is always true for variable - references, but they are write protected in module name - spaces. */ - if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) - goto not_configurable; - } - /* update the reference */ - set_value(ctx, pr->u.var_ref->pvalue, - js_dup(val)); - } - /* if writable is set to false, no longer a - reference (for mapped arguments) */ - if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { - JSValue val1; - if (js_shape_prepare_update(ctx, p, &prs)) - return -1; - val1 = js_dup(*pr->u.var_ref->pvalue); - free_var_ref(ctx->rt, pr->u.var_ref); - pr->u.value = val1; - prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); - } - } else if (prs->flags & JS_PROP_LENGTH) { - if (flags & JS_PROP_HAS_VALUE) { - /* Note: no JS code is executable because - 'val' is guaranted to be a Uint32 */ - res = set_array_length(ctx, p, js_dup(val), - flags); - } else { - res = true; - } - /* still need to reset the writable flag if - needed. The JS_PROP_LENGTH is kept because the - Uint32 test is still done if the length - property is read-only. */ - if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == - JS_PROP_HAS_WRITABLE) { - prs = get_shape_prop(p->shape); - if (js_update_property_flags(ctx, p, &prs, - prs->flags & ~JS_PROP_WRITABLE)) - return -1; - } - return res; - } else { - if (flags & JS_PROP_HAS_VALUE) { - JS_FreeValue(ctx, pr->u.value); - pr->u.value = js_dup(val); - } - if (flags & JS_PROP_HAS_WRITABLE) { - if (js_update_property_flags(ctx, p, &prs, - (prs->flags & ~JS_PROP_WRITABLE) | - (flags & JS_PROP_WRITABLE))) - return -1; - } - } - } - } - mask = 0; - if (flags & JS_PROP_HAS_CONFIGURABLE) - mask |= JS_PROP_CONFIGURABLE; - if (flags & JS_PROP_HAS_ENUMERABLE) - mask |= JS_PROP_ENUMERABLE; - if (js_update_property_flags(ctx, p, &prs, - (prs->flags & ~mask) | (flags & mask))) - return -1; - return true; - } - - /* handle modification of fast array elements */ - if (p->fast_array) { - uint32_t idx; - uint32_t prop_flags; - if (p->class_id == JS_CLASS_ARRAY) { - if (__JS_AtomIsTaggedInt(prop)) { - idx = __JS_AtomToUInt32(prop); - if (idx < p->u.array.count) { - prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); - if (prop_flags != JS_PROP_C_W_E) - goto convert_to_slow_array; - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { - convert_to_slow_array: - if (convert_fast_array_to_array(ctx, p)) - return -1; - else - goto redo_prop_update; - } - if (flags & JS_PROP_HAS_VALUE) { - set_value(ctx, &p->u.array.u.values[idx], js_dup(val)); - } - return true; - } - } - } else if (is_typed_array(p->class_id)) { - JSValue num; - int ret; - - if (!__JS_AtomIsTaggedInt(prop)) { - /* slow path with to handle all numeric indexes */ - num = JS_AtomIsNumericIndex1(ctx, prop); - if (JS_IsUndefined(num)) - goto typed_array_done; - if (JS_IsException(num)) - return -1; - ret = JS_NumberIsInteger(ctx, num); - if (ret < 0) { - JS_FreeValue(ctx, num); - return -1; - } - if (!ret) { - JS_FreeValue(ctx, num); - return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); - } - ret = JS_NumberIsNegativeOrMinusZero(ctx, num); - JS_FreeValue(ctx, num); - if (ret) { - return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); - } - if (!__JS_AtomIsTaggedInt(prop)) - goto typed_array_oob; - } - idx = __JS_AtomToUInt32(prop); - /* if the typed array is detached, p->u.array.count = 0 */ - if (idx >= typed_array_get_length(ctx, p)) { - typed_array_oob: - return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); - } - prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || - prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { - return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); - } - if (flags & JS_PROP_HAS_VALUE) { - return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), js_dup(val), flags); - } - return true; - typed_array_done: ; - } - } - - return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); -} - -static int JS_DefineAutoInitProperty(JSContext *ctx, JSValue this_obj, - JSAtom prop, JSAutoInitIDEnum id, - void *opaque, int flags) -{ - JSObject *p; - JSProperty *pr; - - if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) - return false; - - p = JS_VALUE_GET_OBJ(this_obj); - - if (find_own_property(&pr, p, prop)) { - /* property already exists */ - abort(); - return false; - } - - /* Specialized CreateProperty */ - pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); - if (unlikely(!pr)) - return -1; - pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); - assert((pr->u.init.realm_and_id & 3) == 0); - assert(id <= 3); - pr->u.init.realm_and_id |= id; - pr->u.init.opaque = opaque; - return true; -} - -/* shortcut to add or redefine a new property value */ -int JS_DefinePropertyValue(JSContext *ctx, JSValue this_obj, - JSAtom prop, JSValue val, int flags) -{ - int ret; - ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, - flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); - JS_FreeValue(ctx, val); - return ret; -} - -int JS_DefinePropertyValueValue(JSContext *ctx, JSValue this_obj, - JSValue prop, JSValue val, int flags) -{ - JSAtom atom; - int ret; - atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if (unlikely(atom == JS_ATOM_NULL)) { - JS_FreeValue(ctx, val); - return -1; - } - ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); - JS_FreeAtom(ctx, atom); - return ret; -} - -int JS_DefinePropertyValueUint32(JSContext *ctx, JSValue this_obj, - uint32_t idx, JSValue val, int flags) -{ - return JS_DefinePropertyValueValue(ctx, this_obj, js_uint32(idx), - val, flags); -} - -int JS_DefinePropertyValueInt64(JSContext *ctx, JSValue this_obj, - int64_t idx, JSValue val, int flags) -{ - return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), - val, flags); -} - -/* `prop` may be pure ASCII or UTF-8 encoded */ -int JS_DefinePropertyValueStr(JSContext *ctx, JSValue this_obj, - const char *prop, JSValue val, int flags) -{ - JSAtom atom; - int ret; - atom = JS_NewAtom(ctx, prop); - ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); - JS_FreeAtom(ctx, atom); - return ret; -} - -/* shortcut to add getter & setter */ -int JS_DefinePropertyGetSet(JSContext *ctx, JSValue this_obj, - JSAtom prop, JSValue getter, JSValue setter, - int flags) -{ - int ret; - ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, - flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | - JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); - JS_FreeValue(ctx, getter); - JS_FreeValue(ctx, setter); - return ret; -} - -static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValue this_obj, - int64_t idx, JSValue val, int flags) -{ - return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), - val, flags | JS_PROP_CONFIGURABLE | - JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); -} - - -/* return true if 'obj' has a non empty 'name' string */ -static bool js_object_has_name(JSContext *ctx, JSValue obj) -{ - JSProperty *pr; - JSShapeProperty *prs; - JSValue val; - JSString *p; - - prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); - if (!prs) - return false; - if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) - return true; - val = pr->u.value; - if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) - return true; - p = JS_VALUE_GET_STRING(val); - return (p->len != 0); -} - -static int JS_DefineObjectName(JSContext *ctx, JSValue obj, - JSAtom name, int flags) -{ - if (name != JS_ATOM_NULL - && JS_IsObject(obj) - && !js_object_has_name(ctx, obj) - && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { - return -1; - } - return 0; -} - -static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj, - JSValue str, int flags) -{ - if (JS_IsObject(obj) && - !js_object_has_name(ctx, obj)) { - JSAtom prop; - JSValue name_str; - prop = JS_ValueToAtom(ctx, str); - if (prop == JS_ATOM_NULL) - return -1; - name_str = js_get_function_name(ctx, prop); - JS_FreeAtom(ctx, prop); - if (JS_IsException(name_str)) - return -1; - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) - return -1; - } - return 0; -} - -#define DEFINE_GLOBAL_LEX_VAR (1 << 7) -#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) - -static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) -{ - return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); -} - -/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ -/* XXX: could support exotic global object. */ -static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) -{ - JSObject *p; - JSShapeProperty *prs; - - p = JS_VALUE_GET_OBJ(ctx->global_obj); - prs = find_own_property1(p, prop); - /* XXX: should handle JS_PROP_AUTOINIT */ - if (flags & DEFINE_GLOBAL_LEX_VAR) { - if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) - goto fail_redeclaration; - } else { - if (!prs && !p->extensible) - goto define_error; - if (flags & DEFINE_GLOBAL_FUNC_VAR) { - if (prs) { - if (!(prs->flags & JS_PROP_CONFIGURABLE) && - ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || - ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != - (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { - define_error: - JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", - prop); - return -1; - } - } - } - } - /* check if there already is a lexical declaration */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property1(p, prop); - if (prs) { - fail_redeclaration: - JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); - return -1; - } - return 0; -} - -/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ -/* XXX: could support exotic global object. */ -static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - JSValue val; - int flags; - - if (def_flags & DEFINE_GLOBAL_LEX_VAR) { - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | - JS_PROP_CONFIGURABLE; - val = JS_UNINITIALIZED; - } else { - p = JS_VALUE_GET_OBJ(ctx->global_obj); - flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | - (def_flags & JS_PROP_CONFIGURABLE); - val = JS_UNDEFINED; - } - prs = find_own_property1(p, prop); - if (prs) - return 0; - if (!p->extensible) - return 0; - pr = add_property(ctx, p, prop, flags); - if (unlikely(!pr)) - return -1; - pr->u.value = val; - return 0; -} - -/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ -/* XXX: could support exotic global object. */ -static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, - JSValue func, int def_flags) -{ - - JSObject *p; - JSShapeProperty *prs; - int flags; - - p = JS_VALUE_GET_OBJ(ctx->global_obj); - prs = find_own_property1(p, prop); - flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; - if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { - flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | - JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; - } - if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, - JS_UNDEFINED, JS_UNDEFINED, flags) < 0) - return -1; - return 0; -} - -static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, - bool throw_ref_error) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - /* XXX: should handle JS_PROP_TMASK properties */ - if (unlikely(JS_IsUninitialized(pr->u.value))) - return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return js_dup(pr->u.value); - } - return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, - ctx->global_obj, throw_ref_error); -} - -/* construct a reference to a global variable */ -static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - /* XXX: should handle JS_PROP_AUTOINIT properties? */ - /* XXX: conformance: do these tests in - OP_put_var_ref/OP_get_var_ref ? */ - if (unlikely(JS_IsUninitialized(pr->u.value))) { - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { - return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); - } - sp[0] = js_dup(ctx->global_var_obj); - } else { - int ret; - ret = JS_HasProperty(ctx, ctx->global_obj, prop); - if (ret < 0) - return -1; - if (ret) { - sp[0] = js_dup(ctx->global_obj); - } else { - sp[0] = JS_UNDEFINED; - } - } - sp[1] = JS_AtomToValue(ctx, prop); - return 0; -} - -/* use for strict variable access: test if the variable exists */ -static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) -{ - JSObject *p; - JSShapeProperty *prs; - int ret; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property1(p, prop); - if (prs) { - ret = true; - } else { - ret = JS_HasProperty(ctx, ctx->global_obj, prop); - if (ret < 0) - return -1; - } - return ret; -} - -/* flag = 0: normal variable write - flag = 1: initialize lexical variable - flag = 2: normal variable write, strict check was done before -*/ -static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, - int flag) -{ - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - int flags; - - /* no exotic behavior is possible in global_var_obj */ - p = JS_VALUE_GET_OBJ(ctx->global_var_obj); - prs = find_own_property(&pr, p, prop); - if (prs) { - /* XXX: should handle JS_PROP_AUTOINIT properties? */ - if (flag != 1) { - if (unlikely(JS_IsUninitialized(pr->u.value))) { - JS_FreeValue(ctx, val); - JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); - return -1; - } - if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); - } - } - set_value(ctx, &pr->u.value, val); - return 0; - } - flags = JS_PROP_THROW_STRICT; - if (is_strict_mode(ctx)) - flags |= JS_PROP_NO_ADD; - return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags); -} - -/* return -1, false or true. return false if not configurable or - invalid object. return -1 in case of exception. - flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ -int JS_DeleteProperty(JSContext *ctx, JSValue obj, JSAtom prop, int flags) -{ - JSValue obj1; - JSObject *p; - int res; - - obj1 = JS_ToObject(ctx, obj); - if (JS_IsException(obj1)) - return -1; - p = JS_VALUE_GET_OBJ(obj1); - res = delete_property(ctx, p, prop); - JS_FreeValue(ctx, obj1); - if (res != false) - return res; - if ((flags & JS_PROP_THROW) || - ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeError(ctx, "could not delete property"); - return -1; - } - return false; -} - -int JS_DeletePropertyInt64(JSContext *ctx, JSValue obj, int64_t idx, int flags) -{ - JSAtom prop; - int res; - - if ((uint64_t)idx <= JS_ATOM_MAX_INT) { - /* fast path for fast arrays */ - return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); - } - prop = JS_NewAtomInt64(ctx, idx); - if (prop == JS_ATOM_NULL) - return -1; - res = JS_DeleteProperty(ctx, obj, prop, flags); - JS_FreeAtom(ctx, prop); - return res; -} - -bool JS_IsFunction(JSContext *ctx, JSValue val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - switch(p->class_id) { - case JS_CLASS_BYTECODE_FUNCTION: - return true; - case JS_CLASS_PROXY: - return p->u.proxy_data->is_func; - default: - return (ctx->rt->class_array[p->class_id].call != NULL); - } -} - -bool JS_IsCFunction(JSContext *ctx, JSValue val, JSCFunction *func, int magic) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - if (p->class_id == JS_CLASS_C_FUNCTION) - return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); - else - return false; -} - -bool JS_IsConstructor(JSContext *ctx, JSValue val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - return p->is_constructor; -} - -bool JS_SetConstructorBit(JSContext *ctx, JSValue func_obj, bool val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(func_obj); - p->is_constructor = val; - return true; -} - -bool JS_IsRegExp(JSValue val) -{ - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - return JS_VALUE_GET_OBJ(val)->class_id == JS_CLASS_REGEXP; -} - -bool JS_IsMap(JSValue val) -{ - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - return JS_VALUE_GET_OBJ(val)->class_id == JS_CLASS_MAP; -} - -bool JS_IsError(JSContext *ctx, JSValue val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - return (p->class_id == JS_CLASS_ERROR); -} - -/* used to avoid catching interrupt exceptions */ -bool JS_IsUncatchableError(JSContext *ctx, JSValue val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(val); - return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; -} - -static void js_set_uncatchable_error(JSContext *ctx, JSValue val, bool flag) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(val); - if (p->class_id == JS_CLASS_ERROR) - p->is_uncatchable_error = flag; -} - -void JS_SetUncatchableError(JSContext *ctx, JSValue val) -{ - js_set_uncatchable_error(ctx, val, true); -} - -void JS_ClearUncatchableError(JSContext *ctx, JSValue val) -{ - js_set_uncatchable_error(ctx, val, false); -} - -void JS_ResetUncatchableError(JSContext *ctx) -{ - js_set_uncatchable_error(ctx, ctx->rt->current_exception, false); -} - -int JS_SetOpaque(JSValue obj, void *opaque) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(obj); - // User code can't set the opaque of internal objects. - if (p->class_id >= JS_CLASS_INIT_COUNT || p->class_id == JS_CLASS_OBJECT) { - p->u.opaque = opaque; - return 0; - } - } - - return -1; -} - -/* |obj| must be a JSObject of an internal class. */ -static void JS_SetOpaqueInternal(JSValue obj, void *opaque) -{ - JSObject *p; - assert(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT); - p = JS_VALUE_GET_OBJ(obj); - assert(p->class_id < JS_CLASS_INIT_COUNT); - p->u.opaque = opaque; -} - -/* return NULL if not an object of class class_id */ -void *JS_GetOpaque(JSValue obj, JSClassID class_id) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return NULL; - p = JS_VALUE_GET_OBJ(obj); - if (p->class_id != class_id) - return NULL; - return p->u.opaque; -} - -void *JS_GetOpaque2(JSContext *ctx, JSValue obj, JSClassID class_id) -{ - void *p = JS_GetOpaque(obj, class_id); - if (unlikely(!p)) { - JS_ThrowTypeErrorInvalidClass(ctx, class_id); - } - return p; -} - -void *JS_GetAnyOpaque(JSValue obj, JSClassID *class_id) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { - *class_id = 0; - return NULL; - } - p = JS_VALUE_GET_OBJ(obj); - *class_id = p->class_id; - return p->u.opaque; -} - -static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) -{ - int i; - bool force_ordinary; - - JSAtom method_name; - JSValue method, ret; - if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) - return val; - force_ordinary = hint & HINT_FORCE_ORDINARY; - hint &= ~HINT_FORCE_ORDINARY; - if (!force_ordinary) { - method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); - if (JS_IsException(method)) - goto exception; - /* ECMA says *If exoticToPrim is not undefined* but tests in - test262 use null as a non callable converter */ - if (!JS_IsUndefined(method) && !JS_IsNull(method)) { - JSAtom atom; - JSValue arg; - switch(hint) { - case HINT_STRING: - atom = JS_ATOM_string; - break; - case HINT_NUMBER: - atom = JS_ATOM_number; - break; - default: - case HINT_NONE: - atom = JS_ATOM_default; - break; - } - arg = JS_AtomToString(ctx, atom); - ret = JS_CallFree(ctx, method, val, 1, &arg); - JS_FreeValue(ctx, arg); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, val); - if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) - return ret; - JS_FreeValue(ctx, ret); - return JS_ThrowTypeError(ctx, "toPrimitive"); - } - } - if (hint != HINT_STRING) - hint = HINT_NUMBER; - for(i = 0; i < 2; i++) { - if ((i ^ hint) == 0) { - method_name = JS_ATOM_toString; - } else { - method_name = JS_ATOM_valueOf; - } - method = JS_GetProperty(ctx, val, method_name); - if (JS_IsException(method)) - goto exception; - if (JS_IsFunction(ctx, method)) { - ret = JS_CallFree(ctx, method, val, 0, NULL); - if (JS_IsException(ret)) - goto exception; - if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { - JS_FreeValue(ctx, val); - return ret; - } - JS_FreeValue(ctx, ret); - } else { - JS_FreeValue(ctx, method); - } - } - JS_ThrowTypeError(ctx, "toPrimitive"); -exception: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint) -{ - return JS_ToPrimitiveFree(ctx, js_dup(val), hint); -} - -void JS_SetIsHTMLDDA(JSContext *ctx, JSValue obj) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return; - p = JS_VALUE_GET_OBJ(obj); - p->is_HTMLDDA = true; -} - -static inline bool JS_IsHTMLDDA(JSContext *ctx, JSValue obj) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return false; - p = JS_VALUE_GET_OBJ(obj); - return p->is_HTMLDDA; -} - -static int JS_ToBoolFree(JSContext *ctx, JSValue val) -{ - uint32_t tag = JS_VALUE_GET_TAG(val); - switch(tag) { - case JS_TAG_INT: - return JS_VALUE_GET_INT(val) != 0; - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - return JS_VALUE_GET_INT(val); - case JS_TAG_EXCEPTION: - return -1; - case JS_TAG_STRING: - { - bool ret = JS_VALUE_GET_STRING(val)->len != 0; - JS_FreeValue(ctx, val); - return ret; - } - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - bool ret; - ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; - JS_FreeValue(ctx, val); - return ret; - } - case JS_TAG_OBJECT: - { - JSObject *p = JS_VALUE_GET_OBJ(val); - bool ret = !p->is_HTMLDDA; - JS_FreeValue(ctx, val); - return ret; - } - break; - default: - if (JS_TAG_IS_FLOAT64(tag)) { - double d = JS_VALUE_GET_FLOAT64(val); - return !isnan(d) && d != 0; - } else { - JS_FreeValue(ctx, val); - return true; - } - } -} - -int JS_ToBool(JSContext *ctx, JSValue val) -{ - return JS_ToBoolFree(ctx, js_dup(val)); -} - -/* pc points to pure ASCII or UTF-8, null terminated contents */ -static int skip_spaces(const char *pc) -{ - const uint8_t *p, *p_next, *p_start; - uint32_t c; - - p = p_start = (const uint8_t *)pc; - for (;;) { - c = *p++; - if (c < 0x80) { - if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) - break; - } else { - c = utf8_decode(p - 1, &p_next); - /* no need to test for invalid UTF-8, 0xFFFD is not a space */ - if (!lre_is_space(c)) - break; - p = p_next; - } - } - return p - 1 - p_start; -} - -static inline int to_digit(int c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - else if (c >= 'a' && c <= 'z') - return c - 'a' + 10; - else - return 36; -} - -/* XXX: remove */ -static double js_strtod(const char *str, int radix, bool is_float) -{ - double d; - int c; - - if (!is_float || radix != 10) { - const char *p = str; - uint64_t n_max, n; - int int_exp, is_neg; - - is_neg = 0; - if (*p == '-') { - is_neg = 1; - p++; - } - - /* skip leading zeros */ - while (*p == '0') - p++; - n = 0; - if (radix == 10) - n_max = ((uint64_t)-1 - 9) / 10; /* most common case */ - else - n_max = ((uint64_t)-1 - (radix - 1)) / radix; - /* XXX: could be more precise */ - int_exp = 0; - while ((c = to_digit(*p)) < radix) { - if (n <= n_max) { - n = n * radix + c; - } else { - if (radix == 10) - goto strtod_case; - int_exp++; - } - p++; - } - d = n; - if (int_exp != 0) { - d *= pow(radix, int_exp); - } - if (is_neg) - d = -d; - } else { - strtod_case: - d = strtod(str, NULL); - } - return d; -} - -static JSValue js_string_to_bigint(JSContext *ctx, const char *buf, int radix) -{ - bf_t *a; - int ret; - JSValue val; - val = JS_NewBigInt(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigInt(val); - ret = bf_atof(a, buf, NULL, radix, BF_PREC_INF, BF_RNDZ); - if (ret & BF_ST_MEM_ERROR) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - return JS_CompactBigInt1(ctx, val); -} - -/* `js_atof(ctx, p, len, pp, radix, flags)` - Convert the string pointed to by `p` to a number value. - Return an exception in case of memory error. - Return `JS_NAN` if invalid syntax. - - `p` points to a null terminated UTF-8 encoded char array, - - `len` the length of the array, - - `pp` if not null receives a pointer to the next character, - - `radix` must be in range 2 to 36, else return `JS_NAN`. - - `flags` is a combination of the flags below. - There is a null byte at `p[len]`, but there might be embedded null - bytes between `p[0]` and `p[len]` which must produce `JS_NAN` if - the `ATOD_NO_TRAILING_CHARS` flag is present. - */ - -#define ATOD_TRIM_SPACES (1 << 0) /* trim white space */ -#define ATOD_ACCEPT_EMPTY (1 << 1) /* accept an empty string, value is 0 */ -#define ATOD_ACCEPT_FLOAT (1 << 2) /* parse decimal floating point syntax */ -#define ATOD_ACCEPT_INFINITY (1 << 3) /* parse Infinity as a float point number */ -#define ATOD_ACCEPT_BIN_OCT (1 << 4) /* accept 0o and 0b prefixes */ -#define ATOD_ACCEPT_HEX_PREFIX (1 << 5) /* accept 0x prefix for radix 16 */ -#define ATOD_ACCEPT_UNDERSCORES (1 << 6) /* accept _ between digits as a digit separator */ -#define ATOD_ACCEPT_SUFFIX (1 << 7) /* allow 'n' suffix to produce BigInt */ -#define ATOD_WANT_BIG_INT (1 << 8) /* return type must be BigInt */ -#define ATOD_DECIMAL_AFTER_SIGN (1 << 9) /* only accept decimal number after sign */ -#define ATOD_NO_TRAILING_CHARS (1 << 10) /* do not accept trailing characters */ - -static JSValue js_atof(JSContext *ctx, const char *p, size_t len, - const char **pp, int radix, int flags) -{ - const char *p_start; - const char *end = p + len; - int sep; - bool is_float; - char buf1[64], *buf = buf1; - size_t i, j; - JSValue val = JS_NAN; - double d; - char sign; - - if (radix < 2 || radix > 36) - goto done; - - /* optional separator between digits */ - sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; - sign = 0; - if (flags & ATOD_TRIM_SPACES) - p += skip_spaces(p); - if (p == end && (flags & ATOD_ACCEPT_EMPTY)) { - if (pp) *pp = p; - if (flags & ATOD_WANT_BIG_INT) - return JS_NewBigInt64(ctx, 0); - else - return js_int32(0); - } - if (*p == '+' || *p == '-') { - sign = *p; - p++; - if (flags & ATOD_DECIMAL_AFTER_SIGN) - flags &= ~(ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT); - } - if (p[0] == '0') { - if ((p[1] == 'x' || p[1] == 'X') && - ((flags & ATOD_ACCEPT_HEX_PREFIX) || radix == 16)) { - p += 2; - radix = 16; - } else if (flags & ATOD_ACCEPT_BIN_OCT) { - if (p[1] == 'o' || p[1] == 'O') { - p += 2; - radix = 8; - } else if (p[1] == 'b' || p[1] == 'B') { - p += 2; - radix = 2; - } - } - } else { - if (*p == 'I' && (flags & ATOD_ACCEPT_INFINITY) && js__strstart(p, "Infinity", &p)) { - d = INF; - if (sign == '-') - d = -d; - val = js_float64(d); - goto done; - } - } - is_float = false; - p_start = p; - while (to_digit(*p) < radix) { - p++; - if (*p == sep && to_digit(p[1]) < radix) - p++; - } - if ((flags & ATOD_ACCEPT_FLOAT) && radix == 10) { - if (*p == '.' && (p > p_start || to_digit(p[1]) < radix)) { - is_float = true; - p++; - while (to_digit(*p) < radix) { - p++; - if (*p == sep && to_digit(p[1]) < radix) - p++; - } - } - if (p > p_start && (*p == 'e' || *p == 'E')) { - i = 1; - if (p[1] == '+' || p[1] == '-') { - i++; - } - if (is_digit(p[i])) { - is_float = true; - p += i + 1; - while (is_digit(*p) || (*p == sep && is_digit(p[1]))) - p++; - } - } - } - if (p == p_start) - goto done; - - len = p - p_start; - if (unlikely((len + 2) > sizeof(buf1))) { - buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ - if (!buf) { - if (pp) *pp = p; - return JS_ThrowOutOfMemory(ctx); - } - } - /* remove the separators and the radix prefix */ - j = 0; - if (sign == '-') - buf[j++] = '-'; - for (i = 0; i < len; i++) { - if (p_start[i] != '_') - buf[j++] = p_start[i]; - } - buf[j] = '\0'; - - if (flags & ATOD_ACCEPT_SUFFIX) { - if (*p == 'n') { - p++; - flags |= ATOD_WANT_BIG_INT; - } - } - - if (flags & ATOD_WANT_BIG_INT) { - if (!is_float) - val = js_string_to_bigint(ctx, buf, radix); - } else { - d = js_strtod(buf, radix, is_float); - val = js_number(d); /* return int or float64 */ - } - - done: - if (flags & ATOD_NO_TRAILING_CHARS) { - if (flags & ATOD_TRIM_SPACES) - p += skip_spaces(p); - if (p != end) { - JS_FreeValue(ctx, val); - val = JS_NAN; - } - } - if (buf != buf1) - js_free_rt(ctx->rt, buf); - if (pp) *pp = p; - return val; -} - -typedef enum JSToNumberHintEnum { - TON_FLAG_NUMBER, - TON_FLAG_NUMERIC, -} JSToNumberHintEnum; - -static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, - JSToNumberHintEnum flag) -{ - uint32_t tag; - JSValue ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_BIG_INT: - if (flag != TON_FLAG_NUMERIC) { - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert BigInt to number"); - } - ret = val; - break; - case JS_TAG_FLOAT64: - case JS_TAG_INT: - case JS_TAG_EXCEPTION: - ret = val; - break; - case JS_TAG_BOOL: - case JS_TAG_NULL: - ret = js_int32(JS_VALUE_GET_INT(val)); - break; - case JS_TAG_UNDEFINED: - ret = JS_NAN; - break; - case JS_TAG_OBJECT: - val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); - if (JS_IsException(val)) - return JS_EXCEPTION; - goto redo; - case JS_TAG_STRING: - { - const char *str; - size_t len; - int flags; - - str = JS_ToCStringLen(ctx, &len, val); - JS_FreeValue(ctx, val); - if (!str) - return JS_EXCEPTION; - flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY | - ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY | - ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | - ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS; - ret = js_atof(ctx, str, len, NULL, 10, flags); - JS_FreeCString(ctx, str); - } - break; - case JS_TAG_SYMBOL: - JS_FreeValue(ctx, val); - return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); - default: - JS_FreeValue(ctx, val); - ret = JS_NAN; - break; - } - return ret; -} - -static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) -{ - return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); -} - -static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) -{ - return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); -} - -static JSValue JS_ToNumeric(JSContext *ctx, JSValue val) -{ - return JS_ToNumericFree(ctx, js_dup(val)); -} - -static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, - JSValue val) -{ - double d; - uint32_t tag; - - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = JS_FLOAT64_NAN; - return -1; - } - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - d = JS_VALUE_GET_INT(val); - break; - case JS_TAG_FLOAT64: - d = JS_VALUE_GET_FLOAT64(val); - break; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - /* XXX: there can be a double rounding issue with some - primitives (such as JS_ToUint8ClampFree()), but it is - not critical to fix it. */ - bf_get_float64(&p->num, &d, BF_RNDN); - JS_FreeValue(ctx, val); - } - break; - default: - abort(); - } - *pres = d; - return 0; -} - -static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) -{ - uint32_t tag; - - tag = JS_VALUE_GET_TAG(val); - if (tag <= JS_TAG_NULL) { - *pres = JS_VALUE_GET_INT(val); - return 0; - } else if (JS_TAG_IS_FLOAT64(tag)) { - *pres = JS_VALUE_GET_FLOAT64(val); - return 0; - } else { - return __JS_ToFloat64Free(ctx, pres, val); - } -} - -int JS_ToFloat64(JSContext *ctx, double *pres, JSValue val) -{ - return JS_ToFloat64Free(ctx, pres, js_dup(val)); -} - -JSValue JS_ToNumber(JSContext *ctx, JSValue val) -{ - return JS_ToNumberFree(ctx, js_dup(val)); -} - -/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ -static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) -{ - uint32_t tag; - JSValue ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = js_int32(JS_VALUE_GET_INT(val)); - break; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - ret = js_int32(0); - } else { - /* convert -0 to +0 */ - d = trunc(d) + 0.0; - ret = js_number(d); - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return val; - goto redo; - } - return ret; -} - -/* Note: the integer value is satured to 32 bits */ -static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) -{ - uint32_t tag; - int ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = JS_VALUE_GET_INT(val); - break; - case JS_TAG_EXCEPTION: - *pres = 0; - return -1; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - ret = 0; - } else { - if (d < INT32_MIN) - ret = INT32_MIN; - else if (d > INT32_MAX) - ret = INT32_MAX; - else - ret = (int)d; - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val) -{ - return JS_ToInt32SatFree(ctx, pres, js_dup(val)); -} - -int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val, - int min, int max, int min_offset) -{ - int res = JS_ToInt32SatFree(ctx, pres, js_dup(val)); - if (res == 0) { - if (*pres < min) { - *pres += min_offset; - if (*pres < min) - *pres = min; - } else { - if (*pres > max) - *pres = max; - } - } - return res; -} - -static int JS_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) -{ - uint32_t tag; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - *pres = JS_VALUE_GET_INT(val); - return 0; - case JS_TAG_EXCEPTION: - *pres = 0; - return -1; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - *pres = 0; - } else { - if (d < INT64_MIN) - *pres = INT64_MIN; - else if (d >= 0x1p63) - *pres = INT64_MAX; - else - *pres = (int64_t)d; - } - } - return 0; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } -} - -int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValue val) -{ - return JS_ToInt64SatFree(ctx, pres, js_dup(val)); -} - -int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValue val, - int64_t min, int64_t max, int64_t neg_offset) -{ - int res = JS_ToInt64SatFree(ctx, pres, js_dup(val)); - if (res == 0) { - if (*pres < 0) - *pres += neg_offset; - if (*pres < min) - *pres = min; - else if (*pres > max) - *pres = max; - } - return res; -} - -/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) - in case of exception */ -static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) -{ - uint32_t tag; - int64_t ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = JS_VALUE_GET_INT(val); - break; - case JS_TAG_FLOAT64: - { - JSFloat64Union u; - double d; - int e; - d = JS_VALUE_GET_FLOAT64(val); - u.d = d; - /* we avoid doing fmod(x, 2^64) */ - e = (u.u64 >> 52) & 0x7ff; - if (likely(e <= (1023 + 62))) { - /* fast case */ - ret = (int64_t)d; - } else if (e <= (1023 + 62 + 53)) { - uint64_t v; - /* remainder modulo 2^64 */ - v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); - ret = v << ((e - 1023) - 52); - /* take the sign into account */ - if (u.u64 >> 63) - if (ret != INT64_MIN) - ret = -ret; - } else { - ret = 0; /* also handles NaN and +inf */ - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValue val) -{ - return JS_ToInt64Free(ctx, pres, js_dup(val)); -} - -int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValue val) -{ - if (JS_IsBigInt(ctx, val)) - return JS_ToBigInt64(ctx, pres, val); - else - return JS_ToInt64(ctx, pres, val); -} - -/* return (<0, 0) in case of exception */ -static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) -{ - uint32_t tag; - int32_t ret; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - ret = JS_VALUE_GET_INT(val); - break; - case JS_TAG_FLOAT64: - { - JSFloat64Union u; - double d; - int e; - d = JS_VALUE_GET_FLOAT64(val); - u.d = d; - /* we avoid doing fmod(x, 2^32) */ - e = (u.u64 >> 52) & 0x7ff; - if (likely(e <= (1023 + 30))) { - /* fast case */ - ret = (int32_t)d; - } else if (e <= (1023 + 30 + 53)) { - uint64_t v; - /* remainder modulo 2^32 */ - v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); - v = v << ((e - 1023) - 52 + 32); - ret = v >> 32; - /* take the sign into account */ - if (u.u64 >> 63) - if (ret != INT32_MIN) - ret = -ret; - } else { - ret = 0; /* also handles NaN and +inf */ - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValue val) -{ - return JS_ToInt32Free(ctx, pres, js_dup(val)); -} - -static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) -{ - return JS_ToInt32Free(ctx, (int32_t *)pres, val); -} - -static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) -{ - uint32_t tag; - int res; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - res = JS_VALUE_GET_INT(val); - res = max_int(0, min_int(255, res)); - break; - case JS_TAG_FLOAT64: - { - double d = JS_VALUE_GET_FLOAT64(val); - if (isnan(d)) { - res = 0; - } else { - if (d < 0) - res = 0; - else if (d > 255) - res = 255; - else - res = lrint(d); - } - } - break; - default: - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = res; - return 0; -} - -static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, - JSValue val, bool is_array_ctor) -{ - uint32_t tag, len; - - tag = JS_VALUE_GET_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - { - int v; - v = JS_VALUE_GET_INT(val); - if (v < 0) - goto fail; - len = v; - } - break; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - bf_t a; - bool res; - bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD); - bf_init(ctx->bf_ctx, &a); - bf_set_ui(&a, len); - res = bf_cmp_eq(&a, &p->num); - bf_delete(&a); - JS_FreeValue(ctx, val); - if (!res) - goto fail; - } - break; - default: - if (JS_TAG_IS_FLOAT64(tag)) { - double d; - d = JS_VALUE_GET_FLOAT64(val); - if (!(d >= 0 && d <= UINT32_MAX)) - goto fail; - len = (uint32_t)d; - if (len != d) - goto fail; - } else { - uint32_t len1; - - if (is_array_ctor) { - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return -1; - /* cannot recurse because val is a number */ - if (JS_ToArrayLengthFree(ctx, &len, val, true)) - return -1; - } else { - /* legacy behavior: must do the conversion twice and compare */ - if (JS_ToUint32(ctx, &len, val)) { - JS_FreeValue(ctx, val); - return -1; - } - val = JS_ToNumberFree(ctx, val); - if (JS_IsException(val)) - return -1; - /* cannot recurse because val is a number */ - if (JS_ToArrayLengthFree(ctx, &len1, val, false)) - return -1; - if (len1 != len) { - fail: - JS_ThrowRangeError(ctx, "invalid array length"); - return -1; - } - } - } - break; - } - *plen = len; - return 0; -} - -#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) - -static bool is_safe_integer(double d) -{ - return isfinite(d) && floor(d) == d && - fabs(d) <= (double)MAX_SAFE_INTEGER; -} - -int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val) -{ - int64_t v; - if (JS_ToInt64Sat(ctx, &v, val)) - return -1; - if (v < 0 || v > MAX_SAFE_INTEGER) { - JS_ThrowRangeError(ctx, "invalid array index"); - *plen = 0; - return -1; - } - *plen = v; - return 0; -} - -/* convert a value to a length between 0 and MAX_SAFE_INTEGER. - return -1 for exception */ -static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, - JSValue val) -{ - int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); - JS_FreeValue(ctx, val); - return res; -} - -/* Note: can return an exception */ -static int JS_NumberIsInteger(JSContext *ctx, JSValue val) -{ - double d; - if (!JS_IsNumber(val)) - return false; - if (unlikely(JS_ToFloat64(ctx, &d, val))) - return -1; - return isfinite(d) && floor(d) == d; -} - -static bool JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValue val) -{ - uint32_t tag; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - { - int v; - v = JS_VALUE_GET_INT(val); - return (v < 0); - } - case JS_TAG_FLOAT64: - { - JSFloat64Union u; - u.d = JS_VALUE_GET_FLOAT64(val); - return (u.u64 >> 63); - } - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - /* Note: integer zeros are not necessarily positive */ - return p->num.sign && !bf_is_zero(&p->num); - } - default: - return false; - } -} - -static JSValue js_bigint_to_string1(JSContext *ctx, JSValue val, int radix) -{ - JSValue ret; - bf_t a_s, *a; - char *str; - int saved_sign; - size_t len; - - a = JS_ToBigInt(ctx, &a_s, val); - if (!a) - return JS_EXCEPTION; - saved_sign = a->sign; - if (a->expn == BF_EXP_ZERO) - a->sign = 0; - str = bf_ftoa(&len, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC | - BF_FTOA_JS_QUIRKS); - a->sign = saved_sign; - JS_FreeBigInt(ctx, a, &a_s); - if (!str) - return JS_ThrowOutOfMemory(ctx); - ret = js_new_string8_len(ctx, str, len); - bf_free(ctx->bf_ctx, str); - return ret; -} - -static JSValue js_bigint_to_string(JSContext *ctx, JSValue val) -{ - return js_bigint_to_string1(ctx, val, 10); -} - -/*---- floating point number to string conversions ----*/ - -/* JavaScript rounding is specified as round to nearest tie away - from zero (RNDNA), but in `printf` the "ties" case is not - specified (in most cases it is RNDN, round to nearest, tie to even), - so we must round manually. We generate 2 extra places and make - an extra call to snprintf if these are exactly '50'. - We set the current rounding mode to FE_DOWNWARD to check if the - last 2 places become '49'. If not, we must round up, which is - performed in place using the string digits. - - Note that we cannot rely on snprintf for rounding up: - the code below fails on macOS for `0.5.toFixed(0)`: gives `0` expected `1` - fesetround(FE_UPWARD); - snprintf(dest, size, "%.*f", n_digits, d); - fesetround(FE_TONEAREST); - */ - -/* `js_fcvt` minimum buffer length: - - up to 21 digits in integral part - - 1 potential decimal point - - up to 102 decimals - - 1 null terminator - */ -#define JS_FCVT_BUF_SIZE (21+1+102+1) - -/* `js_ecvt` minimum buffer length: - - 1 leading digit - - 1 potential decimal point - - up to 102 decimals - - 5 exponent characters (from 'e-324' to 'e+308') - - 1 null terminator - */ -#define JS_ECVT_BUF_SIZE (1+1+102+5+1) - -/* `js_dtoa` minimum buffer length: - - 8 byte prefix - - either JS_FCVT_BUF_SIZE or JS_ECVT_BUF_SIZE - - JS_FCVT_BUF_SIZE is larger than JS_ECVT_BUF_SIZE - */ -#define JS_DTOA_BUF_SIZE (8+JS_FCVT_BUF_SIZE) - -/* `js_ecvt1`: compute the digits and decimal point spot for a double - - `d` is finite, positive or zero - - `n_digits` number of significant digits in range 1..103 - - `buf` receives the printf result - - `buf` has a fixed format: n_digits with a decimal point at offset 1 - and exponent 'e{+/-}xx[x]' at offset n_digits+1 - Return n_digits - Store the position of the decimal point into `*decpt` - */ -static int js_ecvt1(double d, int n_digits, - char dest[minimum_length(JS_ECVT_BUF_SIZE)], - size_t size, int *decpt) -{ - /* d is positive, ensure decimal point is always present */ - snprintf(dest, size, "%#.*e", n_digits - 1, d); - /* dest contents: - 0: first digit - 1: '.' decimal point (locale specific) - 2..n_digits: (n_digits-1) additional digits - n_digits+1: 'e' exponent mark - n_digits+2..: exponent sign, value and null terminator - */ - /* extract the exponent (actually the position of the decimal point) */ - *decpt = 1 + atoi(dest + n_digits + 2); - return n_digits; -} - -/* `js_ecvt`: compute the digits and decimal point spot for a double - with proper javascript rounding. We cannot use `ecvt` for multiple - resasons: portability, because of the number of digits is typically - limited to 17, finally because the default rounding is inadequate. - `d` is finite and positive or zero. - `n_digits` number of significant digits in range 1..101 - or 0 for automatic (only as many digits as necessary) - Return the number of digits produced in `dest`. - Store the position of the decimal point into `*decpt` - */ -static int js_ecvt(double d, int n_digits, - char dest[minimum_length(JS_ECVT_BUF_SIZE)], - size_t size, int *decpt) -{ - if (n_digits == 0) { - /* find the minimum number of digits (XXX: inefficient but simple) */ - // TODO(chqrlie) use direct method from quickjs-printf - unsigned int n_digits_min = 1; - unsigned int n_digits_max = 17; - for (;;) { - n_digits = (n_digits_min + n_digits_max) / 2; - js_ecvt1(d, n_digits, dest, size, decpt); - if (n_digits_min == n_digits_max) - return n_digits; - /* dest contents: - 0: first digit - 1: '.' decimal point (locale specific) - 2..n_digits: (n_digits-1) additional digits - n_digits+1: 'e' exponent mark - n_digits+2..: exponent sign, value and null terminator - */ - if (strtod(dest, NULL) == d) { - unsigned int n0 = n_digits; - /* enough digits */ - /* strip the trailing zeros */ - while (dest[n_digits] == '0') - n_digits--; - if (n_digits == n_digits_min) - return n_digits; - /* done if trailing zeros and not denormal or huge */ - if (n_digits < n0 && d > 3e-308 && d < 8e307) - return n_digits; - n_digits_max = n_digits; - } else { - /* need at least one more digit */ - n_digits_min = n_digits + 1; - } - } - } else { -#if defined(FE_DOWNWARD) && defined(FE_TONEAREST) - int i; - /* generate 2 extra digits: 99% chances to avoid 2 calls */ - js_ecvt1(d, n_digits + 2, dest, size, decpt); - if (dest[n_digits + 1] < '5') - return n_digits; /* truncate the 2 extra digits */ - if (dest[n_digits + 1] == '5' && dest[n_digits + 2] == '0') { - /* close to half-way: try rounding toward 0 */ - fesetround(FE_DOWNWARD); - js_ecvt1(d, n_digits + 2, dest, size, decpt); - fesetround(FE_TONEAREST); - if (dest[n_digits + 1] < '5') - return n_digits; /* truncate the 2 extra digits */ - } - /* round up in the string */ - for(i = n_digits;; i--) { - /* ignore the locale specific decimal point */ - if (is_digit(dest[i])) { - if (dest[i]++ < '9') - break; - dest[i] = '0'; - if (i == 0) { - dest[0] = '1'; - (*decpt)++; - break; - } - } - } - return n_digits; /* truncate the 2 extra digits */ -#else - /* No disambiguation available, eg: __wasi__ targets */ - return js_ecvt1(d, n_digits, dest, size, decpt); -#endif - } -} - -/* `js_fcvt`: convert a floating point value to %f format using RNDNA - `d` is finite and positive or zero. - `n_digits` number of decimal places in range 0..100 - Return the number of characters produced in `dest`. - */ -static size_t js_fcvt(double d, int n_digits, - char dest[minimum_length(JS_FCVT_BUF_SIZE)], size_t size) -{ -#if defined(FE_DOWNWARD) && defined(FE_TONEAREST) - int i, n1; - /* generate 2 extra digits: 99% chances to avoid 2 calls */ - n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2; - if (dest[n1] >= '5') { - if (dest[n1] == '5' && dest[n1 + 1] == '0') { - /* close to half-way: try rounding toward 0 */ - fesetround(FE_DOWNWARD); - n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2; - fesetround(FE_TONEAREST); - } - if (dest[n1] >= '5') { /* number should be rounded up */ - /* d is either exactly half way or greater: round the string manually */ - for (i = n1 - 1;; i--) { - /* ignore the locale specific decimal point */ - if (is_digit(dest[i])) { - if (dest[i]++ < '9') - break; - dest[i] = '0'; - if (i == 0) { - dest[0] = '1'; - dest[n1] = '0'; - dest[n1 - n_digits - 1] = '0'; - dest[n1 - n_digits] = '.'; - n1++; - break; - } - } - } - } - } - /* truncate the extra 2 digits and the decimal point if !n_digits */ - n1 -= !n_digits; - //dest[n1] = '\0'; // optional - return n1; -#else - /* No disambiguation available, eg: __wasi__ targets */ - return snprintf(dest, size, "%.*f", n_digits, d); -#endif -} - -static JSValue js_dtoa_infinite(JSContext *ctx, double d) -{ - // TODO(chqrlie) use atoms for NaN and Infinite? - if (isnan(d)) - return js_new_string8(ctx, "NaN"); - if (d < 0) - return js_new_string8(ctx, "-Infinity"); - else - return js_new_string8(ctx, "Infinity"); -} - -#define JS_DTOA_TOSTRING 0 /* use as many digits as necessary */ -#define JS_DTOA_EXPONENTIAL 1 /* use exponential notation either fixed or variable digits */ -#define JS_DTOA_FIXED 2 /* force fixed number of fractional digits */ -#define JS_DTOA_PRECISION 3 /* use n_digits significant digits (1 <= n_digits <= 101) */ - -/* `js_dtoa`: convert a floating point number to a string - - `mode`: one of the 4 supported formats - - `n_digits`: digit number according to mode - - TOSTRING: 0 only. As many digits as necessary - - EXPONENTIAL: 0 as many decimals as necessary - - 1..101 number of significant digits - - FIXED: 0..100 number of decimal places - - PRECISION: 1..101 number of significant digits - */ -// XXX: should use libbf or quickjs-printf. -static JSValue js_dtoa(JSContext *ctx, double d, int n_digits, int mode) -{ - char buf[JS_DTOA_BUF_SIZE]; - size_t len; - char *start; - int sign, decpt, exp, i, k, n, n_max; - - if (!isfinite(d)) - return js_dtoa_infinite(ctx, d); - - sign = (d < 0); - start = buf + 8; - d = fabs(d); /* also converts -0 to 0 */ - - if (mode != JS_DTOA_EXPONENTIAL && n_digits == 0) { - /* fast path for exact integers in variable format: - clip to MAX_SAFE_INTEGER because to ensure insignificant - digits are generated as 0. - used for JS_DTOA_TOSTRING and JS_DTOA_FIXED without decimals. - */ - if (d <= (double)MAX_SAFE_INTEGER) { - uint64_t u64 = (uint64_t)d; - if (d == u64) { - len = u64toa(start, u64); - goto done; - } - } - } - if (mode == JS_DTOA_FIXED) { - len = js_fcvt(d, n_digits, start, sizeof(buf) - 8); - // TODO(chqrlie) patch the locale specific decimal point - goto done; - } - - n_max = (n_digits > 0) ? n_digits : 21; - /* the number has k digits (1 <= k <= n_max) */ - k = js_ecvt(d, n_digits, start, sizeof(buf) - 8, &decpt); - /* buffer contents: - 0: first digit - 1: '.' decimal point - 2..k: (k-1) additional digits - */ - n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */ - if (mode != JS_DTOA_EXPONENTIAL) { - /* mode is JS_DTOA_PRECISION or JS_DTOA_TOSTRING */ - if (n >= 1 && n <= n_max) { - /* between 1 and n_max digits before the decimal point */ - if (k <= n) { - /* all digits before the point, append zeros */ - start[1] = start[0]; - start++; - for(i = k; i < n; i++) - start[i] = '0'; - len = n; - } else { - /* k > n: move digits before the point */ - for(i = 1; i < n; i++) - start[i] = start[i + 1]; - start[i] = '.'; - len = 1 + k; - } - goto done; - } - if (n >= -5 && n <= 0) { - /* insert -n leading 0 decimals and a '0.' prefix */ - n = -n; - start[1] = start[0]; - start -= n + 1; - start[0] = '0'; - start[1] = '.'; - for(i = 0; i < n; i++) - start[2 + i] = '0'; - len = 2 + k + n; - goto done; - } - } - /* exponential notation */ - exp = n - 1; - /* count the digits and the decimal point if at least one decimal */ - len = k + (k > 1); - start[1] = '.'; /* patch the locale specific decimal point */ - start[len] = 'e'; - start[len + 1] = '+'; - if (exp < 0) { - start[len + 1] = '-'; - exp = -exp; - } - len += 2 + 1 + (exp > 9) + (exp > 99); - for (i = len - 1; exp > 9;) { - int quo = exp / 10; - start[i--] = (char)('0' + exp % 10); - exp = quo; - } - start[i] = (char)('0' + exp); - - done: - start[-1] = '-'; /* prepend the sign if negative */ - return js_new_string8_len(ctx, start - sign, len + sign); -} - -/* `js_dtoa_radix`: convert a floating point number using a specific base - - `d` must be finite - - `radix` must be in range 2..36 - */ -static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix) -{ - char buf[2200], *ptr, *ptr2, *ptr3; - int sign, digit; - double frac, d0; - int64_t n0; - - if (!isfinite(d)) - return js_dtoa_infinite(ctx, d); - - sign = (d < 0); - d = fabs(d); - d0 = trunc(d); - n0 = 0; - frac = d - d0; - ptr2 = buf + 1100; /* ptr2 points to the end of the string */ - ptr = ptr2; /* ptr points to the beginning of the string */ - if (d0 <= MAX_SAFE_INTEGER) { - int64_t n = n0 = (int64_t)d0; - while (n >= radix) { - digit = n % radix; - n = n / radix; - *--ptr = digits36[digit]; - } - *--ptr = digits36[(size_t)n]; - } else { - /* no decimals */ - while (d0 >= radix) { - digit = fmod(d0, radix); - d0 = trunc(d0 / radix); - if (d0 >= MAX_SAFE_INTEGER) - digit = 0; - *--ptr = digits36[digit]; - } - *--ptr = digits36[(size_t)d0]; - goto done; - } - if (frac != 0) { - double log2_radix = log2(radix); - double prec = 1023 + 51; // handle subnormals - *ptr2++ = '.'; - while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) { - frac *= radix; - digit = trunc(frac); - frac -= digit; - *ptr2++ = digits36[digit]; - n0 = n0 * radix + digit; - prec -= log2_radix; - } - if (frac * radix >= radix / 2) { - /* round up the string representation manually */ - char nine = digits36[radix - 1]; - while (ptr2[-1] == nine) { - /* strip trailing '9' or equivalent digits */ - ptr2--; - } - if (ptr2[-1] == '.') { - /* strip the 'decimal' point */ - ptr2--; - /* increment the integral part */ - for (ptr3 = ptr2;;) { - if (ptr3[-1] != nine) { - ptr3[-1] = (ptr3[-1] == '9') ? 'a' : ptr3[-1] + 1; - break; - } - *--ptr3 = '0'; - if (ptr3 <= ptr) { - /* prepend a '1' if number was all nines */ - *--ptr = '1'; - break; - } - } - } else { - /* increment the last fractional digit */ - ptr2[-1] = (ptr2[-1] == '9') ? 'a' : ptr2[-1] + 1; - } - } else { - /* strip trailing fractional zeros */ - while (ptr2[-1] == '0') - ptr2--; - /* strip the 'decimal' point if last */ - ptr2 -= (ptr2[-1] == '.'); - } - } -done: - ptr[-1] = '-'; - ptr -= sign; - return js_new_string8_len(ctx, ptr, ptr2 - ptr); -} - -JSValue JS_ToStringInternal(JSContext *ctx, JSValue val, bool is_ToPropertyKey) -{ - uint32_t tag; - char buf[32]; - size_t len; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_STRING: - return js_dup(val); - case JS_TAG_INT: - len = i32toa(buf, JS_VALUE_GET_INT(val)); - return js_new_string8_len(ctx, buf, len); - case JS_TAG_BOOL: - return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? - JS_ATOM_true : JS_ATOM_false); - case JS_TAG_NULL: - return JS_AtomToString(ctx, JS_ATOM_null); - case JS_TAG_UNDEFINED: - return JS_AtomToString(ctx, JS_ATOM_undefined); - case JS_TAG_EXCEPTION: - return JS_EXCEPTION; - case JS_TAG_OBJECT: - { - JSValue val1, ret; - val1 = JS_ToPrimitive(ctx, val, HINT_STRING); - if (JS_IsException(val1)) - return val1; - ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey); - JS_FreeValue(ctx, val1); - return ret; - } - break; - case JS_TAG_FUNCTION_BYTECODE: - return js_new_string8(ctx, "[function bytecode]"); - case JS_TAG_SYMBOL: - if (is_ToPropertyKey) { - return js_dup(val); - } else { - return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); - } - case JS_TAG_FLOAT64: - return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 0, JS_DTOA_TOSTRING); - case JS_TAG_BIG_INT: - return js_bigint_to_string(ctx, val); - case JS_TAG_UNINITIALIZED: - return js_new_string8(ctx, "[uninitialized]"); - default: - return js_new_string8(ctx, "[unsupported type]"); - } -} - -JSValue JS_ToString(JSContext *ctx, JSValue val) -{ - return JS_ToStringInternal(ctx, val, false); -} - -static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) -{ - JSValue ret; - ret = JS_ToString(ctx, val); - JS_FreeValue(ctx, val); - return ret; -} - -static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) -{ - if (JS_IsUndefined(val) || JS_IsNull(val)) - return JS_ToStringFree(ctx, val); - return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); -} - -JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val) -{ - return JS_ToStringInternal(ctx, val, true); -} - -static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val) -{ - uint32_t tag = JS_VALUE_GET_TAG(val); - if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) - return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); - return JS_ToString(ctx, val); -} - -static JSValue JS_ToQuotedString(JSContext *ctx, JSValue val1) -{ - JSValue val; - JSString *p; - int i; - uint32_t c; - StringBuffer b_s, *b = &b_s; - char buf[16]; - - val = JS_ToStringCheckObject(ctx, val1); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_STRING(val); - - if (string_buffer_init(ctx, b, p->len + 2)) - goto fail; - - if (string_buffer_putc8(b, '\"')) - goto fail; - for(i = 0; i < p->len; ) { - c = string_getc(p, &i); - switch(c) { - case '\t': - c = 't'; - goto quote; - case '\r': - c = 'r'; - goto quote; - case '\n': - c = 'n'; - goto quote; - case '\b': - c = 'b'; - goto quote; - case '\f': - c = 'f'; - goto quote; - case '\"': - case '\\': - quote: - if (string_buffer_putc8(b, '\\')) - goto fail; - if (string_buffer_putc8(b, c)) - goto fail; - break; - default: - if (c < 32 || is_surrogate(c)) { - snprintf(buf, sizeof(buf), "\\u%04x", c); - if (string_buffer_write8(b, (uint8_t*)buf, 6)) - goto fail; - } else { - if (string_buffer_putc(b, c)) - goto fail; - } - break; - } - } - if (string_buffer_putc8(b, '\"')) - goto fail; - JS_FreeValue(ctx, val); - return string_buffer_end(b); - fail: - JS_FreeValue(ctx, val); - string_buffer_free(b); - return JS_EXCEPTION; -} - -static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) -{ - printf("%14s %4s %4s %14s %10s %s\n", - "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); -} - -/* for debug only: dump an object without side effect */ -static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) -{ - uint32_t i; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - JSShape *sh; - JSShapeProperty *prs; - JSProperty *pr; - bool is_first = true; - - /* XXX: should encode atoms with special characters */ - sh = p->shape; /* the shape can be NULL while freeing an object */ - printf("%14p %4d ", - (void *)p, - p->header.ref_count); - if (sh) { - printf("%3d%c %14p ", - sh->header.ref_count, - " *"[sh->is_hashed], - (void *)sh->proto); - } else { - printf("%3s %14s ", "-", "-"); - } - printf("%10s ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); - if (p->is_exotic && p->fast_array) { - printf("[ "); - for(i = 0; i < p->u.array.count; i++) { - if (i != 0) - printf(", "); - switch (p->class_id) { - case JS_CLASS_ARRAY: - case JS_CLASS_ARGUMENTS: - JS_DumpValue(rt, p->u.array.u.values[i]); - break; - case JS_CLASS_UINT8C_ARRAY: - case JS_CLASS_INT8_ARRAY: - case JS_CLASS_UINT8_ARRAY: - case JS_CLASS_INT16_ARRAY: - case JS_CLASS_UINT16_ARRAY: - case JS_CLASS_INT32_ARRAY: - case JS_CLASS_UINT32_ARRAY: - case JS_CLASS_BIG_INT64_ARRAY: - case JS_CLASS_BIG_UINT64_ARRAY: - case JS_CLASS_FLOAT16_ARRAY: - case JS_CLASS_FLOAT32_ARRAY: - case JS_CLASS_FLOAT64_ARRAY: - { - int size = 1 << typed_array_size_log2(p->class_id); - const uint8_t *b = p->u.array.u.uint8_ptr + i * size; - while (size-- > 0) - printf("%02X", *b++); - } - break; - } - } - printf(" ] "); - } - - if (sh) { - printf("{ "); - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - if (prs->atom != JS_ATOM_NULL) { - pr = &p->prop[i]; - if (!is_first) - printf(", "); - printf("%s: ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); - if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { - printf("[getset %p %p]", (void *)pr->u.getset.getter, - (void *)pr->u.getset.setter); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { - printf("[varref %p]", (void *)pr->u.var_ref); - } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { - printf("[autoinit %p %d %p]", - (void *)js_autoinit_get_realm(pr), - js_autoinit_get_id(pr), - (void *)pr->u.init.opaque); - } else { - JS_DumpValue(rt, pr->u.value); - } - is_first = false; - } - } - printf(" }"); - } - - if (js_class_has_bytecode(p->class_id)) { - JSFunctionBytecode *b = p->u.func.function_bytecode; - JSVarRef **var_refs; - if (b->closure_var_count) { - var_refs = p->u.func.var_refs; - printf(" Closure:"); - for(i = 0; i < b->closure_var_count; i++) { - printf(" "); - JS_DumpValue(rt, var_refs[i]->value); - } - if (p->u.func.home_object) { - printf(" HomeObject: "); - JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); - } - } - } - printf("\n"); -} - -static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) -{ - if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { - JS_DumpObject(rt, (JSObject *)p); - } else { - printf("%14p %4d ", - (void *)p, - p->ref_count); - switch(p->gc_obj_type) { - case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: - printf("[function bytecode]"); - break; - case JS_GC_OBJ_TYPE_SHAPE: - printf("[shape]"); - break; - case JS_GC_OBJ_TYPE_VAR_REF: - printf("[var_ref]"); - break; - case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: - printf("[async_function]"); - break; - case JS_GC_OBJ_TYPE_JS_CONTEXT: - printf("[js_context]"); - break; - default: - printf("[unknown %d]", p->gc_obj_type); - break; - } - printf("\n"); - } -} - -static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val) -{ - uint32_t tag = JS_VALUE_GET_NORM_TAG(val); - const char *str; - - switch(tag) { - case JS_TAG_INT: - printf("%d", JS_VALUE_GET_INT(val)); - break; - case JS_TAG_BOOL: - if (JS_VALUE_GET_BOOL(val)) - str = "true"; - else - str = "false"; - goto print_str; - case JS_TAG_NULL: - str = "null"; - goto print_str; - case JS_TAG_EXCEPTION: - str = "exception"; - goto print_str; - case JS_TAG_UNINITIALIZED: - str = "uninitialized"; - goto print_str; - case JS_TAG_UNDEFINED: - str = "undefined"; - print_str: - printf("%s", str); - break; - case JS_TAG_FLOAT64: - printf("%.14g", JS_VALUE_GET_FLOAT64(val)); - break; - case JS_TAG_BIG_INT: - { - JSBigInt *p = JS_VALUE_GET_PTR(val); - char *str; - str = bf_ftoa(NULL, &p->num, 10, 0, - BF_RNDZ | BF_FTOA_FORMAT_FRAC); - printf("%sn", str); - bf_realloc(&rt->bf_ctx, str, 0); - } - break; - case JS_TAG_STRING: - { - JSString *p; - p = JS_VALUE_GET_STRING(val); - JS_DumpString(rt, p); - } - break; - case JS_TAG_FUNCTION_BYTECODE: - { - JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); - char buf[ATOM_GET_STR_BUF_SIZE]; - if (b->func_name) { - printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); - } else { - printf("[bytecode (anonymous)]"); - } - } - break; - case JS_TAG_OBJECT: - { - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAtom atom = rt->class_array[p->class_id].class_name; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("[%s %p]", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p = JS_VALUE_GET_PTR(val); - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("Symbol(%s)", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); - } - break; - case JS_TAG_MODULE: - printf("[module]"); - break; - default: - printf("[unknown tag %d]", tag); - break; - } -} - -/* return -1 if exception (proxy case) or true/false */ -int JS_IsArray(JSContext *ctx, JSValue val) -{ - JSObject *p; - if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { - p = JS_VALUE_GET_OBJ(val); - if (unlikely(p->class_id == JS_CLASS_PROXY)) - return js_proxy_isArray(ctx, val); - else - return p->class_id == JS_CLASS_ARRAY; - } else { - return false; - } -} - -static double js_math_pow(double a, double b) -{ - if (unlikely(!isfinite(b)) && fabs(a) == 1) { - /* not compatible with IEEE 754 */ - return JS_FLOAT64_NAN; - } else { - return pow(a, b); - } -} - -JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) -{ - JSValue val; - bf_t *a; - val = JS_NewBigInt(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigInt(val); - if (bf_set_si(a, v)) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - return val; -} - -JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) -{ - JSValue val; - bf_t *a; - val = JS_NewBigInt(ctx); - if (JS_IsException(val)) - return val; - a = JS_GetBigInt(val); - if (bf_set_ui(a, v)) { - JS_FreeValue(ctx, val); - return JS_ThrowOutOfMemory(ctx); - } - - return val; -} - -/* if the returned bigint is allocated it is equal to - 'buf'. Otherwise it is a pointer to the bigint in 'val'. Return - NULL in case of error. */ -// TODO(bnoordhuis) Merge with JS_ToBigInt() -static bf_t *JS_ToBigInt1(JSContext *ctx, bf_t *buf, JSValue val) -{ - uint32_t tag; - bf_t *r; - JSBigInt *p; - - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - r = buf; - bf_init(ctx->bf_ctx, r); - if (bf_set_si(r, JS_VALUE_GET_INT(val))) - goto fail; - break; - case JS_TAG_FLOAT64: - r = buf; - bf_init(ctx->bf_ctx, r); - if (bf_set_float64(r, JS_VALUE_GET_FLOAT64(val))) { - fail: - bf_delete(r); - return NULL; - } - break; - case JS_TAG_BIG_INT: - p = JS_VALUE_GET_PTR(val); - r = &p->num; - break; - case JS_TAG_UNDEFINED: - default: - r = buf; - bf_init(ctx->bf_ctx, r); - bf_set_nan(r); - break; - } - return r; -} - -/* return NaN if bad bigint literal */ -static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) -{ - const char *str; - size_t len; - int flags; - - str = JS_ToCStringLen(ctx, &len, val); - JS_FreeValue(ctx, val); - if (!str) - return JS_EXCEPTION; - flags = ATOD_WANT_BIG_INT | - ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY | - ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | - ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS; - val = js_atof(ctx, str, len, NULL, 10, flags); - JS_FreeCString(ctx, str); - return val; -} - -static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) -{ - val = JS_StringToBigInt(ctx, val); - if (JS_VALUE_IS_NAN(val)) - return JS_ThrowSyntaxError(ctx, "invalid BigInt literal"); - return val; -} - -/* if the returned bigint is allocated it is equal to - 'buf'. Otherwise it is a pointer to the bigint in 'val'. */ -static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val) -{ - uint32_t tag; - bf_t *r; - JSBigInt *p; - - redo: - tag = JS_VALUE_GET_NORM_TAG(val); - switch(tag) { - case JS_TAG_INT: - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - case JS_TAG_FLOAT64: - goto fail; - case JS_TAG_BOOL: - r = buf; - bf_init(ctx->bf_ctx, r); - bf_set_si(r, JS_VALUE_GET_INT(val)); - break; - case JS_TAG_BIG_INT: - p = JS_VALUE_GET_PTR(val); - r = &p->num; - break; - case JS_TAG_STRING: - val = JS_StringToBigIntErr(ctx, val); - if (JS_IsException(val)) - return NULL; - goto redo; - case JS_TAG_OBJECT: - val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); - if (JS_IsException(val)) - return NULL; - goto redo; - default: - fail: - JS_FreeValue(ctx, val); - JS_ThrowTypeError(ctx, "cannot convert to BigInt"); - return NULL; - } - return r; -} - -static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValue val) -{ - return JS_ToBigIntFree(ctx, buf, js_dup(val)); -} - -static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val) -{ - if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) { - return val; - } else { - bf_t a_s, *a, *r; - int ret; - JSValue res; - - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) - return JS_EXCEPTION; - a = JS_ToBigIntFree(ctx, &a_s, val); - if (!a) { - JS_FreeValue(ctx, res); - return JS_EXCEPTION; - } - r = JS_GetBigInt(res); - ret = bf_set(r, a); - JS_FreeBigInt(ctx, a, &a_s); - if (ret) { - JS_FreeValue(ctx, res); - return JS_ThrowOutOfMemory(ctx); - } - return JS_CompactBigInt(ctx, res); - } -} - -/* free the bf_t allocated by JS_ToBigInt */ -static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf) -{ - if (a == buf) { - bf_delete(a); - } else { - JSBigInt *p = (JSBigInt *)((uint8_t *)a - offsetof(JSBigInt, num)); - JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_INT, p)); - } -} - -/* XXX: merge with JS_ToInt64Free with a specific flag */ -static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) -{ - bf_t a_s, *a; - - a = JS_ToBigIntFree(ctx, &a_s, val); - if (!a) { - *pres = 0; - return -1; - } - bf_get_int64(pres, a, BF_GET_INT_MOD); - JS_FreeBigInt(ctx, a, &a_s); - return 0; -} - -int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValue val) -{ - return JS_ToBigInt64Free(ctx, pres, js_dup(val)); -} - -int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValue val) -{ - return JS_ToBigInt64Free(ctx, (int64_t *)pres, js_dup(val)); -} - -static JSValue JS_NewBigInt(JSContext *ctx) -{ - JSBigInt *p; - p = js_malloc(ctx, sizeof(*p)); - if (!p) - return JS_EXCEPTION; - p->header.ref_count = 1; - bf_init(ctx->bf_ctx, &p->num); - return JS_MKPTR(JS_TAG_BIG_INT, p); -} - -static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val) -{ - if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT) - return val; /* fail safe */ - bf_t *a = JS_GetBigInt(val); - if (a->expn == BF_EXP_ZERO && a->sign) { - assert(((JSBigInt*)JS_VALUE_GET_PTR(val))->header.ref_count == 1); - a->sign = 0; - } - return val; -} - -/* Nnormalize the zero representation. Could also be used to convert the bigint - to a short bigint value. The reference count of the value must be - 1. Cannot fail */ -static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val) -{ - return JS_CompactBigInt1(ctx, val); -} - -static JSValue throw_bf_exception(JSContext *ctx, int status) -{ - const char *str; - if (status & BF_ST_MEM_ERROR) - return JS_ThrowOutOfMemory(ctx); - if (status & BF_ST_DIVIDE_ZERO) { - str = "division by zero"; - } else if (status & BF_ST_INVALID_OP) { - str = "invalid operation"; - } else { - str = "integer overflow"; - } - return JS_ThrowRangeError(ctx, "%s", str); -} - -static int js_unary_arith_bigint(JSContext *ctx, - JSValue *pres, OPCodeEnum op, JSValue op1) -{ - bf_t a_s, *r, *a; - int ret, v; - JSValue res; - - if (op == OP_plus) { - JS_ThrowTypeError(ctx, "BigInt argument with unary +"); - JS_FreeValue(ctx, op1); - return -1; - } - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) { - JS_FreeValue(ctx, op1); - return -1; - } - r = JS_GetBigInt(res); - a = JS_ToBigIntFree(ctx, &a_s, op1); // infallible, always a bigint - ret = 0; - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); - break; - case OP_plus: - ret = bf_set(r, a); - break; - case OP_neg: - ret = bf_set(r, a); - bf_neg(r); - break; - case OP_not: - ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ); - bf_neg(r); - break; - default: - abort(); - } - JS_FreeBigInt(ctx, a, &a_s); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - res = JS_CompactBigInt(ctx, res); - *pres = res; - return 0; -} - -static no_inline __exception int js_unary_arith_slow(JSContext *ctx, - JSValue *sp, - OPCodeEnum op) -{ - JSValue op1; - int v; - uint32_t tag; - - op1 = sp[-1]; - /* fast path for float64 */ - if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) - goto handle_float64; - - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) - goto exception; - tag = JS_VALUE_GET_TAG(op1); - switch(tag) { - case JS_TAG_INT: - { - int64_t v64; - v64 = JS_VALUE_GET_INT(op1); - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - v64 += v; - break; - case OP_plus: - break; - case OP_neg: - if (v64 == 0) { - sp[-1] = js_float64(-0.0); - return 0; - } else { - v64 = -v64; - } - break; - default: - abort(); - } - sp[-1] = js_int64(v64); - } - break; - case JS_TAG_BIG_INT: - if (js_unary_arith_bigint(ctx, sp - 1, op, op1)) - goto exception; - break; - default: - handle_float64: - { - double d = JS_VALUE_GET_FLOAT64(op1); - switch(op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - d += v; - break; - case OP_plus: - break; - case OP_neg: - d = -d; - break; - default: - abort(); - } - sp[-1] = js_float64(d); - } - break; - } - return 0; - exception: - sp[-1] = JS_UNDEFINED; - return -1; -} - -static __exception int js_post_inc_slow(JSContext *ctx, - JSValue *sp, OPCodeEnum op) -{ - JSValue op1; - - /* XXX: allow custom operators */ - op1 = sp[-1]; - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - sp[-1] = JS_UNDEFINED; - return -1; - } - sp[-1] = op1; - sp[0] = js_dup(op1); - return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); -} - -static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1; - - op1 = JS_ToNumericFree(ctx, sp[-1]); - if (JS_IsException(op1)) - goto exception; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { - if (js_unary_arith_bigint(ctx, sp - 1, OP_not, op1)) - goto exception; - } else { - int32_t v1; - if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) - goto exception; - sp[-1] = js_int32(~v1); - } - return 0; - exception: - sp[-1] = JS_UNDEFINED; - return -1; -} - -static int js_binary_arith_bigint(JSContext *ctx, OPCodeEnum op, - JSValue *pres, JSValue op1, JSValue op2) -{ - bf_t a_s, b_s, *r, *a, *b; - int ret; - JSValue res; - - a = JS_ToBigIntFree(ctx, &a_s, op1); - if (!a) { - JS_FreeValue(ctx, op2); - return -1; - } - b = JS_ToBigIntFree(ctx, &b_s, op2); - if (!b) { - JS_FreeBigInt(ctx, a, &a_s); - return -1; - } - res = JS_NewBigInt(ctx); - if (JS_IsException(res)) { - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - return -1; - } - r = JS_GetBigInt(res); - ret = 0; - switch(op) { - case OP_add: - ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_sub: - ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_mul: - ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ); - break; - case OP_div: - { - bf_t rem_s, *rem = &rem_s; - bf_init(ctx->bf_ctx, rem); - ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); - bf_delete(rem); - } - break; - case OP_mod: - ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, - BF_RNDZ) & BF_ST_INVALID_OP; - break; - case OP_pow: - if (b->sign) { - ret = BF_ST_INVALID_OP; - } else { - ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUIRKS); - } - break; - - /* logical operations */ - case OP_shl: - case OP_sar: - { - slimb_t v2; -#if LIMB_BITS == 32 - bf_get_int32(&v2, b, 0); - if (v2 == INT32_MIN) - v2 = INT32_MIN + 1; -#else - bf_get_int64(&v2, b, 0); - if (v2 == INT64_MIN) - v2 = INT64_MIN + 1; -#endif - if (op == OP_sar) - v2 = -v2; - ret = bf_set(r, a); - ret |= bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ); - if (v2 < 0) { - ret |= bf_rint(r, BF_RNDD) & (BF_ST_OVERFLOW | BF_ST_MEM_ERROR); - } - } - break; - case OP_and: - ret = bf_logic_and(r, a, b); - break; - case OP_or: - ret = bf_logic_or(r, a, b); - break; - case OP_xor: - ret = bf_logic_xor(r, a, b); - break; - default: - abort(); - } - JS_FreeBigInt(ctx, a, &a_s); - JS_FreeBigInt(ctx, b, &b_s); - if (unlikely(ret)) { - JS_FreeValue(ctx, res); - throw_bf_exception(ctx, ret); - return -1; - } - *pres = JS_CompactBigInt(ctx, res); - return 0; -} - -static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - double d1, d2; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - /* fast path for float operations */ - if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - d2 = JS_VALUE_GET_FLOAT64(op2); - goto handle_float64; - } - - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - int32_t v1, v2; - int64_t v; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - switch(op) { - case OP_sub: - v = (int64_t)v1 - (int64_t)v2; - break; - case OP_mul: - v = (int64_t)v1 * (int64_t)v2; - if (v == 0 && (v1 | v2) < 0) { - sp[-2] = js_float64(-0.0); - return 0; - } - break; - case OP_div: - sp[-2] = js_float64((double)v1 / (double)v2); - return 0; - case OP_mod: - if (v1 < 0 || v2 <= 0) { - sp[-2] = js_number(fmod(v1, v2)); - return 0; - } else { - v = (int64_t)v1 % (int64_t)v2; - } - break; - case OP_pow: - sp[-2] = js_number(js_math_pow(v1, v2)); - return 0; - default: - abort(); - } - sp[-2] = js_int64(v); - } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - if (js_binary_arith_bigint(ctx, op, sp - 2, op1, op2)) - goto exception; - } else { - double dr; - /* float64 result */ - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - handle_float64: - switch(op) { - case OP_sub: - dr = d1 - d2; - break; - case OP_mul: - dr = d1 * d2; - break; - case OP_div: - dr = d1 / d2; - break; - case OP_mod: - dr = fmod(d1, d2); - break; - case OP_pow: - dr = js_math_pow(d1, d2); - break; - default: - abort(); - } - sp[-2] = js_float64(dr); - } - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - - op1 = sp[-2]; - op2 = sp[-1]; - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - /* fast path for float64 */ - if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { - double d1, d2; - d1 = JS_VALUE_GET_FLOAT64(op1); - d2 = JS_VALUE_GET_FLOAT64(op2); - sp[-2] = js_float64(d1 + d2); - return 0; - } - - if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - } - - if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { - sp[-2] = JS_ConcatString(ctx, op1, op2); - if (JS_IsException(sp[-2])) - goto exception; - return 0; - } - - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - int32_t v1, v2; - int64_t v; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - v = (int64_t)v1 + (int64_t)v2; - sp[-2] = js_int64(v); - } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - if (js_binary_arith_bigint(ctx, OP_add, sp - 2, op1, op2)) - goto exception; - } else { - double d1, d2; - /* float64 result */ - if (JS_ToFloat64Free(ctx, &d1, op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (JS_ToFloat64Free(ctx, &d2, op2)) - goto exception; - sp[-2] = js_float64(d1 + d2); - } - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline __exception int js_binary_logic_slow(JSContext *ctx, - JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - uint32_t tag1, tag2; - uint32_t v1, v2, r; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - - tag1 = JS_VALUE_GET_TAG(op1); - tag2 = JS_VALUE_GET_TAG(op2); - if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - if (tag1 != tag2) { - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - JS_ThrowTypeError(ctx, "both operands must be BigInt"); - goto exception; - } else if (js_binary_arith_bigint(ctx, op, sp - 2, op1, op2)) { - goto exception; - } - } else { - if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { - JS_FreeValue(ctx, op2); - goto exception; - } - if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) - goto exception; - switch(op) { - case OP_shl: - r = v1 << (v2 & 0x1f); - break; - case OP_sar: - r = (int)v1 >> (v2 & 0x1f); - break; - case OP_and: - r = v1 & v2; - break; - case OP_or: - r = v1 | v2; - break; - case OP_xor: - r = v1 ^ v2; - break; - default: - abort(); - } - sp[-2] = js_int32(r); - } - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, - JSValue op1, JSValue op2) -{ - bf_t a_s, b_s, *a, *b; - int res; - - a = JS_ToBigInt1(ctx, &a_s, op1); - if (!a) { - JS_FreeValue(ctx, op2); - return -1; - } - b = JS_ToBigInt1(ctx, &b_s, op2); - if (!b) { - if (a == &a_s) - bf_delete(a); - JS_FreeValue(ctx, op1); - return -1; - } - switch(op) { - case OP_lt: - res = bf_cmp_lt(a, b); /* if NaN return false */ - break; - case OP_lte: - res = bf_cmp_le(a, b); /* if NaN return false */ - break; - case OP_gt: - res = bf_cmp_lt(b, a); /* if NaN return false */ - break; - case OP_gte: - res = bf_cmp_le(b, a); /* if NaN return false */ - break; - case OP_eq: - res = bf_cmp_eq(a, b); /* if NaN return false */ - break; - default: - abort(); - } - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - return res; -} - -static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, - OPCodeEnum op) -{ - JSValue op1, op2; - int res; - uint32_t tag1, tag2; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { - JSString *p1, *p2; - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = js_string_compare(p1, p2); - switch(op) { - case OP_lt: - res = (res < 0); - break; - case OP_lte: - res = (res <= 0); - break; - case OP_gt: - res = (res > 0); - break; - default: - case OP_gte: - res = (res >= 0); - break; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && - (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { - /* fast path for float64/int */ - goto float64_compare; - } else { - if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) || - (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING))) { - if (tag1 == JS_TAG_STRING) { - op1 = JS_StringToBigInt(ctx, op1); - if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) - goto invalid_bigint_string; - } - if (tag2 == JS_TAG_STRING) { - op2 = JS_StringToBigInt(ctx, op2); - if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { - invalid_bigint_string: - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - res = false; - goto done; - } - } - } else { - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - } - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - - if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { - res = js_compare_bigint(ctx, op, op1, op2); - if (res < 0) - goto exception; - } else { - double d1, d2; - - float64_compare: - /* can use floating point comparison */ - if (tag1 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - } else { - d1 = JS_VALUE_GET_INT(op1); - } - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else { - d2 = JS_VALUE_GET_INT(op2); - } - switch(op) { - case OP_lt: - res = (d1 < d2); /* if NaN return false */ - break; - case OP_lte: - res = (d1 <= d2); /* if NaN return false */ - break; - case OP_gt: - res = (d1 > d2); /* if NaN return false */ - break; - default: - case OP_gte: - res = (d1 >= d2); /* if NaN return false */ - break; - } - } - } - done: - sp[-2] = js_bool(res); - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static bool tag_is_number(uint32_t tag) -{ - return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT || - tag == JS_TAG_FLOAT64); -} - -static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, - bool is_neq) -{ - JSValue op1, op2; - int res; - uint32_t tag1, tag2; - - op1 = sp[-2]; - op2 = sp[-1]; - redo: - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - if (tag_is_number(tag1) && tag_is_number(tag2)) { - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); - } else if ((tag1 == JS_TAG_FLOAT64 && - (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || - (tag2 == JS_TAG_FLOAT64 && - (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { - double d1, d2; - if (tag1 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64(op1); - } else { - d1 = JS_VALUE_GET_INT(op1); - } - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else { - d2 = JS_VALUE_GET_INT(op2); - } - res = (d1 == d2); - } else { - res = js_compare_bigint(ctx, OP_eq, op1, op2); - if (res < 0) - goto exception; - } - } else if (tag1 == tag2) { - res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); - } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || - (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { - res = true; - } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || - (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { - - if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT)) { - if (tag1 == JS_TAG_STRING) { - op1 = JS_StringToBigInt(ctx, op1); - if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) - goto invalid_bigint_string; - } - if (tag2 == JS_TAG_STRING) { - op2 = JS_StringToBigInt(ctx, op2); - if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { - invalid_bigint_string: - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - res = false; - goto done; - } - } - } else { - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - } - res = js_strict_eq(ctx, op1, op2); - } else if (tag1 == JS_TAG_BOOL) { - op1 = js_int32(JS_VALUE_GET_INT(op1)); - goto redo; - } else if (tag2 == JS_TAG_BOOL) { - op2 = js_int32(JS_VALUE_GET_INT(op2)); - goto redo; - } else if ((tag1 == JS_TAG_OBJECT && - (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || - (tag2 == JS_TAG_OBJECT && - (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - goto redo; - } else { - /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ - if ((JS_IsHTMLDDA(ctx, op1) && - (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || - (JS_IsHTMLDDA(ctx, op2) && - (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { - res = true; - } else { - res = false; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - } - done: - sp[-2] = js_bool(res ^ is_neq); - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - uint32_t v1, v2, r; - - op1 = sp[-2]; - op2 = sp[-1]; - op1 = JS_ToNumericFree(ctx, op1); - if (JS_IsException(op1)) { - JS_FreeValue(ctx, op2); - goto exception; - } - op2 = JS_ToNumericFree(ctx, op2); - if (JS_IsException(op2)) { - JS_FreeValue(ctx, op1); - goto exception; - } - - if ((JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || - JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) { - JS_ThrowTypeError(ctx, "BigInt operands are forbidden for >>>"); - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - goto exception; - } - /* cannot give an exception */ - JS_ToUint32Free(ctx, &v1, op1); - JS_ToUint32Free(ctx, &v2, op2); - r = v1 >> (v2 & 0x1f); - sp[-2] = js_uint32(r); - return 0; - exception: - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -/* XXX: Should take JSValue arguments */ -static bool js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, - JSStrictEqModeEnum eq_mode) -{ - bool res; - int tag1, tag2; - double d1, d2; - - tag1 = JS_VALUE_GET_NORM_TAG(op1); - tag2 = JS_VALUE_GET_NORM_TAG(op2); - switch(tag1) { - case JS_TAG_BOOL: - if (tag1 != tag2) { - res = false; - } else { - res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); - goto done_no_free; - } - break; - case JS_TAG_NULL: - case JS_TAG_UNDEFINED: - res = (tag1 == tag2); - break; - case JS_TAG_STRING: - { - JSString *p1, *p2; - if (tag1 != tag2) { - res = false; - } else { - p1 = JS_VALUE_GET_STRING(op1); - p2 = JS_VALUE_GET_STRING(op2); - res = js_string_eq(p1, p2); - } - } - break; - case JS_TAG_SYMBOL: - { - JSAtomStruct *p1, *p2; - if (tag1 != tag2) { - res = false; - } else { - p1 = JS_VALUE_GET_PTR(op1); - p2 = JS_VALUE_GET_PTR(op2); - res = (p1 == p2); - } - } - break; - case JS_TAG_OBJECT: - if (tag1 != tag2) - res = false; - else - res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); - break; - case JS_TAG_INT: - d1 = JS_VALUE_GET_INT(op1); - if (tag2 == JS_TAG_INT) { - d2 = JS_VALUE_GET_INT(op2); - goto number_test; - } else if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - goto number_test; - } else { - res = false; - } - break; - case JS_TAG_FLOAT64: - d1 = JS_VALUE_GET_FLOAT64(op1); - if (tag2 == JS_TAG_FLOAT64) { - d2 = JS_VALUE_GET_FLOAT64(op2); - } else if (tag2 == JS_TAG_INT) { - d2 = JS_VALUE_GET_INT(op2); - } else { - res = false; - break; - } - number_test: - if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { - JSFloat64Union u1, u2; - /* NaN is not always normalized, so this test is necessary */ - if (isnan(d1) || isnan(d2)) { - res = isnan(d1) == isnan(d2); - } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { - res = (d1 == d2); /* +0 == -0 */ - } else { - u1.d = d1; - u2.d = d2; - res = (u1.u64 == u2.u64); /* +0 != -0 */ - } - } else { - res = (d1 == d2); /* if NaN return false and +0 == -0 */ - } - goto done_no_free; - case JS_TAG_BIG_INT: - { - bf_t a_s, *a, b_s, *b; - if (tag1 != tag2) { - res = false; - break; - } - a = JS_ToBigInt1(ctx, &a_s, op1); - b = JS_ToBigInt1(ctx, &b_s, op2); - res = bf_cmp_eq(a, b); - if (a == &a_s) - bf_delete(a); - if (b == &b_s) - bf_delete(b); - } - break; - default: - res = false; - break; - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - done_no_free: - return res; -} - -static bool js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) -{ - return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); -} - -static bool js_same_value(JSContext *ctx, JSValue op1, JSValue op2) -{ - return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE); -} - -static bool js_same_value_zero(JSContext *ctx, JSValue op1, JSValue op2) -{ - return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE_ZERO); -} - -static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, - bool is_neq) -{ - bool res; - res = js_strict_eq(ctx, sp[-2], sp[-1]); - sp[-2] = js_bool(res ^ is_neq); - return 0; -} - -static __exception int js_operator_in(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - JSAtom atom; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - - if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { - JS_ThrowTypeError(ctx, "invalid 'in' operand"); - return -1; - } - atom = JS_ValueToAtom(ctx, op1); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - ret = JS_HasProperty(ctx, op2, atom); - JS_FreeAtom(ctx, atom); - if (ret < 0) - return -1; - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - int ret; - op1 = sp[-2]; /* object */ - op2 = sp[-1]; /* field name or method function */ - if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { - JS_ThrowTypeError(ctx, "invalid 'in' operand"); - return -1; - } - if (JS_IsObject(op2)) { - /* method: use the brand */ - ret = JS_CheckBrand(ctx, op1, op2); - if (ret < 0) - return -1; - } else { - JSAtom atom; - JSObject *p; - JSShapeProperty *prs; - JSProperty *pr; - /* field */ - atom = JS_ValueToAtom(ctx, op2); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - p = JS_VALUE_GET_OBJ(op1); - prs = find_own_property(&pr, p, atom); - JS_FreeAtom(ctx, atom); - ret = (prs != NULL); - } - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = JS_NewBool(ctx, ret); - return 0; -} - -static __exception int js_has_unscopable(JSContext *ctx, JSValue obj, - JSAtom atom) -{ - JSValue arr, val; - int ret; - - arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); - if (JS_IsException(arr)) - return -1; - ret = 0; - if (JS_IsObject(arr)) { - val = JS_GetProperty(ctx, arr, atom); - ret = JS_ToBoolFree(ctx, val); - } - JS_FreeValue(ctx, arr); - return ret; -} - -static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - ret = JS_IsInstanceOf(ctx, op1, op2); - if (ret < 0) - return ret; - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) -{ - JSAtom atom; - uint32_t tag; - - tag = JS_VALUE_GET_NORM_TAG(op1); - switch(tag) { - case JS_TAG_BIG_INT: - atom = JS_ATOM_bigint; - break; - case JS_TAG_INT: - case JS_TAG_FLOAT64: - atom = JS_ATOM_number; - break; - case JS_TAG_UNDEFINED: - atom = JS_ATOM_undefined; - break; - case JS_TAG_BOOL: - atom = JS_ATOM_boolean; - break; - case JS_TAG_STRING: - atom = JS_ATOM_string; - break; - case JS_TAG_OBJECT: - { - JSObject *p; - p = JS_VALUE_GET_OBJ(op1); - if (unlikely(p->is_HTMLDDA)) - atom = JS_ATOM_undefined; - else if (JS_IsFunction(ctx, op1)) - atom = JS_ATOM_function; - else - goto obj_type; - } - break; - case JS_TAG_NULL: - obj_type: - atom = JS_ATOM_object; - break; - case JS_TAG_SYMBOL: - atom = JS_ATOM_symbol; - break; - default: - atom = JS_ATOM_unknown; - break; - } - return atom; -} - -static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) -{ - JSValue op1, op2; - JSAtom atom; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - atom = JS_ValueToAtom(ctx, op2); - if (unlikely(atom == JS_ATOM_NULL)) - return -1; - ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); - JS_FreeAtom(ctx, atom); - if (unlikely(ret < 0)) - return -1; - JS_FreeValue(ctx, op1); - JS_FreeValue(ctx, op2); - sp[-2] = js_bool(ret); - return 0; -} - -static JSValue js_throw_type_error(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - return JS_ThrowTypeError(ctx, "invalid property access"); -} - -/* XXX: not 100% compatible, but mozilla seems to use a similar - implementation to ensure that caller in non strict mode does not - throw (ES5 compatibility) */ -static JSValue js_function_proto_caller(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (!b || b->is_strict_mode || !b->has_prototype) { - return js_throw_type_error(ctx, this_val, 0, NULL); - } - return JS_UNDEFINED; -} - -static JSValue js_function_proto_fileName(JSContext *ctx, - JSValue this_val) -{ - JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (b) { - return JS_AtomToString(ctx, b->filename); - } - return JS_UNDEFINED; -} - -static JSValue js_function_proto_int32(JSContext *ctx, - JSValue this_val, - int magic) -{ - JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); - if (b) { - int *field = (int *) ((char *)b + magic); - return js_int32(*field); - } - return JS_UNDEFINED; -} - -static int js_arguments_define_own_property(JSContext *ctx, - JSValue this_obj, - JSAtom prop, JSValue val, - JSValue getter, JSValue setter, int flags) -{ - JSObject *p; - uint32_t idx; - p = JS_VALUE_GET_OBJ(this_obj); - /* convert to normal array when redefining an existing numeric field */ - if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && - idx < p->u.array.count) { - if (convert_fast_array_to_array(ctx, p)) - return -1; - } - /* run the default define own property */ - return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, - flags | JS_PROP_NO_EXOTIC); -} - -static const JSClassExoticMethods js_arguments_exotic_methods = { - .define_own_property = js_arguments_define_own_property, -}; - -static JSValue js_build_arguments(JSContext *ctx, int argc, JSValue *argv) -{ - JSValue val, *tab; - JSProperty *pr; - JSObject *p; - int i; - - val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], - JS_CLASS_ARGUMENTS); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_OBJ(val); - - /* add the length field (cannot fail) */ - pr = add_property(ctx, p, JS_ATOM_length, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (!pr) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - pr->u.value = js_int32(argc); - - /* initialize the fast array part */ - tab = NULL; - if (argc > 0) { - tab = js_malloc(ctx, sizeof(tab[0]) * argc); - if (!tab) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - for(i = 0; i < argc; i++) { - tab[i] = js_dup(argv[i]); - } - } - p->u.array.u.values = tab; - p->u.array.count = argc; - - JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, - js_dup(ctx->array_proto_values), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - /* add callee property to throw a TypeError in strict mode */ - JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED, - ctx->throw_type_error, ctx->throw_type_error, - JS_PROP_HAS_GET | JS_PROP_HAS_SET); - return val; -} - -#define GLOBAL_VAR_OFFSET 0x40000000 -#define ARGUMENT_VAR_OFFSET 0x20000000 - -/* legacy arguments object: add references to the function arguments */ -static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, - JSValue *argv, - JSStackFrame *sf, int arg_count) -{ - JSValue val; - JSProperty *pr; - JSObject *p; - int i; - - val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], - JS_CLASS_MAPPED_ARGUMENTS); - if (JS_IsException(val)) - return val; - p = JS_VALUE_GET_OBJ(val); - - /* add the length field (cannot fail) */ - pr = add_property(ctx, p, JS_ATOM_length, - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (!pr) - goto fail; - pr->u.value = js_int32(argc); - - for(i = 0; i < arg_count; i++) { - JSVarRef *var_ref; - var_ref = get_var_ref(ctx, sf, i, true); - if (!var_ref) - goto fail; - pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); - if (!pr) { - free_var_ref(ctx->rt, var_ref); - goto fail; - } - pr->u.var_ref = var_ref; - } - - /* the arguments not mapped to the arguments of the function can - be normal properties */ - for(i = arg_count; i < argc; i++) { - if (JS_DefinePropertyValueUint32(ctx, val, i, - js_dup(argv[i]), - JS_PROP_C_W_E) < 0) - goto fail; - } - - JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, - js_dup(ctx->array_proto_values), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - /* callee returns this function in non strict mode */ - JS_DefinePropertyValue(ctx, val, JS_ATOM_callee, - js_dup(ctx->rt->current_stack_frame->cur_func), - JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); - return val; - fail: - JS_FreeValue(ctx, val); - return JS_EXCEPTION; -} - -static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValue *argv) -{ - JSValue val; - int i, ret; - - val = JS_NewArray(ctx); - if (JS_IsException(val)) - return val; - for (i = first; i < argc; i++) { - ret = JS_DefinePropertyValueUint32(ctx, val, i - first, - js_dup(argv[i]), - JS_PROP_C_W_E); - if (ret < 0) { - JS_FreeValue(ctx, val); - return JS_EXCEPTION; - } - } - return val; -} - -static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) -{ - JSObject *p; - JSPropertyEnum *tab_atom; - int i; - JSValue enum_obj, obj1; - JSForInIterator *it; - uint32_t tag, tab_atom_count; - - tag = JS_VALUE_GET_TAG(obj); - if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { - obj = JS_ToObjectFree(ctx, obj); - } - - it = js_malloc(ctx, sizeof(*it)); - if (!it) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); - if (JS_IsException(enum_obj)) { - js_free(ctx, it); - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - it->is_array = false; - it->obj = obj; - it->idx = 0; - p = JS_VALUE_GET_OBJ(enum_obj); - p->u.for_in_iterator = it; - - if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) - return enum_obj; - - /* fast path: assume no enumerable properties in the prototype chain */ - obj1 = js_dup(obj); - for(;;) { - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - if (JS_IsException(obj1)) - goto fail; - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, - JS_VALUE_GET_OBJ(obj1), - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - if (tab_atom_count != 0) { - JS_FreeValue(ctx, obj1); - goto slow_path; - } - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - } - - p = JS_VALUE_GET_OBJ(obj); - - if (p->fast_array) { - JSShape *sh; - JSShapeProperty *prs; - /* check that there are no enumerable normal fields */ - sh = p->shape; - for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { - if (prs->flags & JS_PROP_ENUMERABLE) - goto normal_case; - } - /* for fast arrays, we only store the number of elements */ - it->is_array = true; - it->array_length = p->u.array.count; - } else { - normal_case: - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) - goto fail; - for(i = 0; i < tab_atom_count; i++) { - JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0); - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - } - return enum_obj; - - slow_path: - /* non enumerable properties hide the enumerables ones in the - prototype chain */ - obj1 = js_dup(obj); - for(;;) { - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, - JS_VALUE_GET_OBJ(obj1), - JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - for(i = 0; i < tab_atom_count; i++) { - JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, - (tab_atom[i].is_enumerable ? - JS_PROP_ENUMERABLE : 0)); - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - obj1 = JS_GetPrototypeFree(ctx, obj1); - if (JS_IsNull(obj1)) - break; - if (JS_IsException(obj1)) - goto fail; - /* must check for timeout to avoid infinite loop */ - if (js_poll_interrupts(ctx)) { - JS_FreeValue(ctx, obj1); - goto fail; - } - } - return enum_obj; - - fail: - JS_FreeValue(ctx, enum_obj); - return JS_EXCEPTION; -} - -/* obj -> enum_obj */ -static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) -{ - sp[-1] = build_for_in_iterator(ctx, sp[-1]); - if (JS_IsException(sp[-1])) - return -1; - return 0; -} - -/* enum_obj -> enum_obj value done */ -static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) -{ - JSValue enum_obj; - JSObject *p; - JSAtom prop; - JSForInIterator *it; - int ret; - - enum_obj = sp[-1]; - /* fail safe */ - if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) - goto done; - p = JS_VALUE_GET_OBJ(enum_obj); - if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) - goto done; - it = p->u.for_in_iterator; - - for(;;) { - if (it->is_array) { - if (it->idx >= it->array_length) - goto done; - prop = __JS_AtomFromUInt32(it->idx); - it->idx++; - } else { - JSShape *sh = p->shape; - JSShapeProperty *prs; - if (it->idx >= sh->prop_count) - goto done; - prs = get_shape_prop(sh) + it->idx; - prop = prs->atom; - it->idx++; - if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) - continue; - } - // check if the property was deleted unless we're dealing with a proxy - JSValue obj = it->obj; - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_PROXY) - break; - } - ret = JS_HasProperty(ctx, obj, prop); - if (ret < 0) - return ret; - if (ret) - break; - } - /* return the property */ - sp[0] = JS_AtomToValue(ctx, prop); - sp[1] = JS_FALSE; - return 0; - done: - /* return the end */ - sp[0] = JS_UNDEFINED; - sp[1] = JS_TRUE; - return 0; -} - -static JSValue JS_GetIterator2(JSContext *ctx, JSValue obj, - JSValue method) -{ - JSValue enum_obj; - - enum_obj = JS_Call(ctx, method, obj, 0, NULL); - if (JS_IsException(enum_obj)) - return enum_obj; - if (!JS_IsObject(enum_obj)) { - JS_FreeValue(ctx, enum_obj); - return JS_ThrowTypeErrorNotAnObject(ctx); - } - return enum_obj; -} - -static JSValue JS_GetIterator(JSContext *ctx, JSValue obj, bool is_async) -{ - JSValue method, ret, sync_iter; - - if (is_async) { - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); - if (JS_IsException(method)) - return method; - if (JS_IsUndefined(method) || JS_IsNull(method)) { - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); - if (JS_IsException(method)) - return method; - sync_iter = JS_GetIterator2(ctx, obj, method); - JS_FreeValue(ctx, method); - if (JS_IsException(sync_iter)) - return sync_iter; - ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); - JS_FreeValue(ctx, sync_iter); - return ret; - } - } else { - method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); - if (JS_IsException(method)) - return method; - } - if (!JS_IsFunction(ctx, method)) { - JS_FreeValue(ctx, method); - return JS_ThrowTypeError(ctx, "value is not iterable"); - } - ret = JS_GetIterator2(ctx, obj, method); - JS_FreeValue(ctx, method); - return ret; -} - -/* return *pdone = 2 if the iterator object is not parsed */ -static JSValue JS_IteratorNext2(JSContext *ctx, JSValue enum_obj, - JSValue method, - int argc, JSValue *argv, int *pdone) -{ - JSValue obj; - - /* fast path for the built-in iterators (avoid creating the - intermediate result object) */ - if (JS_IsObject(method)) { - JSObject *p = JS_VALUE_GET_OBJ(method); - if (p->class_id == JS_CLASS_C_FUNCTION && - p->u.cfunc.cproto == JS_CFUNC_iterator_next) { - JSCFunctionType func; - JSValue args[1]; - - /* in case the function expects one argument */ - if (argc == 0) { - args[0] = JS_UNDEFINED; - argv = args; - } - func = p->u.cfunc.c_function; - return func.iterator_next(ctx, enum_obj, argc, argv, - pdone, p->u.cfunc.magic); - } - } - obj = JS_Call(ctx, method, enum_obj, argc, argv); - if (JS_IsException(obj)) - goto fail; - if (!JS_IsObject(obj)) { - JS_FreeValue(ctx, obj); - JS_ThrowTypeError(ctx, "iterator must return an object"); - goto fail; - } - *pdone = 2; - return obj; - fail: - *pdone = false; - return JS_EXCEPTION; -} - -static JSValue JS_IteratorNext(JSContext *ctx, JSValue enum_obj, - JSValue method, - int argc, JSValue *argv, int *pdone) -{ - JSValue obj, value, done_val; - int done; - - obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); - if (JS_IsException(obj)) - goto fail; - if (done != 2) { - *pdone = done; - return obj; - } else { - done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); - if (JS_IsException(done_val)) - goto fail; - *pdone = JS_ToBoolFree(ctx, done_val); - value = JS_UNDEFINED; - if (!*pdone) { - value = JS_GetProperty(ctx, obj, JS_ATOM_value); - } - JS_FreeValue(ctx, obj); - return value; - } - fail: - JS_FreeValue(ctx, obj); - *pdone = false; - return JS_EXCEPTION; -} - -/* return < 0 in case of exception */ -static int JS_IteratorClose(JSContext *ctx, JSValue enum_obj, - bool is_exception_pending) -{ - JSValue method, ret, ex_obj; - int res; - - if (is_exception_pending) { - ex_obj = ctx->rt->current_exception; - ctx->rt->current_exception = JS_UNINITIALIZED; - res = -1; - } else { - ex_obj = JS_UNDEFINED; - res = 0; - } - method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); - if (JS_IsException(method)) { - res = -1; - goto done; - } - if (JS_IsUndefined(method) || JS_IsNull(method)) { - goto done; - } - ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); - if (!is_exception_pending) { - if (JS_IsException(ret)) { - res = -1; - } else if (!JS_IsObject(ret)) { - JS_ThrowTypeErrorNotAnObject(ctx); - res = -1; - } - } - JS_FreeValue(ctx, ret); - done: - if (is_exception_pending) { - JS_Throw(ctx, ex_obj); - } - return res; -} - -/* obj -> enum_rec (3 slots) */ -static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, - bool is_async) -{ - JSValue op1, obj, method; - op1 = sp[-1]; - obj = JS_GetIterator(ctx, op1, is_async); - if (JS_IsException(obj)) - return -1; - JS_FreeValue(ctx, op1); - sp[-1] = obj; - method = JS_GetProperty(ctx, obj, JS_ATOM_next); - if (JS_IsException(method)) - return -1; - sp[0] = method; - return 0; -} - -/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' - objs. If 'done' is true or in case of exception, 'enum_rec' is set - to undefined. If 'done' is true, 'value' is always set to - undefined. */ -static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) -{ - JSValue value = JS_UNDEFINED; - int done = 1; - - if (likely(!JS_IsUndefined(sp[offset]))) { - value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); - if (JS_IsException(value)) - done = -1; - if (done) { - /* value is JS_UNDEFINED or JS_EXCEPTION */ - /* replace the iteration object with undefined */ - JS_FreeValue(ctx, sp[offset]); - sp[offset] = JS_UNDEFINED; - if (done < 0) { - return -1; - } else { - JS_FreeValue(ctx, value); - value = JS_UNDEFINED; - } - } - } - sp[0] = value; - sp[1] = js_bool(done); - return 0; -} - -static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValue obj, - int *pdone) -{ - JSValue done_val, value; - int done; - done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); - if (JS_IsException(done_val)) - goto fail; - done = JS_ToBoolFree(ctx, done_val); - value = JS_GetProperty(ctx, obj, JS_ATOM_value); - if (JS_IsException(value)) - goto fail; - *pdone = done; - return value; - fail: - *pdone = false; - return JS_EXCEPTION; -} - -static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) -{ - JSValue obj, value; - int done; - obj = sp[-1]; - if (!JS_IsObject(obj)) { - JS_ThrowTypeError(ctx, "iterator must return an object"); - return -1; - } - value = JS_IteratorGetCompleteValue(ctx, obj, &done); - if (JS_IsException(value)) - return -1; - JS_FreeValue(ctx, obj); - sp[-1] = value; - sp[0] = js_bool(done); - return 0; -} - -static JSValue js_create_iterator_result(JSContext *ctx, - JSValue val, - bool done) -{ - JSValue obj; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, val); - return obj; - } - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, - val, JS_PROP_C_W_E) < 0) { - goto fail; - } - if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, - js_bool(done), JS_PROP_C_W_E) < 0) { - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - return obj; -} - -static JSValue js_array_iterator_next(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, - int *pdone, int magic); - -static JSValue js_create_array_iterator(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, int magic); - -static bool js_is_fast_array(JSContext *ctx, JSValue obj) -{ - /* Try and handle fast arrays explicitly */ - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { - return true; - } - } - return false; -} - -/* Access an Array's internal JSValue array if available */ -static bool js_get_fast_array(JSContext *ctx, JSValue obj, - JSValue **arrpp, uint32_t *countp) -{ - /* Try and handle fast arrays explicitly */ - if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { - JSObject *p = JS_VALUE_GET_OBJ(obj); - if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { - *countp = p->u.array.count; - *arrpp = p->u.array.u.values; - return true; - } - } - return false; -} - -static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) -{ - JSValue iterator, enumobj, method, value; - int is_array_iterator; - JSValue *arrp; - uint32_t i, count32, pos; - - if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { - JS_ThrowInternalError(ctx, "invalid index for append"); - return -1; - } - - pos = JS_VALUE_GET_INT(sp[-2]); - - /* XXX: further optimisations: - - use ctx->array_proto_values? - - check if array_iterator_prototype next method is built-in and - avoid constructing actual iterator object? - - build this into js_for_of_start and use in all `for (x of o)` loops - */ - iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); - if (JS_IsException(iterator)) - return -1; - /* Used to squelch a -Wcast-function-type warning. */ - JSCFunctionType ft = { .generic_magic = js_create_array_iterator }; - is_array_iterator = JS_IsCFunction(ctx, iterator, - ft.generic, - JS_ITERATOR_KIND_VALUE); - JS_FreeValue(ctx, iterator); - - enumobj = JS_GetIterator(ctx, sp[-1], false); - if (JS_IsException(enumobj)) - return -1; - method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); - if (JS_IsException(method)) { - JS_FreeValue(ctx, enumobj); - return -1; - } - /* Used to squelch a -Wcast-function-type warning. */ - JSCFunctionType ft2 = { .iterator_next = js_array_iterator_next }; - if (is_array_iterator - && JS_IsCFunction(ctx, method, ft2.generic, 0) - && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { - uint32_t len; - if (js_get_length32(ctx, &len, sp[-1])) - goto exception; - /* if len > count32, the elements >= count32 might be read in - the prototypes and might have side effects */ - if (len != count32) - goto general_case; - /* Handle fast arrays explicitly */ - for (i = 0; i < count32; i++) { - if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, - js_dup(arrp[i]), JS_PROP_C_W_E) < 0) - goto exception; - } - } else { - general_case: - for (;;) { - int done; - value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); - if (JS_IsException(value)) - goto exception; - if (done) { - /* value is JS_UNDEFINED */ - break; - } - if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) - goto exception; - } - } - /* Note: could raise an error if too many elements */ - sp[-2] = js_int32(pos); - JS_FreeValue(ctx, enumobj); - JS_FreeValue(ctx, method); - return 0; - -exception: - JS_IteratorClose(ctx, enumobj, true); - JS_FreeValue(ctx, enumobj); - JS_FreeValue(ctx, method); - return -1; -} - -static __exception int JS_CopyDataProperties(JSContext *ctx, - JSValue target, - JSValue source, - JSValue excluded, - bool setprop) -{ - JSPropertyEnum *tab_atom; - JSValue val; - uint32_t i, tab_atom_count; - JSObject *p; - JSObject *pexcl = NULL; - int ret, gpn_flags; - JSPropertyDescriptor desc; - bool is_enumerable; - - if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) - return 0; - - if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) - pexcl = JS_VALUE_GET_OBJ(excluded); - - p = JS_VALUE_GET_OBJ(source); - - gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; - if (p->is_exotic) { - const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; - /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it - introduces a visible change */ - if (em && em->get_own_property_names) { - gpn_flags &= ~JS_GPN_ENUM_ONLY; - } - } - if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, - gpn_flags)) - return -1; - - for (i = 0; i < tab_atom_count; i++) { - if (pexcl) { - ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); - if (ret) { - if (ret < 0) - goto exception; - continue; - } - } - if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { - /* test if the property is enumerable */ - ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); - if (ret < 0) - goto exception; - if (!ret) - continue; - is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; - js_free_desc(ctx, &desc); - if (!is_enumerable) - continue; - } - val = JS_GetProperty(ctx, source, tab_atom[i].atom); - if (JS_IsException(val)) - goto exception; - if (setprop) - ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); - else - ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, - JS_PROP_C_W_E); - if (ret < 0) - goto exception; - } - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - return 0; - exception: - js_free_prop_enum(ctx, tab_atom, tab_atom_count); - return -1; -} - -/* only valid inside C functions */ -static JSValue JS_GetActiveFunction(JSContext *ctx) -{ - return ctx->rt->current_stack_frame->cur_func; -} - -static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, - int var_idx, bool is_arg) -{ - JSVarRef *var_ref; - struct list_head *el; - - list_for_each(el, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) { - var_ref->header.ref_count++; - return var_ref; - } - } - /* create a new one */ - var_ref = js_malloc(ctx, sizeof(JSVarRef)); - if (!var_ref) - return NULL; - var_ref->header.ref_count = 1; - var_ref->is_detached = false; - var_ref->is_arg = is_arg; - var_ref->var_idx = var_idx; - list_add_tail(&var_ref->header.link, &sf->var_ref_list); - if (is_arg) - var_ref->pvalue = &sf->arg_buf[var_idx]; - else - var_ref->pvalue = &sf->var_buf[var_idx]; - var_ref->value = JS_UNDEFINED; - return var_ref; -} - -static JSValue js_closure2(JSContext *ctx, JSValue func_obj, - JSFunctionBytecode *b, - JSVarRef **cur_var_refs, - JSStackFrame *sf) -{ - JSObject *p; - JSVarRef **var_refs; - int i; - - p = JS_VALUE_GET_OBJ(func_obj); - p->u.func.function_bytecode = b; - p->u.func.home_object = NULL; - p->u.func.var_refs = NULL; - if (b->closure_var_count) { - var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); - if (!var_refs) - goto fail; - p->u.func.var_refs = var_refs; - for(i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - JSVarRef *var_ref; - if (cv->is_local) { - /* reuse the existing variable reference if it already exists */ - var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg); - if (!var_ref) - goto fail; - } else { - var_ref = cur_var_refs[cv->var_idx]; - var_ref->header.ref_count++; - } - var_refs[i] = var_ref; - } - } - return func_obj; - fail: - /* bfunc is freed when func_obj is freed */ - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; -} - -static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) -{ - JSValue obj, this_val; - int ret; - - this_val = JS_MKPTR(JS_TAG_OBJECT, p); - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - set_cycle_flag(ctx, obj); - set_cycle_flag(ctx, this_val); - ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, - js_dup(this_val), - JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - if (ret < 0) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - return obj; -} - -static const uint16_t func_kind_to_class_id[] = { - [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, - [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, - [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, - [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, -}; - -static JSValue js_closure(JSContext *ctx, JSValue bfunc, - JSVarRef **cur_var_refs, - JSStackFrame *sf) -{ - JSFunctionBytecode *b; - JSValue func_obj; - JSAtom name_atom; - - b = JS_VALUE_GET_PTR(bfunc); - func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); - if (JS_IsException(func_obj)) { - JS_FreeValue(ctx, bfunc); - return JS_EXCEPTION; - } - func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); - if (JS_IsException(func_obj)) { - /* bfunc has been freed */ - goto fail; - } - name_atom = b->func_name; - if (name_atom == JS_ATOM_NULL) - name_atom = JS_ATOM_empty_string; - js_function_set_properties(ctx, func_obj, name_atom, - b->defined_arg_count); - - if (b->func_kind & JS_FUNC_GENERATOR) { - JSValue proto; - int proto_class_id; - /* generators have a prototype field which is used as - prototype for the generator object */ - if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) - proto_class_id = JS_CLASS_ASYNC_GENERATOR; - else - proto_class_id = JS_CLASS_GENERATOR; - proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); - if (JS_IsException(proto)) - goto fail; - JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, - JS_PROP_WRITABLE); - } else if (b->has_prototype) { - /* add the 'prototype' property: delay instantiation to avoid - creating cycles for every javascript function. The prototype - object is created on the fly when first accessed */ - JS_SetConstructorBit(ctx, func_obj, true); - JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, - JS_AUTOINIT_ID_PROTOTYPE, NULL, - JS_PROP_WRITABLE); - } - return func_obj; - fail: - /* bfunc is freed when func_obj is freed */ - JS_FreeValue(ctx, func_obj); - return JS_EXCEPTION; -} - -#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) - -static int js_op_define_class(JSContext *ctx, JSValue *sp, - JSAtom class_name, int class_flags, - JSVarRef **cur_var_refs, - JSStackFrame *sf, bool is_computed_name) -{ - JSValue bfunc, parent_class, proto = JS_UNDEFINED; - JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; - JSFunctionBytecode *b; - - parent_class = sp[-2]; - bfunc = sp[-1]; - - if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { - if (JS_IsNull(parent_class)) { - parent_proto = JS_NULL; - parent_class = js_dup(ctx->function_proto); - } else { - if (!JS_IsConstructor(ctx, parent_class)) { - JS_ThrowTypeError(ctx, "parent class must be constructor"); - goto fail; - } - parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); - if (JS_IsException(parent_proto)) - goto fail; - if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { - JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); - goto fail; - } - } - } else { - /* parent_class is JS_UNDEFINED in this case */ - parent_proto = js_dup(ctx->class_proto[JS_CLASS_OBJECT]); - parent_class = js_dup(ctx->function_proto); - } - proto = JS_NewObjectProto(ctx, parent_proto); - if (JS_IsException(proto)) - goto fail; - - b = JS_VALUE_GET_PTR(bfunc); - assert(b->func_kind == JS_FUNC_NORMAL); - ctor = JS_NewObjectProtoClass(ctx, parent_class, - JS_CLASS_BYTECODE_FUNCTION); - if (JS_IsException(ctor)) - goto fail; - ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); - bfunc = JS_UNDEFINED; - if (JS_IsException(ctor)) - goto fail; - js_method_set_home_object(ctx, ctor, proto); - JS_SetConstructorBit(ctx, ctor, true); - - JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, - js_int32(b->defined_arg_count), - JS_PROP_CONFIGURABLE); - - if (is_computed_name) { - if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], - JS_PROP_CONFIGURABLE) < 0) - goto fail; - } else { - if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) - goto fail; - } - - /* the constructor property must be first. It can be overriden by - computed property names */ - if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, - js_dup(ctor), - JS_PROP_CONFIGURABLE | - JS_PROP_WRITABLE | JS_PROP_THROW) < 0) - goto fail; - /* set the prototype property */ - if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, - js_dup(proto), JS_PROP_THROW) < 0) - goto fail; - set_cycle_flag(ctx, ctor); - set_cycle_flag(ctx, proto); - - JS_FreeValue(ctx, parent_proto); - JS_FreeValue(ctx, parent_class); - - sp[-2] = ctor; - sp[-1] = proto; - return 0; - fail: - JS_FreeValue(ctx, parent_class); - JS_FreeValue(ctx, parent_proto); - JS_FreeValue(ctx, bfunc); - JS_FreeValue(ctx, proto); - JS_FreeValue(ctx, ctor); - sp[-2] = JS_UNDEFINED; - sp[-1] = JS_UNDEFINED; - return -1; -} - -static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) -{ - struct list_head *el, *el1; - JSVarRef *var_ref; - int var_idx; - - list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - var_idx = var_ref->var_idx; - if (var_ref->is_arg) - var_ref->value = js_dup(sf->arg_buf[var_idx]); - else - var_ref->value = js_dup(sf->var_buf[var_idx]); - var_ref->pvalue = &var_ref->value; - /* the reference is no longer to a local variable */ - var_ref->is_detached = true; - add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); - } -} - -static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int var_idx) -{ - struct list_head *el, *el1; - JSVarRef *var_ref; - - list_for_each_safe(el, el1, &sf->var_ref_list) { - var_ref = list_entry(el, JSVarRef, header.link); - if (var_idx == var_ref->var_idx && !var_ref->is_arg) { - var_ref->value = js_dup(sf->var_buf[var_idx]); - var_ref->pvalue = &var_ref->value; - list_del(&var_ref->header.link); - /* the reference is no longer to a local variable */ - var_ref->is_detached = true; - add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); - } - } -} - -#define JS_CALL_FLAG_COPY_ARGV (1 << 1) -#define JS_CALL_FLAG_GENERATOR (1 << 2) - -static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, int flags) -{ - JSRuntime *rt = ctx->rt; - JSCFunctionType func; - JSObject *p; - JSStackFrame sf_s, *sf = &sf_s, *prev_sf; - JSValue ret_val; - JSValue *arg_buf; - int arg_count, i; - JSCFunctionEnum cproto; - - p = JS_VALUE_GET_OBJ(func_obj); - cproto = p->u.cfunc.cproto; - arg_count = p->u.cfunc.length; - - /* better to always check stack overflow */ - if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) - return JS_ThrowStackOverflow(ctx); - - prev_sf = rt->current_stack_frame; - sf->prev_frame = prev_sf; - rt->current_stack_frame = sf; - ctx = p->u.cfunc.realm; /* change the current realm */ - - sf->is_strict_mode = false; - sf->cur_func = func_obj; - sf->arg_count = argc; - arg_buf = argv; - - if (unlikely(argc < arg_count)) { - /* ensure that at least argc_count arguments are readable */ - arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); - for(i = 0; i < argc; i++) - arg_buf[i] = argv[i]; - for(i = argc; i < arg_count; i++) - arg_buf[i] = JS_UNDEFINED; - sf->arg_count = arg_count; - } - sf->arg_buf = arg_buf; - - func = p->u.cfunc.c_function; - switch(cproto) { - case JS_CFUNC_constructor: - case JS_CFUNC_constructor_or_func: - if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { - if (cproto == JS_CFUNC_constructor) { - not_a_constructor: - ret_val = JS_ThrowTypeError(ctx, "must be called with new"); - break; - } else { - this_obj = JS_UNDEFINED; - } - } - /* here this_obj is new_target */ - /* fall thru */ - case JS_CFUNC_generic: - ret_val = func.generic(ctx, this_obj, argc, arg_buf); - break; - case JS_CFUNC_constructor_magic: - case JS_CFUNC_constructor_or_func_magic: - if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { - if (cproto == JS_CFUNC_constructor_magic) { - goto not_a_constructor; - } else { - this_obj = JS_UNDEFINED; - } - } - /* fall thru */ - case JS_CFUNC_generic_magic: - ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, - p->u.cfunc.magic); - break; - case JS_CFUNC_getter: - ret_val = func.getter(ctx, this_obj); - break; - case JS_CFUNC_setter: - ret_val = func.setter(ctx, this_obj, arg_buf[0]); - break; - case JS_CFUNC_getter_magic: - ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); - break; - case JS_CFUNC_setter_magic: - ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); - break; - case JS_CFUNC_f_f: - { - double d1; - - if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { - ret_val = JS_EXCEPTION; - break; - } - ret_val = js_number(func.f_f(d1)); - } - break; - case JS_CFUNC_f_f_f: - { - double d1, d2; - - if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { - ret_val = JS_EXCEPTION; - break; - } - if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { - ret_val = JS_EXCEPTION; - break; - } - ret_val = js_number(func.f_f_f(d1, d2)); - } - break; - case JS_CFUNC_iterator_next: - { - int done; - ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, - &done, p->u.cfunc.magic); - if (!JS_IsException(ret_val) && done != 2) { - ret_val = js_create_iterator_result(ctx, ret_val, done); - } - } - break; - default: - abort(); - } - - rt->current_stack_frame = sf->prev_frame; - return ret_val; -} - -static JSValue js_call_bound_function(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, int flags) -{ - JSObject *p; - JSBoundFunction *bf; - JSValue *arg_buf, new_target; - int arg_count, i; - - p = JS_VALUE_GET_OBJ(func_obj); - bf = p->u.bound_function; - arg_count = bf->argc + argc; - if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) - return JS_ThrowStackOverflow(ctx); - arg_buf = alloca(sizeof(JSValue) * arg_count); - for(i = 0; i < bf->argc; i++) { - arg_buf[i] = bf->argv[i]; - } - for(i = 0; i < argc; i++) { - arg_buf[bf->argc + i] = argv[i]; - } - if (flags & JS_CALL_FLAG_CONSTRUCTOR) { - new_target = this_obj; - if (js_same_value(ctx, func_obj, new_target)) - new_target = bf->func_obj; - return JS_CallConstructor2(ctx, bf->func_obj, new_target, - arg_count, arg_buf); - } else { - return JS_Call(ctx, bf->func_obj, bf->this_val, - arg_count, arg_buf); - } -} - -/* argument of OP_special_object */ -typedef enum { - OP_SPECIAL_OBJECT_ARGUMENTS, - OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, - OP_SPECIAL_OBJECT_THIS_FUNC, - OP_SPECIAL_OBJECT_NEW_TARGET, - OP_SPECIAL_OBJECT_HOME_OBJECT, - OP_SPECIAL_OBJECT_VAR_OBJECT, - OP_SPECIAL_OBJECT_IMPORT_META, -} OPSpecialObjectEnum; - -#define FUNC_RET_AWAIT 0 -#define FUNC_RET_YIELD 1 -#define FUNC_RET_YIELD_STAR 2 - -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* -static void dump_single_byte_code(JSContext *ctx, const uint8_t *pc, - JSFunctionBytecode *b, int start_pos); -static void print_func_name(JSFunctionBytecode *b); -#endif - -/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ -static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, - JSValue this_obj, JSValue new_target, - int argc, JSValue *argv, int flags) -{ - JSRuntime *rt = caller_ctx->rt; - JSContext *ctx; - JSObject *p; - JSFunctionBytecode *b; - JSStackFrame sf_s, *sf = &sf_s; - uint8_t *pc; - int opcode, arg_allocated_size, i; - JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; - JSVarRef **var_refs; - size_t alloca_size; - JSInlineCache *ic; - -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP -#define DUMP_BYTECODE_OR_DONT(pc) \ - if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) dump_single_byte_code(ctx, pc, b, 0); -#else -#define DUMP_BYTECODE_OR_DONT(pc) -#endif - -#if !DIRECT_DISPATCH -#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) switch (opcode = *pc++) -#define CASE(op) case op -#define DEFAULT default -#define BREAK break -#else - __extension__ static const void * const dispatch_table[256] = { -#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" - [ OP_COUNT ... 255 ] = &&case_default - }; -#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); -#define CASE(op) case_ ## op -#define DEFAULT case_default -#define BREAK SWITCH(pc) -#endif - - if (js_poll_interrupts(caller_ctx)) - return JS_EXCEPTION; - if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { - if (flags & JS_CALL_FLAG_GENERATOR) { - JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); - /* func_obj get contains a pointer to JSFuncAsyncState */ - /* the stack frame is already allocated */ - sf = &s->frame; - p = JS_VALUE_GET_OBJ(sf->cur_func); - b = p->u.func.function_bytecode; - ctx = b->realm; - var_refs = p->u.func.var_refs; - local_buf = arg_buf = sf->arg_buf; - var_buf = sf->var_buf; - stack_buf = sf->var_buf + b->var_count; - sp = sf->cur_sp; - sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ - pc = sf->cur_pc; - sf->prev_frame = rt->current_stack_frame; - rt->current_stack_frame = sf; - ic = b->ic; - if (s->throw_flag) - goto exception; - else - goto restart; - } else { - goto not_a_function; - } - } - p = JS_VALUE_GET_OBJ(func_obj); - if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { - JSClassCall *call_func; - call_func = rt->class_array[p->class_id].call; - if (!call_func) { - not_a_function: - return JS_ThrowTypeError(caller_ctx, "not a function"); - } - return call_func(caller_ctx, func_obj, this_obj, argc, - argv, flags); - } - b = p->u.func.function_bytecode; - - if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { - arg_allocated_size = b->arg_count; - } else { - arg_allocated_size = 0; - } - - alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + - b->stack_size); - if (js_check_stack_overflow(rt, alloca_size)) - return JS_ThrowStackOverflow(caller_ctx); - - sf->is_strict_mode = b->is_strict_mode; - arg_buf = argv; - sf->arg_count = argc; - sf->cur_func = func_obj; - init_list_head(&sf->var_ref_list); - var_refs = p->u.func.var_refs; - - local_buf = alloca(alloca_size); - if (unlikely(arg_allocated_size)) { - int n = min_int(argc, b->arg_count); - arg_buf = local_buf; - for(i = 0; i < n; i++) - arg_buf[i] = JS_DupValue(caller_ctx, argv[i]); - for(; i < b->arg_count; i++) - arg_buf[i] = JS_UNDEFINED; - sf->arg_count = b->arg_count; - } - var_buf = local_buf + arg_allocated_size; - sf->var_buf = var_buf; - sf->arg_buf = arg_buf; - - for(i = 0; i < b->var_count; i++) - var_buf[i] = JS_UNDEFINED; - - stack_buf = var_buf + b->var_count; - sp = stack_buf; - pc = b->byte_code_buf; - /* sf->cur_pc must we set to pc before any recursive calls to JS_CallInternal. */ - sf->cur_pc = NULL; - sf->prev_frame = rt->current_stack_frame; - rt->current_stack_frame = sf; - ctx = b->realm; /* set the current realm */ - ic = b->ic; - -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_STEP - if (check_dump_flag(ctx->rt, JS_DUMP_BYTECODE_STEP)) - print_func_name(b); -#endif - - restart: - for(;;) { - int call_argc; - JSValue *call_argv; - - SWITCH(pc) { - CASE(OP_push_i32): - *sp++ = js_int32(get_u32(pc)); - pc += 4; - BREAK; - CASE(OP_push_const): - *sp++ = js_dup(b->cpool[get_u32(pc)]); - pc += 4; - BREAK; - CASE(OP_push_minus1): - CASE(OP_push_0): - CASE(OP_push_1): - CASE(OP_push_2): - CASE(OP_push_3): - CASE(OP_push_4): - CASE(OP_push_5): - CASE(OP_push_6): - CASE(OP_push_7): - *sp++ = js_int32(opcode - OP_push_0); - BREAK; - CASE(OP_push_i8): - *sp++ = js_int32(get_i8(pc)); - pc += 1; - BREAK; - CASE(OP_push_i16): - *sp++ = js_int32(get_i16(pc)); - pc += 2; - BREAK; - CASE(OP_push_const8): - *sp++ = js_dup(b->cpool[*pc++]); - BREAK; - CASE(OP_fclosure8): - *sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - BREAK; - CASE(OP_push_empty_string): - *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); - BREAK; - CASE(OP_get_length): - { - JSValue val; - - sf->cur_pc = pc; - val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - CASE(OP_push_atom_value): - *sp++ = JS_AtomToValue(ctx, get_u32(pc)); - pc += 4; - BREAK; - CASE(OP_undefined): - *sp++ = JS_UNDEFINED; - BREAK; - CASE(OP_null): - *sp++ = JS_NULL; - BREAK; - CASE(OP_push_this): - /* OP_push_this is only called at the start of a function */ - { - JSValue val; - if (!b->is_strict_mode) { - uint32_t tag = JS_VALUE_GET_TAG(this_obj); - if (likely(tag == JS_TAG_OBJECT)) - goto normal_this; - if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { - val = js_dup(ctx->global_obj); - } else { - val = JS_ToObject(ctx, this_obj); - if (JS_IsException(val)) - goto exception; - } - } else { - normal_this: - val = js_dup(this_obj); - } - *sp++ = val; - } - BREAK; - CASE(OP_push_false): - *sp++ = JS_FALSE; - BREAK; - CASE(OP_push_true): - *sp++ = JS_TRUE; - BREAK; - CASE(OP_object): - *sp++ = JS_NewObject(ctx); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - BREAK; - CASE(OP_special_object): - { - int arg = *pc++; - switch(arg) { - case OP_SPECIAL_OBJECT_ARGUMENTS: - *sp++ = js_build_arguments(ctx, argc, argv); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: - *sp++ = js_build_mapped_arguments(ctx, argc, argv, - sf, min_int(argc, b->arg_count)); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_THIS_FUNC: - *sp++ = js_dup(sf->cur_func); - break; - case OP_SPECIAL_OBJECT_NEW_TARGET: - *sp++ = js_dup(new_target); - break; - case OP_SPECIAL_OBJECT_HOME_OBJECT: - { - JSObject *p1; - p1 = p->u.func.home_object; - if (unlikely(!p1)) - *sp++ = JS_UNDEFINED; - else - *sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); - } - break; - case OP_SPECIAL_OBJECT_VAR_OBJECT: - *sp++ = JS_NewObjectProto(ctx, JS_NULL); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - case OP_SPECIAL_OBJECT_IMPORT_META: - *sp++ = js_import_meta(ctx); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - break; - default: - abort(); - } - } - BREAK; - CASE(OP_rest): - { - int first = get_u16(pc); - pc += 2; - *sp++ = js_build_rest(ctx, first, argc, argv); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - } - BREAK; - - CASE(OP_drop): - JS_FreeValue(ctx, sp[-1]); - sp--; - BREAK; - CASE(OP_nip): - JS_FreeValue(ctx, sp[-2]); - sp[-2] = sp[-1]; - sp--; - BREAK; - CASE(OP_nip1): /* a b c -> b c */ - JS_FreeValue(ctx, sp[-3]); - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp--; - BREAK; - CASE(OP_dup): - sp[0] = js_dup(sp[-1]); - sp++; - BREAK; - CASE(OP_dup2): /* a b -> a b a b */ - sp[0] = js_dup(sp[-2]); - sp[1] = js_dup(sp[-1]); - sp += 2; - BREAK; - CASE(OP_dup3): /* a b c -> a b c a b c */ - sp[0] = js_dup(sp[-3]); - sp[1] = js_dup(sp[-2]); - sp[2] = js_dup(sp[-1]); - sp += 3; - BREAK; - CASE(OP_dup1): /* a b -> a a b */ - sp[0] = sp[-1]; - sp[-1] = js_dup(sp[-2]); - sp++; - BREAK; - CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = js_dup(sp[0]); - sp++; - BREAK; - CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = js_dup(sp[0]); - sp++; - BREAK; - CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = js_dup(sp[0]); - sp++; - BREAK; - CASE(OP_perm3): /* obj a b -> a obj b (213) */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = tmp; - } - BREAK; - CASE(OP_rot3l): /* x a b -> a b x (231) */ - { - JSValue tmp; - tmp = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_rot4l): /* x a b c -> a b c x */ - { - JSValue tmp; - tmp = sp[-4]; - sp[-4] = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_rot5l): /* x a b c d -> a b c d x */ - { - JSValue tmp; - tmp = sp[-5]; - sp[-5] = sp[-4]; - sp[-4] = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_rot3r): /* a b x -> x a b (312) */ - { - JSValue tmp; - tmp = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = tmp; - } - BREAK; - CASE(OP_perm4): /* obj prop a b -> a obj prop b */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = tmp; - } - BREAK; - CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = sp[-5]; - sp[-5] = tmp; - } - BREAK; - CASE(OP_swap): /* a b -> b a */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE(OP_swap2): /* a b c d -> c d a b */ - { - JSValue tmp1, tmp2; - tmp1 = sp[-4]; - tmp2 = sp[-3]; - sp[-4] = sp[-2]; - sp[-3] = sp[-1]; - sp[-2] = tmp1; - sp[-1] = tmp2; - } - BREAK; - - CASE(OP_fclosure): - { - JSValue bfunc = js_dup(b->cpool[get_u32(pc)]); - pc += 4; - *sp++ = js_closure(ctx, bfunc, var_refs, sf); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - } - BREAK; - CASE(OP_call0): - CASE(OP_call1): - CASE(OP_call2): - CASE(OP_call3): - call_argc = opcode - OP_call0; - goto has_call_argc; - CASE(OP_call): - CASE(OP_tail_call): - { - call_argc = get_u16(pc); - pc += 2; - goto has_call_argc; - has_call_argc: - call_argv = sp - call_argc; - sf->cur_pc = pc; - ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, - JS_UNDEFINED, call_argc, call_argv, 0); - if (unlikely(JS_IsException(ret_val))) - goto exception; - if (opcode == OP_tail_call) - goto done; - for(i = -1; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 1; - *sp++ = ret_val; - } - BREAK; - CASE(OP_call_constructor): - { - call_argc = get_u16(pc); - pc += 2; - call_argv = sp - call_argc; - sf->cur_pc = pc; - ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], - call_argv[-1], - call_argc, call_argv, 0); - if (unlikely(JS_IsException(ret_val))) - goto exception; - for(i = -2; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 2; - *sp++ = ret_val; - } - BREAK; - CASE(OP_call_method): - CASE(OP_tail_call_method): - { - call_argc = get_u16(pc); - pc += 2; - call_argv = sp - call_argc; - sf->cur_pc = pc; - ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], - JS_UNDEFINED, call_argc, call_argv, 0); - if (unlikely(JS_IsException(ret_val))) - goto exception; - if (opcode == OP_tail_call_method) - goto done; - for(i = -2; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 2; - *sp++ = ret_val; - } - BREAK; - CASE(OP_array_from): - { - int i, ret; - - call_argc = get_u16(pc); - pc += 2; - ret_val = JS_NewArray(ctx); - if (unlikely(JS_IsException(ret_val))) - goto exception; - call_argv = sp - call_argc; - for(i = 0; i < call_argc; i++) { - ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i], - JS_PROP_C_W_E | JS_PROP_THROW); - call_argv[i] = JS_UNDEFINED; - if (ret < 0) { - JS_FreeValue(ctx, ret_val); - goto exception; - } - } - sp -= call_argc; - *sp++ = ret_val; - } - BREAK; - - CASE(OP_apply): - { - int magic; - magic = get_u16(pc); - pc += 2; - sf->cur_pc = pc; - - ret_val = js_function_apply(ctx, sp[-3], 2, &sp[-2], magic); - if (unlikely(JS_IsException(ret_val))) - goto exception; - JS_FreeValue(ctx, sp[-3]); - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-1]); - sp -= 3; - *sp++ = ret_val; - } - BREAK; - CASE(OP_return): - ret_val = *--sp; - goto done; - CASE(OP_return_undef): - ret_val = JS_UNDEFINED; - goto done; - - CASE(OP_check_ctor_return): - /* return true if 'this' should be returned */ - if (!JS_IsObject(sp[-1])) { - if (!JS_IsUndefined(sp[-1])) { - JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); - goto exception; - } - sp[0] = JS_TRUE; - } else { - sp[0] = JS_FALSE; - } - sp++; - BREAK; - CASE(OP_check_ctor): - if (JS_IsUndefined(new_target)) { - non_ctor_call: - JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); - goto exception; - } - BREAK; - CASE(OP_init_ctor): - { - JSValue super, ret; - sf->cur_pc = pc; - if (JS_IsUndefined(new_target)) - goto non_ctor_call; - super = JS_GetPrototype(ctx, func_obj); - if (JS_IsException(super)) - goto exception; - ret = JS_CallConstructor2(ctx, super, new_target, argc, argv); - JS_FreeValue(ctx, super); - if (JS_IsException(ret)) - goto exception; - *sp++ = ret; - } - BREAK; - CASE(OP_check_brand): - { - int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); - if (ret < 0) - goto exception; - if (!ret) { - JS_ThrowTypeError(ctx, "invalid brand on object"); - goto exception; - } - } - BREAK; - CASE(OP_add_brand): - if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) - goto exception; - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-1]); - sp -= 2; - BREAK; - - CASE(OP_throw): - JS_Throw(ctx, *--sp); - goto exception; - - CASE(OP_throw_error): -#define JS_THROW_VAR_RO 0 -#define JS_THROW_VAR_REDECL 1 -#define JS_THROW_VAR_UNINITIALIZED 2 -#define JS_THROW_ERROR_DELETE_SUPER 3 -#define JS_THROW_ERROR_ITERATOR_THROW 4 - { - JSAtom atom; - int type; - atom = get_u32(pc); - type = pc[4]; - pc += 5; - if (type == JS_THROW_VAR_RO) - JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); - else - if (type == JS_THROW_VAR_REDECL) - JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); - else - if (type == JS_THROW_VAR_UNINITIALIZED) - JS_ThrowReferenceErrorUninitialized(ctx, atom); - else - if (type == JS_THROW_ERROR_DELETE_SUPER) - JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); - else - if (type == JS_THROW_ERROR_ITERATOR_THROW) - JS_ThrowTypeError(ctx, "iterator does not have a throw method"); - else - JS_ThrowInternalError(ctx, "invalid throw var type %d", type); - } - goto exception; - - CASE(OP_eval): - { - JSValue obj; - int scope_idx; - call_argc = get_u16(pc); - scope_idx = get_u16(pc + 2) - 1; - pc += 4; - call_argv = sp - call_argc; - sf->cur_pc = pc; - if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { - if (call_argc >= 1) - obj = call_argv[0]; - else - obj = JS_UNDEFINED; - ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, - JS_EVAL_TYPE_DIRECT, scope_idx); - } else { - ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, - JS_UNDEFINED, call_argc, call_argv, 0); - } - if (unlikely(JS_IsException(ret_val))) - goto exception; - for(i = -1; i < call_argc; i++) - JS_FreeValue(ctx, call_argv[i]); - sp -= call_argc + 1; - *sp++ = ret_val; - } - BREAK; - /* could merge with OP_apply */ - CASE(OP_apply_eval): - { - int scope_idx; - uint32_t len; - JSValue *tab; - JSValue obj; - - scope_idx = get_u16(pc) - 1; - pc += 2; - sf->cur_pc = pc; - tab = build_arg_list(ctx, &len, sp[-1]); - if (!tab) - goto exception; - if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { - if (len >= 1) - obj = tab[0]; - else - obj = JS_UNDEFINED; - ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, - JS_EVAL_TYPE_DIRECT, scope_idx); - } else { - ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, - tab); - } - free_arg_list(ctx, tab, len); - if (unlikely(JS_IsException(ret_val))) - goto exception; - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-1]); - sp -= 2; - *sp++ = ret_val; - } - BREAK; - - CASE(OP_regexp): - { - sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED, - sp[-2], sp[-1]); - sp--; - } - BREAK; - - CASE(OP_get_super): - { - JSValue proto; - proto = JS_GetPrototype(ctx, sp[-1]); - if (JS_IsException(proto)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = proto; - } - BREAK; - - CASE(OP_import): - { - JSValue val; - sf->cur_pc = pc; - val = js_dynamic_import(ctx, sp[-1]); - if (JS_IsException(val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - - CASE(OP_check_var): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_CheckGlobalVar(ctx, atom); - if (ret < 0) - goto exception; - *sp++ = js_bool(ret); - } - BREAK; - - CASE(OP_get_var_undef): - CASE(OP_get_var): - { - JSValue val; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - - val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - } - BREAK; - - CASE(OP_put_var): - CASE(OP_put_var_init): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - - ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); - sp--; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_put_var_strict): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - - /* sp[-2] is JS_TRUE or JS_FALSE */ - if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - goto exception; - } - ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_check_define_var): - { - JSAtom atom; - int flags; - atom = get_u32(pc); - flags = pc[4]; - pc += 5; - if (JS_CheckDefineGlobalVar(ctx, atom, flags)) - goto exception; - } - BREAK; - CASE(OP_define_var): - { - JSAtom atom; - int flags; - atom = get_u32(pc); - flags = pc[4]; - pc += 5; - if (JS_DefineGlobalVar(ctx, atom, flags)) - goto exception; - } - BREAK; - CASE(OP_define_func): - { - JSAtom atom; - int flags; - atom = get_u32(pc); - flags = pc[4]; - pc += 5; - if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp--; - } - BREAK; - - CASE(OP_get_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - sp[0] = js_dup(var_buf[idx]); - sp++; - } - BREAK; - CASE(OP_put_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &var_buf[idx], js_dup(sp[-1])); - } - BREAK; - CASE(OP_get_arg): - { - int idx; - idx = get_u16(pc); - pc += 2; - sp[0] = js_dup(arg_buf[idx]); - sp++; - } - BREAK; - CASE(OP_put_arg): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &arg_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_arg): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &arg_buf[idx], js_dup(sp[-1])); - } - BREAK; - - CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK; - CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; - CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; - - // Observation: get_loc0 and get_loc1 are individually very - // frequent opcodes _and_ they are very often paired together, - // making them ideal candidates for opcode fusion. - CASE(OP_get_loc0_loc1): - *sp++ = js_dup(var_buf[0]); - *sp++ = js_dup(var_buf[1]); - BREAK; - - CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK; - CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK; - CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK; - CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK; - CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; - CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; - CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; - CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; - CASE(OP_set_loc0): set_value(ctx, &var_buf[0], js_dup(sp[-1])); BREAK; - CASE(OP_set_loc1): set_value(ctx, &var_buf[1], js_dup(sp[-1])); BREAK; - CASE(OP_set_loc2): set_value(ctx, &var_buf[2], js_dup(sp[-1])); BREAK; - CASE(OP_set_loc3): set_value(ctx, &var_buf[3], js_dup(sp[-1])); BREAK; - CASE(OP_get_arg0): *sp++ = js_dup(arg_buf[0]); BREAK; - CASE(OP_get_arg1): *sp++ = js_dup(arg_buf[1]); BREAK; - CASE(OP_get_arg2): *sp++ = js_dup(arg_buf[2]); BREAK; - CASE(OP_get_arg3): *sp++ = js_dup(arg_buf[3]); BREAK; - CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; - CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; - CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; - CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; - CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], js_dup(sp[-1])); BREAK; - CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK; - CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK; - CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; - CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK; - CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK; - CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK; - CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK; - CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; - CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; - CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; - CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; - CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, js_dup(sp[-1])); BREAK; - CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, js_dup(sp[-1])); BREAK; - CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, js_dup(sp[-1])); BREAK; - CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, js_dup(sp[-1])); BREAK; - - CASE(OP_get_var_ref): - { - int idx; - JSValue val; - idx = get_u16(pc); - pc += 2; - val = *var_refs[idx]->pvalue; - sp[0] = js_dup(val); - sp++; - } - BREAK; - CASE(OP_put_var_ref): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, var_refs[idx]->pvalue, sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_var_ref): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, var_refs[idx]->pvalue, js_dup(sp[-1])); - } - BREAK; - CASE(OP_get_var_ref_check): - { - int idx; - JSValue val; - idx = get_u16(pc); - pc += 2; - val = *var_refs[idx]->pvalue; - if (unlikely(JS_IsUninitialized(val))) { - JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); - goto exception; - } - sp[0] = js_dup(val); - sp++; - } - BREAK; - CASE(OP_put_var_ref_check): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { - JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); - goto exception; - } - set_value(ctx, var_refs[idx]->pvalue, sp[-1]); - sp--; - } - BREAK; - CASE(OP_put_var_ref_check_init): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { - JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, true); - goto exception; - } - set_value(ctx, var_refs[idx]->pvalue, sp[-1]); - sp--; - } - BREAK; - CASE(OP_set_loc_uninitialized): - { - int idx; - idx = get_u16(pc); - pc += 2; - set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); - } - BREAK; - CASE(OP_get_loc_check): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(JS_IsUninitialized(var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, - false); - goto exception; - } - sp[0] = js_dup(var_buf[idx]); - sp++; - } - BREAK; - CASE(OP_put_loc_check): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(JS_IsUninitialized(var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, - false); - goto exception; - } - set_value(ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_put_loc_check_init): - { - int idx; - idx = get_u16(pc); - pc += 2; - if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { - JS_ThrowReferenceError(caller_ctx, - "'this' can be initialized only once"); - goto exception; - } - set_value(ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE(OP_close_loc): - { - int idx; - idx = get_u16(pc); - pc += 2; - close_lexical_var(ctx, sf, idx); - } - BREAK; - - CASE(OP_make_loc_ref): - CASE(OP_make_arg_ref): - CASE(OP_make_var_ref_ref): - { - JSVarRef *var_ref; - JSProperty *pr; - JSAtom atom; - int idx; - atom = get_u32(pc); - idx = get_u16(pc + 4); - pc += 6; - *sp++ = JS_NewObjectProto(ctx, JS_NULL); - if (unlikely(JS_IsException(sp[-1]))) - goto exception; - if (opcode == OP_make_var_ref_ref) { - var_ref = var_refs[idx]; - var_ref->header.ref_count++; - } else { - var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); - if (!var_ref) - goto exception; - } - pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, - JS_PROP_WRITABLE | JS_PROP_VARREF); - if (!pr) { - free_var_ref(rt, var_ref); - goto exception; - } - pr->u.var_ref = var_ref; - *sp++ = JS_AtomToValue(ctx, atom); - } - BREAK; - CASE(OP_make_var_ref): - { - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - if (JS_GetGlobalVarRef(ctx, atom, sp)) - goto exception; - sp += 2; - } - BREAK; - - CASE(OP_goto): - pc += (int32_t)get_u32(pc); - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - BREAK; - CASE(OP_goto16): - pc += (int16_t)get_u16(pc); - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - BREAK; - CASE(OP_goto8): - pc += (int8_t)pc[0]; - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - BREAK; - CASE(OP_if_true): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 4; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (res) { - pc += (int32_t)get_u32(pc - 4) - 4; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_if_false): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 4; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (!res) { - pc += (int32_t)get_u32(pc - 4) - 4; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_if_true8): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 1; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (res) { - pc += (int8_t)pc[-1] - 1; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_if_false8): - { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 1; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1); - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp--; - if (!res) { - pc += (int8_t)pc[-1] - 1; - } - if (unlikely(js_poll_interrupts(ctx))) - goto exception; - } - BREAK; - CASE(OP_catch): - { - int32_t diff; - diff = get_u32(pc); - sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); - sp++; - pc += 4; - } - BREAK; - CASE(OP_gosub): - { - int32_t diff; - diff = get_u32(pc); - /* XXX: should have a different tag to avoid security flaw */ - sp[0] = js_int32(pc + 4 - b->byte_code_buf); - sp++; - pc += diff; - } - BREAK; - CASE(OP_ret): - { - JSValue op1; - uint32_t pos; - op1 = sp[-1]; - if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) - goto ret_fail; - pos = JS_VALUE_GET_INT(op1); - if (unlikely(pos >= b->byte_code_len)) { - ret_fail: - JS_ThrowInternalError(ctx, "invalid ret value"); - goto exception; - } - sp--; - pc = b->byte_code_buf + pos; - } - BREAK; - - CASE(OP_for_in_start): - sf->cur_pc = pc; - if (js_for_in_start(ctx, sp)) - goto exception; - BREAK; - CASE(OP_for_in_next): - sf->cur_pc = pc; - if (js_for_in_next(ctx, sp)) - goto exception; - sp += 2; - BREAK; - CASE(OP_for_of_start): - sf->cur_pc = pc; - if (js_for_of_start(ctx, sp, false)) - goto exception; - sp += 1; - *sp++ = JS_NewCatchOffset(ctx, 0); - BREAK; - CASE(OP_for_of_next): - { - int offset = -3 - pc[0]; - pc += 1; - sf->cur_pc = pc; - if (js_for_of_next(ctx, sp, offset)) - goto exception; - sp += 2; - } - BREAK; - CASE(OP_for_await_of_start): - sf->cur_pc = pc; - if (js_for_of_start(ctx, sp, true)) - goto exception; - sp += 1; - *sp++ = JS_NewCatchOffset(ctx, 0); - BREAK; - CASE(OP_iterator_get_value_done): - sf->cur_pc = pc; - if (js_iterator_get_value_done(ctx, sp)) - goto exception; - sp += 1; - BREAK; - CASE(OP_iterator_check_object): - if (unlikely(!JS_IsObject(sp[-1]))) { - JS_ThrowTypeError(ctx, "iterator must return an object"); - goto exception; - } - BREAK; - - CASE(OP_iterator_close): - /* iter_obj next catch_offset -> */ - sp--; /* drop the catch offset to avoid getting caught by exception */ - JS_FreeValue(ctx, sp[-1]); /* drop the next method */ - sp--; - if (!JS_IsUndefined(sp[-1])) { - sf->cur_pc = pc; - if (JS_IteratorClose(ctx, sp[-1], false)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - } - sp--; - BREAK; - CASE(OP_nip_catch): - { - JSValue ret_val; - /* catch_offset ... ret_val -> ret_eval */ - ret_val = *--sp; - while (sp > stack_buf && - JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { - JS_FreeValue(ctx, *--sp); - } - if (unlikely(sp == stack_buf)) { - JS_ThrowInternalError(ctx, "nip_catch"); - JS_FreeValue(ctx, ret_val); - goto exception; - } - sp[-1] = ret_val; - } - BREAK; - - CASE(OP_iterator_next): - /* stack: iter_obj next catch_offset val */ - { - JSValue ret; - sf->cur_pc = pc; - ret = JS_Call(ctx, sp[-3], sp[-4], 1, (sp - 1)); - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret; - } - BREAK; - - CASE(OP_iterator_call): - /* stack: iter_obj next catch_offset val */ - { - JSValue method, ret; - bool ret_flag; - int flags; - flags = *pc++; - sf->cur_pc = pc; - method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? - JS_ATOM_throw : JS_ATOM_return); - if (JS_IsException(method)) - goto exception; - if (JS_IsUndefined(method) || JS_IsNull(method)) { - ret_flag = true; - } else { - if (flags & 2) { - /* no argument */ - ret = JS_CallFree(ctx, method, sp[-4], - 0, NULL); - } else { - ret = JS_CallFree(ctx, method, sp[-4], - 1, (sp - 1)); - } - if (JS_IsException(ret)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret; - ret_flag = false; - } - sp[0] = js_bool(ret_flag); - sp += 1; - } - BREAK; - - CASE(OP_lnot): - { - int res; - JSValue op1; - - op1 = sp[-1]; - if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { - res = JS_VALUE_GET_INT(op1) != 0; - } else { - res = JS_ToBoolFree(ctx, op1); - } - sp[-1] = js_bool(!res); - } - BREAK; - - CASE(OP_get_field): - { - JSValue val; - JSAtom atom; - JSInlineCacheUpdate icu; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - icu = (JSInlineCacheUpdate){ic, INLINE_CACHE_MISS}; - val = JS_GetPropertyInternal2(ctx, sp[-1], atom, sp[-1], &icu, false); - if (unlikely(JS_IsException(val))) - goto exception; - if (icu.offset != INLINE_CACHE_MISS) { - put_u8(pc - 5, OP_get_field_ic); - put_u32(pc - 4, icu.offset); - JS_FreeAtom(ctx, atom); - } - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - - CASE(OP_get_field_ic): - { - JSValue val; - JSAtom atom; - uint32_t ic_offset; - JSInlineCacheUpdate icu; - ic_offset = get_u32(pc); - atom = get_ic_atom(ic, ic_offset); - pc += 4; - sf->cur_pc = pc; - icu = (JSInlineCacheUpdate){ic, ic_offset}; - val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], &icu, false); - if (unlikely(JS_IsException(val))) - goto exception; - assert(icu.offset == ic_offset); - JS_FreeValue(ctx, sp[-1]); - sp[-1] = val; - } - BREAK; - - CASE(OP_get_field2): - { - JSValue val; - JSAtom atom; - JSInlineCacheUpdate icu; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - icu = (JSInlineCacheUpdate){ic, INLINE_CACHE_MISS}; - val = JS_GetPropertyInternal2(ctx, sp[-1], atom, sp[-1], &icu, false); - if (unlikely(JS_IsException(val))) - goto exception; - if (icu.offset != INLINE_CACHE_MISS) { - put_u8(pc - 5, OP_get_field2_ic); - put_u32(pc - 4, icu.offset); - JS_FreeAtom(ctx, atom); - } - *sp++ = val; - } - BREAK; - - CASE(OP_get_field2_ic): - { - JSValue val; - JSAtom atom; - uint32_t ic_offset; - JSInlineCacheUpdate icu; - ic_offset = get_u32(pc); - atom = get_ic_atom(ic, ic_offset); - pc += 4; - sf->cur_pc = pc; - icu = (JSInlineCacheUpdate){ic, ic_offset}; - val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], &icu, false); - if (unlikely(JS_IsException(val))) - goto exception; - assert(icu.offset == ic_offset); - *sp++ = val; - } - BREAK; - - CASE(OP_put_field): - { - int ret; - JSAtom atom; - JSInlineCacheUpdate icu; - atom = get_u32(pc); - pc += 4; - sf->cur_pc = pc; - icu = (JSInlineCacheUpdate){ic, INLINE_CACHE_MISS}; - ret = JS_SetPropertyInternal2(ctx, - sp[-2], atom, - sp[-1], sp[-2], - JS_PROP_THROW_STRICT, &icu); - JS_FreeValue(ctx, sp[-2]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - if (icu.offset != INLINE_CACHE_MISS) { - put_u8(pc - 5, OP_put_field_ic); - put_u32(pc - 4, icu.offset); - JS_FreeAtom(ctx, atom); - } - } - BREAK; - - CASE(OP_put_field_ic): - { - int ret; - JSAtom atom; - uint32_t ic_offset; - JSInlineCacheUpdate icu; - ic_offset = get_u32(pc); - atom = get_ic_atom(ic, ic_offset); - pc += 4; - sf->cur_pc = pc; - icu = (JSInlineCacheUpdate){ic, ic_offset}; - ret = JS_SetPropertyInternalWithIC(ctx, sp[-2], atom, sp[-1], - JS_PROP_THROW_STRICT, &icu); - JS_FreeValue(ctx, sp[-2]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - assert(icu.offset == ic_offset); - } - BREAK; - - CASE(OP_private_symbol): - { - JSAtom atom; - JSValue val; - - atom = get_u32(pc); - pc += 4; - val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); - if (JS_IsException(val)) - goto exception; - *sp++ = val; - } - BREAK; - - CASE(OP_get_private_field): - { - JSValue val; - sf->cur_pc = pc; - val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); - JS_FreeValue(ctx, sp[-1]); - JS_FreeValue(ctx, sp[-2]); - sp[-2] = val; - sp--; - if (unlikely(JS_IsException(val))) - goto exception; - } - BREAK; - - CASE(OP_put_private_field): - { - int ret; - sf->cur_pc = pc; - ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); - JS_FreeValue(ctx, sp[-3]); - JS_FreeValue(ctx, sp[-1]); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_define_private_field): - { - int ret; - ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); - JS_FreeValue(ctx, sp[-2]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_define_field): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], - JS_PROP_C_W_E | JS_PROP_THROW); - sp--; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_set_name): - { - int ret; - JSAtom atom; - atom = get_u32(pc); - pc += 4; - - ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - CASE(OP_set_name_computed): - { - int ret; - ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - CASE(OP_set_proto): - { - JSValue proto; - proto = sp[-1]; - if (JS_IsObject(proto) || JS_IsNull(proto)) { - if (JS_SetPrototypeInternal(ctx, sp[-2], proto, true) < 0) - goto exception; - } - JS_FreeValue(ctx, proto); - sp--; - } - BREAK; - CASE(OP_set_home_object): - js_method_set_home_object(ctx, sp[-1], sp[-2]); - BREAK; - CASE(OP_define_method): - CASE(OP_define_method_computed): - { - JSValue getter, setter, value; - JSValue obj; - JSAtom atom; - int flags, ret, op_flags; - bool is_computed; -#define OP_DEFINE_METHOD_METHOD 0 -#define OP_DEFINE_METHOD_GETTER 1 -#define OP_DEFINE_METHOD_SETTER 2 -#define OP_DEFINE_METHOD_ENUMERABLE 4 - - is_computed = (opcode == OP_define_method_computed); - if (is_computed) { - atom = JS_ValueToAtom(ctx, sp[-2]); - if (unlikely(atom == JS_ATOM_NULL)) - goto exception; - opcode += OP_define_method - OP_define_method_computed; - } else { - atom = get_u32(pc); - pc += 4; - } - op_flags = *pc++; - - obj = sp[-2 - is_computed]; - flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | - JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; - if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) - flags |= JS_PROP_ENUMERABLE; - op_flags &= 3; - value = JS_UNDEFINED; - getter = JS_UNDEFINED; - setter = JS_UNDEFINED; - if (op_flags == OP_DEFINE_METHOD_METHOD) { - value = sp[-1]; - flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; - } else if (op_flags == OP_DEFINE_METHOD_GETTER) { - getter = sp[-1]; - flags |= JS_PROP_HAS_GET; - } else { - setter = sp[-1]; - flags |= JS_PROP_HAS_SET; - } - ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); - if (ret >= 0) { - ret = JS_DefineProperty(ctx, obj, atom, value, - getter, setter, flags); - } - JS_FreeValue(ctx, sp[-1]); - if (is_computed) { - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, sp[-2]); - } - sp -= 1 + is_computed; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_define_class): - CASE(OP_define_class_computed): - { - int class_flags; - JSAtom atom; - - atom = get_u32(pc); - class_flags = pc[4]; - pc += 5; - if (js_op_define_class(ctx, sp, atom, class_flags, - var_refs, sf, - (opcode == OP_define_class_computed)) < 0) - goto exception; - } - BREAK; - - CASE(OP_get_array_el): - { - JSValue val; - - sf->cur_pc = pc; - val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); - JS_FreeValue(ctx, sp[-2]); - sp[-2] = val; - sp--; - if (unlikely(JS_IsException(val))) - goto exception; - } - BREAK; - - CASE(OP_get_array_el2): - { - JSValue val; - - sf->cur_pc = pc; - val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); - sp[-1] = val; - if (unlikely(JS_IsException(val))) - goto exception; - } - BREAK; - - CASE(OP_get_ref_value): - { - JSValue val; - sf->cur_pc = pc; - if (unlikely(JS_IsUndefined(sp[-2]))) { - JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); - if (atom != JS_ATOM_NULL) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - JS_FreeAtom(ctx, atom); - } - goto exception; - } - val = JS_GetPropertyValue(ctx, sp[-2], - js_dup(sp[-1])); - if (unlikely(JS_IsException(val))) - goto exception; - sp[0] = val; - sp++; - } - BREAK; - - CASE(OP_get_super_value): - { - JSValue val; - JSAtom atom; - sf->cur_pc = pc; - atom = JS_ValueToAtom(ctx, sp[-1]); - if (unlikely(atom == JS_ATOM_NULL)) - goto exception; - val = JS_GetPropertyInternal2(ctx, sp[-2], atom, sp[-3], NULL, false); - JS_FreeAtom(ctx, atom); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - JS_FreeValue(ctx, sp[-2]); - JS_FreeValue(ctx, sp[-3]); - sp[-3] = val; - sp -= 2; - } - BREAK; - - CASE(OP_put_array_el): - { - int ret; - sf->cur_pc = pc; - ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp[-3]); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_put_ref_value): - { - int ret, flags; - sf->cur_pc = pc; - flags = JS_PROP_THROW_STRICT; - if (unlikely(JS_IsUndefined(sp[-3]))) { - if (is_strict_mode(ctx)) { - JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); - if (atom != JS_ATOM_NULL) { - JS_ThrowReferenceErrorNotDefined(ctx, atom); - JS_FreeAtom(ctx, atom); - } - goto exception; - } else { - sp[-3] = js_dup(ctx->global_obj); - } - } else { - if (is_strict_mode(ctx)) - flags |= JS_PROP_NO_ADD; - } - ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags); - JS_FreeValue(ctx, sp[-3]); - sp -= 3; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_put_super_value): - { - int ret; - JSAtom atom; - sf->cur_pc = pc; - if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { - JS_ThrowTypeErrorNotAnObject(ctx); - goto exception; - } - atom = JS_ValueToAtom(ctx, sp[-2]); - if (unlikely(atom == JS_ATOM_NULL)) - goto exception; - ret = JS_SetPropertyInternal2(ctx, - sp[-3], atom, - sp[-1], sp[-4], - JS_PROP_THROW_STRICT, NULL); - JS_FreeAtom(ctx, atom); - JS_FreeValue(ctx, sp[-4]); - JS_FreeValue(ctx, sp[-3]); - JS_FreeValue(ctx, sp[-2]); - sp -= 4; - if (ret < 0) - goto exception; - } - BREAK; - - CASE(OP_define_array_el): - { - int ret; - ret = JS_DefinePropertyValueValue(ctx, sp[-3], js_dup(sp[-2]), sp[-1], - JS_PROP_C_W_E | JS_PROP_THROW); - sp -= 1; - if (unlikely(ret < 0)) - goto exception; - } - BREAK; - - CASE(OP_append): /* array pos enumobj -- array pos */ - { - sf->cur_pc = pc; - if (js_append_enumerate(ctx, sp)) - goto exception; - JS_FreeValue(ctx, *--sp); - } - BREAK; - - CASE(OP_copy_data_properties): /* target source excludeList */ - { - /* stack offsets (-1 based): - 2 bits for target, - 3 bits for source, - 2 bits for exclusionList */ - int mask; - - mask = *pc++; - sf->cur_pc = pc; - if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], - sp[-1 - ((mask >> 2) & 7)], - sp[-1 - ((mask >> 5) & 7)], 0)) - goto exception; - } - BREAK; - - CASE(OP_add): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); - if (unlikely((int)r != r)) - goto add_slow; - sp[-2] = js_int32(r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) + - JS_VALUE_GET_FLOAT64(op2)); - sp--; - } else { - add_slow: - sf->cur_pc = pc; - if (js_add_slow(ctx, sp)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_add_loc): - { - JSValue *pv; - int idx; - idx = *pc; - pc += 1; - - pv = &var_buf[idx]; - if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT(*pv) + - JS_VALUE_GET_INT(sp[-1]); - if (unlikely((int)r != r)) - goto add_loc_slow; - *pv = js_int32(r); - sp--; - } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { - JSValue op1; - op1 = sp[-1]; - sp--; - sf->cur_pc = pc; - op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); - if (JS_IsException(op1)) - goto exception; - op1 = JS_ConcatString(ctx, js_dup(*pv), op1); - if (JS_IsException(op1)) - goto exception; - set_value(ctx, pv, op1); - } else { - JSValue ops[2]; - add_loc_slow: - /* In case of exception, js_add_slow frees ops[0] - and ops[1], so we must duplicate *pv */ - sf->cur_pc = pc; - ops[0] = js_dup(*pv); - ops[1] = sp[-1]; - sp--; - if (js_add_slow(ctx, ops + 2)) - goto exception; - set_value(ctx, pv, ops[0]); - } - } - BREAK; - CASE(OP_sub): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); - if (unlikely((int)r != r)) - goto binary_arith_slow; - sp[-2] = js_int32(r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) - - JS_VALUE_GET_FLOAT64(op2)); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_mul): - { - JSValue op1, op2; - double d; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int32_t v1, v2; - int64_t r; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - r = (int64_t)v1 * v2; - if (unlikely((int)r != r)) { - d = (double)r; - goto mul_fp_res; - } - /* need to test zero case for -0 result */ - if (unlikely(r == 0 && (v1 | v2) < 0)) { - d = -0.0; - goto mul_fp_res; - } - sp[-2] = js_int32(r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); - mul_fp_res: - sp[-2] = js_float64(d); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_div): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int v1, v2; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - sp[-2] = js_number((double)v1 / (double)v2); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_mod): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - int v1, v2, r; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2); - /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = - -1 and the cases where the result is -0. */ - if (unlikely(v1 < 0 || v2 <= 0)) - goto binary_arith_slow; - r = v1 % v2; - sp[-2] = js_int32(r); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE(OP_pow): - binary_arith_slow: - sf->cur_pc = pc; - if (js_binary_arith_slow(ctx, sp, opcode)) - goto exception; - sp--; - BREAK; - - CASE(OP_plus): - { - JSValue op1; - uint32_t tag; - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG(op1); - if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { - } else { - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_neg): - { - JSValue op1; - uint32_t tag; - int val; - double d; - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG(op1); - if (tag == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - /* Note: -0 cannot be expressed as integer */ - if (unlikely(val == 0)) { - d = -0.0; - goto neg_fp_res; - } - if (unlikely(val == INT32_MIN)) { - d = -(double)val; - goto neg_fp_res; - } - sp[-1] = js_int32(-val); - } else if (JS_TAG_IS_FLOAT64(tag)) { - d = -JS_VALUE_GET_FLOAT64(op1); - neg_fp_res: - sp[-1] = js_float64(d); - } else { - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_inc): - { - JSValue op1; - int val; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MAX)) - goto inc_slow; - sp[-1] = js_int32(val + 1); - } else { - inc_slow: - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_dec): - { - JSValue op1; - int val; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MIN)) - goto dec_slow; - sp[-1] = js_int32(val - 1); - } else { - dec_slow: - sf->cur_pc = pc; - if (js_unary_arith_slow(ctx, sp, opcode)) - goto exception; - } - } - BREAK; - CASE(OP_post_inc): - CASE(OP_post_dec): - sf->cur_pc = pc; - if (js_post_inc_slow(ctx, sp, opcode)) - goto exception; - sp++; - BREAK; - CASE(OP_inc_loc): - { - JSValue op1; - int val; - int idx; - idx = *pc; - pc += 1; - - op1 = var_buf[idx]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MAX)) - goto inc_loc_slow; - var_buf[idx] = js_int32(val + 1); - } else { - inc_loc_slow: - sf->cur_pc = pc; - /* must duplicate otherwise the variable value may - be destroyed before JS code accesses it */ - op1 = js_dup(op1); - if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) - goto exception; - set_value(ctx, &var_buf[idx], op1); - } - } - BREAK; - CASE(OP_dec_loc): - { - JSValue op1; - int val; - int idx; - idx = *pc; - pc += 1; - - op1 = var_buf[idx]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT(op1); - if (unlikely(val == INT32_MIN)) - goto dec_loc_slow; - var_buf[idx] = js_int32(val - 1); - } else { - dec_loc_slow: - sf->cur_pc = pc; - /* must duplicate otherwise the variable value may - be destroyed before JS code accesses it */ - op1 = js_dup(op1); - if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) - goto exception; - set_value(ctx, &var_buf[idx], op1); - } - } - BREAK; - CASE(OP_not): - { - JSValue op1; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { - sp[-1] = js_int32(~JS_VALUE_GET_INT(op1)); - } else { - sf->cur_pc = pc; - if (js_not_slow(ctx, sp)) - goto exception; - } - } - BREAK; - - CASE(OP_shl): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - uint32_t v1, v2; - v1 = JS_VALUE_GET_INT(op1); - v2 = JS_VALUE_GET_INT(op2) & 0x1f; - sp[-2] = js_int32(v1 << v2); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_shr): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - uint32_t v2; - v2 = JS_VALUE_GET_INT(op2); - v2 &= 0x1f; - sp[-2] = js_uint32((uint32_t)JS_VALUE_GET_INT(op1) >> v2); - sp--; - } else { - sf->cur_pc = pc; - if (js_shr_slow(ctx, sp)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_sar): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - uint32_t v2; - v2 = JS_VALUE_GET_INT(op2); - if (unlikely(v2 > 0x1f)) { - v2 &= 0x1f; - } - sp[-2] = js_int32((int)JS_VALUE_GET_INT(op1) >> v2); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_and): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - sp[-2] = js_int32(JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2)); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_or): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - sp[-2] = js_int32(JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2)); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - CASE(OP_xor): - { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { - sp[-2] = js_int32(JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2)); - sp--; - } else { - sf->cur_pc = pc; - if (js_binary_logic_slow(ctx, sp, opcode)) - goto exception; - sp--; - } - } - BREAK; - - -#define OP_CMP(opcode, binary_op, slow_call) \ - CASE(opcode): \ - { \ - JSValue op1, op2; \ - op1 = sp[-2]; \ - op2 = sp[-1]; \ - if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ - sp[-2] = js_bool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ - sp--; \ - } else { \ - sf->cur_pc = pc; \ - if (slow_call) \ - goto exception; \ - sp--; \ - } \ - } \ - BREAK - - OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); - OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); - OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); - OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); - OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); - - CASE(OP_in): - sf->cur_pc = pc; - if (js_operator_in(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_private_in): - if (js_operator_private_in(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_instanceof): - sf->cur_pc = pc; - if (js_operator_instanceof(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_typeof): - { - JSValue op1; - JSAtom atom; - - op1 = sp[-1]; - atom = js_operator_typeof(ctx, op1); - JS_FreeValue(ctx, op1); - sp[-1] = JS_AtomToString(ctx, atom); - } - BREAK; - CASE(OP_delete): - sf->cur_pc = pc; - if (js_operator_delete(ctx, sp)) - goto exception; - sp--; - BREAK; - CASE(OP_delete_var): - { - JSAtom atom; - int ret; - - atom = get_u32(pc); - pc += 4; - - sf->cur_pc = pc; - ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0); - if (unlikely(ret < 0)) - goto exception; - *sp++ = js_bool(ret); - } - BREAK; - - CASE(OP_to_object): - if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { - sf->cur_pc = pc; - ret_val = JS_ToObject(ctx, sp[-1]); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret_val; - } - BREAK; - - CASE(OP_to_propkey): - switch (JS_VALUE_GET_TAG(sp[-1])) { - case JS_TAG_INT: - case JS_TAG_STRING: - case JS_TAG_SYMBOL: - break; - default: - sf->cur_pc = pc; - ret_val = JS_ToPropertyKey(ctx, sp[-1]); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret_val; - break; - } - BREAK; - - CASE(OP_to_propkey2): - /* must be tested first */ - if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { - JS_ThrowTypeError(ctx, "value has no property"); - goto exception; - } - switch (JS_VALUE_GET_TAG(sp[-1])) { - case JS_TAG_INT: - case JS_TAG_STRING: - case JS_TAG_SYMBOL: - break; - default: - sf->cur_pc = pc; - ret_val = JS_ToPropertyKey(ctx, sp[-1]); - if (JS_IsException(ret_val)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = ret_val; - break; - } - BREAK; - CASE(OP_with_get_var): - CASE(OP_with_put_var): - CASE(OP_with_delete_var): - CASE(OP_with_make_ref): - CASE(OP_with_get_ref): - CASE(OP_with_get_ref_undef): - { - JSAtom atom; - int32_t diff; - JSValue obj, val; - int ret, is_with; - atom = get_u32(pc); - diff = get_u32(pc + 4); - is_with = pc[8]; - pc += 9; - sf->cur_pc = pc; - - obj = sp[-1]; - ret = JS_HasProperty(ctx, obj, atom); - if (unlikely(ret < 0)) - goto exception; - if (ret) { - if (is_with) { - ret = js_has_unscopable(ctx, obj, atom); - if (unlikely(ret < 0)) - goto exception; - if (ret) - goto no_with; - } - switch (opcode) { - case OP_with_get_var: - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - set_value(ctx, &sp[-1], val); - break; - case OP_with_put_var: - /* XXX: check if strict mode */ - ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], - JS_PROP_THROW_STRICT); - JS_FreeValue(ctx, sp[-1]); - sp -= 2; - if (unlikely(ret < 0)) - goto exception; - break; - case OP_with_delete_var: - ret = JS_DeleteProperty(ctx, obj, atom, 0); - if (unlikely(ret < 0)) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = js_bool(ret); - break; - case OP_with_make_ref: - /* produce a pair object/propname on the stack */ - *sp++ = JS_AtomToValue(ctx, atom); - break; - case OP_with_get_ref: - /* produce a pair object/method on the stack */ - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - *sp++ = val; - break; - case OP_with_get_ref_undef: - /* produce a pair undefined/function on the stack */ - val = JS_GetProperty(ctx, obj, atom); - if (unlikely(JS_IsException(val))) - goto exception; - JS_FreeValue(ctx, sp[-1]); - sp[-1] = JS_UNDEFINED; - *sp++ = val; - break; - } - pc += diff - 5; - } else { - no_with: - /* if not jumping, drop the object argument */ - JS_FreeValue(ctx, sp[-1]); - sp--; - } - } - BREAK; - - CASE(OP_await): - ret_val = js_int32(FUNC_RET_AWAIT); - goto done_generator; - CASE(OP_yield): - ret_val = js_int32(FUNC_RET_YIELD); - goto done_generator; - CASE(OP_yield_star): - CASE(OP_async_yield_star): - ret_val = js_int32(FUNC_RET_YIELD_STAR); - goto done_generator; - CASE(OP_return_async): - CASE(OP_initial_yield): - ret_val = JS_UNDEFINED; - goto done_generator; - - CASE(OP_nop): - BREAK; - CASE(OP_is_undefined_or_null): - if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || - JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { - goto set_true; - } else { - goto free_and_set_false; - } - CASE(OP_is_undefined): - if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { - goto set_true; - } else { - goto free_and_set_false; - } - CASE(OP_is_null): - if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { - goto set_true; - } else { - goto free_and_set_false; - } - /* XXX: could merge to a single opcode */ - CASE(OP_typeof_is_undefined): - /* different from OP_is_undefined because of isHTMLDDA */ - if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { - goto free_and_set_true; - } else { - goto free_and_set_false; - } - CASE(OP_typeof_is_function): - if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { - goto free_and_set_true; - } else { - goto free_and_set_false; - } - free_and_set_true: - JS_FreeValue(ctx, sp[-1]); - set_true: - sp[-1] = JS_TRUE; - BREAK; - free_and_set_false: - JS_FreeValue(ctx, sp[-1]); - sp[-1] = JS_FALSE; - BREAK; - CASE(OP_invalid): - DEFAULT: - JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", - (int)(pc - b->byte_code_buf - 1), opcode); - goto exception; - } - } - exception: - sf->cur_pc = pc; - build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0); - if (!JS_IsUncatchableError(ctx, rt->current_exception)) { - while (sp > stack_buf) { - JSValue val = *--sp; - JS_FreeValue(ctx, val); - if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { - int pos = JS_VALUE_GET_INT(val); - if (pos == 0) { - /* enumerator: close it with a throw */ - JS_FreeValue(ctx, sp[-1]); /* drop the next method */ - sp--; - JS_IteratorClose(ctx, sp[-1], true); - } else { - *sp++ = rt->current_exception; - rt->current_exception = JS_UNINITIALIZED; - JS_FreeValueRT(rt, ctx->error_back_trace); - ctx->error_back_trace = JS_UNDEFINED; - pc = b->byte_code_buf + pos; - goto restart; - } - } - } - } - ret_val = JS_EXCEPTION; - /* the local variables are freed by the caller in the generator - case. Hence the label 'done' should never be reached in a - generator function. */ - if (b->func_kind != JS_FUNC_NORMAL) { - done_generator: - sf->cur_pc = pc; - sf->cur_sp = sp; - } else { - done: - if (unlikely(!list_empty(&sf->var_ref_list))) { - /* variable references reference the stack: must close them */ - close_var_refs(rt, sf); - } - /* free the local variables and stack */ - for(pval = local_buf; pval < sp; pval++) { - JS_FreeValue(ctx, *pval); - } - } - rt->current_stack_frame = sf->prev_frame; - return ret_val; -} - -JSValue JS_Call(JSContext *ctx, JSValue func_obj, JSValue this_obj, - int argc, JSValue *argv) -{ - return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, - argc, argv, JS_CALL_FLAG_COPY_ARGV); -} - -static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValue this_obj, - int argc, JSValue *argv) -{ - JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, - argc, argv, JS_CALL_FLAG_COPY_ARGV); - JS_FreeValue(ctx, func_obj); - return res; -} - -/* warning: the refcount of the context is not incremented. Return - NULL in case of exception (case of revoked proxy only) */ -static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValue func_obj) -{ - JSObject *p; - JSContext *realm; - - if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) - return ctx; - p = JS_VALUE_GET_OBJ(func_obj); - switch(p->class_id) { - case JS_CLASS_C_FUNCTION: - realm = p->u.cfunc.realm; - break; - case JS_CLASS_BYTECODE_FUNCTION: - case JS_CLASS_GENERATOR_FUNCTION: - case JS_CLASS_ASYNC_FUNCTION: - case JS_CLASS_ASYNC_GENERATOR_FUNCTION: - { - JSFunctionBytecode *b; - b = p->u.func.function_bytecode; - realm = b->realm; - } - break; - case JS_CLASS_PROXY: - { - JSProxyData *s = p->u.opaque; - if (!s) - return ctx; - if (s->is_revoked) { - JS_ThrowTypeErrorRevokedProxy(ctx); - return NULL; - } else { - realm = JS_GetFunctionRealm(ctx, s->target); - } - } - break; - case JS_CLASS_BOUND_FUNCTION: - { - JSBoundFunction *bf = p->u.bound_function; - realm = JS_GetFunctionRealm(ctx, bf->func_obj); - } - break; - default: - realm = ctx; - break; - } - return realm; -} - -static JSValue js_create_from_ctor(JSContext *ctx, JSValue ctor, - int class_id) -{ - JSValue proto, obj; - JSContext *realm; - - if (JS_IsUndefined(ctor)) { - proto = js_dup(ctx->class_proto[class_id]); - } else { - proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); - if (JS_IsException(proto)) - return proto; - if (!JS_IsObject(proto)) { - JS_FreeValue(ctx, proto); - realm = JS_GetFunctionRealm(ctx, ctor); - if (!realm) - return JS_EXCEPTION; - proto = js_dup(realm->class_proto[class_id]); - } - } - obj = JS_NewObjectProtoClass(ctx, proto, class_id); - JS_FreeValue(ctx, proto); - return obj; -} - -/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ -static JSValue JS_CallConstructorInternal(JSContext *ctx, - JSValue func_obj, - JSValue new_target, - int argc, JSValue *argv, int flags) -{ - JSObject *p; - JSFunctionBytecode *b; - - if (js_poll_interrupts(ctx)) - return JS_EXCEPTION; - flags |= JS_CALL_FLAG_CONSTRUCTOR; - if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) - goto not_a_function; - p = JS_VALUE_GET_OBJ(func_obj); - if (unlikely(!p->is_constructor)) - return JS_ThrowTypeError(ctx, "not a constructor"); - if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { - JSClassCall *call_func; - call_func = ctx->rt->class_array[p->class_id].call; - if (!call_func) { - not_a_function: - return JS_ThrowTypeError(ctx, "not a function"); - } - return call_func(ctx, func_obj, new_target, argc, - argv, flags); - } - - b = p->u.func.function_bytecode; - if (b->is_derived_class_constructor) { - return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); - } else { - JSValue obj, ret; - /* legacy constructor behavior */ - obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); - if (JS_IsException(obj)) - return JS_EXCEPTION; - ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); - if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || - JS_IsException(ret)) { - JS_FreeValue(ctx, obj); - return ret; - } else { - JS_FreeValue(ctx, ret); - return obj; - } - } -} - -JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj, - JSValue new_target, - int argc, JSValue *argv) -{ - return JS_CallConstructorInternal(ctx, func_obj, new_target, - argc, argv, - JS_CALL_FLAG_COPY_ARGV); -} - -JSValue JS_CallConstructor(JSContext *ctx, JSValue func_obj, - int argc, JSValue *argv) -{ - return JS_CallConstructorInternal(ctx, func_obj, func_obj, - argc, argv, - JS_CALL_FLAG_COPY_ARGV); -} - -JSValue JS_Invoke(JSContext *ctx, JSValue this_val, JSAtom atom, - int argc, JSValue *argv) -{ - JSValue func_obj; - func_obj = JS_GetProperty(ctx, this_val, atom); - if (JS_IsException(func_obj)) - return func_obj; - return JS_CallFree(ctx, func_obj, this_val, argc, argv); -} - -static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, - int argc, JSValue *argv) -{ - JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); - JS_FreeValue(ctx, this_val); - return res; -} - -/* JSAsyncFunctionState (used by generator and async functions) */ -static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, - JSValue func_obj, JSValue this_obj, - int argc, JSValue *argv) -{ - JSObject *p; - JSFunctionBytecode *b; - JSStackFrame *sf; - int local_count, i, arg_buf_len, n; - - sf = &s->frame; - init_list_head(&sf->var_ref_list); - p = JS_VALUE_GET_OBJ(func_obj); - b = p->u.func.function_bytecode; - sf->is_strict_mode = b->is_strict_mode; - sf->cur_pc = b->byte_code_buf; - arg_buf_len = max_int(b->arg_count, argc); - local_count = arg_buf_len + b->var_count + b->stack_size; - sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); - if (!sf->arg_buf) - return -1; - sf->cur_func = js_dup(func_obj); - s->this_val = js_dup(this_obj); - s->argc = argc; - sf->arg_count = arg_buf_len; - sf->var_buf = sf->arg_buf + arg_buf_len; - sf->cur_sp = sf->var_buf + b->var_count; - for(i = 0; i < argc; i++) - sf->arg_buf[i] = js_dup(argv[i]); - n = arg_buf_len + b->var_count; - for(i = argc; i < n; i++) - sf->arg_buf[i] = JS_UNDEFINED; - return 0; -} - -static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, - JS_MarkFunc *mark_func) -{ - JSStackFrame *sf; - JSValue *sp; - - sf = &s->frame; - JS_MarkValue(rt, sf->cur_func, mark_func); - JS_MarkValue(rt, s->this_val, mark_func); - if (sf->cur_sp) { - /* if the function is running, cur_sp is not known so we - cannot mark the stack. Marking the variables is not needed - because a running function cannot be part of a removable - cycle */ - for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) - JS_MarkValue(rt, *sp, mark_func); - } -} - -static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) -{ - JSStackFrame *sf; - JSValue *sp; - - sf = &s->frame; - - /* close the closure variables. */ - close_var_refs(rt, sf); - - if (sf->arg_buf) { - /* cannot free the function if it is running */ - assert(sf->cur_sp != NULL); - for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { - JS_FreeValueRT(rt, *sp); - } - js_free_rt(rt, sf->arg_buf); - } - JS_FreeValueRT(rt, sf->cur_func); - JS_FreeValueRT(rt, s->this_val); -} - -static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) -{ - JSValue func_obj; - - if (js_check_stack_overflow(ctx->rt, 0)) - return JS_ThrowStackOverflow(ctx); - - /* the tag does not matter provided it is not an object */ - func_obj = JS_MKPTR(JS_TAG_INT, s); - return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, - s->argc, s->frame.arg_buf, JS_CALL_FLAG_GENERATOR); -} - - -/* Generators */ - -typedef enum JSGeneratorStateEnum { - JS_GENERATOR_STATE_SUSPENDED_START, - JS_GENERATOR_STATE_SUSPENDED_YIELD, - JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, - JS_GENERATOR_STATE_EXECUTING, - JS_GENERATOR_STATE_COMPLETED, -} JSGeneratorStateEnum; - -typedef struct JSGeneratorData { - JSGeneratorStateEnum state; - JSAsyncFunctionState func_state; -} JSGeneratorData; - -static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) -{ - if (s->state == JS_GENERATOR_STATE_COMPLETED) - return; - async_func_free(rt, &s->func_state); - s->state = JS_GENERATOR_STATE_COMPLETED; -} - -static void js_generator_finalizer(JSRuntime *rt, JSValue obj) -{ - JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); - - if (s) { - free_generator_stack_rt(rt, s); - js_free_rt(rt, s); - } -} - -static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) -{ - free_generator_stack_rt(ctx->rt, s); -} - -static void js_generator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSGeneratorData *s = p->u.generator_data; - - if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) - return; - async_func_mark(rt, &s->func_state, mark_func); -} - -/* XXX: use enum */ -#define GEN_MAGIC_NEXT 0 -#define GEN_MAGIC_RETURN 1 -#define GEN_MAGIC_THROW 2 - -static JSValue js_generator_next(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, - int *pdone, int magic) -{ - JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); - JSStackFrame *sf; - JSValue ret, func_ret; - - *pdone = true; - if (!s) - return JS_ThrowTypeError(ctx, "not a generator"); - sf = &s->func_state.frame; - switch(s->state) { - default: - case JS_GENERATOR_STATE_SUSPENDED_START: - if (magic == GEN_MAGIC_NEXT) { - goto exec_no_arg; - } else { - free_generator_stack(ctx, s); - goto done; - } - break; - case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: - case JS_GENERATOR_STATE_SUSPENDED_YIELD: - /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ - ret = js_dup(argv[0]); - if (magic == GEN_MAGIC_THROW && - s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { - JS_Throw(ctx, ret); - s->func_state.throw_flag = true; - } else { - sf->cur_sp[-1] = ret; - sf->cur_sp[0] = js_int32(magic); - sf->cur_sp++; - exec_no_arg: - s->func_state.throw_flag = false; - } - s->state = JS_GENERATOR_STATE_EXECUTING; - func_ret = async_func_resume(ctx, &s->func_state); - s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; - if (JS_IsException(func_ret)) { - /* finalize the execution in case of exception */ - free_generator_stack(ctx, s); - return func_ret; - } - if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { - /* get the returned yield value at the top of the stack */ - ret = sf->cur_sp[-1]; - sf->cur_sp[-1] = JS_UNDEFINED; - if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { - s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; - /* return (value, done) object */ - *pdone = 2; - } else { - *pdone = false; - } - } else { - /* end of iterator */ - ret = sf->cur_sp[-1]; - sf->cur_sp[-1] = JS_UNDEFINED; - JS_FreeValue(ctx, func_ret); - free_generator_stack(ctx, s); - } - break; - case JS_GENERATOR_STATE_COMPLETED: - done: - /* execution is finished */ - switch(magic) { - default: - case GEN_MAGIC_NEXT: - ret = JS_UNDEFINED; - break; - case GEN_MAGIC_RETURN: - ret = js_dup(argv[0]); - break; - case GEN_MAGIC_THROW: - ret = JS_Throw(ctx, js_dup(argv[0])); - break; - } - break; - case JS_GENERATOR_STATE_EXECUTING: - ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); - break; - } - return ret; -} - -static JSValue js_generator_function_call(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, - int flags) -{ - JSValue obj, func_ret; - JSGeneratorData *s; - - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->state = JS_GENERATOR_STATE_SUSPENDED_START; - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - s->state = JS_GENERATOR_STATE_COMPLETED; - goto fail; - } - - /* execute the function up to 'OP_initial_yield' */ - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) - goto fail; - JS_FreeValue(ctx, func_ret); - - obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); - if (JS_IsException(obj)) - goto fail; - JS_SetOpaqueInternal(obj, s); - return obj; - fail: - free_generator_stack_rt(ctx->rt, s); - js_free(ctx, s); - return JS_EXCEPTION; -} - -/* AsyncFunction */ - -static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) -{ - if (s->is_active) { - async_func_free(rt, &s->func_state); - s->is_active = false; - } -} - -static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) -{ - js_async_function_terminate(rt, s); - JS_FreeValueRT(rt, s->resolving_funcs[0]); - JS_FreeValueRT(rt, s->resolving_funcs[1]); - remove_gc_object(&s->header); - js_free_rt(rt, s); -} - -static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) -{ - if (--s->header.ref_count == 0) { - js_async_function_free0(rt, s); - } -} - -static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAsyncFunctionData *s = p->u.async_function_data; - if (s) { - js_async_function_free(rt, s); - } -} - -static void js_async_function_resolve_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSObject *p = JS_VALUE_GET_OBJ(val); - JSAsyncFunctionData *s = p->u.async_function_data; - if (s) { - mark_func(rt, &s->header); - } -} - -static int js_async_function_resolve_create(JSContext *ctx, - JSAsyncFunctionData *s, - JSValue *resolving_funcs) -{ - int i; - JSObject *p; - - for(i = 0; i < 2; i++) { - resolving_funcs[i] = - JS_NewObjectProtoClass(ctx, ctx->function_proto, - JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); - if (JS_IsException(resolving_funcs[i])) { - if (i == 1) - JS_FreeValue(ctx, resolving_funcs[0]); - return -1; - } - p = JS_VALUE_GET_OBJ(resolving_funcs[i]); - s->header.ref_count++; - p->u.async_function_data = s; - } - return 0; -} - -static bool js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) -{ - bool is_success = true; - JSValue func_ret, ret2; - - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) { - fail: - if (unlikely(JS_IsUncatchableError(ctx, ctx->rt->current_exception))) { - is_success = false; - } else { - JSValue error = JS_GetException(ctx); - ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, 1, &error); - JS_FreeValue(ctx, error); - resolved: - if (unlikely(JS_IsException(ret2))) { - if (JS_IsUncatchableError(ctx, ctx->rt->current_exception)) { - is_success = false; - } else { - abort(); /* BUG */ - } - } - JS_FreeValue(ctx, ret2); - } - js_async_function_terminate(ctx->rt, s); - } else { - JSValue value; - value = s->func_state.frame.cur_sp[-1]; - s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; - if (JS_IsUndefined(func_ret)) { - /* function returned */ - ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, - 1, &value); - JS_FreeValue(ctx, value); - goto resolved; - } else { - JSValue promise, resolving_funcs[2], resolving_funcs1[2]; - int i, res; - - /* await */ - JS_FreeValue(ctx, func_ret); /* not used */ - promise = js_promise_resolve(ctx, ctx->promise_ctor, - 1, &value, 0); - JS_FreeValue(ctx, value); - if (JS_IsException(promise)) - goto fail; - if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { - JS_FreeValue(ctx, promise); - goto fail; - } - - /* Note: no need to create 'thrownawayCapability' as in - the spec */ - for(i = 0; i < 2; i++) - resolving_funcs1[i] = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - resolving_funcs, - resolving_funcs1); - JS_FreeValue(ctx, promise); - for(i = 0; i < 2; i++) - JS_FreeValue(ctx, resolving_funcs[i]); - if (res) - goto fail; - } - } - return is_success; -} - -static JSValue js_async_function_resolve_call(JSContext *ctx, - JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, - int flags) -{ - JSObject *p = JS_VALUE_GET_OBJ(func_obj); - JSAsyncFunctionData *s = p->u.async_function_data; - bool is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; - JSValue arg; - - if (argc > 0) - arg = argv[0]; - else - arg = JS_UNDEFINED; - s->func_state.throw_flag = is_reject; - if (is_reject) { - JS_Throw(ctx, js_dup(arg)); - } else { - /* return value of await */ - s->func_state.frame.cur_sp[-1] = js_dup(arg); - } - if (!js_async_function_resume(ctx, s)) - return JS_EXCEPTION; - return JS_UNDEFINED; -} - -static JSValue js_async_function_call(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, int flags) -{ - JSValue promise; - JSAsyncFunctionData *s; - - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->header.ref_count = 1; - add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); - s->is_active = false; - s->resolving_funcs[0] = JS_UNDEFINED; - s->resolving_funcs[1] = JS_UNDEFINED; - - promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); - if (JS_IsException(promise)) - goto fail; - - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - fail: - JS_FreeValue(ctx, promise); - js_async_function_free(ctx->rt, s); - return JS_EXCEPTION; - } - s->is_active = true; - - if (!js_async_function_resume(ctx, s)) - goto fail; - - js_async_function_free(ctx->rt, s); - - return promise; -} - -/* AsyncGenerator */ - -typedef enum JSAsyncGeneratorStateEnum { - JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, - JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, - JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, - JS_ASYNC_GENERATOR_STATE_EXECUTING, - JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, - JS_ASYNC_GENERATOR_STATE_COMPLETED, -} JSAsyncGeneratorStateEnum; - -typedef struct JSAsyncGeneratorRequest { - struct list_head link; - /* completion */ - int completion_type; /* GEN_MAGIC_x */ - JSValue result; - /* promise capability */ - JSValue promise; - JSValue resolving_funcs[2]; -} JSAsyncGeneratorRequest; - -typedef struct JSAsyncGeneratorData { - JSObject *generator; /* back pointer to the object (const) */ - JSAsyncGeneratorStateEnum state; - JSAsyncFunctionState func_state; - struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ -} JSAsyncGeneratorData; - -static void js_async_generator_free(JSRuntime *rt, - JSAsyncGeneratorData *s) -{ - struct list_head *el, *el1; - JSAsyncGeneratorRequest *req; - - list_for_each_safe(el, el1, &s->queue) { - req = list_entry(el, JSAsyncGeneratorRequest, link); - JS_FreeValueRT(rt, req->result); - JS_FreeValueRT(rt, req->promise); - JS_FreeValueRT(rt, req->resolving_funcs[0]); - JS_FreeValueRT(rt, req->resolving_funcs[1]); - js_free_rt(rt, req); - } - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && - s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { - async_func_free(rt, &s->func_state); - } - js_free_rt(rt, s); -} - -static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj) -{ - JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); - - if (s) { - js_async_generator_free(rt, s); - } -} - -static void js_async_generator_mark(JSRuntime *rt, JSValue val, - JS_MarkFunc *mark_func) -{ - JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); - struct list_head *el; - JSAsyncGeneratorRequest *req; - if (s) { - list_for_each(el, &s->queue) { - req = list_entry(el, JSAsyncGeneratorRequest, link); - JS_MarkValue(rt, req->result, mark_func); - JS_MarkValue(rt, req->promise, mark_func); - JS_MarkValue(rt, req->resolving_funcs[0], mark_func); - JS_MarkValue(rt, req->resolving_funcs[1], mark_func); - } - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && - s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { - async_func_mark(rt, &s->func_state, mark_func); - } - } -} - -static JSValue js_async_generator_resolve_function(JSContext *ctx, - JSValue this_obj, - int argc, JSValue *argv, - int magic, JSValue *func_data); - -static int js_async_generator_resolve_function_create(JSContext *ctx, - JSValue generator, - JSValue *resolving_funcs, - bool is_resume_next) -{ - int i; - JSValue func; - - for(i = 0; i < 2; i++) { - func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, - i + is_resume_next * 2, 1, &generator); - if (JS_IsException(func)) { - if (i == 1) - JS_FreeValue(ctx, resolving_funcs[0]); - return -1; - } - resolving_funcs[i] = func; - } - return 0; -} - -static int js_async_generator_await(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue value) -{ - JSValue promise, resolving_funcs[2], resolving_funcs1[2]; - int i, res; - - promise = js_promise_resolve(ctx, ctx->promise_ctor, - 1, &value, 0); - if (JS_IsException(promise)) - goto fail; - - if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), - resolving_funcs, false)) { - JS_FreeValue(ctx, promise); - goto fail; - } - - /* Note: no need to create 'thrownawayCapability' as in - the spec */ - for(i = 0; i < 2; i++) - resolving_funcs1[i] = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - resolving_funcs, - resolving_funcs1); - JS_FreeValue(ctx, promise); - for(i = 0; i < 2; i++) - JS_FreeValue(ctx, resolving_funcs[i]); - if (res) - goto fail; - return 0; - fail: - return -1; -} - -static void js_async_generator_resolve_or_reject(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue result, - int is_reject) -{ - JSAsyncGeneratorRequest *next; - JSValue ret; - - next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); - list_del(&next->link); - ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, - &result); - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, next->result); - JS_FreeValue(ctx, next->promise); - JS_FreeValue(ctx, next->resolving_funcs[0]); - JS_FreeValue(ctx, next->resolving_funcs[1]); - js_free(ctx, next); -} - -static void js_async_generator_resolve(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue value, - bool done) -{ - JSValue result; - result = js_create_iterator_result(ctx, js_dup(value), done); - /* XXX: better exception handling ? */ - js_async_generator_resolve_or_reject(ctx, s, result, 0); - JS_FreeValue(ctx, result); - } - -static void js_async_generator_reject(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue exception) -{ - js_async_generator_resolve_or_reject(ctx, s, exception, 1); -} - -static void js_async_generator_complete(JSContext *ctx, - JSAsyncGeneratorData *s) -{ - if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - async_func_free(ctx->rt, &s->func_state); - } -} - -static int js_async_generator_completed_return(JSContext *ctx, - JSAsyncGeneratorData *s, - JSValue value) -{ - JSValue promise, resolving_funcs[2], resolving_funcs1[2]; - int res; - - // Can fail looking up JS_ATOM_constructor when is_reject==0. - promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, - /*is_reject*/0); - // A poisoned .constructor property is observable and the resulting - // exception should be delivered to the catch handler. - if (JS_IsException(promise)) { - JSValue err = JS_GetException(ctx); - promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &err, - /*is_reject*/1); - JS_FreeValue(ctx, err); - if (JS_IsException(promise)) - return -1; - } - if (js_async_generator_resolve_function_create(ctx, - JS_MKPTR(JS_TAG_OBJECT, s->generator), - resolving_funcs1, - true)) { - JS_FreeValue(ctx, promise); - return -1; - } - resolving_funcs[0] = JS_UNDEFINED; - resolving_funcs[1] = JS_UNDEFINED; - res = perform_promise_then(ctx, promise, - resolving_funcs1, - resolving_funcs); - JS_FreeValue(ctx, resolving_funcs1[0]); - JS_FreeValue(ctx, resolving_funcs1[1]); - JS_FreeValue(ctx, promise); - return res; -} - -static void js_async_generator_resume_next(JSContext *ctx, - JSAsyncGeneratorData *s) -{ - JSAsyncGeneratorRequest *next; - JSValue func_ret, value; - - for(;;) { - if (list_empty(&s->queue)) - break; - next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); - switch(s->state) { - case JS_ASYNC_GENERATOR_STATE_EXECUTING: - /* only happens when restarting execution after await() */ - goto resume_exec; - case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: - goto done; - case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: - if (next->completion_type == GEN_MAGIC_NEXT) { - goto exec_no_arg; - } else { - js_async_generator_complete(ctx, s); - } - break; - case JS_ASYNC_GENERATOR_STATE_COMPLETED: - if (next->completion_type == GEN_MAGIC_NEXT) { - js_async_generator_resolve(ctx, s, JS_UNDEFINED, true); - } else if (next->completion_type == GEN_MAGIC_RETURN) { - s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; - js_async_generator_completed_return(ctx, s, next->result); - } else { - js_async_generator_reject(ctx, s, next->result); - } - goto done; - case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: - case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: - value = js_dup(next->result); - if (next->completion_type == GEN_MAGIC_THROW && - s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { - JS_Throw(ctx, value); - s->func_state.throw_flag = true; - } else { - /* 'yield' returns a value. 'yield *' also returns a value - in case the 'throw' method is called */ - s->func_state.frame.cur_sp[-1] = value; - s->func_state.frame.cur_sp[0] = - js_int32(next->completion_type); - s->func_state.frame.cur_sp++; - exec_no_arg: - s->func_state.throw_flag = false; - } - s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; - resume_exec: - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) { - value = JS_GetException(ctx); - js_async_generator_complete(ctx, s); - js_async_generator_reject(ctx, s, value); - JS_FreeValue(ctx, value); - } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { - int func_ret_code, ret; - value = s->func_state.frame.cur_sp[-1]; - s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; - func_ret_code = JS_VALUE_GET_INT(func_ret); - switch(func_ret_code) { - case FUNC_RET_YIELD: - case FUNC_RET_YIELD_STAR: - if (func_ret_code == FUNC_RET_YIELD_STAR) - s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; - else - s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; - js_async_generator_resolve(ctx, s, value, false); - JS_FreeValue(ctx, value); - break; - case FUNC_RET_AWAIT: - ret = js_async_generator_await(ctx, s, value); - JS_FreeValue(ctx, value); - if (ret < 0) { - /* exception: throw it */ - s->func_state.throw_flag = true; - goto resume_exec; - } - goto done; - default: - abort(); - } - } else { - assert(JS_IsUndefined(func_ret)); - /* end of function */ - value = s->func_state.frame.cur_sp[-1]; - s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; - js_async_generator_complete(ctx, s); - js_async_generator_resolve(ctx, s, value, true); - JS_FreeValue(ctx, value); - } - break; - default: - abort(); - } - } - done: ; -} - -static JSValue js_async_generator_resolve_function(JSContext *ctx, - JSValue this_obj, - int argc, JSValue *argv, - int magic, JSValue *func_data) -{ - bool is_reject = magic & 1; - JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); - JSValue arg = argv[0]; - - /* XXX: what if s == NULL */ - - if (magic >= 2) { - /* resume next case in AWAITING_RETURN state */ - assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || - s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - if (is_reject) { - js_async_generator_reject(ctx, s, arg); - } else { - js_async_generator_resolve(ctx, s, arg, true); - } - } else { - /* restart function execution after await() */ - assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); - s->func_state.throw_flag = is_reject; - if (is_reject) { - JS_Throw(ctx, js_dup(arg)); - } else { - /* return value of await */ - s->func_state.frame.cur_sp[-1] = js_dup(arg); - } - js_async_generator_resume_next(ctx, s); - } - return JS_UNDEFINED; -} - -/* magic = GEN_MAGIC_x */ -static JSValue js_async_generator_next(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv, - int magic) -{ - JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); - JSValue promise, resolving_funcs[2]; - JSAsyncGeneratorRequest *req; - - promise = JS_NewPromiseCapability(ctx, resolving_funcs); - if (JS_IsException(promise)) - return JS_EXCEPTION; - if (!s) { - JSValue err, res2; - JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); - err = JS_GetException(ctx); - res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, - 1, &err); - JS_FreeValue(ctx, err); - JS_FreeValue(ctx, res2); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return promise; - } - req = js_mallocz(ctx, sizeof(*req)); - if (!req) - goto fail; - req->completion_type = magic; - req->result = js_dup(argv[0]); - req->promise = js_dup(promise); - req->resolving_funcs[0] = resolving_funcs[0]; - req->resolving_funcs[1] = resolving_funcs[1]; - list_add_tail(&req->link, &s->queue); - if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { - js_async_generator_resume_next(ctx, s); - } - return promise; - fail: - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - JS_FreeValue(ctx, promise); - return JS_EXCEPTION; -} - -static JSValue js_async_generator_function_call(JSContext *ctx, JSValue func_obj, - JSValue this_obj, - int argc, JSValue *argv, - int flags) -{ - JSValue obj, func_ret; - JSAsyncGeneratorData *s; - - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - return JS_EXCEPTION; - s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; - init_list_head(&s->queue); - if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { - s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; - goto fail; - } - - /* execute the function up to 'OP_initial_yield' (no yield nor - await are possible) */ - func_ret = async_func_resume(ctx, &s->func_state); - if (JS_IsException(func_ret)) - goto fail; - JS_FreeValue(ctx, func_ret); - - obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); - if (JS_IsException(obj)) - goto fail; - s->generator = JS_VALUE_GET_OBJ(obj); - JS_SetOpaqueInternal(obj, s); - return obj; - fail: - js_async_generator_free(ctx->rt, s); - return JS_EXCEPTION; -} - -/* JS parser */ - -enum { - TOK_NUMBER = -128, - TOK_STRING, - TOK_TEMPLATE, - TOK_IDENT, - TOK_REGEXP, - /* warning: order matters (see js_parse_assign_expr) */ - TOK_MUL_ASSIGN, - TOK_DIV_ASSIGN, - TOK_MOD_ASSIGN, - TOK_PLUS_ASSIGN, - TOK_MINUS_ASSIGN, - TOK_SHL_ASSIGN, - TOK_SAR_ASSIGN, - TOK_SHR_ASSIGN, - TOK_AND_ASSIGN, - TOK_XOR_ASSIGN, - TOK_OR_ASSIGN, - TOK_POW_ASSIGN, - TOK_LAND_ASSIGN, - TOK_LOR_ASSIGN, - TOK_DOUBLE_QUESTION_MARK_ASSIGN, - TOK_DEC, - TOK_INC, - TOK_SHL, - TOK_SAR, - TOK_SHR, - TOK_LT, - TOK_LTE, - TOK_GT, - TOK_GTE, - TOK_EQ, - TOK_STRICT_EQ, - TOK_NEQ, - TOK_STRICT_NEQ, - TOK_LAND, - TOK_LOR, - TOK_POW, - TOK_ARROW, - TOK_ELLIPSIS, - TOK_DOUBLE_QUESTION_MARK, - TOK_QUESTION_MARK_DOT, - TOK_ERROR, - TOK_PRIVATE_NAME, - TOK_EOF, - /* keywords: WARNING: same order as atoms */ - TOK_NULL, /* must be first */ - TOK_FALSE, - TOK_TRUE, - TOK_IF, - TOK_ELSE, - TOK_RETURN, - TOK_VAR, - TOK_THIS, - TOK_DELETE, - TOK_VOID, - TOK_TYPEOF, - TOK_NEW, - TOK_IN, - TOK_INSTANCEOF, - TOK_DO, - TOK_WHILE, - TOK_FOR, - TOK_BREAK, - TOK_CONTINUE, - TOK_SWITCH, - TOK_CASE, - TOK_DEFAULT, - TOK_THROW, - TOK_TRY, - TOK_CATCH, - TOK_FINALLY, - TOK_FUNCTION, - TOK_DEBUGGER, - TOK_WITH, - /* FutureReservedWord */ - TOK_CLASS, - TOK_CONST, - TOK_ENUM, - TOK_EXPORT, - TOK_EXTENDS, - TOK_IMPORT, - TOK_SUPER, - /* FutureReservedWords when parsing strict mode code */ - TOK_IMPLEMENTS, - TOK_INTERFACE, - TOK_LET, - TOK_PACKAGE, - TOK_PRIVATE, - TOK_PROTECTED, - TOK_PUBLIC, - TOK_STATIC, - TOK_YIELD, - TOK_AWAIT, /* must be last */ - TOK_OF, /* only used for js_parse_skip_parens_token() */ -}; - -#define TOK_FIRST_KEYWORD TOK_NULL -#define TOK_LAST_KEYWORD TOK_AWAIT - -/* unicode code points */ -#define CP_NBSP 0x00a0 -#define CP_BOM 0xfeff - -#define CP_LS 0x2028 -#define CP_PS 0x2029 - -typedef struct BlockEnv { - struct BlockEnv *prev; - JSAtom label_name; /* JS_ATOM_NULL if none */ - int label_break; /* -1 if none */ - int label_cont; /* -1 if none */ - int drop_count; /* number of stack elements to drop */ - int label_finally; /* -1 if none */ - int scope_level; - uint8_t has_iterator : 1; - uint8_t is_regular_stmt : 1; // i.e. not a loop statement -} BlockEnv; - -typedef struct JSGlobalVar { - int cpool_idx; /* if >= 0, index in the constant pool for hoisted - function defintion*/ - uint8_t force_init : 1; /* force initialization to undefined */ - uint8_t is_lexical : 1; /* global let/const definition */ - uint8_t is_const : 1; /* const definition */ - int scope_level; /* scope of definition */ - JSAtom var_name; /* variable name */ -} JSGlobalVar; - -typedef struct RelocEntry { - struct RelocEntry *next; - uint32_t addr; /* address to patch */ - int size; /* address size: 1, 2 or 4 bytes */ -} RelocEntry; - -typedef struct JumpSlot { - int op; - int size; - int pos; - int label; -} JumpSlot; - -typedef struct LabelSlot { - int ref_count; - int pos; /* phase 1 address, -1 means not resolved yet */ - int pos2; /* phase 2 address, -1 means not resolved yet */ - int addr; /* phase 3 address, -1 means not resolved yet */ - RelocEntry *first_reloc; -} LabelSlot; - -typedef struct SourceLocSlot { - uint32_t pc; - int line_num; - int col_num; -} SourceLocSlot; - -typedef enum JSParseFunctionEnum { - JS_PARSE_FUNC_STATEMENT, - JS_PARSE_FUNC_VAR, - JS_PARSE_FUNC_EXPR, - JS_PARSE_FUNC_ARROW, - JS_PARSE_FUNC_GETTER, - JS_PARSE_FUNC_SETTER, - JS_PARSE_FUNC_METHOD, - JS_PARSE_FUNC_CLASS_STATIC_INIT, - JS_PARSE_FUNC_CLASS_CONSTRUCTOR, - JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, -} JSParseFunctionEnum; - -typedef enum JSParseExportEnum { - JS_PARSE_EXPORT_NONE, - JS_PARSE_EXPORT_NAMED, - JS_PARSE_EXPORT_DEFAULT, -} JSParseExportEnum; - -typedef struct JSFunctionDef { - JSContext *ctx; - struct JSFunctionDef *parent; - int parent_cpool_idx; /* index in the constant pool of the parent - or -1 if none */ - int parent_scope_level; /* scope level in parent at point of definition */ - struct list_head child_list; /* list of JSFunctionDef.link */ - struct list_head link; - - bool is_eval; /* true if eval code */ - int eval_type; /* only valid if is_eval = true */ - bool is_global_var; /* true if variables are not defined locally: - eval global, eval module or non strict eval */ - bool is_func_expr; /* true if function expression */ - bool has_home_object; /* true if the home object is available */ - bool has_prototype; /* true if a prototype field is necessary */ - bool has_simple_parameter_list; - bool has_parameter_expressions; /* if true, an argument scope is created */ - bool has_use_strict; /* to reject directive in special cases */ - bool has_eval_call; /* true if the function contains a call to eval() */ - bool has_arguments_binding; /* true if the 'arguments' binding is - available in the function */ - bool has_this_binding; /* true if the 'this' and new.target binding are - available in the function */ - bool new_target_allowed; /* true if the 'new.target' does not - throw a syntax error */ - bool super_call_allowed; /* true if super() is allowed */ - bool super_allowed; /* true if super. or super[] is allowed */ - bool arguments_allowed; /* true if the 'arguments' identifier is allowed */ - bool is_derived_class_constructor; - bool in_function_body; - bool backtrace_barrier; - JSFunctionKindEnum func_kind : 8; - JSParseFunctionEnum func_type : 7; - uint8_t is_strict_mode : 1; - JSAtom func_name; /* JS_ATOM_NULL if no name */ - - JSVarDef *vars; - uint32_t *vars_htab; // indexes into vars[] - int var_size; /* allocated size for vars[] */ - int var_count; - JSVarDef *args; - int arg_size; /* allocated size for args[] */ - int arg_count; /* number of arguments */ - int defined_arg_count; - int var_object_idx; /* -1 if none */ - int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ - int arguments_var_idx; /* -1 if none */ - int arguments_arg_idx; /* argument variable definition in argument scope, - -1 if none */ - int func_var_idx; /* variable containing the current function (-1 - if none, only used if is_func_expr is true) */ - int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ - int this_var_idx; /* variable containg the 'this' value, -1 if none */ - int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ - int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ - int home_object_var_idx; - bool need_home_object; - - int scope_level; /* index into fd->scopes if the current lexical scope */ - int scope_first; /* index into vd->vars of first lexically scoped variable */ - int scope_size; /* allocated size of fd->scopes array */ - int scope_count; /* number of entries used in the fd->scopes array */ - JSVarScope *scopes; - JSVarScope def_scope_array[4]; - int body_scope; /* scope of the body of the function or eval */ - - int global_var_count; - int global_var_size; - JSGlobalVar *global_vars; - - DynBuf byte_code; - int last_opcode_pos; /* -1 if no last opcode */ - bool use_short_opcodes; /* true if short opcodes are used in byte_code */ - - LabelSlot *label_slots; - int label_size; /* allocated size for label_slots[] */ - int label_count; - BlockEnv *top_break; /* break/continue label stack */ - - /* constant pool (strings, functions, numbers) */ - JSValue *cpool; - int cpool_count; - int cpool_size; - - /* list of variables in the closure */ - int closure_var_count; - int closure_var_size; - JSClosureVar *closure_var; - - JumpSlot *jump_slots; - int jump_size; - int jump_count; - - SourceLocSlot *source_loc_slots; - int source_loc_size; - int source_loc_count; - int line_number_last; - int line_number_last_pc; - int col_number_last; - - /* pc2line table */ - JSAtom filename; - int line_num; - int col_num; - DynBuf pc2line; - - char *source; /* raw source, utf-8 encoded */ - int source_len; - - JSModuleDef *module; /* != NULL when parsing a module */ - bool has_await; /* true if await is used (used in module eval) */ - JSInlineCache *ic; /* inline cache for field op */ -} JSFunctionDef; - -typedef struct JSToken { - int val; - int line_num; /* line number of token start */ - int col_num; /* column number of token start */ - const uint8_t *ptr; - union { - struct { - JSValue str; - int sep; - } str; - struct { - JSValue val; - } num; - struct { - JSAtom atom; - bool has_escape; - bool is_reserved; - } ident; - struct { - JSValue body; - JSValue flags; - } regexp; - } u; -} JSToken; - -typedef struct JSParseState { - JSContext *ctx; - int last_line_num; /* line number of last token */ - int last_col_num; /* column number of last token */ - int line_num; /* line number of current offset */ - int col_num; /* column number of current offset */ - const char *filename; - JSToken token; - bool got_lf; /* true if got line feed before the current token */ - const uint8_t *last_ptr; - const uint8_t *buf_start; - const uint8_t *buf_ptr; - const uint8_t *buf_end; - const uint8_t *eol; // most recently seen end-of-line character - const uint8_t *mark; // first token character, invariant: eol < mark - - /* current function code */ - JSFunctionDef *cur_func; - bool is_module; /* parsing a module */ - bool allow_html_comments; -} JSParseState; - -typedef struct JSOpCode { -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* - const char *name; -#endif - uint8_t size; /* in bytes */ - /* the opcodes remove n_pop items from the top of the stack, then - pushes n_push items */ - uint8_t n_pop; - uint8_t n_push; - uint8_t fmt; -} JSOpCode; - -static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { -#define FMT(f) -#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_* -#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, -#else -#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, -#endif -#include "quickjs-opcode.h" -#undef DEF -#undef FMT -}; - -/* After the final compilation pass, short opcodes are used. Their - opcodes overlap with the temporary opcodes which cannot appear in - the final bytecode. Their description is after the temporary - opcodes in opcode_info[]. */ -#define short_opcode_info(op) \ - opcode_info[(op) >= OP_TEMP_START ? \ - (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] - -static __exception int next_token(JSParseState *s); - -static void free_token(JSParseState *s, JSToken *token) -{ - switch(token->val) { - case TOK_NUMBER: - JS_FreeValue(s->ctx, token->u.num.val); - break; - case TOK_STRING: - case TOK_TEMPLATE: - JS_FreeValue(s->ctx, token->u.str.str); - break; - case TOK_REGEXP: - JS_FreeValue(s->ctx, token->u.regexp.body); - JS_FreeValue(s->ctx, token->u.regexp.flags); - break; - case TOK_IDENT: - case TOK_PRIVATE_NAME: - JS_FreeAtom(s->ctx, token->u.ident.atom); - break; - default: - if (token->val >= TOK_FIRST_KEYWORD && - token->val <= TOK_LAST_KEYWORD) { - JS_FreeAtom(s->ctx, token->u.ident.atom); - } - break; - } -} - -static void __attribute((unused)) dump_token(JSParseState *s, - const JSToken *token) -{ - printf("%d:%d ", token->line_num, token->col_num); - switch(token->val) { - case TOK_NUMBER: - { - double d; - JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ - printf("number: %.14g\n", d); - } - break; - case TOK_IDENT: - dump_atom: - { - char buf[ATOM_GET_STR_BUF_SIZE]; - printf("ident: '%s'\n", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); - } - break; - case TOK_STRING: - { - const char *str; - /* XXX: quote the string */ - str = JS_ToCString(s->ctx, token->u.str.str); - printf("string: '%s'\n", str); - JS_FreeCString(s->ctx, str); - } - break; - case TOK_TEMPLATE: - { - const char *str; - str = JS_ToCString(s->ctx, token->u.str.str); - printf("template: `%s`\n", str); - JS_FreeCString(s->ctx, str); - } - break; - case TOK_REGEXP: - { - const char *str, *str2; - str = JS_ToCString(s->ctx, token->u.regexp.body); - str2 = JS_ToCString(s->ctx, token->u.regexp.flags); - printf("regexp: '%s' '%s'\n", str, str2); - JS_FreeCString(s->ctx, str); - JS_FreeCString(s->ctx, str2); - } - break; - case TOK_EOF: - printf("eof\n"); - break; - default: - if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { - goto dump_atom; - } else if (s->token.val >= 256) { - printf("token: %d\n", token->val); - } else { - printf("token: '%c'\n", token->val); - } - break; - } -} - -int JS_PRINTF_FORMAT_ATTR(2, 3) js_parse_error(JSParseState *s, JS_PRINTF_FORMAT const char *fmt, ...) -{ - JSContext *ctx = s->ctx; - va_list ap; - int backtrace_flags; - - va_start(ap, fmt); - JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, false); - va_end(ap); - backtrace_flags = 0; - if (s->cur_func && s->cur_func->backtrace_barrier) - backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; - build_backtrace(ctx, ctx->rt->current_exception, JS_UNDEFINED, s->filename, - s->line_num, s->col_num, backtrace_flags); - return -1; -} - -static int js_parse_expect(JSParseState *s, int tok) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - - if (s->token.val == tok) - return next_token(s); - - switch(s->token.val) { - case TOK_EOF: - return js_parse_error(s, "Unexpected end of input"); - case TOK_NUMBER: - return js_parse_error(s, "Unexpected number"); - case TOK_STRING: - return js_parse_error(s, "Unexpected string"); - case TOK_TEMPLATE: - return js_parse_error(s, "Unexpected string template"); - case TOK_REGEXP: - return js_parse_error(s, "Unexpected regexp"); - case TOK_IDENT: - return js_parse_error(s, "Unexpected identifier '%s'", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), - s->token.u.ident.atom)); - case TOK_ERROR: - return js_parse_error(s, "Invalid or unexpected token"); - default: - return js_parse_error(s, "Unexpected token '%.*s'", - (int)(s->buf_ptr - s->token.ptr), - (const char *)s->token.ptr); - } -} - -static int js_parse_expect_semi(JSParseState *s) -{ - if (s->token.val != ';') { - /* automatic insertion of ';' */ - if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { - return 0; - } - return js_parse_error(s, "expecting '%c'", ';'); - } - return next_token(s); -} - -static int js_parse_error_reserved_identifier(JSParseState *s) -{ - char buf1[ATOM_GET_STR_BUF_SIZE]; - return js_parse_error(s, "'%s' is a reserved identifier", - JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), - s->token.u.ident.atom)); -} - -static __exception int js_parse_template_part(JSParseState *s, - const uint8_t *p) -{ - const uint8_t *p_next; - uint32_t c; - StringBuffer b_s, *b = &b_s; - - /* p points to the first byte of the template part */ - if (string_buffer_init(s->ctx, b, 32)) - goto fail; - for(;;) { - if (p >= s->buf_end) - goto unexpected_eof; - c = *p++; - if (c == '`') { - /* template end part */ - break; - } - if (c == '$' && *p == '{') { - /* template start or middle part */ - p++; - break; - } - if (c == '\\') { - if (string_buffer_putc8(b, c)) - goto fail; - if (p >= s->buf_end) - goto unexpected_eof; - c = *p++; - } - /* newline sequences are normalized as single '\n' bytes */ - if (c == '\r') { - if (*p == '\n') - p++; - c = '\n'; - } - if (c == '\n') { - s->line_num++; - s->eol = &p[-1]; - s->mark = p; - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) { - js_parse_error(s, "invalid UTF-8 sequence"); - goto fail; - } - p = p_next; - } - if (string_buffer_putc(b, c)) - goto fail; - } - s->token.val = TOK_TEMPLATE; - s->token.u.str.sep = c; - s->token.u.str.str = string_buffer_end(b); - s->buf_ptr = p; - return 0; - - unexpected_eof: - js_parse_error(s, "unexpected end of string"); - fail: - string_buffer_free(b); - return -1; -} - -static __exception int js_parse_string(JSParseState *s, int sep, - bool do_throw, const uint8_t *p, - JSToken *token, const uint8_t **pp) -{ - const uint8_t *p_next; - int ret; - uint32_t c; - StringBuffer b_s, *b = &b_s; - - /* string */ - if (string_buffer_init(s->ctx, b, 32)) - goto fail; - for(;;) { - if (p >= s->buf_end) - goto invalid_char; - c = *p; - if (c < 0x20) { - if (sep == '`') { - if (c == '\r') { - if (p[1] == '\n') - p++; - c = '\n'; - } - /* do not update s->line_num */ - } else if (c == '\n' || c == '\r') - goto invalid_char; - } - p++; - if (c == sep) - break; - if (c == '$' && *p == '{' && sep == '`') { - /* template start or middle part */ - p++; - break; - } - if (c == '\\') { - c = *p; - switch(c) { - case '\0': - if (p >= s->buf_end) { - if (sep != '`') - goto invalid_char; - if (do_throw) - js_parse_error(s, "Unexpected end of input"); - goto fail; - } - p++; - break; - case '\'': - case '\"': - case '\\': - p++; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { - p++; - } - /* fall thru */ - case '\n': - /* ignore escaped newline sequence */ - p++; - if (sep != '`') { - s->line_num++; - s->eol = &p[-1]; - s->mark = p; - } - continue; - default: - if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { - /* accept isolated \0 */ - p++; - c = '\0'; - } else - if ((c >= '0' && c <= '9') - && (s->cur_func->is_strict_mode || sep == '`')) { - if (do_throw) { - js_parse_error(s, "%s are not allowed in %s", - (c >= '8') ? "\\8 and \\9" : "Octal escape sequences", - (sep == '`') ? "template strings" : "strict mode"); - } - goto fail; - } else if (c >= 0x80) { - c = utf8_decode(p, &p_next); - if (p_next == p + 1) { - goto invalid_utf8; - } - p = p_next; - /* LS or PS are skipped */ - if (c == CP_LS || c == CP_PS) - continue; - } else { - ret = lre_parse_escape(&p, true); - if (ret == -1) { - if (do_throw) { - js_parse_error(s, "Invalid %s escape sequence", - c == 'u' ? "Unicode" : "hexadecimal"); - } - goto fail; - } else if (ret < 0) { - /* ignore the '\' (could output a warning) */ - p++; - } else { - c = ret; - } - } - break; - } - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) - goto invalid_utf8; - p = p_next; - } - if (string_buffer_putc(b, c)) - goto fail; - } - token->val = TOK_STRING; - token->u.str.sep = c; - token->u.str.str = string_buffer_end(b); - *pp = p; - return 0; - - invalid_utf8: - if (do_throw) - js_parse_error(s, "invalid UTF-8 sequence"); - goto fail; - invalid_char: - if (do_throw) - js_parse_error(s, "unexpected end of string"); - fail: - string_buffer_free(b); - return -1; -} - -static inline bool token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { - return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && - !s->token.u.ident.has_escape; -} - -static __exception int js_parse_regexp(JSParseState *s) -{ - const uint8_t *p, *p_next; - bool in_class; - StringBuffer b_s, *b = &b_s; - StringBuffer b2_s, *b2 = &b2_s; - uint32_t c; - - p = s->buf_ptr; - p++; - in_class = false; - if (string_buffer_init(s->ctx, b, 32)) - return -1; - if (string_buffer_init(s->ctx, b2, 1)) - goto fail; - for(;;) { - if (p >= s->buf_end) { - eof_error: - js_parse_error(s, "unexpected end of regexp"); - goto fail; - } - c = *p++; - if (c == '\n' || c == '\r') { - goto eol_error; - } else if (c == '/') { - if (!in_class) - break; - } else if (c == '[') { - in_class = true; - } else if (c == ']') { - /* XXX: incorrect as the first character in a class */ - in_class = false; - } else if (c == '\\') { - if (string_buffer_putc8(b, c)) - goto fail; - c = *p++; - if (c == '\n' || c == '\r') - goto eol_error; - else if (c == '\0' && p >= s->buf_end) - goto eof_error; - else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) { - goto invalid_utf8; - } - p = p_next; - if (c == CP_LS || c == CP_PS) - goto eol_error; - } - } else if (c >= 0x80) { - c = utf8_decode(p - 1, &p_next); - if (p_next == p) { - invalid_utf8: - js_parse_error(s, "invalid UTF-8 sequence"); - goto fail; - } - p = p_next; - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - eol_error: - js_parse_error(s, "unexpected line terminator in regexp"); - goto fail; - } - } - if (string_buffer_putc(b, c)) - goto fail; - } - - /* flags */ - for(;;) { - c = utf8_decode(p, &p_next); - /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ - if (!lre_js_is_ident_next(c)) - break; - if (string_buffer_putc(b2, c)) - goto fail; - p = p_next; - } - - s->token.val = TOK_REGEXP; - s->token.u.regexp.body = string_buffer_end(b); - s->token.u.regexp.flags = string_buffer_end(b2); - s->buf_ptr = p; - return 0; - fail: - string_buffer_free(b); - string_buffer_free(b2); - return -1; -} - -static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, - char *static_buf) -{ - char *buf, *new_buf; - size_t size, new_size; - - buf = *pbuf; - size = *psize; - if (size >= (SIZE_MAX / 3) * 2) - new_size = SIZE_MAX; - else - new_size = size + (size >> 1); - if (buf == static_buf) { - new_buf = js_malloc(ctx, new_size); - if (!new_buf) - return -1; - memcpy(new_buf, buf, size); - } else { - new_buf = js_realloc(ctx, buf, new_size); - if (!new_buf) - return -1; - } - *pbuf = new_buf; - *psize = new_size; - return 0; -} - -/* convert a TOK_IDENT to a keyword when needed */ -static void update_token_ident(JSParseState *s) -{ - if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || - (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && - s->cur_func->is_strict_mode) || - (s->token.u.ident.atom == JS_ATOM_yield && - ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || - (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && - !s->cur_func->in_function_body && s->cur_func->parent && - (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || - (s->token.u.ident.atom == JS_ATOM_await && - (s->is_module || - (s->cur_func->func_kind & JS_FUNC_ASYNC) || - s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || - (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && - !s->cur_func->in_function_body && s->cur_func->parent && - ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || - s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { - if (s->token.u.ident.has_escape) { - s->token.u.ident.is_reserved = true; - s->token.val = TOK_IDENT; - } else { - /* The keywords atoms are pre allocated */ - s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; - } - } -} - -/* if the current token is an identifier or keyword, reparse it - according to the current function type */ -static void reparse_ident_token(JSParseState *s) -{ - if (s->token.val == TOK_IDENT || - (s->token.val >= TOK_FIRST_KEYWORD && - s->token.val <= TOK_LAST_KEYWORD)) { - s->token.val = TOK_IDENT; - s->token.u.ident.is_reserved = false; - update_token_ident(s); - } -} - -/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ -static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, - bool *pident_has_escape, int c, bool is_private) -{ - const uint8_t *p, *p_next; - char ident_buf[128], *buf; - size_t ident_size, ident_pos; - JSAtom atom = JS_ATOM_NULL; - - p = *pp; - buf = ident_buf; - ident_size = sizeof(ident_buf); - ident_pos = 0; - if (is_private) - buf[ident_pos++] = '#'; - for(;;) { - if (c < 0x80) { - buf[ident_pos++] = c; - } else { - ident_pos += utf8_encode((uint8_t*)buf + ident_pos, c); - } - c = *p; - p_next = p + 1; - if (c == '\\' && *p_next == 'u') { - c = lre_parse_escape(&p_next, true); - *pident_has_escape = true; - } else if (c >= 0x80) { - c = utf8_decode(p, &p_next); - /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ - } - if (!lre_js_is_ident_next(c)) - break; - p = p_next; - if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { - if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) - goto done; - } - } - /* buf is pure ASCII or UTF-8 encoded */ - atom = JS_NewAtomLen(s->ctx, buf, ident_pos); - done: - if (unlikely(buf != ident_buf)) - js_free(s->ctx, buf); - *pp = p; - return atom; -} - - -static __exception int next_token(JSParseState *s) -{ - const uint8_t *p, *p_next; - int c; - bool ident_has_escape; - JSAtom atom; - int flags, radix; - - if (js_check_stack_overflow(s->ctx->rt, 1000)) { - JS_ThrowStackOverflow(s->ctx); - return -1; - } - - free_token(s, &s->token); - - p = s->last_ptr = s->buf_ptr; - s->got_lf = false; - s->last_line_num = s->token.line_num; - s->last_col_num = s->token.col_num; - redo: - s->token.line_num = s->line_num; - s->token.col_num = s->col_num; - s->token.ptr = p; - c = *p; - switch(c) { - case 0: - if (p >= s->buf_end) { - s->token.val = TOK_EOF; - } else { - goto def_token; - } - break; - case '`': - if (js_parse_template_part(s, p + 1)) - goto fail; - p = s->buf_ptr; - break; - case '\'': - case '\"': - if (js_parse_string(s, c, true, p + 1, &s->token, &p)) - goto fail; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { - p++; - } - /* fall thru */ - case '\n': - p++; - line_terminator: - s->eol = &p[-1]; - s->mark = p; - s->got_lf = true; - s->line_num++; - goto redo; - case '\f': - case '\v': - case ' ': - case '\t': - s->mark = ++p; - goto redo; - case '/': - if (p[1] == '*') { - /* comment */ - p += 2; - for(;;) { - if (*p == '\0' && p >= s->buf_end) { - js_parse_error(s, "unexpected end of comment"); - goto fail; - } - if (p[0] == '*' && p[1] == '/') { - p += 2; - break; - } - if (*p == '\n') { - s->line_num++; - s->got_lf = true; /* considered as LF for ASI */ - s->eol = p++; - s->mark = p; - } else if (*p == '\r') { - s->got_lf = true; /* considered as LF for ASI */ - p++; - } else if (*p >= 0x80) { - c = utf8_decode(p, &p); - /* ignore invalid UTF-8 in comments */ - if (c == CP_LS || c == CP_PS) { - s->got_lf = true; /* considered as LF for ASI */ - } - } else { - p++; - } - } - s->mark = p; - goto redo; - } else if (p[1] == '/') { - /* line comment */ - p += 2; - skip_line_comment: - for(;;) { - if (*p == '\0' && p >= s->buf_end) - break; - if (*p == '\r' || *p == '\n') - break; - if (*p >= 0x80) { - c = utf8_decode(p, &p); - /* ignore invalid UTF-8 in comments */ - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - break; - } - } else { - p++; - } - } - s->mark = p; - goto redo; - } else if (p[1] == '=') { - p += 2; - s->token.val = TOK_DIV_ASSIGN; - } else { - p++; - s->token.val = c; - } - break; - case '\\': - if (p[1] == 'u') { - const uint8_t *p1 = p + 1; - int c1 = lre_parse_escape(&p1, true); - if (c1 >= 0 && lre_js_is_ident_first(c1)) { - c = c1; - p = p1; - ident_has_escape = true; - goto has_ident; - } else { - /* XXX: syntax error? */ - } - } - goto def_token; - case 'a': case 'b': case 'c': case 'd': - case 'e': case 'f': case 'g': case 'h': - case 'i': case 'j': case 'k': case 'l': - case 'm': case 'n': case 'o': case 'p': - case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': - case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': - case 'E': case 'F': case 'G': case 'H': - case 'I': case 'J': case 'K': case 'L': - case 'M': case 'N': case 'O': case 'P': - case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': - case 'Y': case 'Z': - case '_': - case '$': - /* identifier */ - s->mark = p; - p++; - ident_has_escape = false; - has_ident: - atom = parse_ident(s, &p, &ident_has_escape, c, false); - if (atom == JS_ATOM_NULL) - goto fail; - s->token.u.ident.atom = atom; - s->token.u.ident.has_escape = ident_has_escape; - s->token.u.ident.is_reserved = false; - s->token.val = TOK_IDENT; - update_token_ident(s); - break; - case '#': - /* private name */ - { - p++; - c = *p; - p_next = p + 1; - if (c == '\\' && *p_next == 'u') { - c = lre_parse_escape(&p_next, true); - } else if (c >= 0x80) { - c = utf8_decode(p, &p_next); - if (p_next == p + 1) - goto invalid_utf8; - } - if (!lre_js_is_ident_first(c)) { - js_parse_error(s, "invalid first character of private name"); - goto fail; - } - p = p_next; - ident_has_escape = false; /* not used */ - atom = parse_ident(s, &p, &ident_has_escape, c, true); - if (atom == JS_ATOM_NULL) - goto fail; - s->token.u.ident.atom = atom; - s->token.val = TOK_PRIVATE_NAME; - } - break; - case '.': - if (p[1] == '.' && p[2] == '.') { - p += 3; - s->token.val = TOK_ELLIPSIS; - break; - } - if (p[1] >= '0' && p[1] <= '9') { - flags = ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_FLOAT; - radix = 10; - goto parse_number; - } - goto def_token; - case '0': - if (is_digit(p[1])) { /* handle legacy octal */ - if (s->cur_func->is_strict_mode) { - js_parse_error(s, "Octal literals are not allowed in strict mode"); - goto fail; - } - /* Legacy octal: no separators, no suffix, no floats, - base 8 unless non octal digits are detected */ - flags = 0; - radix = 8; - while (is_digit(*p)) { - if (*p >= '8' && *p <= '9') - radix = 10; - p++; - } - p = s->token.ptr; - goto parse_number; - } - if (p[1] == '_') { - js_parse_error(s, "Numeric separator can not be used after leading 0"); - goto fail; - } - flags = ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | - ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; - radix = 10; - goto parse_number; - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': - case '9': - /* number */ - { - JSValue ret; - const char *p1; - - flags = ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; - radix = 10; - parse_number: - p1 = (const char *)p; - ret = js_atof(s->ctx, p1, s->buf_end - p, &p1, radix, flags); - p = (const uint8_t *)p1; - if (JS_IsException(ret)) - goto fail; - /* reject `10instanceof Number` */ - if (JS_VALUE_IS_NAN(ret) || - lre_js_is_ident_next(utf8_decode(p, &p_next))) { - JS_FreeValue(s->ctx, ret); - js_parse_error(s, "invalid number literal"); - goto fail; - } - s->token.val = TOK_NUMBER; - s->token.u.num.val = ret; - } - break; - case '*': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MUL_ASSIGN; - } else if (p[1] == '*') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_POW_ASSIGN; - } else { - p += 2; - s->token.val = TOK_POW; - } - } else { - goto def_token; - } - break; - case '%': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MOD_ASSIGN; - } else { - goto def_token; - } - break; - case '+': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_PLUS_ASSIGN; - } else if (p[1] == '+') { - p += 2; - s->token.val = TOK_INC; - } else { - goto def_token; - } - break; - case '-': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MINUS_ASSIGN; - } else if (p[1] == '-') { - if (s->allow_html_comments && p[2] == '>' && - (s->got_lf || s->last_ptr == s->buf_start)) { - /* Annex B: `-->` at beginning of line is an html comment end. - It extends to the end of the line. - */ - goto skip_line_comment; - } - p += 2; - s->token.val = TOK_DEC; - } else { - goto def_token; - } - break; - case '<': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_LTE; - } else if (p[1] == '<') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_SHL_ASSIGN; - } else { - p += 2; - s->token.val = TOK_SHL; - } - } else if (s->allow_html_comments && - p[1] == '!' && p[2] == '-' && p[3] == '-') { - /* Annex B: handle ` array, false --> object). + + @param[in] is_array Determines if the element list being read is to be + treated as an object (@a is_array == false), or as an + array (@a is_array == true). + @return whether a valid BSON-object/array was passed to the SAX parser + */ + bool parse_bson_element_list(const bool is_array) + { + string_t key; + + while (auto element_type = get()) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) + { + return false; + } + + const std::size_t element_type_parse_position = chars_read; + if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) + { + return false; + } + + if (!is_array && !sax->key(key)) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) + { + return false; + } + + // get_bson_cstr only appends + key.clear(); + } + + return true; + } + + /*! + @brief Reads an array from the BSON input and passes it to the SAX-parser. + @return whether a valid BSON-array was passed to the SAX parser + */ + bool parse_bson_array() + { + std::int32_t document_size{}; + get_number(input_format_t::bson, document_size); + + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) + { + return false; + } + + return sax->end_array(); + } + + ////////// + // CBOR // + ////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true) or whether the last read character should + be considered instead (false) + @param[in] tag_handler how CBOR tags should be treated + + @return whether a valid CBOR value was passed to the SAX parser + */ + + template + bool get_cbor_negative_integer() + { + NumberType number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::cbor, number))) + { + return false; + } + const auto max_val = static_cast((std::numeric_limits::max)()); + if (number > max_val) + { + return sax->parse_error(chars_read, get_token_string(), + parse_error::create(112, chars_read, + exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr)); + } + return sax->number_integer(static_cast(-1) - static_cast(number)); + } + + bool parse_cbor_internal(const bool get_char, + const cbor_tag_handler_t tag_handler) + { + switch (get_char ? get() : current) + { + // EOF + case char_traits::eof(): + return unexpect_eof(input_format_t::cbor, "value"); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return sax->number_unsigned(static_cast(current)); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + std::uint8_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + std::uint16_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + { + std::uint32_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + { + std::uint64_t number{}; + return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return sax->number_integer(static_cast(0x20 - 1 - current)); + + case 0x38: // Negative integer (one-byte uint8_t follows) + return get_cbor_negative_integer(); + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + return get_cbor_negative_integer(); + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + return get_cbor_negative_integer(); + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + return get_cbor_negative_integer(); + + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: // Binary data (one-byte uint8_t for n follows) + case 0x59: // Binary data (two-byte uint16_t for n follow) + case 0x5A: // Binary data (four-byte uint32_t for n follow) + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + case 0x5F: // Binary data (indefinite length) + { + binary_t b; + return get_cbor_binary(b) && sax->binary(b); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + string_t s; + return get_cbor_string(s) && sax->string(s); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + return get_cbor_array( + conditional_static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0x98: // array (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast(len), tag_handler); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast(len), tag_handler); + } + + case 0x9F: // array (indefinite length) + return get_cbor_array(detail::unknown_size(), tag_handler); + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + return get_cbor_object(conditional_static_cast(static_cast(current) & 0x1Fu), tag_handler); + + case 0xB8: // map (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast(len), tag_handler); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast(len), tag_handler); + } + + case 0xBF: // map (indefinite length) + return get_cbor_object(detail::unknown_size(), tag_handler); + + case 0xC6: // tagged item + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD8: // tagged item (1 byte follows) + case 0xD9: // tagged item (2 bytes follow) + case 0xDA: // tagged item (4 bytes follow) + case 0xDB: // tagged item (8 bytes follow) + { + switch (tag_handler) + { + case cbor_tag_handler_t::error: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::cbor, concat("invalid byte: 0x", last_token), "value"), nullptr)); + } + + case cbor_tag_handler_t::ignore: + { + // ignore binary subtype + switch (current) + { + case 0xD8: + { + std::uint8_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + case 0xD9: + { + std::uint16_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + case 0xDA: + { + std::uint32_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + case 0xDB: + { + std::uint64_t subtype_to_ignore{}; + get_number(input_format_t::cbor, subtype_to_ignore); + break; + } + default: + break; + } + return parse_cbor_internal(true, tag_handler); + } + + case cbor_tag_handler_t::store: + { + binary_t b; + // use binary subtype and store in a binary container + switch (current) + { + case 0xD8: + { + std::uint8_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xD9: + { + std::uint16_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xDA: + { + std::uint32_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + case 0xDB: + { + std::uint64_t subtype{}; + get_number(input_format_t::cbor, subtype); + b.set_subtype(detail::conditional_static_cast(subtype)); + break; + } + default: + return parse_cbor_internal(true, tag_handler); + } + get(); + return get_cbor_binary(b) && sax->binary(b); + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + case 0xF4: // false + return sax->boolean(false); + + case 0xF5: // true + return sax->boolean(true); + + case 0xF6: // null + return sax->null(); + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // Code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte1 << 8u) + byte2); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + float number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + double number{}; + return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); + } + + default: // anything else (0xFF is handled inside the other types) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::cbor, concat("invalid byte: 0x", last_token), "value"), nullptr)); + } + } + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_cbor_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) + { + return false; + } + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + while (get() != 0xFF) + { + string_t chunk; + if (!get_cbor_string(chunk)) + { + return false; + } + result.append(chunk); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format_t::cbor, concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr)); + } + } + } + + /*! + @brief reads a CBOR byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into the byte array. + Additionally, CBOR's byte arrays with indefinite lengths are supported. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_cbor_binary(binary_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) + { + return false; + } + + switch (current) + { + // Binary data (0x00..0x17 bytes follow) + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + { + return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); + } + + case 0x58: // Binary data (one-byte uint8_t for n follows) + { + std::uint8_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x59: // Binary data (two-byte uint16_t for n follow) + { + std::uint16_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5A: // Binary data (four-byte uint32_t for n follow) + { + std::uint32_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5B: // Binary data (eight-byte uint64_t for n follow) + { + std::uint64_t len{}; + return get_number(input_format_t::cbor, len) && + get_binary(input_format_t::cbor, len, result); + } + + case 0x5F: // Binary data (indefinite length) + { + while (get() != 0xFF) + { + binary_t chunk; + if (!get_cbor_binary(chunk)) + { + return false; + } + result.insert(result.end(), chunk.begin(), chunk.end()); + } + return true; + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format_t::cbor, concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr)); + } + } + } + + /*! + @param[in] len the length of the array or detail::unknown_size() for an + array of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether array creation completed + */ + bool get_cbor_array(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + if (len != detail::unknown_size()) + { + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) + { + return false; + } + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object or detail::unknown_size() for an + object of indefinite size + @param[in] tag_handler how CBOR tags should be treated + @return whether object creation completed + */ + bool get_cbor_object(const std::size_t len, + const cbor_tag_handler_t tag_handler) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + if (len != 0) + { + string_t key; + if (len != detail::unknown_size()) + { + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + else + { + while (get() != 0xFF) + { + if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) + { + return false; + } + key.clear(); + } + } + } + + return sax->end_object(); + } + + ///////////// + // MsgPack // + ///////////// + + /*! + @return whether a valid MessagePack value was passed to the SAX parser + */ + bool parse_msgpack_internal() + { + switch (get()) + { + // EOF + case char_traits::eof(): + return unexpect_eof(input_format_t::msgpack, "value"); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return sax->number_unsigned(static_cast(current)); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + return get_msgpack_object(conditional_static_cast(static_cast(current) & 0x0Fu)); + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + return get_msgpack_array(conditional_static_cast(static_cast(current) & 0x0Fu)); + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + { + string_t s; + return get_msgpack_string(s) && sax->string(s); + } + + case 0xC0: // nil + return sax->null(); + + case 0xC2: // false + return sax->boolean(false); + + case 0xC3: // true + return sax->boolean(true); + + case 0xC4: // bin 8 + case 0xC5: // bin 16 + case 0xC6: // bin 32 + case 0xC7: // ext 8 + case 0xC8: // ext 16 + case 0xC9: // ext 32 + case 0xD4: // fixext 1 + case 0xD5: // fixext 2 + case 0xD6: // fixext 4 + case 0xD7: // fixext 8 + case 0xD8: // fixext 16 + { + binary_t b; + return get_msgpack_binary(b) && sax->binary(b); + } + + case 0xCA: // float 32 + { + float number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCB: // float 64 + { + double number{}; + return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); + } + + case 0xCC: // uint 8 + { + std::uint8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCD: // uint 16 + { + std::uint16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCE: // uint 32 + { + std::uint32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xCF: // uint 64 + { + std::uint64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); + } + + case 0xD0: // int 8 + { + std::int8_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD1: // int 16 + { + std::int16_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD2: // int 32 + { + std::int32_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xD3: // int 64 + { + std::int64_t number{}; + return get_number(input_format_t::msgpack, number) && sax->number_integer(number); + } + + case 0xDC: // array 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); + } + + case 0xDD: // array 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_array(conditional_static_cast(len)); + } + + case 0xDE: // map 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); + } + + case 0xDF: // map 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_msgpack_object(conditional_static_cast(len)); + } + + // negative fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return sax->number_integer(static_cast(current)); + + default: // anything else + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format_t::msgpack, concat("invalid byte: 0x", last_token), "value"), nullptr)); + } + } + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @param[out] result created string + + @return whether string creation completed + */ + bool get_msgpack_string(string_t& result) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) + { + return false; + } + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); + } + + case 0xD9: // str 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDA: // str 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + case 0xDB: // str 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); + } + + default: + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format_t::msgpack, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", last_token), "string"), nullptr)); + } + } + } + + /*! + @brief reads a MessagePack byte array + + This function first reads starting bytes to determine the expected + byte array length and then copies this number of bytes into a byte array. + + @param[out] result created byte array + + @return whether byte array creation completed + */ + bool get_msgpack_binary(binary_t& result) + { + // helper function to set the subtype + auto assign_and_return_true = [&result](std::int8_t subtype) + { + result.set_subtype(static_cast(subtype)); + return true; + }; + + switch (current) + { + case 0xC4: // bin 8 + { + std::uint8_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC5: // bin 16 + { + std::uint16_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC6: // bin 32 + { + std::uint32_t len{}; + return get_number(input_format_t::msgpack, len) && + get_binary(input_format_t::msgpack, len, result); + } + + case 0xC7: // ext 8 + { + std::uint8_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC8: // ext 16 + { + std::uint16_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xC9: // ext 32 + { + std::uint32_t len{}; + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, len) && + get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, len, result) && + assign_and_return_true(subtype); + } + + case 0xD4: // fixext 1 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 1, result) && + assign_and_return_true(subtype); + } + + case 0xD5: // fixext 2 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 2, result) && + assign_and_return_true(subtype); + } + + case 0xD6: // fixext 4 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 4, result) && + assign_and_return_true(subtype); + } + + case 0xD7: // fixext 8 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 8, result) && + assign_and_return_true(subtype); + } + + case 0xD8: // fixext 16 + { + std::int8_t subtype{}; + return get_number(input_format_t::msgpack, subtype) && + get_binary(input_format_t::msgpack, 16, result) && + assign_and_return_true(subtype); + } + + default: // LCOV_EXCL_LINE + return false; // LCOV_EXCL_LINE + } + } + + /*! + @param[in] len the length of the array + @return whether array creation completed + */ + bool get_msgpack_array(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) + { + return false; + } + + for (std::size_t i = 0; i < len; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + } + + return sax->end_array(); + } + + /*! + @param[in] len the length of the object + @return whether object creation completed + */ + bool get_msgpack_object(const std::size_t len) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) + { + return false; + } + + string_t key; + for (std::size_t i = 0; i < len; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) + { + return false; + } + + if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) + { + return false; + } + key.clear(); + } + + return sax->end_object(); + } + + //////////// + // UBJSON // + //////////// + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether a valid UBJSON value was passed to the SAX parser + */ + bool parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[out] result created string + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return whether string creation completed + */ + bool get_ubjson_string(string_t& result, const bool get_char = true) + { + if (get_char) + { + get(); // TODO(niels): may we ignore N here? + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) + { + return false; + } + + switch (current) + { + case 'U': + { + std::uint8_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'i': + { + std::int8_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'I': + { + std::int16_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'l': + { + std::int32_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'L': + { + std::int64_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t len{}; + return get_number(input_format, len) && get_string(input_format, len, result); + } + + default: + break; + } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "string"), nullptr)); + } + + /*! + @param[out] dim an integer vector storing the ND array dimensions + @return whether reading ND array size vector is successful + */ + bool get_ubjson_ndarray_size(std::vector& dim) + { + std::pair size_and_type; + size_t dimlen = 0; + bool no_ndarray = true; + + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray))) + { + return false; + } + + if (size_and_type.first != npos) + { + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray))) + { + return false; + } + dim.push_back(dimlen); + } + } + } + else + { + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current))) + { + return false; + } + dim.push_back(dimlen); + get_ignore_noop(); + } + } + return true; + } + + /*! + @param[out] result determined size + @param[in,out] is_ndarray for input, `true` means already inside an ndarray vector + or ndarray dimension is not allowed; `false` means ndarray + is allowed; for output, `true` means an ndarray is found; + is_ndarray can only return `true` when its initial value + is `false` + @param[in] prefix type marker if already read, otherwise set to 0 + + @return whether size determination completed + */ + bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0) + { + if (prefix == 0) + { + prefix = get_ignore_noop(); + } + + switch (prefix) + { + case 'U': + { + std::uint8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'i': + { + std::int8_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char + return true; + } + + case 'I': + { + std::int16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + result = static_cast(number); + return true; + } + + case 'l': + { + std::int32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + result = static_cast(number); + return true; + } + + case 'L': + { + std::int64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (number < 0) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "count in an optimized container must be positive", "size"), nullptr)); + } + if (!value_in_range_of(number)) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); + } + result = static_cast(number); + return true; + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = static_cast(number); + return true; + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + result = conditional_static_cast(number); + return true; + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number))) + { + return false; + } + if (!value_in_range_of(number)) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, + exception_message(input_format, "integer value overflow", "size"), nullptr)); + } + result = detail::conditional_static_cast(number); + return true; + } + + case '[': + { + if (input_format != input_format_t::bjdata) + { + break; + } + if (is_ndarray) // ndarray dimensional vector can only contain integers and cannot embed another array + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, "ndarray dimensional vector is not allowed", "size"), nullptr)); + } + std::vector dim; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim))) + { + return false; + } + if (dim.size() == 1 || (dim.size() == 2 && dim.at(0) == 1)) // return normal array size if 1D row vector + { + result = dim.at(dim.size() - 1); + return true; + } + if (!dim.empty()) // if ndarray, convert to an object in JData annotated array format + { + for (auto i : dim) // test if any dimension in an ndarray is 0, if so, return a 1D empty container + { + if ( i == 0 ) + { + result = 0; + return true; + } + } + + string_t key = "_ArraySize_"; + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size()))) + { + return false; + } + result = 1; + for (auto i : dim) + { + // Pre-multiplication overflow check: if i > 0 and result > SIZE_MAX/i, then result*i would overflow. + // This check must happen before multiplication since overflow detection after the fact is unreliable + // as modular arithmetic can produce any value, not just 0 or SIZE_MAX. + if (JSON_HEDLEY_UNLIKELY(i > 0 && result > (std::numeric_limits::max)() / i)) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); + } + result *= i; + // Additional post-multiplication check to catch any edge cases the pre-check might miss + if (result == 0 || result == npos) + { + return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, "excessive ndarray size caused overflow", "size"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast(i)))) + { + return false; + } + } + is_ndarray = true; + return sax->end_array(); + } + result = 0; + return true; + } + + default: + break; + } + auto last_token = get_token_string(); + std::string message; + + if (input_format != input_format_t::bjdata) + { + message = "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token; + } + else + { + message = "expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x" + last_token; + } + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, "size"), nullptr)); + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @param[out] result pair of the size and the type + @param[in] inside_ndarray whether the parser is parsing an ND array dimensional vector + + @return whether pair creation completed + */ + bool get_ubjson_size_type(std::pair& result, bool inside_ndarray = false) + { + result.first = npos; // size + result.second = 0; // type + bool is_ndarray = false; + + get_ignore_noop(); + + if (current == '$') + { + result.second = get(); // must not ignore 'N', because 'N' maybe the type + if (input_format == input_format_t::bjdata + && JSON_HEDLEY_UNLIKELY(std::binary_search(bjd_optimized_type_markers.begin(), bjd_optimized_type_markers.end(), result.second))) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, concat("marker 0x", last_token, " is not a permitted optimized array type"), "type"), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "type"))) + { + return false; + } + + get_ignore_noop(); + if (JSON_HEDLEY_UNLIKELY(current != '#')) + { + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) + { + return false; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, concat("expected '#' after type information; last byte: 0x", last_token), "size"), nullptr)); + } + + const bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + if (inside_ndarray) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray can not be recursive", "size"), nullptr)); + } + result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters + } + return is_error; + } + + if (current == '#') + { + const bool is_error = get_ubjson_size_value(result.first, is_ndarray); + if (input_format == input_format_t::bjdata && is_ndarray) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read, + exception_message(input_format, "ndarray requires both type and size", "size"), nullptr)); + } + return is_error; + } + + return true; + } + + /*! + @param prefix the previously read or set type prefix + @return whether value creation completed + */ + bool get_ubjson_value(const char_int_type prefix) + { + switch (prefix) + { + case char_traits::eof(): // EOF + return unexpect_eof(input_format, "value"); + + case 'T': // true + return sax->boolean(true); + case 'F': // false + return sax->boolean(false); + + case 'Z': // null + return sax->null(); + + case 'B': // byte + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'U': + { + std::uint8_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'i': + { + std::int8_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'I': + { + std::int16_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'l': + { + std::int32_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'L': + { + std::int64_t number{}; + return get_number(input_format, number) && sax->number_integer(number); + } + + case 'u': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint16_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'm': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint32_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'M': + { + if (input_format != input_format_t::bjdata) + { + break; + } + std::uint64_t number{}; + return get_number(input_format, number) && sax->number_unsigned(number); + } + + case 'h': + { + if (input_format != input_format_t::bjdata) + { + break; + } + const auto byte1_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + const auto byte2_raw = get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + + const auto byte1 = static_cast(byte1_raw); + const auto byte2 = static_cast(byte2_raw); + + // Code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const auto half = static_cast((byte2 << 8u) + byte1); + const double val = [&half] + { + const int exp = (half >> 10u) & 0x1Fu; + const unsigned int mant = half & 0x3FFu; + JSON_ASSERT(0 <= exp&& exp <= 32); + JSON_ASSERT(mant <= 1024); + switch (exp) + { + case 0: + return std::ldexp(mant, -24); + case 31: + return (mant == 0) + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + default: + return std::ldexp(mant + 1024, exp - 25); + } + }(); + return sax->number_float((half & 0x8000u) != 0 + ? static_cast(-val) + : static_cast(val), ""); + } + + case 'd': + { + float number{}; + return get_number(input_format, number) && sax->number_float(static_cast(number), ""); + } + + case 'D': + { + double number{}; + return get_number(input_format, number) && sax->number_float(static_cast(number), ""); + } + + case 'H': + { + return get_ubjson_high_precision_number(); + } + + case 'C': // char + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "char"))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(current > 127)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, + exception_message(input_format, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", last_token), "char"), nullptr)); + } + string_t s(1, static_cast(current)); + return sax->string(s); + } + + case 'S': // string + { + string_t s; + return get_ubjson_string(s) && sax->string(s); + } + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + break; + } + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format, "invalid byte: 0x" + last_token, "value"), nullptr)); + } + + /*! + @return whether array creation completed + */ + bool get_ubjson_array() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata): + // {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]} + + if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) + { + size_and_type.second &= ~(static_cast(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker + auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), size_and_type.second, [](const bjd_type & p, char_int_type t) + { + return p.first < t; + }); + string_t key = "_ArrayType_"; + if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != size_and_type.second)) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "invalid byte: 0x" + last_token, "type"), nullptr)); + } + + string_t type = it->second; // sax->string() takes a reference + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type))) + { + return false; + } + + if (size_and_type.second == 'C' || size_and_type.second == 'B') + { + size_and_type.second = 'U'; + } + + key = "_ArrayData_"; + if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) )) + { + return false; + } + + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + + return (sax->end_array() && sax->end_object()); + } + + // If BJData type marker is 'B' decode as binary + if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B') + { + binary_t result; + return get_binary(input_format, size_and_type.first, result) && sax->binary(result); + } + + if (size_and_type.first != npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + } + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) + { + return false; + } + + while (current != ']') + { + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) + { + return false; + } + get_ignore_noop(); + } + } + + return sax->end_array(); + } + + /*! + @return whether object creation completed + */ + bool get_ubjson_object() + { + std::pair size_and_type; + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) + { + return false; + } + + // do not accept ND-array size in objects in BJData + if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0) + { + auto last_token = get_token_string(); + return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, + exception_message(input_format, "BJData object does not support ND-array size in optimized format", "object"), nullptr)); + } + + string_t key; + if (size_and_type.first != npos) + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) + { + return false; + } + + if (size_and_type.second != 0) + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) + { + return false; + } + key.clear(); + } + } + else + { + for (std::size_t i = 0; i < size_and_type.first; ++i) + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + key.clear(); + } + } + } + else + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) + { + return false; + } + + while (current != '}') + { + if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) + { + return false; + } + if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) + { + return false; + } + get_ignore_noop(); + key.clear(); + } + } + + return sax->end_object(); + } + + // Note, no reader for UBJSON binary types is implemented because they do + // not exist + + bool get_ubjson_high_precision_number() + { + // get the size of the following number string + std::size_t size{}; + bool no_ndarray = true; + auto res = get_ubjson_size_value(size, no_ndarray); + if (JSON_HEDLEY_UNLIKELY(!res)) + { + return res; + } + + // get number string + std::vector number_vector; + for (std::size_t i = 0; i < size; ++i) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "number"))) + { + return false; + } + number_vector.push_back(static_cast(current)); + } + + // parse number string + using ia_type = decltype(detail::input_adapter(number_vector)); + auto number_lexer = detail::lexer(detail::input_adapter(number_vector), false); + const auto result_number = number_lexer.scan(); + const auto number_string = number_lexer.get_token_string(); + const auto result_remainder = number_lexer.scan(); + + using token_type = typename detail::lexer_base::token_type; + + if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) + { + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + } + + switch (result_number) + { + case token_type::value_integer: + return sax->number_integer(number_lexer.get_number_integer()); + case token_type::value_unsigned: + return sax->number_unsigned(number_lexer.get_number_unsigned()); + case token_type::value_float: + return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); + case token_type::uninitialized: + case token_type::literal_true: + case token_type::literal_false: + case token_type::literal_null: + case token_type::value_string: + case token_type::begin_array: + case token_type::begin_object: + case token_type::end_array: + case token_type::end_object: + case token_type::name_separator: + case token_type::value_separator: + case token_type::parse_error: + case token_type::end_of_input: + case token_type::literal_or_value: + default: + return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, + exception_message(input_format, concat("invalid number text: ", number_lexer.get_token_string()), "high-precision number"), nullptr)); + } + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `char_traits::eof()` in that case. + + @return character read from the input + */ + char_int_type get() + { + ++chars_read; + return current = ia.get_character(); + } + + /*! + @brief get_to read into a primitive type + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns false instead + + @return bool, whether the read was successful + */ + template + bool get_to(T& dest, const input_format_t format, const char* context) + { + auto new_chars_read = ia.get_elements(&dest); + chars_read += new_chars_read; + if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T))) + { + // in case of failure, advance position by 1 to report the failing location + ++chars_read; + sax->parse_error(chars_read, "", parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + return false; + } + return true; + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + char_int_type get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + template + static void byte_swap(NumberType& number) + { + constexpr std::size_t sz = sizeof(number); +#ifdef __cpp_lib_byteswap + if constexpr (sz == 1) + { + return; + } + else if constexpr(std::is_integral_v) + { + number = std::byteswap(number); + return; + } + else + { +#endif + auto* ptr = reinterpret_cast(&number); + for (std::size_t i = 0; i < sz / 2; ++i) + { + std::swap(ptr[i], ptr[sz - i - 1]); + } +#ifdef __cpp_lib_byteswap + } +#endif + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[out] result number of type @a NumberType + + @return whether conversion completed + + @note This function needs to respect the system's endianness, because + bytes in CBOR, MessagePack, and UBJSON are stored in network order + (big endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. + */ + template + bool get_number(const input_format_t format, NumberType& result) + { + // read in the original format + + if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, "number"))) + { + return false; + } + if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata)) + { + byte_swap(result); + } + return true; + } + + /*! + @brief create a string by reading characters from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of characters to read + @param[out] result string created by reading @a len bytes + + @return whether string creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + */ + template + bool get_string(const input_format_t format, + const NumberType len, + string_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + } + return success; + } + + /*! + @brief create a byte array by reading bytes from the input + + @tparam NumberType the type of the number + @param[in] format the current format (for diagnostics) + @param[in] len number of bytes to read + @param[out] result byte array created by reading @a len bytes + + @return whether byte array creation completed + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of memory. + */ + template + bool get_binary(const input_format_t format, + const NumberType len, + binary_t& result) + { + bool success = true; + for (NumberType i = 0; i < len; i++) + { + get(); + if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) + { + success = false; + break; + } + result.push_back(static_cast(current)); + } + return success; + } + + /*! + @param[in] format the current format (for diagnostics) + @param[in] context further context information (for diagnostics) + @return whether the last read character is not EOF + */ + JSON_HEDLEY_NON_NULL(3) + bool unexpect_eof(const input_format_t format, const char* context) const + { + if (JSON_HEDLEY_UNLIKELY(current == char_traits::eof())) + { + return sax->parse_error(chars_read, "", + parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context), nullptr)); + } + return true; + } + + /*! + @return a string representation of the last read byte + */ + std::string get_token_string() const + { + std::array cr{{}}; + static_cast((std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + return std::string{cr.data()}; + } + + /*! + @param[in] format the current format + @param[in] detail a detailed error message + @param[in] context further context information + @return a message string to use in the parse_error exceptions + */ + std::string exception_message(const input_format_t format, + const std::string& detail, + const std::string& context) const + { + std::string error_msg = "syntax error while parsing "; + + switch (format) + { + case input_format_t::cbor: + error_msg += "CBOR"; + break; + + case input_format_t::msgpack: + error_msg += "MessagePack"; + break; + + case input_format_t::ubjson: + error_msg += "UBJSON"; + break; + + case input_format_t::bson: + error_msg += "BSON"; + break; + + case input_format_t::bjdata: + error_msg += "BJData"; + break; + + case input_format_t::json: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + + return concat(error_msg, ' ', context, ": ", detail); + } + + private: + static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size(); + + /// input adapter + InputAdapterType ia; + + /// the current character + char_int_type current = char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianness + const bool is_little_endian = little_endianness(); + + /// input format + const input_format_t input_format = input_format_t::json; + + /// the SAX parser + json_sax_t* sax = nullptr; + + // excluded markers in bjdata optimized type +#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \ + make_array('F', 'H', 'N', 'S', 'T', 'Z', '[', '{') + +#define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \ + make_array( \ + bjd_type{'B', "byte"}, \ + bjd_type{'C', "char"}, \ + bjd_type{'D', "double"}, \ + bjd_type{'I', "int16"}, \ + bjd_type{'L', "int64"}, \ + bjd_type{'M', "uint64"}, \ + bjd_type{'U', "uint8"}, \ + bjd_type{'d', "single"}, \ + bjd_type{'i', "int8"}, \ + bjd_type{'l', "int32"}, \ + bjd_type{'m', "uint32"}, \ + bjd_type{'u', "uint16"}) + + JSON_PRIVATE_UNLESS_TESTED: + // lookup tables + // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) + const decltype(JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_) bjd_optimized_type_markers = + JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_; + + using bjd_type = std::pair; + // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) + const decltype(JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_) bjd_types_map = + JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_; + +#undef JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ +#undef JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr std::size_t binary_reader::npos; +#endif + +} // namespace detail +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 + + + +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move +#include // vector + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ +//////////// +// parser // +//////////// + +enum class parse_event_t : std::uint8_t +{ + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value +}; + +template +using parser_callback_t = + std::function; + +/*! +@brief syntax analysis + +This class implements a recursive descent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + /// a parser reading from an input adapter + explicit parser(InputAdapterType&& adapter, + parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas_ = false) + : callback(std::move(cb)) + , m_lexer(std::move(adapter), ignore_comments) + , allow_exceptions(allow_exceptions_) + , ignore_trailing_commas(ignore_trailing_commas_) + { + // read first token + get_token(); + } + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + if (callback) + { + json_sax_dom_callback_parser sdp(result, callback, allow_exceptions, &m_lexer); + sax_parse_internal(&sdp); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + exception_message(token_type::end_of_input, "value"), nullptr)); + } + + // in case of an error, return a discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + else + { + json_sax_dom_parser sdp(result, allow_exceptions, &m_lexer); + sax_parse_internal(&sdp); + + // in strict mode, input must be completely read + if (strict && (get_token() != token_type::end_of_input)) + { + sdp.parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + } + + // in case of an error, return a discarded value + if (sdp.is_errored()) + { + result = value_t::discarded; + return; + } + } + + result.assert_invariant(); + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + json_sax_acceptor sax_acceptor; + return sax_parse(&sax_acceptor, strict); + } + + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse(SAX* sax, const bool strict = true) + { + (void)detail::is_sax_static_asserts {}; + const bool result = sax_parse_internal(sax); + + // strict mode: next byte must be EOF + if (result && strict && (get_token() != token_type::end_of_input)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr)); + } + + return result; + } + + private: + template + JSON_HEDLEY_NON_NULL(2) + bool sax_parse_internal(SAX* sax) + { + // stack to remember the hierarchy of structured values we are parsing + // true = array; false = object + std::vector states; + // value to avoid a goto (see comment where set to true) + bool skip_to_state_evaluation = false; + + while (true) + { + if (!skip_to_state_evaluation) + { + // invariant: get_token() was called before each iteration + switch (last_token) + { + case token_type::begin_object: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) + { + return false; + } + + // closing } -> we are done + if (get_token() == token_type::end_object) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + break; + } + + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + } + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + } + + // remember we are now inside an object + states.push_back(false); + + // parse values + get_token(); + continue; + } + + case token_type::begin_array: + { + if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) + { + return false; + } + + // closing ] -> we are done + if (get_token() == token_type::end_array) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + break; + } + + // remember we are now inside an array + states.push_back(true); + + // parse values (no need to call get_token) + continue; + } + + case token_type::value_float: + { + const auto res = m_lexer.get_number_float(); + + if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + out_of_range::create(406, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) + { + return false; + } + + break; + } + + case token_type::literal_false: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) + { + return false; + } + break; + } + + case token_type::literal_null: + { + if (JSON_HEDLEY_UNLIKELY(!sax->null())) + { + return false; + } + break; + } + + case token_type::literal_true: + { + if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) + { + return false; + } + break; + } + + case token_type::value_integer: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) + { + return false; + } + break; + } + + case token_type::value_string: + { + if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) + { + return false; + } + break; + } + + case token_type::value_unsigned: + { + if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) + { + return false; + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid an "expected" message + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr)); + } + case token_type::end_of_input: + { + if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), + "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr)); + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + } + case token_type::uninitialized: + case token_type::end_array: + case token_type::end_object: + case token_type::name_separator: + case token_type::value_separator: + case token_type::literal_or_value: + default: // the last token was unexpected + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr)); + } + } + } + else + { + skip_to_state_evaluation = false; + } + + // we reached this line after we successfully parsed a value + if (states.empty()) + { + // empty stack: we reached the end of the hierarchy: done + return true; + } + + if (states.back()) // array + { + // comma -> next value + // or end of array (ignore_trailing_commas = true) + if (get_token() == token_type::value_separator) + { + // parse a new value + get_token(); + + // if ignore_trailing_commas and last_token is ], we can continue to "closing ]" + if (!(ignore_trailing_commas && last_token == token_type::end_array)) + { + continue; + } + } + + // closing ] + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) + { + return false; + } + + // We are done with this array. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr)); + } + + // states.back() is false -> object + + // comma -> next value + // or end of object (ignore_trailing_commas = true) + if (get_token() == token_type::value_separator) + { + get_token(); + + // if ignore_trailing_commas and last_token is }, we can continue to "closing }" + if (!(ignore_trailing_commas && last_token == token_type::end_object)) + { + // parse key + if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr)); + } + + if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) + { + return false; + } + + // parse separator (:) + if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) + { + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr)); + } + + // parse values + get_token(); + continue; + } + } + + // closing } + if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) + { + if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) + { + return false; + } + + // We are done with this object. Before we can parse a + // new value, we need to evaluate the new state first. + // By setting skip_to_state_evaluation to false, we + // are effectively jumping to the beginning of this if. + JSON_ASSERT(!states.empty()); + states.pop_back(); + skip_to_state_evaluation = true; + continue; + } + + return sax->parse_error(m_lexer.get_position(), + m_lexer.get_token_string(), + parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr)); + } + } + + /// get next token from lexer + token_type get_token() + { + return last_token = m_lexer.scan(); + } + + std::string exception_message(const token_type expected, const std::string& context) + { + std::string error_msg = "syntax error "; + + if (!context.empty()) + { + error_msg += concat("while parsing ", context, ' '); + } + + error_msg += "- "; + + if (last_token == token_type::parse_error) + { + error_msg += concat(m_lexer.get_error_message(), "; last read: '", + m_lexer.get_token_string(), '\''); + } + else + { + error_msg += concat("unexpected ", lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += concat("; expected ", lexer_t::token_type_name(expected)); + } + + return error_msg; + } + + private: + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; + /// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false) + const bool ignore_trailing_commas = false; +}; + +} // 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 + +// #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 // ptrdiff_t +#include // numeric_limits + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). Its only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin and +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + JSON_PRIVATE_UNLESS_TESTED: + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t operator++(int)& noexcept // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + ++m_it; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t operator--(int)& noexcept // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + --m_it; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; + +} // 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 // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// forward declare to be able to friend it later on +template class iteration_proxy; +template class iteration_proxy_value; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) +{ + /// the iterator with BasicJsonType of different const-ness + using other_iter_impl = iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + /// allow basic_json to access private members + friend other_iter_impl; + friend BasicJsonType; + friend iteration_proxy; + friend iteration_proxy_value; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + // superficial check for the LegacyBidirectionalIterator named requirement + static_assert(std::is_base_of::value + && std::is_base_of::iterator_category>::value, + "basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement."); + + public: + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + iter_impl() = default; + ~iter_impl() = default; + iter_impl(iter_impl&&) noexcept = default; + iter_impl& operator=(iter_impl&&) noexcept = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief const copy constructor + @param[in] other const iterator to copy from + @note This copy constructor had to be defined explicitly to circumvent a bug + occurring on msvc v19.0 compiler (VS 2015) debug build. For more + information refer to: https://github.com/nlohmann/json/issues/1608 + */ + iter_impl(const iter_impl& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl& other) noexcept + { + if (&other != this) + { + m_object = other.m_object; + m_it = other.m_it; + } + return *this; + } + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief converting assignment + @param[in] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept // NOLINT(cert-oop54-cpp) + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_data.m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_data.m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_data.m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_data.m_value.array->end(); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end()); + return &*m_it.array_iterator; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator--(int)& // NOLINT(cert-dcl21-cpp) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. + */ + template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > + bool operator==(const IterImpl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + } + + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + return true; + } + + switch (m_object->m_data.m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. + */ + template < typename IterImpl, detail::enable_if_t < (std::is_same::value || std::is_same::value), std::nullptr_t > = nullptr > + bool operator!=(const IterImpl& other) const + { + return !operator==(other); + } + + /*! + @brief comparison: smaller + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object)); + } + + // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493 + if (m_object == nullptr) + { + // the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false + return false; + } + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object)); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. + */ + bool operator<=(const iter_impl& other) const + { + return !other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized. + */ + bool operator>(const iter_impl& other) const + { + return !operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized. + */ + bool operator>=(const iter_impl& other) const + { + return !operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object)); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + JSON_ASSERT(m_object != nullptr); + + switch (m_object->m_data.m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object)); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object)); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + const typename object_t::key_type& key() const + { + JSON_ASSERT(m_object != nullptr); + + if (JSON_HEDLEY_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object)); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + JSON_PRIVATE_UNLESS_TESTED: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it {}; +}; + +} // 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 // ptrdiff_t +#include // reverse_iterator +#include // declval + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator operator++(int)& // NOLINT(cert-dcl21-cpp) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int)& // NOLINT(cert-dcl21-cpp) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; + +} // 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 // conditional, is_same + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief Default base class of the @ref basic_json class. + +So that the correct implementations of the copy / move ctors / assign operators +of @ref basic_json do not require complex case distinctions +(no base class / custom base class used as customization point), +@ref basic_json always has a base class. +By default, this class is used because it is empty and thus has no effect +on the behavior of @ref basic_json. +*/ +struct json_default_base {}; + +template +using json_base_class = typename std::conditional < + std::is_same::value, + json_default_base, + T + >::type; + +} // 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 // all_of +#include // isdigit +#include // errno, ERANGE +#include // strtoull +#ifndef JSON_NO_IO + #include // ostream +#endif // JSON_NO_IO +#include // max +#include // accumulate +#include // string +#include // move +#include // vector + +// #include + +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// @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 +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + template + friend class json_pointer; + + template + struct string_t_helper + { + using type = T; + }; + + NLOHMANN_BASIC_JSON_TPL_DECLARATION + struct string_t_helper + { + using type = StringType; + }; + + public: + // for backwards compatibility accept BasicJsonType + using string_t = typename string_t_helper::type; + + /// @brief create JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/ + explicit json_pointer(const string_t& s = "") + : reference_tokens(split(s)) + {} + + /// @brief return a string representation of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/to_string/ + string_t to_string() const + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + string_t{}, + [](const string_t& a, const string_t& b) + { + return detail::concat(a, '/', detail::escape(b)); + }); + } + + /// @brief return a string representation of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string()) + operator string_t() const + { + return to_string(); + } + +#ifndef JSON_NO_IO + /// @brief write string representation of the JSON pointer to stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr) + { + o << ptr.to_string(); + return o; + } +#endif + + /// @brief append another JSON pointer at the end of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ + json_pointer& operator/=(const json_pointer& ptr) + { + reference_tokens.insert(reference_tokens.end(), + ptr.reference_tokens.begin(), + ptr.reference_tokens.end()); + return *this; + } + + /// @brief append an unescaped reference token at the end of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ + json_pointer& operator/=(string_t token) + { + push_back(std::move(token)); + return *this; + } + + /// @brief append an array index at the end of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/ + json_pointer& operator/=(std::size_t array_idx) + { + return *this /= std::to_string(array_idx); + } + + /// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ + friend json_pointer operator/(const json_pointer& lhs, + const json_pointer& rhs) + { + return json_pointer(lhs) /= rhs; + } + + /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ + friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param) + { + return json_pointer(lhs) /= std::move(token); + } + + /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/ + friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx) + { + return json_pointer(lhs) /= array_idx; + } + + /// @brief returns the parent of this JSON pointer + /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/ + json_pointer parent_pointer() const + { + if (empty()) + { + return *this; + } + + json_pointer res = *this; + res.pop_back(); + return res; + } + + /// @brief remove last reference token + /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/ + void pop_back() + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + reference_tokens.pop_back(); + } + + /// @brief return last reference token + /// @sa https://json.nlohmann.me/api/json_pointer/back/ + const string_t& back() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + return reference_tokens.back(); + } + + /// @brief append an unescaped token at the end of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_back/ + void push_back(const string_t& token) + { + reference_tokens.push_back(token); + } + + /// @brief append an unescaped token at the end of the reference pointer + /// @sa https://json.nlohmann.me/api/json_pointer/push_back/ + void push_back(string_t&& token) + { + reference_tokens.push_back(std::move(token)); + } + + /// @brief return whether pointer points to the root document + /// @sa https://json.nlohmann.me/api/json_pointer/empty/ + bool empty() const noexcept + { + return reference_tokens.empty(); + } + + private: + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index begins not with a digit + @throw out_of_range.404 if string @a s could not be converted to an integer + @throw out_of_range.410 if an array index exceeds size_type + */ + template + static typename BasicJsonType::size_type array_index(const string_t& s) + { + using size_type = typename BasicJsonType::size_type; + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr)); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) + { + JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr)); + } + + const char* p = s.c_str(); + char* p_end = nullptr; // NOLINT(misc-const-correctness) + errno = 0; // strtoull doesn't reset errno + const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int) + if (p == p_end // invalid input or empty string + || errno == ERANGE // out of range + || JSON_HEDLEY_UNLIKELY(static_cast(p_end - p) != s.size())) // incomplete read + { + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", s, "'"), nullptr)); + } + + // only triggered on special platforms (like 32bit), see also + // https://github.com/nlohmann/json/pull/2203 + if (res >= static_cast((std::numeric_limits::max)())) // NOLINT(runtime/int) + { + JSON_THROW(detail::out_of_range::create(410, detail::concat("array index ", s, " exceeds size_type"), nullptr)); // LCOV_EXCL_LINE + } + + return static_cast(res); + } + + JSON_PRIVATE_UNLESS_TESTED: + json_pointer top() const + { + if (JSON_HEDLEY_UNLIKELY(empty())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent", nullptr)); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + private: + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + template + BasicJsonType& get_and_create(BasicJsonType& j) const + { + auto* result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->type()) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if the reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + result = &result->operator[](array_index(reference_token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + a single value; that is, with an empty list of reference tokens. + */ + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j)); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->is_null()) + { + // check if the reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const unsigned char x) + { + return std::isdigit(x); + }); + + // change value to an array for numbers or "-" or to object otherwise + *ptr = (nums || reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_data.m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](array_index(reference_token)); + } + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, detail::concat( + "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), + ") is out of range"), ptr)); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr)); + } + + // use unchecked array access + ptr = &ptr->operator[](array_index(reference_token)); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + template + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, detail::concat( + "array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), + ") is out of range"), ptr)); + } + + // note: at performs range check + ptr = &ptr->at(array_index(reference_token)); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::out_of_range::create(404, detail::concat("unresolved reference token '", reference_token, "'"), ptr)); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + */ + template + bool contains(const BasicJsonType* ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->type()) + { + case detail::value_t::object: + { + if (!ptr->contains(reference_token)) + { + // we did not find the key in the object + return false; + } + + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) + { + // invalid char + return false; + } + if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) + { + if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) + { + // the first char should be between '1' and '9' + return false; + } + for (std::size_t i = 1; i < reference_token.size(); i++) + { + if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) + { + // other char should be between '0' and '9' + return false; + } + } + } + + const auto idx = array_index(reference_token); + if (idx >= ptr->size()) + { + // index out of range + return false; + } + + ptr = &ptr->operator[](idx); + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + { + // we do not expect primitive values if there is still a + // reference token to process + return false; + } + } + } + + // no reference token left means we found a primitive value + return true; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const string_t& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if a nonempty reference string begins with slash + if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr)); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == 0 (if slash == string_t::npos) + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == string_t::npos) + start = (slash == string_t::npos) ? 0 : slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != string_t::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + JSON_ASSERT(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || + (reference_token[pos + 1] != '0' && + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + } + } + + // finally, store the reference token + detail::unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + template + static void flatten(const string_t& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.type()) + { + case detail::value_t::array: + { + if (value.m_data.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as a reference string + for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i) + { + flatten(detail::concat(reference_string, '/', std::to_string(i)), + value.m_data.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_data.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_data.m_value.object) + { + flatten(detail::concat(reference_string, '/', detail::escape(element.first)), element.second, result); + } + } + break; + } + + case detail::value_t::null: + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + { + // add a primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + template + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_HEDLEY_UNLIKELY(!value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened", &value)); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_data.m_value.object) + { + if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive", &element.second)); + } + + // Assign the value to the reference pointed to by JSON pointer. Note + // that if the JSON pointer is "" (i.e., points to the whole value), + // function get_and_create returns a reference to the result itself. + // An assignment will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + // can't use the conversion operator because of ambiguity + json_pointer convert() const& + { + json_pointer result; + result.reference_tokens = reference_tokens; + return result; + } + + json_pointer convert()&& + { + json_pointer result; + result.reference_tokens = std::move(reference_tokens); + return result; + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief compares two JSON pointers for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template + bool operator==(const json_pointer& rhs) const noexcept + { + return reference_tokens == rhs.reference_tokens; + } + + /// @brief compares JSON pointer and string for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer)) + bool operator==(const string_t& rhs) const + { + return *this == json_pointer(rhs); + } + + /// @brief 3-way compares two JSON pointers + template + std::strong_ordering operator<=>(const json_pointer& rhs) const noexcept // *NOPAD* + { + return reference_tokens <=> rhs.reference_tokens; // *NOPAD* + } +#else + /// @brief compares two JSON pointers for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator==(const json_pointer& lhs, + const json_pointer& rhs) noexcept; + + /// @brief compares JSON pointer and string for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator==(const json_pointer& lhs, + const StringType& rhs); + + /// @brief compares string and JSON pointer for equality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/ + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator==(const StringType& lhs, + const json_pointer& rhs); + + /// @brief compares two JSON pointers for inequality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/ + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator!=(const json_pointer& lhs, + const json_pointer& rhs) noexcept; + + /// @brief compares JSON pointer and string for inequality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/ + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator!=(const json_pointer& lhs, + const StringType& rhs); + + /// @brief compares string and JSON pointer for inequality + /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/ + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator!=(const StringType& lhs, + const json_pointer& rhs); + + /// @brief compares two JSON pointer for less-than + template + // NOLINTNEXTLINE(readability-redundant-declaration) + friend bool operator<(const json_pointer& lhs, + const json_pointer& rhs) noexcept; +#endif + + private: + /// the reference tokens + std::vector reference_tokens; +}; + +#if !JSON_HAS_THREE_WAY_COMPARISON +// functions cannot be defined inside the class due to ODR violations +template +inline bool operator==(const json_pointer& lhs, + const json_pointer& rhs) noexcept +{ + return lhs.reference_tokens == rhs.reference_tokens; +} + +template::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer)) +inline bool operator==(const json_pointer& lhs, + const StringType& rhs) +{ + return lhs == json_pointer(rhs); +} + +template::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer)) +inline bool operator==(const StringType& lhs, + const json_pointer& rhs) +{ + return json_pointer(lhs) == rhs; +} + +template +inline bool operator!=(const json_pointer& lhs, + const json_pointer& rhs) noexcept +{ + return !(lhs == rhs); +} + +template::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer)) +inline bool operator!=(const json_pointer& lhs, + const StringType& rhs) +{ + return !(lhs == rhs); +} + +template::string_t> +JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer)) +inline bool operator!=(const StringType& lhs, + const json_pointer& rhs) +{ + return !(lhs == rhs); +} + +template +inline bool operator<(const json_pointer& lhs, + const json_pointer& rhs) noexcept +{ + return lhs.reference_tokens < rhs.reference_tokens; +} +#endif + +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 +#include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)) + {} + + json_ref(const value_type& value) + : value_ref(&value) + {} + + json_ref(std::initializer_list init) + : owned_value(init) + {} + + template < + class... Args, + enable_if_t::value, int> = 0 > + json_ref(Args && ... args) + : owned_value(std::forward(args)...) + {} + + // class should be movable only + json_ref(json_ref&&) noexcept = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + json_ref& operator=(json_ref&&) = delete; + ~json_ref() = default; + + value_type moved_or_copied() const + { + if (value_ref == nullptr) + { + return std::move(owned_value); + } + return *value_ref; + } + + value_type const& operator*() const + { + return value_ref ? *value_ref : owned_value; + } + + value_type const* operator->() const + { + return &** this; + } + + private: + mutable value_type owned_value = nullptr; + value_type const* value_ref = nullptr; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include + +// #include + +// #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 + + + +#include // reverse +#include // array +#include // map +#include // isnan, isinf +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits +#include // string +#include // move +#include // vector + +// #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 + + + +#include // copy +#include // size_t +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_string +#include // vector + +#ifndef JSON_NO_IO + #include // streamsize + #include // basic_ostream +#endif // JSON_NO_IO + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; + + output_adapter_protocol() = default; + output_adapter_protocol(const output_adapter_protocol&) = default; + output_adapter_protocol(output_adapter_protocol&&) noexcept = default; + output_adapter_protocol& operator=(const output_adapter_protocol&) = default; + output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template> +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) noexcept + : v(vec) + {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + v.insert(v.end(), s, s + length); + } + + private: + std::vector& v; +}; + +#ifndef JSON_NO_IO +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) noexcept + : stream(s) + {} + + void write_character(CharType c) override + { + stream.put(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; +#endif // JSON_NO_IO + +/// output adapter for basic_string +template> +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(StringType& s) noexcept + : str(s) + {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + JSON_HEDLEY_NON_NULL(2) + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + StringType& str; +}; + +template> +class output_adapter +{ + public: + template> + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + +#ifndef JSON_NO_IO + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} +#endif // JSON_NO_IO + + output_adapter(StringType& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// how to encode BJData +enum class bjdata_version_t +{ + draft2, + draft3, +}; + +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + using string_t = typename BasicJsonType::string_t; + using binary_t = typename BasicJsonType::binary_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(std::move(adapter)) + { + JSON_ASSERT(oa); + } + + /*! + @param[in] j JSON value to serialize + @pre j.type() == value_t::object + */ + void write_bson(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + { + write_bson_object(*j.m_data.m_value.object); + break; + } + + case value_t::null: + case value_t::array: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + JSON_THROW(type_error::create(317, concat("to serialize to BSON, top-level type must be object, but is ", j.type_name()), &j)); + } + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(to_char_type(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_data.m_value.boolean + ? to_char_type(0xF5) + : to_char_type(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_data.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_data.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_data.m_value.number_integer; + if (j.m_data.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(to_char_type(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_data.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_data.m_value.number_unsigned)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x18)); + write_number(static_cast(j.m_data.m_value.number_unsigned)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x19)); + write_number(static_cast(j.m_data.m_value.number_unsigned)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x1A)); + write_number(static_cast(j.m_data.m_value.number_unsigned)); + } + else + { + oa->write_character(to_char_type(0x1B)); + write_number(static_cast(j.m_data.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: + { + if (std::isnan(j.m_data.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_data.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_data.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::cbor); + } + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_data.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_data.m_value.string->c_str()), + j.m_data.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_data.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::binary: + { + if (j.m_data.m_value.binary->has_subtype()) + { + if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xd8)); + write_number(static_cast(j.m_data.m_value.binary->subtype())); + } + else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xd9)); + write_number(static_cast(j.m_data.m_value.binary->subtype())); + } + else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xda)); + write_number(static_cast(j.m_data.m_value.binary->subtype())); + } + else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits::max)()) + { + write_number(static_cast(0xdb)); + write_number(static_cast(j.m_data.m_value.binary->subtype())); + } + } + + // step 1: write control byte and the binary array size + const auto N = j.m_data.m_value.binary->size(); + if (N <= 0x17) + { + write_number(static_cast(0x40 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x58)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x59)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0x5B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + oa->write_characters( + reinterpret_cast(j.m_data.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_data.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(to_char_type(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + case value_t::discarded: + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(to_char_type(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_data.m_value.boolean + ? to_char_type(0xC3) + : to_char_type(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_data.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_data.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + } + else + { + if (j.m_data.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(to_char_type(0xD0)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(to_char_type(0xD1)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(to_char_type(0xD2)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(to_char_type(0xD3)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_data.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(to_char_type(0xCC)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(to_char_type(0xCD)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(to_char_type(0xCE)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(to_char_type(0xCF)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + break; + } + + case value_t::number_float: + { + write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::msgpack); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_data.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(to_char_type(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(to_char_type(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(to_char_type(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_data.m_value.string->c_str()), + j.m_data.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_data.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(to_char_type(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(to_char_type(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::binary: + { + // step 0: determine if the binary type has a set subtype to + // determine whether to use the ext or fixext types + const bool use_ext = j.m_data.m_value.binary->has_subtype(); + + // step 1: write control byte and the byte string length + const auto N = j.m_data.m_value.binary->size(); + if (N <= (std::numeric_limits::max)()) + { + std::uint8_t output_type{}; + bool fixed = true; + if (use_ext) + { + switch (N) + { + case 1: + output_type = 0xD4; // fixext 1 + break; + case 2: + output_type = 0xD5; // fixext 2 + break; + case 4: + output_type = 0xD6; // fixext 4 + break; + case 8: + output_type = 0xD7; // fixext 8 + break; + case 16: + output_type = 0xD8; // fixext 16 + break; + default: + output_type = 0xC7; // ext 8 + fixed = false; + break; + } + + } + else + { + output_type = 0xC4; // bin 8 + fixed = false; + } + + oa->write_character(to_char_type(output_type)); + if (!fixed) + { + write_number(static_cast(N)); + } + } + else if (N <= (std::numeric_limits::max)()) + { + const std::uint8_t output_type = use_ext + ? 0xC8 // ext 16 + : 0xC5; // bin 16 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + const std::uint8_t output_type = use_ext + ? 0xC9 // ext 32 + : 0xC6; // bin 32 + + oa->write_character(to_char_type(output_type)); + write_number(static_cast(N)); + } + + // step 1.5: if this is an ext type, write the subtype + if (use_ext) + { + write_number(static_cast(j.m_data.m_value.binary->subtype())); + } + + // step 2: write the byte string + oa->write_characters( + reinterpret_cast(j.m_data.m_value.binary->data()), + N); + + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_data.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(to_char_type(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(to_char_type(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_data.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + case value_t::discarded: + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + @param[in] use_bjdata whether write in BJData format, default is false + @param[in] bjdata_version which BJData version to use, default is draft2 + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true, + const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2) + { + const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3; + + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(to_char_type('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + { + oa->write_character(j.m_data.m_value.boolean + ? to_char_type('T') + : to_char_type('F')); + } + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_data.m_value.number_integer, add_prefix, use_bjdata); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_data.m_value.number_unsigned, add_prefix, use_bjdata); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_data.m_value.number_float, add_prefix, use_bjdata); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(to_char_type('S')); + } + write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata); + oa->write_characters( + reinterpret_cast(j.m_data.m_value.string->c_str()), + j.m_data.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + bool prefix_required = true; + if (use_type && !j.m_data.m_value.array->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix, use_bjdata](const BasicJsonType & v) + { + return ubjson_prefix(v, use_bjdata) == first_prefix; + }); + + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata); + } + + for (const auto& el : *j.m_data.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version); + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::binary: + { + if (add_prefix) + { + oa->write_character(to_char_type('[')); + } + + if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty())) + { + JSON_ASSERT(use_count); + oa->write_character(to_char_type('$')); + oa->write_character(bjdata_draft3 ? 'B' : 'U'); + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_data.m_value.binary->size(), true, use_bjdata); + } + + if (use_type) + { + oa->write_characters( + reinterpret_cast(j.m_data.m_value.binary->data()), + j.m_data.m_value.binary->size()); + } + else + { + for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i) + { + oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U')); + oa->write_character(j.m_data.m_value.binary->data()[i]); + } + } + + if (!use_count) + { + oa->write_character(to_char_type(']')); + } + + break; + } + + case value_t::object: + { + if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end()) + { + if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata) + { + break; + } + } + + if (add_prefix) + { + oa->write_character(to_char_type('{')); + } + + bool prefix_required = true; + if (use_type && !j.m_data.m_value.object->empty()) + { + JSON_ASSERT(use_count); + const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix, use_bjdata](const BasicJsonType & v) + { + return ubjson_prefix(v, use_bjdata) == first_prefix; + }); + + std::vector bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type + + if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end())) + { + prefix_required = false; + oa->write_character(to_char_type('$')); + oa->write_character(first_prefix); + } + } + + if (use_count) + { + oa->write_character(to_char_type('#')); + write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata); + } + + for (const auto& el : *j.m_data.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version); + } + + if (!use_count) + { + oa->write_character(to_char_type('}')); + } + + break; + } + + case value_t::discarded: + default: + break; + } + } + + private: + ////////// + // BSON // + ////////// + + /*! + @return The size of a BSON document entry header, including the id marker + and the entry name size (and its null-terminator). + */ + static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j) + { + const auto it = name.find(static_cast(0)); + if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) + { + JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j)); + } + + static_cast(j); + return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; + } + + /*! + @brief Writes the given @a element_type and @a name to the output adapter + */ + void write_bson_entry_header(const string_t& name, + const std::uint8_t element_type) + { + oa->write_character(to_char_type(element_type)); // boolean + oa->write_characters( + reinterpret_cast(name.c_str()), + name.size() + 1u); + } + + /*! + @brief Writes a BSON element with key @a name and boolean value @a value + */ + void write_bson_boolean(const string_t& name, + const bool value) + { + write_bson_entry_header(name, 0x08); + oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and double value @a value + */ + void write_bson_double(const string_t& name, + const double value) + { + write_bson_entry_header(name, 0x01); + write_number(value, true); + } + + /*! + @return The size of the BSON-encoded string in @a value + */ + static std::size_t calc_bson_string_size(const string_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and string value @a value + */ + void write_bson_string(const string_t& name, + const string_t& value) + { + write_bson_entry_header(name, 0x02); + + write_number(static_cast(value.size() + 1ul), true); + oa->write_characters( + reinterpret_cast(value.c_str()), + value.size() + 1); + } + + /*! + @brief Writes a BSON element with key @a name and null value + */ + void write_bson_null(const string_t& name) + { + write_bson_entry_header(name, 0x0A); + } + + /*! + @return The size of the BSON-encoded integer @a value + */ + static std::size_t calc_bson_integer_size(const std::int64_t value) + { + return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and integer @a value + */ + void write_bson_integer(const string_t& name, + const std::int64_t value) + { + if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) + { + write_bson_entry_header(name, 0x10); // int32 + write_number(static_cast(value), true); + } + else + { + write_bson_entry_header(name, 0x12); // int64 + write_number(static_cast(value), true); + } + } + + /*! + @return The size of the BSON-encoded unsigned integer in @a j + */ + static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept + { + return (value <= static_cast((std::numeric_limits::max)())) + ? sizeof(std::int32_t) + : sizeof(std::int64_t); + } + + /*! + @brief Writes a BSON element with key @a name and unsigned @a value + */ + void write_bson_unsigned(const string_t& name, + const BasicJsonType& j) + { + if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x10 /* int32 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); + } + else if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + write_bson_entry_header(name, 0x12 /* int64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); + } + else + { + write_bson_entry_header(name, 0x11 /* uint64 */); + write_number(static_cast(j.m_data.m_value.number_unsigned), true); + } + } + + /*! + @brief Writes a BSON element with key @a name and object @a value + */ + void write_bson_object_entry(const string_t& name, + const typename BasicJsonType::object_t& value) + { + write_bson_entry_header(name, 0x03); // object + write_bson_object(value); + } + + /*! + @return The size of the BSON-encoded array @a value + */ + static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) + { + std::size_t array_index = 0ul; + + const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) + { + return result + calc_bson_element_size(std::to_string(array_index++), el); + }); + + return sizeof(std::int32_t) + embedded_document_size + 1ul; + } + + /*! + @return The size of the BSON-encoded binary array @a value + */ + static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) + { + return sizeof(std::int32_t) + value.size() + 1ul; + } + + /*! + @brief Writes a BSON element with key @a name and array @a value + */ + void write_bson_array(const string_t& name, + const typename BasicJsonType::array_t& value) + { + write_bson_entry_header(name, 0x04); // array + write_number(static_cast(calc_bson_array_size(value)), true); + + std::size_t array_index = 0ul; + + for (const auto& el : value) + { + write_bson_element(std::to_string(array_index++), el); + } + + oa->write_character(to_char_type(0x00)); + } + + /*! + @brief Writes a BSON element with key @a name and binary value @a value + */ + void write_bson_binary(const string_t& name, + const binary_t& value) + { + write_bson_entry_header(name, 0x05); + + write_number(static_cast(value.size()), true); + write_number(value.has_subtype() ? static_cast(value.subtype()) : static_cast(0x00)); + + oa->write_characters(reinterpret_cast(value.data()), value.size()); + } + + /*! + @brief Calculates the size necessary to serialize the JSON value @a j with its @a name + @return The calculated size for the BSON document entry for @a j with the given @a name. + */ + static std::size_t calc_bson_element_size(const string_t& name, + const BasicJsonType& j) + { + const auto header_size = calc_bson_entry_header_size(name, j); + switch (j.type()) + { + case value_t::object: + return header_size + calc_bson_object_size(*j.m_data.m_value.object); + + case value_t::array: + return header_size + calc_bson_array_size(*j.m_data.m_value.array); + + case value_t::binary: + return header_size + calc_bson_binary_size(*j.m_data.m_value.binary); + + case value_t::boolean: + return header_size + 1ul; + + case value_t::number_float: + return header_size + 8ul; + + case value_t::number_integer: + return header_size + calc_bson_integer_size(j.m_data.m_value.number_integer); + + case value_t::number_unsigned: + return header_size + calc_bson_unsigned_size(j.m_data.m_value.number_unsigned); + + case value_t::string: + return header_size + calc_bson_string_size(*j.m_data.m_value.string); + + case value_t::null: + return header_size + 0ul; + + // LCOV_EXCL_START + case value_t::discarded: + default: + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + return 0ul; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Serializes the JSON value @a j to BSON and associates it with the + key @a name. + @param name The name to associate with the JSON entity @a j within the + current BSON document + */ + void write_bson_element(const string_t& name, + const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::object: + return write_bson_object_entry(name, *j.m_data.m_value.object); + + case value_t::array: + return write_bson_array(name, *j.m_data.m_value.array); + + case value_t::binary: + return write_bson_binary(name, *j.m_data.m_value.binary); + + case value_t::boolean: + return write_bson_boolean(name, j.m_data.m_value.boolean); + + case value_t::number_float: + return write_bson_double(name, j.m_data.m_value.number_float); + + case value_t::number_integer: + return write_bson_integer(name, j.m_data.m_value.number_integer); + + case value_t::number_unsigned: + return write_bson_unsigned(name, j); + + case value_t::string: + return write_bson_string(name, *j.m_data.m_value.string); + + case value_t::null: + return write_bson_null(name); + + // LCOV_EXCL_START + case value_t::discarded: + default: + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) + return; + // LCOV_EXCL_STOP + } + } + + /*! + @brief Calculates the size of the BSON serialization of the given + JSON-object @a j. + @param[in] value JSON value to serialize + @pre value.type() == value_t::object + */ + static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) + { + const std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast(0), + [](size_t result, const typename BasicJsonType::object_t::value_type & el) + { + return result += calc_bson_element_size(el.first, el.second); + }); + + return sizeof(std::int32_t) + document_size + 1ul; + } + + /*! + @param[in] value JSON value to serialize + @pre value.type() == value_t::object + */ + void write_bson_object(const typename BasicJsonType::object_t& value) + { + write_number(static_cast(calc_bson_object_size(value)), true); + + for (const auto& el : value) + { + write_bson_element(el.first, el.second); + } + + oa->write_character(to_char_type(0x00)); + } + + ////////// + // CBOR // + ////////// + + static constexpr CharType get_cbor_float_prefix(float /*unused*/) + { + return to_char_type(0xFA); // Single-Precision Float + } + + static constexpr CharType get_cbor_float_prefix(double /*unused*/) + { + return to_char_type(0xFB); // Double-Precision Float + } + + ///////////// + // MsgPack // + ///////////// + + static constexpr CharType get_msgpack_float_prefix(float /*unused*/) + { + return to_char_type(0xCA); // float 32 + } + + static constexpr CharType get_msgpack_float_prefix(double /*unused*/) + { + return to_char_type(0xCB); // float 64 + } + + //////////// + // UBJSON // + //////////// + + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix, + const bool use_bjdata) + { + if (add_prefix) + { + oa->write_character(get_ubjson_float_prefix(n)); + } + write_number(n, use_bjdata); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix, + const bool use_bjdata) + { + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n), use_bjdata); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n), use_bjdata); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast(n), use_bjdata); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast(n), use_bjdata); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('M')); // uint64 - bjdata only + } + write_number(static_cast(n), use_bjdata); + } + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + } + + // UBJSON: write number (signed integer) + template < typename NumberType, typename std::enable_if < + std::is_signed::value&& + !std::is_floating_point::value, int >::type = 0 > + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix, + const bool use_bjdata) + { + if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('i')); // int8 + } + write_number(static_cast(n), use_bjdata); + } + else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(to_char_type('U')); // uint8 + } + write_number(static_cast(n), use_bjdata); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('I')); // int16 + } + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('u')); // uint16 - bjdata only + } + write_number(static_cast(n), use_bjdata); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('l')); // int32 + } + write_number(static_cast(n), use_bjdata); + } + else if (use_bjdata && (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)()))) + { + if (add_prefix) + { + oa->write_character(to_char_type('m')); // uint32 - bjdata only + } + write_number(static_cast(n), use_bjdata); + } + else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(to_char_type('L')); // int64 + } + write_number(static_cast(n), use_bjdata); + } + // LCOV_EXCL_START + else + { + if (add_prefix) + { + oa->write_character(to_char_type('H')); // high-precision number + } + + const auto number = BasicJsonType(n).dump(); + write_number_with_ubjson_prefix(number.size(), true, use_bjdata); + for (std::size_t i = 0; i < number.size(); ++i) + { + oa->write_character(to_char_type(static_cast(number[i]))); + } + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + */ + CharType ubjson_prefix(const BasicJsonType& j, const bool use_bjdata) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_data.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + if (use_bjdata && ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)())) + { + return 'u'; + } + if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + if (use_bjdata && ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)())) + { + return 'm'; + } + if ((std::numeric_limits::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'L'; + } + // anything else is treated as a high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_unsigned: + { + if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'i'; + } + if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'U'; + } + if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'I'; + } + if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'u'; + } + if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'l'; + } + if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'm'; + } + if (j.m_data.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) + { + return 'L'; + } + if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'M'; + } + // anything else is treated as a high-precision number + return 'H'; // LCOV_EXCL_LINE + } + + case value_t::number_float: + return get_ubjson_float_prefix(j.m_data.m_value.number_float); + + case value_t::string: + return 'S'; + + case value_t::array: // fallthrough + case value_t::binary: + return '['; + + case value_t::object: + return '{'; + + case value_t::discarded: + default: // discarded values + return 'N'; + } + } + + static constexpr CharType get_ubjson_float_prefix(float /*unused*/) + { + return 'd'; // float 32 + } + + static constexpr CharType get_ubjson_float_prefix(double /*unused*/) + { + return 'D'; // float 64 + } + + /*! + @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid + */ + bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version) + { + std::map bjdtype = {{"uint8", 'U'}, {"int8", 'i'}, {"uint16", 'u'}, {"int16", 'I'}, + {"uint32", 'm'}, {"int32", 'l'}, {"uint64", 'M'}, {"int64", 'L'}, {"single", 'd'}, {"double", 'D'}, + {"char", 'C'}, {"byte", 'B'} + }; + + string_t key = "_ArrayType_"; + auto it = bjdtype.find(static_cast(value.at(key))); + if (it == bjdtype.end()) + { + return true; + } + CharType dtype = it->second; + + key = "_ArraySize_"; + std::size_t len = (value.at(key).empty() ? 0 : 1); + for (const auto& el : value.at(key)) + { + len *= static_cast(el.m_data.m_value.number_unsigned); + } + + key = "_ArrayData_"; + if (value.at(key).size() != len) + { + return true; + } + + oa->write_character('['); + oa->write_character('$'); + oa->write_character(dtype); + oa->write_character('#'); + + key = "_ArraySize_"; + write_ubjson(value.at(key), use_count, use_type, true, true, bjdata_version); + + key = "_ArrayData_"; + if (dtype == 'U' || dtype == 'C' || dtype == 'B') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'i') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'u') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'I') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'm') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'l') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'M') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_unsigned), true); + } + } + else if (dtype == 'L') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_integer), true); + } + } + else if (dtype == 'd') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_float), true); + } + } + else if (dtype == 'D') + { + for (const auto& el : value.at(key)) + { + write_number(static_cast(el.m_data.m_value.number_float), true); + } + } + return false; + } + + /////////////////////// + // Utility functions // + /////////////////////// + + /* + @brief write a number to output input + @param[in] n number of type @a NumberType + @param[in] OutputIsLittleEndian Set to true if output data is + required to be little endian + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianness, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + On the other hand, BSON and BJData use little endian and should reorder + on big endian systems. + */ + template + void write_number(const NumberType n, const bool OutputIsLittleEndian = false) + { + // step 1: write the number to an array of length NumberType + std::array vec{}; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write the array to output (with possible reordering) + if (is_little_endian != OutputIsLittleEndian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + void write_compact_float(const number_float_t n, detail::input_format_t format) + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + if (!std::isfinite(n) || ((static_cast(n) >= static_cast(std::numeric_limits::lowest()) && + static_cast(n) <= static_cast((std::numeric_limits::max)()) && + static_cast(static_cast(n)) == static_cast(n)))) + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(static_cast(n)) + : get_msgpack_float_prefix(static_cast(n))); + write_number(static_cast(n)); + } + else + { + oa->write_character(format == detail::input_format_t::cbor + ? get_cbor_float_prefix(n) + : get_msgpack_float_prefix(n)); + write_number(n); + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + public: + // The following to_char_type functions are implement the conversion + // between uint8_t and CharType. In case CharType is not unsigned, + // such a conversion is required to allow values greater than 128. + // See for a discussion. + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return *reinterpret_cast(&x); + } + + template < typename C = CharType, + enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > + static CharType to_char_type(std::uint8_t x) noexcept + { + // The std::is_trivial trait is deprecated in C++26. The replacement is to use + // std::is_trivially_copyable and std::is_trivially_default_constructible. + // However, some older library implementations support std::is_trivial + // but not all the std::is_trivially_* traits. + // Since detecting full support across all libraries is difficult, + // we use std::is_trivial unless we are using a standard where it has been deprecated. + // For more details, see: https://github.com/nlohmann/json/pull/4775#issuecomment-2884361627 +#ifdef JSON_HAS_CPP_26 + static_assert(std::is_trivially_copyable::value, "CharType must be trivially copyable"); + static_assert(std::is_trivially_default_constructible::value, "CharType must be trivially default constructible"); +#else + static_assert(std::is_trivial::value, "CharType must be trivial"); +#endif + + static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); + CharType result; + std::memcpy(&result, &x, sizeof(x)); + return result; + } + + template::value>* = nullptr> + static constexpr CharType to_char_type(std::uint8_t x) noexcept + { + return x; + } + + template < typename InputCharType, typename C = CharType, + enable_if_t < + std::is_signed::value && + std::is_signed::value && + std::is_same::type>::value + > * = nullptr > + static constexpr CharType to_char_type(InputCharType x) noexcept + { + return x; + } + + private: + /// whether we can assume little endianness + const bool is_little_endian = little_endianness(); + + /// the output + output_adapter_t oa = nullptr; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // numeric_limits +#include // string, char_traits +#include // setfill, setw +#include // is_same +#include // move + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2009 Florian Loitsch +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove +#include // numeric_limits +#include // conditional + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + std::uint64_t f = 0; + int e = 0; + + constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + JSON_ASSERT(x.e == y.e); + JSON_ASSERT(x.f >= y.f); + + return {x.f - y.f, x.e}; + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; + const std::uint64_t u_hi = x.f >> 32u; + const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; + const std::uint64_t v_hi = y.f >> 32u; + + const std::uint64_t p0 = u_lo * v_lo; + const std::uint64_t p1 = u_lo * v_hi; + const std::uint64_t p2 = u_hi * v_lo; + const std::uint64_t p3 = u_hi * v_hi; + + const std::uint64_t p0_hi = p0 >> 32u; + const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; + const std::uint64_t p1_hi = p1 >> 32u; + const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; + const std::uint64_t p2_hi = p2 >> 32u; + + std::uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively, we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up + + const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); + + return {h, x.e + y.e + 64}; + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + JSON_ASSERT(x.f != 0); + + while ((x.f >> 63u) == 0) + { + x.f <<= 1u; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + JSON_ASSERT(delta >= 0); + JSON_ASSERT(((x.f << delta) >> delta) == x.f); + + return {x.f << delta, target_exponent}; + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional::type; + + const auto bits = static_cast(reinterpret_bits(value)); + const std::uint64_t E = bits >> (kPrecision - 1); + const std::uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = E == 0; + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor of v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = F == 0 && E > 1; + const diyfp m_plus = diyfp((2 * v.f) + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp((4 * v.f) - 1, v.e - 2) // (B) + : diyfp((2 * v.f) - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + std::uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exact power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually, this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr std::array kCachedPowers = + { + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + } + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + JSON_ASSERT(e >= -1500); + JSON_ASSERT(e <= 1500); + const int f = kAlpha - e - 1; + const int k = ((f * 78913) / (1 << 18)) + static_cast(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + JSON_ASSERT(index >= 0); + JSON_ASSERT(static_cast(index) < kCachedPowers.size()); + + const cached_power cached = kCachedPowers[static_cast(index)]; + JSON_ASSERT(kAlpha <= cached.e + e + 64); + JSON_ASSERT(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + if (n >= 100000) + { + pow10 = 100000; + return 6; + } + if (n >= 10000) + { + pow10 = 10000; + return 5; + } + if (n >= 1000) + { + pow10 = 1000; + return 4; + } + if (n >= 100) + { + pow10 = 100; + return 3; + } + if (n >= 10) + { + pow10 = 10; + return 2; + } + + pow10 = 1; + return 1; +} + +inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, + std::uint64_t rest, std::uint64_t ten_k) +{ + JSON_ASSERT(len >= 1); + JSON_ASSERT(dist <= delta); + JSON_ASSERT(rest <= delta); + JSON_ASSERT(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + && delta - rest >= ten_k + && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) + { + JSON_ASSERT(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + JSON_ASSERT(M_plus.e >= kAlpha); + JSON_ASSERT(M_plus.e <= kGamma); + + std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); + + auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + JSON_ASSERT(p1 > 0); + + std::uint32_t pow10{}; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead, it looks as if the buffer + // could be decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + JSON_ASSERT(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); + p2 *= 10; + const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + JSON_ASSERT(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const std::uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +JSON_HEDLEY_NON_NULL(1) +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + JSON_ASSERT(m_plus.e == m_minus.e); + JSON_ASSERT(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +JSON_HEDLEY_NON_NULL(1) +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + JSON_ASSERT(std::isfinite(value)); + JSON_ASSERT(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes floats to doubles, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if) + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* append_exponent(char* buf, int e) +{ + JSON_ASSERT(e > -1000); + JSON_ASSERT(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + auto k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + (k / 10)); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + (k / 100)); + k %= 100; + *buf++ = static_cast('0' + (k / 10)); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +JSON_HEDLEY_NON_NULL(1) +JSON_HEDLEY_RETURNS_NON_NULL +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + JSON_ASSERT(min_exp < 0); + JSON_ASSERT(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n && n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n) - static_cast(k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (static_cast(n) + 2); + } + + if (0 < n && n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + JSON_ASSERT(k > n); + + std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); + buf[n] = '.'; + return buf + (static_cast(k) + 1U); + } + + if (min_exp < n && n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2U + static_cast(-n) + static_cast(k)); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast(k); + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +JSON_HEDLEY_NON_NULL(1, 2) +JSON_HEDLEY_RETURNS_NON_NULL +char* to_chars(char* first, const char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + JSON_ASSERT(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e., the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + JSON_ASSERT(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + JSON_ASSERT(last - first >= kMaxExp + 2); + JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////// +// serialization // +/////////////////// + +/// how to treat decoding errors +enum class error_handler_t +{ + strict, ///< throw a type_error exception in case of invalid UTF-8 + replace, ///< replace invalid UTF-8 sequences with U+FFFD + ignore ///< ignore invalid UTF-8 sequences +}; + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using binary_char_t = typename BasicJsonType::binary_t::value_type; + static constexpr std::uint8_t UTF8_ACCEPT = 0; + static constexpr std::uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + @param[in] error_handler_ how to react on decoding errors + */ + serializer(output_adapter_t s, const char ichar, + error_handler_t error_handler_ = error_handler_t::strict) + : o(std::move(s)) + , loc(std::localeconv()) + , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) + , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) + , indent_char(ichar) + , indent_string(512, indent_char) + , error_handler(error_handler_) + {} + + // deleted because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + serializer(serializer&&) = delete; + serializer& operator=(serializer&&) = delete; + ~serializer() = default; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + - binary values are serialized as objects containing the subtype and the + byte array + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, + const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_data.m_type) + { + case value_t::object: + { + if (val.m_data.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_data.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(i != val.m_data.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_data.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(i != val.m_data.m_value.object->cend()); + JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_data.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_data.m_value.array->cbegin(); + i != val.m_data.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + JSON_ASSERT(!val.m_data.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_data.m_value.array->cbegin(); + i != val.m_data.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + JSON_ASSERT(!val.m_data.m_value.array->empty()); + dump(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_data.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::binary: + { + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"bytes\": [", 10); + + if (!val.m_data.m_value.binary->empty()) + { + for (auto i = val.m_data.m_value.binary->cbegin(); + i != val.m_data.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_characters(", ", 2); + } + dump_integer(val.m_data.m_value.binary->back()); + } + + o->write_characters("],\n", 3); + o->write_characters(indent_string.c_str(), new_indent); + + o->write_characters("\"subtype\": ", 11); + if (val.m_data.m_value.binary->has_subtype()) + { + dump_integer(val.m_data.m_value.binary->subtype()); + } + else + { + o->write_characters("null", 4); + } + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_characters("{\"bytes\":[", 10); + + if (!val.m_data.m_value.binary->empty()) + { + for (auto i = val.m_data.m_value.binary->cbegin(); + i != val.m_data.m_value.binary->cend() - 1; ++i) + { + dump_integer(*i); + o->write_character(','); + } + dump_integer(val.m_data.m_value.binary->back()); + } + + o->write_characters("],\"subtype\":", 12); + if (val.m_data.m_value.binary->has_subtype()) + { + dump_integer(val.m_data.m_value.binary->subtype()); + o->write_character('}'); + } + else + { + o->write_characters("null}", 5); + } + } + return; + } + + case value_t::boolean: + { + if (val.m_data.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_data.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_data.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_data.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + std::uint32_t codepoint{}; + std::uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + // number of bytes written at the point of the last valid byte + std::size_t bytes_after_last_accept = 0; + std::size_t undumped_chars = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + static_cast((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint))); + bytes += 6; + } + else + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + static_cast((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0u + (codepoint >> 10u)), + static_cast(0xDC00u + (codepoint & 0x3FFu)))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + // remember the byte position of this accept + bytes_after_last_accept = bytes; + undumped_chars = 0; + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + switch (error_handler) + { + case error_handler_t::strict: + { + JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_bytes(byte | 0)), nullptr)); + } + + case error_handler_t::ignore: + case error_handler_t::replace: + { + // in case we saw this character the first time, we + // would like to read it again, because the byte + // may be OK for itself, but just not OK for the + // previous sequence + if (undumped_chars > 0) + { + --i; + } + + // reset length buffer to the last accepted index; + // thus removing/ignoring the invalid characters + bytes = bytes_after_last_accept; + + if (error_handler == error_handler_t::replace) + { + // add a replacement character + if (ensure_ascii) + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'u'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'f'; + string_buffer[bytes++] = 'd'; + } + else + { + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); + string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + + bytes_after_last_accept = bytes; + } + + undumped_chars = 0; + + // continue processing the string + state = UTF8_ACCEPT; + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + break; + } + + default: // decode found yet incomplete multibyte code point + { + if (!ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + ++undumped_chars; + break; + } + } + } + + // we finished processing the string + if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + switch (error_handler) + { + case error_handler_t::strict: + { + JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", hex_bytes(static_cast(s.back() | 0))), nullptr)); + } + + case error_handler_t::ignore: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + break; + } + + case error_handler_t::replace: + { + // write all accepted bytes + o->write_characters(string_buffer.data(), bytes_after_last_accept); + // add a replacement character + if (ensure_ascii) + { + o->write_characters("\\ufffd", 6); + } + else + { + o->write_characters("\xEF\xBF\xBD", 3); + } + break; + } + + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + } + } + + private: + /*! + @brief count digits + + Count the number of decimal (base 10) digits for an input unsigned integer. + + @param[in] x unsigned integer number to count its digits + @return number of decimal digits + */ + unsigned int count_digits(number_unsigned_t x) noexcept + { + unsigned int n_digits = 1; + for (;;) + { + if (x < 10) + { + return n_digits; + } + if (x < 100) + { + return n_digits + 1; + } + if (x < 1000) + { + return n_digits + 2; + } + if (x < 10000) + { + return n_digits + 3; + } + x = x / 10000u; + n_digits += 4; + } + } + + /*! + * @brief convert a byte to a uppercase hex representation + * @param[in] byte byte to represent + * @return representation ("00".."FF") + */ + static std::string hex_bytes(std::uint8_t byte) + { + std::string result = "FF"; + constexpr const char* nibble_to_hex = "0123456789ABCDEF"; + result[0] = nibble_to_hex[byte / 16]; + result[1] = nibble_to_hex[byte % 16]; + return result; + } + + // templates to avoid warnings about useless casts + template ::value, int> = 0> + bool is_negative_number(NumberType x) + { + return x < 0; + } + + template < typename NumberType, enable_if_t ::value, int > = 0 > + bool is_negative_number(NumberType /*unused*/) + { + return false; + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template < typename NumberType, detail::enable_if_t < + std::is_integral::value || + std::is_same::value || + std::is_same::value || + std::is_same::value, + int > = 0 > + void dump_integer(NumberType x) + { + static constexpr std::array, 100> digits_to_99 + { + { + {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, + {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, + {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, + {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, + {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, + {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, + {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, + {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, + {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, + {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, + } + }; + + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + // use a pointer to fill the buffer + auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg) + + number_unsigned_t abs_value; + + unsigned int n_chars{}; + + if (is_negative_number(x)) + { + *buffer_ptr = '-'; + abs_value = remove_sign(static_cast(x)); + + // account one more byte for the minus sign + n_chars = 1 + count_digits(abs_value); + } + else + { + abs_value = static_cast(x); + n_chars = count_digits(abs_value); + } + + // spare 1 byte for '\0' + JSON_ASSERT(n_chars < number_buffer.size() - 1); + + // jump to the end to generate the string from backward, + // so we later avoid reversing the result + buffer_ptr += static_cast(n_chars); + + // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu + // See: https://www.youtube.com/watch?v=o4-CwDo2zpg + while (abs_value >= 100) + { + const auto digits_index = static_cast((abs_value % 100)); + abs_value /= 100; + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + + if (abs_value >= 10) + { + const auto digits_index = static_cast(abs_value); + *(--buffer_ptr) = digits_to_99[digits_index][1]; + *(--buffer_ptr) = digits_to_99[digits_index][0]; + } + else + { + *(--buffer_ptr) = static_cast('0' + abs_value); + } + + o->write_characters(number_buffer.data(), n_chars); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (!std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || + (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + auto* begin = number_buffer.data(); + auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get the number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg) + std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + JSON_ASSERT(len > 0); + // check if the buffer was large enough + JSON_ASSERT(static_cast(len) < number_buffer.size()); + + // erase thousands separators + if (thousands_sep != '\0') + { + // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081 + const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + JSON_ASSERT((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' && decimal_point != '.') + { + // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081 + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if we need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return c == '.' || c == 'e'; + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + JSON_ASSERT(static_cast(byte) < utf8d.size()); + const std::uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6u) + : (0xFFu >> type) & (byte); + + const std::size_t index = 256u + (static_cast(state) * 16u) + static_cast(type); + JSON_ASSERT(index < utf8d.size()); + state = utf8d[index]; + return state; + } + + /* + * Overload to make the compiler happy while it is instantiating + * dump_integer for number_unsigned_t. + * Must never be called. + */ + number_unsigned_t remove_sign(number_unsigned_t x) + { + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + return x; // LCOV_EXCL_LINE + } + + /* + * Helper function for dump_integer + * + * This function takes a negative signed integer and returns its absolute + * value as an unsigned integer. The plus/minus shuffling is necessary as we + * cannot directly remove the sign of an arbitrary signed integer as the + * absolute values of INT_MIN and INT_MAX are usually not the same. See + * #1708 for details. + */ + number_unsigned_t remove_sign(number_integer_t x) noexcept + { + JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); // NOLINT(misc-redundant-expression) + return static_cast(-(x + 1)) + 1; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; + + /// error_handler how to react on decoding errors + const error_handler_t error_handler; +}; + +} // namespace detail +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 + + + +#include // equal_to, less +#include // initializer_list +#include // input_iterator_tag, iterator_traits +#include // allocator +#include // for out_of_range +#include // enable_if, is_convertible +#include // pair +#include // vector + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +/// ordered_map: a minimal map-like container that preserves insertion order +/// for use within nlohmann::basic_json +template , + class Allocator = std::allocator>> + struct ordered_map : std::vector, Allocator> +{ + using key_type = Key; + using mapped_type = T; + using Container = std::vector, Allocator>; + using iterator = typename Container::iterator; + using const_iterator = typename Container::const_iterator; + using size_type = typename Container::size_type; + using value_type = typename Container::value_type; +#ifdef JSON_HAS_CPP_14 + using key_compare = std::equal_to<>; +#else + using key_compare = std::equal_to; +#endif + + // Explicit constructors instead of `using Container::Container` + // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) + ordered_map() noexcept(noexcept(Container())) : Container{} {} + explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc))) : Container{alloc} {} + template + ordered_map(It first, It last, const Allocator& alloc = Allocator()) + : Container{first, last, alloc} {} + ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) + : Container{init, alloc} {} + + std::pair emplace(const key_type& key, T&& t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return {it, false}; + } + } + Container::emplace_back(key, std::forward(t)); + return {std::prev(this->end()), true}; + } + + template::value, int> = 0> + std::pair emplace(KeyType && key, T && t) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return {it, false}; + } + } + Container::emplace_back(std::forward(key), std::forward(t)); + return {std::prev(this->end()), true}; + } + + T& operator[](const key_type& key) + { + return emplace(key, T{}).first->second; + } + + template::value, int> = 0> + T & operator[](KeyType && key) + { + return emplace(std::forward(key), T{}).first->second; + } + + const T& operator[](const key_type& key) const + { + return at(key); + } + + template::value, int> = 0> + const T & operator[](KeyType && key) const + { + return at(std::forward(key)); + } + + T& at(const key_type& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + template::value, int> = 0> + T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + const T& at(const key_type& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + template::value, int> = 0> + const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it->second; + } + } + + JSON_THROW(std::out_of_range("key not found")); + } + + size_type erase(const key_type& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + template::value, int> = 0> + size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + // Since we cannot move const Keys, re-construct them in place + for (auto next = it; ++next != this->end(); ++it) + { + it->~value_type(); // Destroy but keep allocation + new (&*it) value_type{std::move(*next)}; + } + Container::pop_back(); + return 1; + } + } + return 0; + } + + iterator erase(iterator pos) + { + return erase(pos, std::next(pos)); + } + + iterator erase(iterator first, iterator last) + { + if (first == last) + { + return first; + } + + const auto elements_affected = std::distance(first, last); + const auto offset = std::distance(Container::begin(), first); + + // This is the start situation. We need to delete elements_affected + // elements (3 in this example: e, f, g), and need to return an + // iterator past the last deleted element (h in this example). + // Note that offset is the distance from the start of the vector + // to first. We will need this later. + + // [ a, b, c, d, e, f, g, h, i, j ] + // ^ ^ + // first last + + // Since we cannot move const Keys, we re-construct them in place. + // We start at first and re-construct (viz. copy) the elements from + // the back of the vector. Example for the first iteration: + + // ,--------. + // v | destroy e and re-construct with h + // [ a, b, c, d, e, f, g, h, i, j ] + // ^ ^ + // it it + elements_affected + + for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it) + { + it->~value_type(); // destroy but keep allocation + new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it + } + + // [ a, b, c, d, h, i, j, h, i, j ] + // ^ ^ + // first last + + // remove the unneeded elements at the end of the vector + Container::resize(this->size() - static_cast(elements_affected)); + + // [ a, b, c, d, h, i, j ] + // ^ ^ + // first last + + // first is now pointing past the last deleted element, but we cannot + // use this iterator, because it may have been invalidated by the + // resize call. Instead, we can return begin() + offset. + return Container::begin() + offset; + } + + size_type count(const key_type& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return 1; + } + } + return 0; + } + + template::value, int> = 0> + size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return 1; + } + } + return 0; + } + + iterator find(const key_type& key) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it; + } + } + return Container::end(); + } + + template::value, int> = 0> + iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it; + } + } + return Container::end(); + } + + const_iterator find(const key_type& key) const + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, key)) + { + return it; + } + } + return Container::end(); + } + + std::pair insert( value_type&& value ) + { + return emplace(value.first, std::move(value.second)); + } + + std::pair insert( const value_type& value ) + { + for (auto it = this->begin(); it != this->end(); ++it) + { + if (m_compare(it->first, value.first)) + { + return {it, false}; + } + } + Container::push_back(value); + return {--this->end(), true}; + } + + template + using require_input_iter = typename std::enable_if::iterator_category, + std::input_iterator_tag>::value>::type; + + template> + void insert(InputIt first, InputIt last) + { + for (auto it = first; it != last; ++it) + { + insert(*it); + } + } + +private: + JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare(); +}; + +NLOHMANN_JSON_NAMESPACE_END + + +#if defined(JSON_HAS_CPP_17) + #if JSON_HAS_STATIC_RTTI + #include + #endif + #include +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +NLOHMANN_JSON_NAMESPACE_BEGIN + +/*! +@brief a class to store JSON values + +@internal +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@note ObjectType trick from https://stackoverflow.com/a/9860911 +@endinternal + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions) + : public ::nlohmann::detail::json_base_class +{ + private: + template friend struct detail::external_constructor; + + template + friend class ::nlohmann::json_pointer; + // can be restored when json_pointer backwards compatibility is removed + // friend ::nlohmann::json_pointer; + + template + friend class ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + template + friend class ::nlohmann::detail::json_sax_dom_parser; + template + friend class ::nlohmann::detail::json_sax_dom_callback_parser; + friend class ::nlohmann::detail::exception; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + using json_base_class_t = ::nlohmann::detail::json_base_class; + + JSON_PRIVATE_UNLESS_TESTED: + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer_base; + + template + static ::nlohmann::detail::parser parser( + InputAdapterType adapter, + detail::parser_callback_tcb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false + ) + { + return ::nlohmann::detail::parser(std::move(adapter), + std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); + } + + private: + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + template + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + JSON_PRIVATE_UNLESS_TESTED: + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// JSON Pointer, see @ref nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// how to treat decoding errors + using error_handler_t = detail::error_handler_t; + /// how to treat CBOR tags + using cbor_tag_handler_t = detail::cbor_tag_handler_t; + /// how to encode BJData + using bjdata_version_t = detail::bjdata_version_t; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + using input_format_t = detail::input_format_t; + /// SAX interface type, see @ref nlohmann::json_sax + using json_sax_t = json_sax; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + using exception = detail::exception; + using parse_error = detail::parse_error; + using invalid_iterator = detail::invalid_iterator; + using type_error = detail::type_error; + using out_of_range = detail::out_of_range; + using other_error = detail::other_error; + + /// @} + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + /// @brief returns the allocator associated with the container + /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /// @brief returns version information on the library + /// @sa https://json.nlohmann.me/api/basic_json/meta/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2026 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.', + std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.', + std::to_string(NLOHMANN_JSON_VERSION_PATCH)); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", detail::concat( + std::to_string(__GNUC__), '.', + std::to_string(__GNUC_MINOR__), '.', + std::to_string(__GNUC_PATCHLEVEL__)) + } + }; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#if defined(_MSVC_LANG) + result["compiler"]["c++"] = std::to_string(_MSVC_LANG); +#elif defined(__cplusplus) + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /// @brief default object key comparator type + /// The actual object key comparator type (@ref object_comparator_t) may be + /// different. + /// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/ +#if defined(JSON_HAS_CPP_14) + // use of transparent comparator avoids unnecessary repeated construction of temporaries + // in functions involving lookup by key with types other than object_t::key_type (aka. StringType) + using default_object_comparator_t = std::less<>; +#else + using default_object_comparator_t = std::less; +#endif + + /// @brief a type for an object + /// @sa https://json.nlohmann.me/api/basic_json/object_t/ + using object_t = ObjectType>>; + + /// @brief a type for an array + /// @sa https://json.nlohmann.me/api/basic_json/array_t/ + using array_t = ArrayType>; + + /// @brief a type for a string + /// @sa https://json.nlohmann.me/api/basic_json/string_t/ + using string_t = StringType; + + /// @brief a type for a boolean + /// @sa https://json.nlohmann.me/api/basic_json/boolean_t/ + using boolean_t = BooleanType; + + /// @brief a type for a number (integer) + /// @sa https://json.nlohmann.me/api/basic_json/number_integer_t/ + using number_integer_t = NumberIntegerType; + + /// @brief a type for a number (unsigned) + /// @sa https://json.nlohmann.me/api/basic_json/number_unsigned_t/ + using number_unsigned_t = NumberUnsignedType; + + /// @brief a type for a number (floating-point) + /// @sa https://json.nlohmann.me/api/basic_json/number_float_t/ + using number_float_t = NumberFloatType; + + /// @brief a type for a packed binary type + /// @sa https://json.nlohmann.me/api/basic_json/binary_t/ + using binary_t = nlohmann::byte_container_with_subtype; + + /// @brief object key comparator type + /// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/ + using object_comparator_t = detail::actual_object_comparator_t; + + /// @} + + private: + + /// helper for exception-safe object creation + template + JSON_HEDLEY_RETURNS_NON_NULL + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * obj) + { + AllocatorTraits::deallocate(alloc, obj, 1); + }; + std::unique_ptr obj(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, obj.get(), std::forward(args)...); + JSON_ASSERT(obj != nullptr); + return obj.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + JSON_PRIVATE_UNLESS_TESTED: + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + binary | binary | pointer to @ref binary_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// binary (stored with pointer to save storage) + binary_t* binary; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::binary: + { + binary = create(); + break; + } + + case value_t::boolean: + { + boolean = static_cast(false); + break; + } + + case value_t::number_integer: + { + number_integer = static_cast(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = static_cast(0); + break; + } + + case value_t::number_float: + { + number_float = static_cast(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + case value_t::discarded: + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0", nullptr)); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) : string(create(value)) {} + + /// constructor for rvalue strings + json_value(string_t&& value) : string(create(std::move(value))) {} + + /// constructor for objects + json_value(const object_t& value) : object(create(value)) {} + + /// constructor for rvalue objects + json_value(object_t&& value) : object(create(std::move(value))) {} + + /// constructor for arrays + json_value(const array_t& value) : array(create(value)) {} + + /// constructor for rvalue arrays + json_value(array_t&& value) : array(create(std::move(value))) {} + + /// constructor for binary arrays + json_value(const typename binary_t::container_type& value) : binary(create(value)) {} + + /// constructor for rvalue binary arrays + json_value(typename binary_t::container_type&& value) : binary(create(std::move(value))) {} + + /// constructor for binary arrays (internal type) + json_value(const binary_t& value) : binary(create(value)) {} + + /// constructor for rvalue binary arrays (internal type) + json_value(binary_t&& value) : binary(create(std::move(value))) {} + + void destroy(value_t t) + { + if ( + (t == value_t::object && object == nullptr) || + (t == value_t::array && array == nullptr) || + (t == value_t::string && string == nullptr) || + (t == value_t::binary && binary == nullptr) + ) + { + // not initialized (e.g., due to exception in the ctor) + return; + } + if (t == value_t::array || t == value_t::object) + { + // flatten the current json_value to a heap-allocated stack + std::vector stack; + + // move the top-level items to stack + if (t == value_t::array) + { + stack.reserve(array->size()); + std::move(array->begin(), array->end(), std::back_inserter(stack)); + } + else + { + stack.reserve(object->size()); + for (auto&& it : *object) + { + stack.push_back(std::move(it.second)); + } + } + + while (!stack.empty()) + { + // move the last item to a local variable to be processed + basic_json current_item(std::move(stack.back())); + stack.pop_back(); + + // if current_item is array/object, move + // its children to the stack to be processed later + if (current_item.is_array()) + { + std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack)); + + current_item.m_data.m_value.array->clear(); + } + else if (current_item.is_object()) + { + for (auto&& it : *current_item.m_data.m_value.object) + { + stack.push_back(std::move(it.second)); + } + + current_item.m_data.m_value.object->clear(); + } + + // it's now safe that current_item gets destructed + // since it doesn't have any children + } + } + + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + case value_t::binary: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, binary); + std::allocator_traits::deallocate(alloc, binary, 1); + break; + } + + case value_t::null: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::discarded: + default: + { + break; + } + } + } + }; + + private: + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + + Furthermore, the parent relation is checked for arrays and objects: If + @a check_parents true and the value is an array or object, then the + container's elements must have the current value as parent. + + @param[in] check_parents whether the parent relation should be checked. + The value is true by default and should only be set to false + during destruction of objects when the invariant does not + need to hold. + */ + void assert_invariant(bool check_parents = true) const noexcept + { + JSON_ASSERT(m_data.m_type != value_t::object || m_data.m_value.object != nullptr); + JSON_ASSERT(m_data.m_type != value_t::array || m_data.m_value.array != nullptr); + JSON_ASSERT(m_data.m_type != value_t::string || m_data.m_value.string != nullptr); + JSON_ASSERT(m_data.m_type != value_t::binary || m_data.m_value.binary != nullptr); + +#if JSON_DIAGNOSTICS + JSON_TRY + { + // cppcheck-suppress assertWithSideEffect + JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j) + { + return j.m_parent == this; + })); + } + JSON_CATCH(...) {} // LCOV_EXCL_LINE +#endif + static_cast(check_parents); + } + + void set_parents() + { +#if JSON_DIAGNOSTICS + switch (m_data.m_type) + { + case value_t::array: + { + for (auto& element : *m_data.m_value.array) + { + element.m_parent = this; + } + break; + } + + case value_t::object: + { + for (auto& element : *m_data.m_value.object) + { + element.second.m_parent = this; + } + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + break; + } +#endif + } + + iterator set_parents(iterator it, typename iterator::difference_type count_set_parents) + { +#if JSON_DIAGNOSTICS + for (typename iterator::difference_type i = 0; i < count_set_parents; ++i) + { + (it + i)->m_parent = this; + } +#else + static_cast(count_set_parents); +#endif + return it; + } + + reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size()) + { +#if JSON_DIAGNOSTICS + if (old_capacity != detail::unknown_size()) + { + // see https://github.com/nlohmann/json/issues/2838 + JSON_ASSERT(type() == value_t::array); + if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity)) + { + // capacity has changed: update all parents + set_parents(); + return j; + } + } + + // ordered_json uses a vector internally, so pointers could have + // been invalidated; see https://github.com/nlohmann/json/issues/2962 +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning(push ) +#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr +#endif + if (detail::is_ordered_map::value) + { + set_parents(); + return j; + } +#ifdef JSON_HEDLEY_MSVC_VERSION +#pragma warning( pop ) +#endif + + j.m_parent = this; +#else + static_cast(j); + static_cast(old_capacity); +#endif + return j; + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /// @brief parser event types + /// @sa https://json.nlohmann.me/api/basic_json/parse_event_t/ + using parse_event_t = detail::parse_event_t; + + /// @brief per-element parser callback type + /// @sa https://json.nlohmann.me/api/basic_json/parser_callback_t/ + using parser_callback_t = detail::parser_callback_t; + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /// @brief create an empty value with a given type + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(const value_t v) + : m_data(v) + { + assert_invariant(); + } + + /// @brief create a null object + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape) + : basic_json(value_t::null) + { + assert_invariant(); + } + + /// @brief create a JSON value from compatible types + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename CompatibleType, + typename U = detail::uncvref_t, + detail::enable_if_t < + !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > + basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + set_parents(); + assert_invariant(); + } + + /// @brief create a JSON value from an existing one + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value&& !std::is_same::value, int > = 0 > + basic_json(const BasicJsonType& val) +#if JSON_DIAGNOSTIC_POSITIONS + : start_position(val.start_pos()), + end_position(val.end_pos()) +#endif + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + using other_binary_t = typename BasicJsonType::binary_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::binary: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_data.m_type = value_t::discarded; + break; + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + JSON_ASSERT(m_data.m_type == val.type()); + + set_parents(); + assert_invariant(); + } + + /// @brief create a container (array or object) from an initializer list + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + // The cast is to ensure op[size_type] is called, bearing in mind size_type may not be int; + // (many string types can be constructed from 0 via its null-pointer guise, so we get a + // broken call to op[key_type], the wrong semantics, and a 4804 warning on Windows) + return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[static_cast(0)].is_string(); + }); + + // adjust type if type deduction is not wanted + if (!type_deduction) + { + // if an array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if an object is wanted but impossible, throw an exception + if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list", nullptr)); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create an object + m_data.m_type = value_t::object; + m_data.m_value = value_t::object; + + for (auto& element_ref : init) + { + auto element = element_ref.moved_or_copied(); + m_data.m_value.object->emplace( + std::move(*((*element.m_data.m_value.array)[0].m_data.m_value.string)), + std::move((*element.m_data.m_value.array)[1])); + } + } + else + { + // the initializer list describes an array -> create an array + m_data.m_type = value_t::array; + m_data.m_value.array = create(init.begin(), init.end()); + } + + set_parents(); + assert_invariant(); + } + + /// @brief explicitly create a binary array (without subtype) + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = init; + return res; + } + + /// @brief explicitly create a binary array (with subtype) + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = binary_t(init, subtype); + return res; + } + + /// @brief explicitly create a binary array + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = std::move(init); + return res; + } + + /// @brief explicitly create a binary array (with subtype) + /// @sa https://json.nlohmann.me/api/basic_json/binary/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) + { + auto res = basic_json(); + res.m_data.m_type = value_t::binary; + res.m_data.m_value = binary_t(std::move(init), subtype); + return res; + } + + /// @brief explicitly create an array from an initializer list + /// @sa https://json.nlohmann.me/api/basic_json/array/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /// @brief explicitly create an object from an initializer list + /// @sa https://json.nlohmann.me/api/basic_json/object/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /// @brief construct an array with count copies of given value + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(size_type cnt, const basic_json& val): + m_data{cnt, val} + { + set_parents(); + assert_invariant(); + } + + /// @brief construct a JSON container given an iterator range + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + template < class InputIT, typename std::enable_if < + std::is_same::value || + std::is_same::value, int >::type = 0 > + basic_json(InputIT first, InputIT last) // NOLINT(performance-unnecessary-value-param) + { + JSON_ASSERT(first.m_object != nullptr); + JSON_ASSERT(last.m_object != nullptr); + + // make sure the iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible", nullptr)); + } + + // copy type from the first iterator + m_data.m_type = first.m_object->m_data.m_type; + + // check if the iterator range is complete for primitive values + switch (m_data.m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range", first.m_object)); + } + break; + } + + case value_t::null: + case value_t::object: + case value_t::array: + case value_t::binary: + case value_t::discarded: + default: + break; + } + + switch (m_data.m_type) + { + case value_t::number_integer: + { + m_data.m_value.number_integer = first.m_object->m_data.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_data.m_value.number_unsigned = first.m_object->m_data.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_data.m_value.number_float = first.m_object->m_data.m_value.number_float; + break; + } + + case value_t::boolean: + { + m_data.m_value.boolean = first.m_object->m_data.m_value.boolean; + break; + } + + case value_t::string: + { + m_data.m_value = *first.m_object->m_data.m_value.string; + break; + } + + case value_t::object: + { + m_data.m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_data.m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::binary: + { + m_data.m_value = *first.m_object->m_data.m_value.binary; + break; + } + + case value_t::null: + case value_t::discarded: + default: + JSON_THROW(invalid_iterator::create(206, detail::concat("cannot construct with iterators from ", first.m_object->type_name()), first.m_object)); + } + + set_parents(); + assert_invariant(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + template, + std::is_same>::value, int> = 0 > + basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} + + /// @brief copy constructor + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(const basic_json& other) + : json_base_class_t(other) +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) + , end_position(other.end_position) +#endif + { + m_data.m_type = other.m_data.m_type; + // check of passed value is valid + other.assert_invariant(); + + switch (m_data.m_type) + { + case value_t::object: + { + m_data.m_value = *other.m_data.m_value.object; + break; + } + + case value_t::array: + { + m_data.m_value = *other.m_data.m_value.array; + break; + } + + case value_t::string: + { + m_data.m_value = *other.m_data.m_value.string; + break; + } + + case value_t::boolean: + { + m_data.m_value = other.m_data.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_data.m_value = other.m_data.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_data.m_value = other.m_data.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_data.m_value = other.m_data.m_value.number_float; + break; + } + + case value_t::binary: + { + m_data.m_value = *other.m_data.m_value.binary; + break; + } + + case value_t::null: + case value_t::discarded: + default: + break; + } + + set_parents(); + assert_invariant(); + } + + /// @brief move constructor + /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ + basic_json(basic_json&& other) noexcept + : json_base_class_t(std::forward(other)), + m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check +#if JSON_DIAGNOSTIC_POSITIONS + , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check + , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check +#endif + { + // check that the passed value is valid + other.assert_invariant(false); // cppcheck-suppress[accessForwarded] + + // invalidate payload + other.m_data.m_type = value_t::null; + other.m_data.m_value = {}; + +#if JSON_DIAGNOSTIC_POSITIONS + other.start_position = std::string::npos; + other.end_position = std::string::npos; +#endif + + set_parents(); + assert_invariant(); + } + + /// @brief copy assignment + /// @sa https://json.nlohmann.me/api/basic_json/operator=/ + basic_json& operator=(basic_json other) noexcept ( // NOLINT(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_assignable::value + ) + { + // check that the passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_data.m_type, other.m_data.m_type); + swap(m_data.m_value, other.m_data.m_value); + +#if JSON_DIAGNOSTIC_POSITIONS + swap(start_position, other.start_position); + swap(end_position, other.end_position); +#endif + + json_base_class_t::operator=(std::move(other)); + + set_parents(); + assert_invariant(); + return *this; + } + + /// @brief destructor + /// @sa https://json.nlohmann.me/api/basic_json/~basic_json/ + ~basic_json() noexcept + { + assert_invariant(false); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /// @brief serialization + /// @sa https://json.nlohmann.me/api/basic_json/dump/ + string_t dump(const int indent = -1, + const char indent_char = ' ', + const bool ensure_ascii = false, + const error_handler_t error_handler = error_handler_t::strict) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char, error_handler); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /// @brief return the type of the JSON value (explicit) + /// @sa https://json.nlohmann.me/api/basic_json/type/ + constexpr value_t type() const noexcept + { + return m_data.m_type; + } + + /// @brief return whether type is primitive + /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/ + constexpr bool is_primitive() const noexcept + { + return is_null() || is_string() || is_boolean() || is_number() || is_binary(); + } + + /// @brief return whether type is structured + /// @sa https://json.nlohmann.me/api/basic_json/is_structured/ + constexpr bool is_structured() const noexcept + { + return is_array() || is_object(); + } + + /// @brief return whether value is null + /// @sa https://json.nlohmann.me/api/basic_json/is_null/ + constexpr bool is_null() const noexcept + { + return m_data.m_type == value_t::null; + } + + /// @brief return whether value is a boolean + /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/ + constexpr bool is_boolean() const noexcept + { + return m_data.m_type == value_t::boolean; + } + + /// @brief return whether value is a number + /// @sa https://json.nlohmann.me/api/basic_json/is_number/ + constexpr bool is_number() const noexcept + { + return is_number_integer() || is_number_float(); + } + + /// @brief return whether value is an integer number + /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/ + constexpr bool is_number_integer() const noexcept + { + return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned; + } + + /// @brief return whether value is an unsigned integer number + /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/ + constexpr bool is_number_unsigned() const noexcept + { + return m_data.m_type == value_t::number_unsigned; + } + + /// @brief return whether value is a floating-point number + /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/ + constexpr bool is_number_float() const noexcept + { + return m_data.m_type == value_t::number_float; + } + + /// @brief return whether value is an object + /// @sa https://json.nlohmann.me/api/basic_json/is_object/ + constexpr bool is_object() const noexcept + { + return m_data.m_type == value_t::object; + } + + /// @brief return whether value is an array + /// @sa https://json.nlohmann.me/api/basic_json/is_array/ + constexpr bool is_array() const noexcept + { + return m_data.m_type == value_t::array; + } + + /// @brief return whether value is a string + /// @sa https://json.nlohmann.me/api/basic_json/is_string/ + constexpr bool is_string() const noexcept + { + return m_data.m_type == value_t::string; + } + + /// @brief return whether value is a binary array + /// @sa https://json.nlohmann.me/api/basic_json/is_binary/ + constexpr bool is_binary() const noexcept + { + return m_data.m_type == value_t::binary; + } + + /// @brief return whether value is discarded + /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/ + constexpr bool is_discarded() const noexcept + { + return m_data.m_type == value_t::discarded; + } + + /// @brief return the type of the JSON value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/ + constexpr operator value_t() const noexcept + { + return m_data.m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_HEDLEY_LIKELY(is_boolean())) + { + return m_data.m_value.boolean; + } + + JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this)); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_data.m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_data.m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_data.m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_data.m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_data.m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_data.m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_data.m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_data.m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_data.m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_data.m_value.number_float : nullptr; + } + + /// get a pointer to the value (binary) + binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept + { + return is_binary() ? m_data.m_value.binary : nullptr; + } + + /// get a pointer to the value (binary) + constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept + { + return is_binary() ? m_data.m_value.binary : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto* ptr = obj.template get_ptr::type>(); + + if (JSON_HEDLEY_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, detail::concat("incompatible ReferenceType for get_ref, actual type is ", obj.type_name()), &obj)); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /// @brief get a pointer value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/ + template::value, int>::type = 0> + auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /// @brief get a pointer value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/ + template < typename PointerType, typename std::enable_if < + std::is_pointer::value&& + std::is_const::type>::value, int >::type = 0 > + constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) + { + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + private: + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < typename ValueType, + detail::enable_if_t < + detail::is_default_constructible::value&& + detail::has_from_json::value, + int > = 0 > + ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + auto ret = ValueType(); + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) + and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < typename ValueType, + detail::enable_if_t < + detail::has_non_default_from_json::value, + int > = 0 > + ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + return JSONSerializer::from_json(*this); + } + + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @a BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.2.0 + */ + template < typename BasicJsonType, + detail::enable_if_t < + detail::is_basic_json::value, + int > = 0 > + BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const + { + return *this; + } + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::value, + int> = 0> + basic_json get_impl(detail::priority_tag<3> /*unused*/) const + { + return *this; + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, + int> = 0> + constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept + -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + public: + /*! + @brief get a (pointer) value (explicit) + + Performs explicit type conversion between the JSON value and a compatible value if required. + + - If the requested type is a pointer to the internally stored JSON value that pointer is returned. + No copies are made. + + - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible + from the current @ref basic_json. + + - Otherwise the value is converted by calling the @ref json_serializer `from_json()` + method. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @tparam ValueType if necessary + + @throw what @ref json_serializer `from_json()` method throws if conversion is required + + @since version 2.1.0 + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t> +#if defined(JSON_HAS_CPP_14) + constexpr +#endif + auto get() const noexcept( + noexcept(std::declval().template get_impl(detail::priority_tag<4> {}))) + -> decltype(std::declval().template get_impl(detail::priority_tag<4> {})) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return get_impl(detail::priority_tag<4> {}); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa see @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + auto get() noexcept -> decltype(std::declval().template get_ptr()) + { + // delegate the call to get_ptr + return get_ptr(); + } + + /// @brief get a value (explicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_to/ + template < typename ValueType, + detail::enable_if_t < + !detail::is_basic_json::value&& + detail::has_from_json::value, + int > = 0 > + ValueType & get_to(ValueType& v) const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + // specialization to allow calling get_to with a basic_json value + // see https://github.com/nlohmann/json/issues/2175 + template::value, + int> = 0> + ValueType & get_to(ValueType& v) const + { + v = *this; + return v; + } + + template < + typename T, std::size_t N, + typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + detail::enable_if_t < + detail::has_from_json::value, int > = 0 > + Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + noexcept(noexcept(JSONSerializer::from_json( + std::declval(), v))) + { + JSONSerializer::from_json(*this, v); + return v; + } + + /// @brief get a reference value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ref/ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /// @brief get a reference value (implicit) + /// @sa https://json.nlohmann.me/api/basic_json/get_ref/ + template < typename ReferenceType, typename std::enable_if < + std::is_reference::value&& + std::is_const::type>::value, int >::type = 0 > + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + detail::conjunction < + detail::negation>, + detail::negation>, + detail::negation>>, + detail::negation>, + detail::negation>, + detail::negation>>, +#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) + detail::negation>, +#endif +#if defined(JSON_HAS_CPP_17) && JSON_HAS_STATIC_RTTI + detail::negation>, +#endif + detail::is_detected_lazy + >::value, int >::type = 0 > + JSON_EXPLICIT operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @brief get a binary value + /// @sa https://json.nlohmann.me/api/basic_json/get_binary/ + binary_t& get_binary() + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + } + + return *get_ptr(); + } + + /// @brief get a binary value + /// @sa https://json.nlohmann.me/api/basic_json/get_binary/ + const binary_t& get_binary() const + { + if (!is_binary()) + { + JSON_THROW(type_error::create(302, detail::concat("type must be binary, but is ", type_name()), this)); + } + + return *get_ptr(); + } + + /// @} + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /// @brief access specified array element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return set_parent(m_data.m_value.array->at(idx)); + } + JSON_CATCH (std::out_of_range&) + { + // create a better exception explanation + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + } // cppcheck-suppress[missingReturn] + } + else + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + } + + /// @brief access specified array element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + JSON_TRY + { + return m_data.m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create a better exception explanation + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + } // cppcheck-suppress[missingReturn] + } + else + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(key); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); + } + return set_parent(it->second); + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + template::value, int> = 0> + reference at(KeyType && key) + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(std::forward(key)); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); + } + return set_parent(it->second); + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(key); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", key, "' not found"), this)); + } + return it->second; + } + + /// @brief access specified object element with bounds checking + /// @sa https://json.nlohmann.me/api/basic_json/at/ + template::value, int> = 0> + const_reference at(KeyType && key) const + { + // at only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(304, detail::concat("cannot use at() with ", type_name()), this)); + } + + auto it = m_data.m_value.object->find(std::forward(key)); + if (it == m_data.m_value.object->end()) + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", string_t(std::forward(key)), "' not found"), this)); + } + return it->second; + } + + /// @brief access specified array element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + reference operator[](size_type idx) + { + // implicitly convert a null value to an empty array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // fill up the array with null values if given idx is outside the range + if (idx >= m_data.m_value.array->size()) + { +#if JSON_DIAGNOSTICS + // remember array size & capacity before resizing + const auto old_size = m_data.m_value.array->size(); + const auto old_capacity = m_data.m_value.array->capacity(); +#endif + m_data.m_value.array->resize(idx + 1); + +#if JSON_DIAGNOSTICS + if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity)) + { + // capacity has changed: update all parents + set_parents(); + } + else + { + // set parent for values added above + set_parents(begin() + static_cast(old_size), static_cast(idx + 1 - old_size)); + } +#endif + assert_invariant(); + } + + return m_data.m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + } + + /// @brief access specified array element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + return m_data.m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a numeric argument with ", type_name()), this)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + reference operator[](typename object_t::key_type key) // NOLINT(performance-unnecessary-value-param) + { + // implicitly convert a null value to an empty object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto result = m_data.m_value.object->emplace(std::move(key), nullptr); + return set_parent(result.first->second); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto it = m_data.m_value.object->find(key); + JSON_ASSERT(it != m_data.m_value.object->end()); + return it->second; + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC + // (they seemingly cannot be constrained to resolve the ambiguity) + template + reference operator[](T* key) + { + return operator[](typename object_t::key_type(key)); + } + + template + const_reference operator[](T* key) const + { + return operator[](typename object_t::key_type(key)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + template::value, int > = 0 > + reference operator[](KeyType && key) + { + // implicitly convert a null value to an empty object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto result = m_data.m_value.object->emplace(std::forward(key), nullptr); + return set_parent(result.first->second); + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + /// @brief access specified object element + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + template::value, int > = 0 > + const_reference operator[](KeyType && key) const + { + // const operator[] only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + auto it = m_data.m_value.object->find(std::forward(key)); + JSON_ASSERT(it != m_data.m_value.object->end()); + return it->second; + } + + JSON_THROW(type_error::create(305, detail::concat("cannot use operator[] with a string argument with ", type_name()), this)); + } + + private: + template + using is_comparable_with_object_key = detail::is_comparable < + object_comparator_t, const typename object_t::key_type&, KeyType >; + + template + using value_return_type = std::conditional < + detail::is_c_string_uncvref::value, + string_t, typename std::decay::type >; + + public: + /// @brief access specified object element with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, detail::enable_if_t < + !detail::is_transparent::value + && detail::is_getable::value + && !std::is_same>::value, int > = 0 > + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto it = find(key); + if (it != end()) + { + return it->template get(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class ReturnType = typename value_return_type::type, + detail::enable_if_t < + !detail::is_transparent::value + && detail::is_getable::value + && !std::is_same>::value, int > = 0 > + ReturnType value(const typename object_t::key_type& key, ValueType && default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto it = find(key); + if (it != end()) + { + return it->template get(); + } + + return std::forward(default_value); + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class KeyType, detail::enable_if_t < + detail::is_transparent::value + && !detail::is_json_pointer::value + && is_comparable_with_object_key::value + && detail::is_getable::value + && !std::is_same>::value, int > = 0 > + ValueType value(KeyType && key, const ValueType& default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto it = find(std::forward(key)); + if (it != end()) + { + return it->template get(); + } + + return default_value; + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element via JSON Pointer with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class KeyType, class ReturnType = typename value_return_type::type, + detail::enable_if_t < + detail::is_transparent::value + && !detail::is_json_pointer::value + && is_comparable_with_object_key::value + && detail::is_getable::value + && !std::is_same>::value, int > = 0 > + ReturnType value(KeyType && key, ValueType && default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // If 'key' is found, return its value. Otherwise, return `default_value'. + const auto it = find(std::forward(key)); + if (it != end()) + { + return it->template get(); + } + + return std::forward(default_value); + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element via JSON Pointer with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, detail::enable_if_t < + detail::is_getable::value + && !std::is_same>::value, int > = 0 > + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. + JSON_TRY + { + return ptr.get_checked(this).template get(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + /// @brief access specified object element via JSON Pointer with default value + /// @sa https://json.nlohmann.me/api/basic_json/value/ + template < class ValueType, class ReturnType = typename value_return_type::type, + detail::enable_if_t < + detail::is_getable::value + && !std::is_same>::value, int > = 0 > + ReturnType value(const json_pointer& ptr, ValueType && default_value) const + { + // value only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + // If the pointer resolves to a value, return it. Otherwise, return + // 'default_value'. + JSON_TRY + { + return ptr.get_checked(this).template get(); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + return std::forward(default_value); + } + } + + JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); + } + + template < class ValueType, class BasicJsonType, detail::enable_if_t < + detail::is_basic_json::value + && detail::is_getable::value + && !std::is_same>::value, int > = 0 > + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + ValueType value(const ::nlohmann::json_pointer& ptr, const ValueType& default_value) const + { + return value(ptr.convert(), default_value); + } + + template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type::type, + detail::enable_if_t < + detail::is_basic_json::value + && detail::is_getable::value + && !std::is_same>::value, int > = 0 > + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + ReturnType value(const ::nlohmann::json_pointer& ptr, ValueType && default_value) const + { + return value(ptr.convert(), std::forward(default_value)); + } + + /// @brief access the first element + /// @sa https://json.nlohmann.me/api/basic_json/front/ + reference front() + { + return *begin(); + } + + /// @brief access the first element + /// @sa https://json.nlohmann.me/api/basic_json/front/ + const_reference front() const + { + return *cbegin(); + } + + /// @brief access the last element + /// @sa https://json.nlohmann.me/api/basic_json/back/ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /// @brief access the last element + /// @sa https://json.nlohmann.me/api/basic_json/back/ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /// @brief remove element given an iterator + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + template < class IteratorType, detail::enable_if_t < + std::is_same::value || + std::is_same::value, int > = 0 > + IteratorType erase(IteratorType pos) // NOLINT(performance-unnecessary-value-param) + { + // make sure the iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + IteratorType result = end(); + + switch (m_data.m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range", this)); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_data.m_value.string); + std::allocator_traits::deallocate(alloc, m_data.m_value.string, 1); + m_data.m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_data.m_value.binary); + std::allocator_traits::deallocate(alloc, m_data.m_value.binary, 1); + m_data.m_value.binary = nullptr; + } + + m_data.m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_data.m_value.array->erase(pos.m_it.array_iterator); + break; + } + + case value_t::null: + case value_t::discarded: + default: + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + return result; + } + + /// @brief remove elements given an iterator range + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + template < class IteratorType, detail::enable_if_t < + std::is_same::value || + std::is_same::value, int > = 0 > + IteratorType erase(IteratorType first, IteratorType last) // NOLINT(performance-unnecessary-value-param) + { + // make sure the iterator fits the current value + if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value", this)); + } + + IteratorType result = end(); + + switch (m_data.m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + case value_t::binary: + { + if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() + || !last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range", this)); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_data.m_value.string); + std::allocator_traits::deallocate(alloc, m_data.m_value.string, 1); + m_data.m_value.string = nullptr; + } + else if (is_binary()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_data.m_value.binary); + std::allocator_traits::deallocate(alloc, m_data.m_value.binary, 1); + m_data.m_value.binary = nullptr; + } + + m_data.m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_data.m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + case value_t::null: + case value_t::discarded: + default: + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + return result; + } + + private: + template < typename KeyType, detail::enable_if_t < + detail::has_erase_with_key_type::value, int > = 0 > + size_type erase_internal(KeyType && key) + { + // this erase only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + return m_data.m_value.object->erase(std::forward(key)); + } + + template < typename KeyType, detail::enable_if_t < + !detail::has_erase_with_key_type::value, int > = 0 > + size_type erase_internal(KeyType && key) + { + // this erase only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + + const auto it = m_data.m_value.object->find(std::forward(key)); + if (it != m_data.m_value.object->end()) + { + m_data.m_value.object->erase(it); + return 1; + } + return 0; + } + + public: + + /// @brief remove element from a JSON object given a key + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + size_type erase(const typename object_t::key_type& key) + { + // the indirection via erase_internal() is added to avoid making this + // function a template and thus de-rank it during overload resolution + return erase_internal(key); + } + + /// @brief remove element from a JSON object given a key + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + template::value, int> = 0> + size_type erase(KeyType && key) + { + return erase_internal(std::forward(key)); + } + + /// @brief remove element from a JSON array given an index + /// @sa https://json.nlohmann.me/api/basic_json/erase/ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + if (JSON_HEDLEY_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), this)); + } + + m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this)); + } + } + + /// @} + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + iterator find(const typename object_t::key_type& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(key); + } + + return result; + } + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + const_iterator find(const typename object_t::key_type& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(key); + } + + return result; + } + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + template::value, int> = 0> + iterator find(KeyType && key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(std::forward(key)); + } + + return result; + } + + /// @brief find an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/find/ + template::value, int> = 0> + const_iterator find(KeyType && key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_data.m_value.object->find(std::forward(key)); + } + + return result; + } + + /// @brief returns the number of occurrences of a key in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/count/ + size_type count(const typename object_t::key_type& key) const + { + // return 0 for all nonobject types + return is_object() ? m_data.m_value.object->count(key) : 0; + } + + /// @brief returns the number of occurrences of a key in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/count/ + template::value, int> = 0> + size_type count(KeyType && key) const + { + // return 0 for all nonobject types + return is_object() ? m_data.m_value.object->count(std::forward(key)) : 0; + } + + /// @brief check the existence of an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/contains/ + bool contains(const typename object_t::key_type& key) const + { + return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end(); + } + + /// @brief check the existence of an element in a JSON object + /// @sa https://json.nlohmann.me/api/basic_json/contains/ + template::value, int> = 0> + bool contains(KeyType && key) const + { + return is_object() && m_data.m_value.object->find(std::forward(key)) != m_data.m_value.object->end(); + } + + /// @brief check the existence of an element in a JSON object given a JSON pointer + /// @sa https://json.nlohmann.me/api/basic_json/contains/ + bool contains(const json_pointer& ptr) const + { + return ptr.contains(this); + } + + template::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + bool contains(const typename ::nlohmann::json_pointer& ptr) const + { + return ptr.contains(this); + } + + /// @} + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /// @brief returns an iterator to the first element + /// @sa https://json.nlohmann.me/api/basic_json/begin/ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /// @brief returns an iterator to the first element + /// @sa https://json.nlohmann.me/api/basic_json/begin/ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /// @brief returns a const iterator to the first element + /// @sa https://json.nlohmann.me/api/basic_json/cbegin/ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /// @brief returns an iterator to one past the last element + /// @sa https://json.nlohmann.me/api/basic_json/end/ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /// @brief returns an iterator to one past the last element + /// @sa https://json.nlohmann.me/api/basic_json/end/ + const_iterator end() const noexcept + { + return cend(); + } + + /// @brief returns an iterator to one past the last element + /// @sa https://json.nlohmann.me/api/basic_json/cend/ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /// @brief returns an iterator to the reverse-beginning + /// @sa https://json.nlohmann.me/api/basic_json/rbegin/ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /// @brief returns an iterator to the reverse-beginning + /// @sa https://json.nlohmann.me/api/basic_json/rbegin/ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /// @brief returns an iterator to the reverse-end + /// @sa https://json.nlohmann.me/api/basic_json/rend/ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /// @brief returns an iterator to the reverse-end + /// @sa https://json.nlohmann.me/api/basic_json/rend/ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /// @brief returns a const reverse iterator to the last element + /// @sa https://json.nlohmann.me/api/basic_json/crbegin/ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /// @brief returns a const reverse iterator to one before the first + /// @sa https://json.nlohmann.me/api/basic_json/crend/ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /// @brief wrapper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + /// @deprecated This function is deprecated since 3.1.0 and will be removed in + /// version 4.0.0 of the library. Please use @ref items() instead; + /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /// @brief wrapper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + /// @deprecated This function is deprecated since 3.1.0 and will be removed in + /// version 4.0.0 of the library. Please use @ref items() instead; + /// that is, replace `json::iterator_wrapper(j)` with `j.items()`. + JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /// @brief helper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /// @brief helper to access iterator member functions in range-based for + /// @sa https://json.nlohmann.me/api/basic_json/items/ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /// @brief checks whether the container is empty. + /// @sa https://json.nlohmann.me/api/basic_json/empty/ + bool empty() const noexcept + { + switch (m_data.m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_data.m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_data.m_value.object->empty(); + } + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // all other types are nonempty + return false; + } + } + } + + /// @brief returns the number of elements + /// @sa https://json.nlohmann.me/api/basic_json/size/ + size_type size() const noexcept + { + switch (m_data.m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_data.m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_data.m_value.object->size(); + } + + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // all other types have size 1 + return 1; + } + } + } + + /// @brief returns the maximum possible number of elements + /// @sa https://json.nlohmann.me/api/basic_json/max_size/ + size_type max_size() const noexcept + { + switch (m_data.m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_data.m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_data.m_value.object->max_size(); + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /// @brief clears the contents + /// @sa https://json.nlohmann.me/api/basic_json/clear/ + void clear() noexcept + { + switch (m_data.m_type) + { + case value_t::number_integer: + { + m_data.m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_data.m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_data.m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_data.m_value.boolean = false; + break; + } + + case value_t::string: + { + m_data.m_value.string->clear(); + break; + } + + case value_t::binary: + { + m_data.m_value.binary->clear(); + break; + } + + case value_t::array: + { + m_data.m_value.array->clear(); + break; + } + + case value_t::object: + { + m_data.m_value.object->clear(); + break; + } + + case value_t::null: + case value_t::discarded: + default: + break; + } + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + } + + // transform a null object into an array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value = value_t::array; + assert_invariant(); + } + + // add the element to the array (move semantics) + const auto old_capacity = m_data.m_value.array->capacity(); + m_data.m_value.array->push_back(std::move(val)); + set_parent(m_data.m_value.array->back(), old_capacity); + // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + } + + // transform a null object into an array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value = value_t::array; + assert_invariant(); + } + + // add the element to the array + const auto old_capacity = m_data.m_value.array->capacity(); + m_data.m_value.array->push_back(val); + set_parent(m_data.m_value.array->back(), old_capacity); + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(308, detail::concat("cannot use push_back() with ", type_name()), this)); + } + + // transform a null object into an object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value = value_t::object; + assert_invariant(); + } + + // add the element to the object + auto res = m_data.m_value.object->insert(val); + set_parent(res.first->second); + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/push_back/ + void push_back(initializer_list_t init) + { + if (is_object() && init.size() == 2 && (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /// @brief add an object to an object + /// @sa https://json.nlohmann.me/api/basic_json/operator+=/ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /// @brief add an object to an array + /// @sa https://json.nlohmann.me/api/basic_json/emplace_back/ + template + reference emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) + { + JSON_THROW(type_error::create(311, detail::concat("cannot use emplace_back() with ", type_name()), this)); + } + + // transform a null object into an array + if (is_null()) + { + m_data.m_type = value_t::array; + m_data.m_value = value_t::array; + assert_invariant(); + } + + // add the element to the array (perfect forwarding) + const auto old_capacity = m_data.m_value.array->capacity(); + m_data.m_value.array->emplace_back(std::forward(args)...); + return set_parent(m_data.m_value.array->back(), old_capacity); + } + + /// @brief add an object to an object if key does not exist + /// @sa https://json.nlohmann.me/api/basic_json/emplace/ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) + { + JSON_THROW(type_error::create(311, detail::concat("cannot use emplace() with ", type_name()), this)); + } + + // transform a null object into an object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value = value_t::object; + assert_invariant(); + } + + // add the element to the array (perfect forwarding) + auto res = m_data.m_value.object->emplace(std::forward(args)...); + set_parent(res.first->second); + + // create a result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /// Helper for insertion of an iterator + /// @note: This uses std::distance to support GCC 4.8, + /// see https://github.com/nlohmann/json/pull/1257 + template + iterator insert_iterator(const_iterator pos, Args&& ... args) // NOLINT(performance-unnecessary-value-param) + { + iterator result(this); + JSON_ASSERT(m_data.m_value.array != nullptr); + + auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator); + m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); + result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos; + + // This could have been written as: + // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val); + // but the return value of insert is missing in GCC 4.8, so it is written this way instead. + + set_parents(); + return result; + } + + /// @brief inserts element into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, val); + } + + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + /// @brief inserts element into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param) + { + return insert(pos, val); + } + + /// @brief inserts copies of element into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param) + { + // insert only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, cnt, val); + } + + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + /// @brief inserts range of elements into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + } + + if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); + } + + /// @brief inserts elements from initializer list into array + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + iterator insert(const_iterator pos, initializer_list_t ilist) // NOLINT(performance-unnecessary-value-param) + { + // insert only works for arrays + if (JSON_HEDLEY_UNLIKELY(!is_array())) + { + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + // check if iterator pos fits to this JSON value + if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value", this)); + } + + // insert to array and return iterator + return insert_iterator(pos, ilist.begin(), ilist.end()); + } + + /// @brief inserts range of elements into object + /// @sa https://json.nlohmann.me/api/basic_json/insert/ + void insert(const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param) + { + // insert only works for objects + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(309, detail::concat("cannot use insert() with ", type_name()), this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects", this)); + } + + m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + set_parents(); + } + + /// @brief updates a JSON object from another object, overwriting existing keys + /// @sa https://json.nlohmann.me/api/basic_json/update/ + void update(const_reference j, bool merge_objects = false) + { + update(j.begin(), j.end(), merge_objects); + } + + /// @brief updates a JSON object from another object, overwriting existing keys + /// @sa https://json.nlohmann.me/api/basic_json/update/ + void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param) + { + // implicitly convert a null value to an empty object + if (is_null()) + { + m_data.m_type = value_t::object; + m_data.m_value.object = create(); + assert_invariant(); + } + + if (JSON_HEDLEY_UNLIKELY(!is_object())) + { + JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this)); + } + + // check if range iterators belong to the same JSON object + if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit", this)); + } + + // passed iterators must belong to objects + if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) + { + JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object)); + } + + for (auto it = first; it != last; ++it) + { + if (merge_objects && it.value().is_object()) + { + auto it2 = m_data.m_value.object->find(it.key()); + if (it2 != m_data.m_value.object->end()) + { + it2->second.update(it.value(), true); + continue; + } + } + m_data.m_value.object->operator[](it.key()) = it.value(); +#if JSON_DIAGNOSTICS + m_data.m_value.object->operator[](it.key()).m_parent = this; +#endif + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_data.m_type, other.m_data.m_type); + std::swap(m_data.m_value, other.m_data.m_value); + + set_parents(); + other.set_parents(); + assert_invariant(); + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + friend void swap(reference left, reference right) noexcept ( + std::is_nothrow_move_constructible::value&& + std::is_nothrow_move_assignable::value&& + std::is_nothrow_move_constructible::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + std::is_nothrow_move_assignable::value + ) + { + left.swap(right); + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for arrays + if (JSON_HEDLEY_LIKELY(is_array())) + { + using std::swap; + swap(*(m_data.m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(array_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for objects + if (JSON_HEDLEY_LIKELY(is_object())) + { + using std::swap; + swap(*(m_data.m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(object_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_string())) + { + using std::swap; + swap(*(m_data.m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(string_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + using std::swap; + swap(*(m_data.m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t&) with ", type_name()), this)); + } + } + + /// @brief exchanges the values + /// @sa https://json.nlohmann.me/api/basic_json/swap/ + void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape) + { + // swap only works for strings + if (JSON_HEDLEY_LIKELY(is_binary())) + { + using std::swap; + swap(*(m_data.m_value.binary), other); + } + else + { + JSON_THROW(type_error::create(310, detail::concat("cannot use swap(binary_t::container_type&) with ", type_name()), this)); + } + } + + /// @} + + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_data.m_value.number_float op static_cast(rhs.m_data.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_data.m_value.number_float op static_cast(rhs.m_data.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_data.m_value.number_integer op static_cast(rhs.m_data.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + bool operator==(const_reference rhs) const noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + bool operator!=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !operator==(rhs); + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as an odd number of inverses of others + // need to be overloaded to emulate the legacy comparison behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(*this < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, ScalarType rhs) noexcept + { + return lhs == basic_json(rhs); + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template::value, int>::type = 0> + friend bool operator==(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) == rhs; + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } + return !(lhs == rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs != basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + template::value, int>::type = 0> + friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) != rhs; + } + + /// @brief comparison: less than + /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + // default_result is used if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) + } + + /// @brief comparison: less than + /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, ScalarType rhs) noexcept + { + return lhs < basic_json(rhs); + } + + /// @brief comparison: less than + /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ + template::value, int>::type = 0> + friend bool operator<(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) < rhs; + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } + return !(rhs < lhs); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs <= basic_json(rhs); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template::value, int>::type = 0> + friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) <= rhs; + } + + /// @brief comparison: greater than + /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } + return !(lhs <= rhs); + } + + /// @brief comparison: greater than + /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, ScalarType rhs) noexcept + { + return lhs > basic_json(rhs); + } + + /// @brief comparison: greater than + /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ + template::value, int>::type = 0> + friend bool operator>(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) > rhs; + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } + return !(lhs < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept + { + return lhs >= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template::value, int>::type = 0> + friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept + { + return basic_json(lhs) >= rhs; + } +#endif + +#undef JSON_IMPLEMENT_OPERATOR + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ +#ifndef JSON_NO_IO + /// @brief serialize to stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as the indentation parameter if nonzero + const bool pretty_print = o.width() > 0; + const auto indentation = pretty_print ? o.width() : 0; + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /// @brief serialize to stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/ + /// @deprecated This function is deprecated since 3.0.0 and will be removed in + /// version 4.0.0 of the library. Please use + /// operator<<(std::ostream&, const basic_json&) instead; that is, + /// replace calls like `j >> o;` with `o << j;`. + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } +#endif // JSON_NO_IO + /// @} + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /// @brief deserialize from a compatible input + /// @sa https://json.nlohmann.me/api/basic_json/parse/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(InputType&& i, + parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + basic_json result; + parser(detail::input_adapter(std::forward(i)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded] + return result; + } + + /// @brief deserialize from a pair of character iterators + /// @sa https://json.nlohmann.me/api/basic_json/parse/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json parse(IteratorType first, + IteratorType last, + parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + basic_json result; + parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + return result; + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) + static basic_json parse(detail::span_input_adapter&& i, + parser_callback_t cb = nullptr, + const bool allow_exceptions = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + basic_json result; + parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved] + return result; + } + + /// @brief check if the input is valid JSON + /// @sa https://json.nlohmann.me/api/basic_json/accept/ + template + static bool accept(InputType&& i, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); + } + + /// @brief check if the input is valid JSON + /// @sa https://json.nlohmann.me/api/basic_json/accept/ + template + static bool accept(IteratorType first, IteratorType last, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) + static bool accept(detail::span_input_adapter&& i, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { + return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); + } + + /// @brief generate SAX events + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + template + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(InputType&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (sax == nullptr) + { + JSON_THROW(other_error::create(502, "SAX handler must not be null", nullptr)); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + auto ia = detail::input_adapter(std::forward(i)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); + } + + /// @brief generate SAX events + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + template + JSON_HEDLEY_NON_NULL(3) + static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (sax == nullptr) + { + JSON_THROW(other_error::create(502, "SAX handler must not be null", nullptr)); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + auto ia = detail::input_adapter(std::move(first), std::move(last)); + return format == input_format_t::json + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); + } + + /// @brief generate SAX events + /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ + /// @deprecated This function is deprecated since 3.8.0 and will be removed in + /// version 4.0.0 of the library. Please use + /// sax_parse(ptr, ptr + len) instead. + template + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) + JSON_HEDLEY_NON_NULL(2) + static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, + input_format_t format = input_format_t::json, + const bool strict = true, + const bool ignore_comments = false, + const bool ignore_trailing_commas = false) + { +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-pointer-compare" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (sax == nullptr) + { + JSON_THROW(other_error::create(502, "SAX handler must not be null", nullptr)); + } +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + auto ia = i.get(); + return format == input_format_t::json + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + ? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict) + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + : detail::binary_reader(std::move(ia), format).sax_parse(format, sax, strict); + } +#ifndef JSON_NO_IO + /// @brief deserialize from stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in + /// version 4.0.0 of the library. Please use + /// operator>>(std::istream&, basic_json&) instead; that is, + /// replace calls like `j << i;` with `i >> j;`. + JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /// @brief deserialize from stream + /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } +#endif // JSON_NO_IO + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /// @brief return the type as string + /// @sa https://json.nlohmann.me/api/basic_json/type_name/ + JSON_HEDLEY_RETURNS_NON_NULL + const char* type_name() const noexcept + { + switch (m_data.m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::binary: + return "binary"; + case value_t::discarded: + return "discarded"; + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + return "number"; + default: + return "invalid"; + } + } + + JSON_PRIVATE_UNLESS_TESTED: + ////////////////////// + // member variables // + ////////////////////// + + struct data + { + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + data(const value_t v) + : m_type(v), m_value(v) + { + } + + data(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + } + + data() noexcept = default; + data(data&&) noexcept = default; + data(const data&) noexcept = delete; + data& operator=(data&&) noexcept = delete; + data& operator=(const data&) noexcept = delete; + + ~data() noexcept + { + m_value.destroy(m_type); + } + }; + + data m_data = {}; + +#if JSON_DIAGNOSTICS + /// a pointer to a parent value (for debugging purposes) + basic_json* m_parent = nullptr; +#endif + +#if JSON_DIAGNOSTIC_POSITIONS + /// the start position of the value + std::size_t start_position = std::string::npos; + /// the end position of the value + std::size_t end_position = std::string::npos; + public: + constexpr std::size_t start_pos() const noexcept + { + return start_position; + } + + constexpr std::size_t end_pos() const noexcept + { + return end_position; + } +#endif + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /// @brief create a CBOR serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + /// @brief create a CBOR serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /// @brief create a CBOR serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/ + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /// @brief create a MessagePack serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + /// @brief create a MessagePack serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /// @brief create a MessagePack serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/ + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /// @brief create a UBJSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + /// @brief create a UBJSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /// @brief create a UBJSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/ + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static std::vector to_bjdata(const basic_json& j, + const bool use_size = false, + const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) + { + std::vector result; + to_bjdata(j, result, use_size, use_type, version); + return result; + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) + { + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); + } + + /// @brief create a BJData serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/ + static void to_bjdata(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false, + const bjdata_version_t version = bjdata_version_t::draft2) + { + binary_writer(o).write_ubjson(j, use_size, use_type, true, true, version); + } + + /// @brief create a BSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ + static std::vector to_bson(const basic_json& j) + { + std::vector result; + to_bson(j, result); + return result; + } + + /// @brief create a BSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + /// @brief create a BSON serialization of a given JSON value + /// @sa https://json.nlohmann.me/api/basic_json/to_bson/ + static void to_bson(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_bson(j); + } + + /// @brief create a JSON value from an input in CBOR format + /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + auto ia = detail::input_adapter(std::forward(i)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in CBOR format + /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_cbor(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) + static basic_json from_cbor(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true, + const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) + { + basic_json result; + auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in MessagePack format + /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::forward(i)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in MessagePack format + /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_msgpack(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_msgpack(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) + static basic_json from_msgpack(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in UBJSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::forward(i)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in UBJSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_ubjson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_ubjson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) + static basic_json from_ubjson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::forward(i)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BJData format + /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bjdata(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(InputType&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::forward(i)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + /// @brief create a JSON value from an input in BSON format + /// @sa https://json.nlohmann.me/api/basic_json/from_bson/ + template + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json from_bson(IteratorType first, IteratorType last, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = detail::input_adapter(std::move(first), std::move(last)); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + + template + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(const T* ptr, std::size_t len, + const bool strict = true, + const bool allow_exceptions = true) + { + return from_bson(ptr, ptr + len, strict, allow_exceptions); + } + + JSON_HEDLEY_WARN_UNUSED_RESULT + JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) + static basic_json from_bson(detail::span_input_adapter&& i, + const bool strict = true, + const bool allow_exceptions = true) + { + basic_json result; + auto ia = i.get(); + detail::json_sax_dom_parser sdp(result, allow_exceptions); + // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg) + const bool res = binary_reader(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved] + return res ? result : basic_json(value_t::discarded); + } + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + template::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + reference operator[](const ::nlohmann::json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + template::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + const_reference operator[](const ::nlohmann::json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/at/ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + template::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + reference at(const ::nlohmann::json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /// @brief access specified element via JSON Pointer + /// @sa https://json.nlohmann.me/api/basic_json/at/ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + template::value, int> = 0> + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer) // NOLINT(readability/alt_tokens) + const_reference at(const ::nlohmann::json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /// @brief return flattened JSON value + /// @sa https://json.nlohmann.me/api/basic_json/flatten/ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /// @brief unflatten a previously flattened JSON value + /// @sa https://json.nlohmann.me/api/basic_json/unflatten/ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /// @brief applies a JSON patch in-place without copying the object + /// @sa https://json.nlohmann.me/api/basic_json/patch/ + void patch_inplace(const basic_json& json_patch) + { + basic_json& result = *this; + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const string_t& op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, const basic_json & val) + { + // adding to the root of the target document means replacing it + if (ptr.empty()) + { + result = val; + return; + } + + // make sure the top element of the pointer exists + json_pointer const top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to the parent of the JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + // parent must exist when performing patch add per RFC6902 specs + basic_json& parent = result.at(ptr); + + switch (parent.m_data.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::template array_index(last_path); + if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, detail::concat("array index ", std::to_string(idx), " is out of range"), &parent)); + } + + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + break; + } + + // if there exists a parent, it cannot be primitive + case value_t::string: // LCOV_EXCL_LINE + case value_t::boolean: // LCOV_EXCL_LINE + case value_t::number_integer: // LCOV_EXCL_LINE + case value_t::number_unsigned: // LCOV_EXCL_LINE + case value_t::number_float: // LCOV_EXCL_LINE + case value_t::binary: // LCOV_EXCL_LINE + case value_t::discarded: // LCOV_EXCL_LINE + default: // LCOV_EXCL_LINE + JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [this, & result](json_pointer & ptr) + { + // get reference to the parent of the JSON pointer ptr + const auto last_path = ptr.back(); + ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_HEDLEY_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, detail::concat("key '", last_path, "' not found"), this)); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(json_pointer::template array_index(last_path)); + } + }; + + // type check: top level value must be an array + if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &json_patch)); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const string_t& op, + const string_t& member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_data.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : detail::concat("operation '", op, '\''); // NOLINT(bugprone-unused-local-non-trivial-variable) + + // check if the desired value is present + if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end())) + { + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) + JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have member '", member, "'"), &val)); + } + + // check if the result is of type string + if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) + { + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) + JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, " must have string member '", member, "'"), &val)); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_HEDLEY_UNLIKELY(!val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects", &val)); + } + + // collect mandatory members + const auto op = get_value("op", "op", true).template get(); + const auto path = get_value(op, "path", true).template get(); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const auto from_path = get_value("move", "from", true).template get(); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json const v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const auto from_path = get_value("copy", "from", true).template get(); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json const v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_INTERNAL_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if the test fails + if (JSON_HEDLEY_UNLIKELY(!success)) + { + JSON_THROW(other_error::create(501, detail::concat("unsuccessful: ", val.dump()), &val)); + } + + break; + } + + case patch_operations::invalid: + default: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, detail::concat("operation value '", op, "' is invalid"), &val)); + } + } + } + } + + /// @brief applies a JSON patch to a copy of the current object + /// @sa https://json.nlohmann.me/api/basic_json/patch/ + basic_json patch(const basic_json& json_patch) const + { + basic_json result = *this; + result.patch_inplace(json_patch); + return result; + } + + /// @brief creates a diff as a JSON patch + /// @sa https://json.nlohmann.me/api/basic_json/diff/ + JSON_HEDLEY_WARN_UNUSED_RESULT + static basic_json diff(const basic_json& source, const basic_json& target, + const string_t& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return an empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + return result; + } + + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() && i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], detail::concat(path, '/', detail::to_string(i))); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // We now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", detail::concat(path, '/', detail::to_string(i))} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", detail::concat(path, "/-")}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path_key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path_key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto path_key = detail::concat(path, '/', detail::escape(it.key())); + result.push_back( + { + {"op", "add"}, {"path", path_key}, + {"value", it.value()} + }); + } + } + + break; + } + + case value_t::null: + case value_t::string: + case value_t::boolean: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::number_float: + case value_t::binary: + case value_t::discarded: + default: + { + // both primitive types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + + return result; + } + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /// @brief applies a JSON Merge Patch + /// @sa https://json.nlohmann.me/api/basic_json/merge_patch/ + void merge_patch(const basic_json& apply_patch) + { + if (apply_patch.is_object()) + { + if (!is_object()) + { + *this = object(); + } + for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = apply_patch; + } + } + + /// @} +}; + +/// @brief user-defined to_string function for JSON values +/// @sa https://json.nlohmann.me/api/basic_json/to_string/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) +{ + return j.dump(); +} + +inline namespace literals +{ +inline namespace json_literals +{ + +/// @brief user-defined string literal for JSON values +/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/ +JSON_HEDLEY_NON_NULL(1) +#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + inline nlohmann::json operator""_json(const char* s, std::size_t n) +#else + // GCC 4.8 requires a space between "" and suffix + inline nlohmann::json operator"" _json(const char* s, std::size_t n) +#endif +{ + return nlohmann::json::parse(s, s + n); +} + +#if defined(__cpp_char8_t) +JSON_HEDLEY_NON_NULL(1) +inline nlohmann::json operator""_json(const char8_t* s, std::size_t n) +{ + return nlohmann::json::parse(reinterpret_cast(s), + reinterpret_cast(s) + n); +} +#endif + +/// @brief user-defined string literal for JSON pointer +/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/ +JSON_HEDLEY_NON_NULL(1) +#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + inline nlohmann::json::json_pointer operator""_json_pointer(const char* s, std::size_t n) +#else + // GCC 4.8 requires a space between "" and suffix + inline nlohmann::json::json_pointer operator"" _json_pointer(const char* s, std::size_t n) +#endif +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +#if defined(__cpp_char8_t) +inline nlohmann::json::json_pointer operator""_json_pointer(const char8_t* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(reinterpret_cast(s), n)); +} +#endif + +} // namespace json_literals +} // namespace literals +NLOHMANN_JSON_NAMESPACE_END + +/////////////////////// +// nonmember support // +/////////////////////// + +namespace std // NOLINT(cert-dcl58-cpp) +{ + +/// @brief hash value for JSON objects +/// @sa https://json.nlohmann.me/api/basic_json/std_hash/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct hash // NOLINT(cert-dcl58-cpp) +{ + std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const + { + return nlohmann::detail::hash(j); + } +}; + +// specialization for std::less +template<> +struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', see https://github.com/nlohmann/json/pull/679 +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept + { +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif + } +}; + +// C++20 prohibit function specialization in the std namespace. +#ifndef JSON_HAS_CPP_20 + +/// @brief exchanges the values of two JSON objects +/// @sa https://json.nlohmann.me/api/basic_json/std_swap/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +inline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept( // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp) + is_nothrow_move_constructible::value&& // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap) + is_nothrow_move_assignable::value) +{ + j1.swap(j2); +} + +#endif + +} // namespace std + +#if JSON_USE_GLOBAL_UDLS + #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + using nlohmann::literals::json_literals::operator""_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator""_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) + #else + // GCC 4.8 requires a space between "" and suffix + using nlohmann::literals::json_literals::operator"" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers) + using nlohmann::literals::json_literals::operator"" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers) + #endif +#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 + + + +// restore clang diagnostic settings +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +// clean up +#undef JSON_ASSERT +#undef JSON_INTERNAL_CATCH +#undef JSON_THROW +#undef JSON_PRIVATE_UNLESS_TESTED +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef JSON_EXPLICIT +#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_INLINE_VARIABLE +#undef JSON_NO_UNIQUE_ADDRESS +#undef JSON_DISABLE_ENUM_SERIALIZATION +#undef JSON_USE_GLOBAL_UDLS + +#ifndef JSON_TEST_KEEP_MACROS + #undef JSON_CATCH + #undef JSON_TRY + #undef JSON_HAS_CPP_11 + #undef JSON_HAS_CPP_14 + #undef JSON_HAS_CPP_17 + #undef JSON_HAS_CPP_20 + #undef JSON_HAS_CPP_23 + #undef JSON_HAS_CPP_26 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_HAS_STATIC_RTTI + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON +#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 + + + +#undef JSON_HEDLEY_ALWAYS_INLINE +#undef JSON_HEDLEY_ARM_VERSION +#undef JSON_HEDLEY_ARM_VERSION_CHECK +#undef JSON_HEDLEY_ARRAY_PARAM +#undef JSON_HEDLEY_ASSUME +#undef JSON_HEDLEY_BEGIN_C_DECLS +#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#undef JSON_HEDLEY_CLANG_HAS_FEATURE +#undef JSON_HEDLEY_CLANG_HAS_WARNING +#undef JSON_HEDLEY_COMPCERT_VERSION +#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#undef JSON_HEDLEY_CONCAT +#undef JSON_HEDLEY_CONCAT3 +#undef JSON_HEDLEY_CONCAT3_EX +#undef JSON_HEDLEY_CONCAT_EX +#undef JSON_HEDLEY_CONST +#undef JSON_HEDLEY_CONSTEXPR +#undef JSON_HEDLEY_CONST_CAST +#undef JSON_HEDLEY_CPP_CAST +#undef JSON_HEDLEY_CRAY_VERSION +#undef JSON_HEDLEY_CRAY_VERSION_CHECK +#undef JSON_HEDLEY_C_DECL +#undef JSON_HEDLEY_DEPRECATED +#undef JSON_HEDLEY_DEPRECATED_FOR +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#undef JSON_HEDLEY_DIAGNOSTIC_POP +#undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#undef JSON_HEDLEY_DMC_VERSION +#undef JSON_HEDLEY_DMC_VERSION_CHECK +#undef JSON_HEDLEY_EMPTY_BASES +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#undef JSON_HEDLEY_END_C_DECLS +#undef JSON_HEDLEY_FLAGS +#undef JSON_HEDLEY_FLAGS_CAST +#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_BUILTIN +#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GCC_HAS_EXTENSION +#undef JSON_HEDLEY_GCC_HAS_FEATURE +#undef JSON_HEDLEY_GCC_HAS_WARNING +#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#undef JSON_HEDLEY_GCC_VERSION +#undef JSON_HEDLEY_GCC_VERSION_CHECK +#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#undef JSON_HEDLEY_GNUC_HAS_FEATURE +#undef JSON_HEDLEY_GNUC_HAS_WARNING +#undef JSON_HEDLEY_GNUC_VERSION +#undef JSON_HEDLEY_GNUC_VERSION_CHECK +#undef JSON_HEDLEY_HAS_ATTRIBUTE +#undef JSON_HEDLEY_HAS_BUILTIN +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#undef JSON_HEDLEY_HAS_EXTENSION +#undef JSON_HEDLEY_HAS_FEATURE +#undef JSON_HEDLEY_HAS_WARNING +#undef JSON_HEDLEY_IAR_VERSION +#undef JSON_HEDLEY_IAR_VERSION_CHECK +#undef JSON_HEDLEY_IBM_VERSION +#undef JSON_HEDLEY_IBM_VERSION_CHECK +#undef JSON_HEDLEY_IMPORT +#undef JSON_HEDLEY_INLINE +#undef JSON_HEDLEY_INTEL_CL_VERSION +#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#undef JSON_HEDLEY_INTEL_VERSION +#undef JSON_HEDLEY_INTEL_VERSION_CHECK +#undef JSON_HEDLEY_IS_CONSTANT +#undef JSON_HEDLEY_IS_CONSTEXPR_ +#undef JSON_HEDLEY_LIKELY +#undef JSON_HEDLEY_MALLOC +#undef JSON_HEDLEY_MCST_LCC_VERSION +#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#undef JSON_HEDLEY_MESSAGE +#undef JSON_HEDLEY_MSVC_VERSION +#undef JSON_HEDLEY_MSVC_VERSION_CHECK +#undef JSON_HEDLEY_NEVER_INLINE +#undef JSON_HEDLEY_NON_NULL +#undef JSON_HEDLEY_NO_ESCAPE +#undef JSON_HEDLEY_NO_RETURN +#undef JSON_HEDLEY_NO_THROW +#undef JSON_HEDLEY_NULL +#undef JSON_HEDLEY_PELLES_VERSION +#undef JSON_HEDLEY_PELLES_VERSION_CHECK +#undef JSON_HEDLEY_PGI_VERSION +#undef JSON_HEDLEY_PGI_VERSION_CHECK +#undef JSON_HEDLEY_PREDICT +#undef JSON_HEDLEY_PRINTF_FORMAT +#undef JSON_HEDLEY_PRIVATE +#undef JSON_HEDLEY_PUBLIC +#undef JSON_HEDLEY_PURE +#undef JSON_HEDLEY_REINTERPRET_CAST +#undef JSON_HEDLEY_REQUIRE +#undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#undef JSON_HEDLEY_REQUIRE_MSG +#undef JSON_HEDLEY_RESTRICT +#undef JSON_HEDLEY_RETURNS_NON_NULL +#undef JSON_HEDLEY_SENTINEL +#undef JSON_HEDLEY_STATIC_ASSERT +#undef JSON_HEDLEY_STATIC_CAST +#undef JSON_HEDLEY_STRINGIFY +#undef JSON_HEDLEY_STRINGIFY_EX +#undef JSON_HEDLEY_SUNPRO_VERSION +#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#undef JSON_HEDLEY_TINYC_VERSION +#undef JSON_HEDLEY_TINYC_VERSION_CHECK +#undef JSON_HEDLEY_TI_ARMCL_VERSION +#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL2000_VERSION +#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL430_VERSION +#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL6X_VERSION +#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CL7X_VERSION +#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#undef JSON_HEDLEY_TI_CLPRU_VERSION +#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#undef JSON_HEDLEY_TI_VERSION +#undef JSON_HEDLEY_TI_VERSION_CHECK +#undef JSON_HEDLEY_UNAVAILABLE +#undef JSON_HEDLEY_UNLIKELY +#undef JSON_HEDLEY_UNPREDICTABLE +#undef JSON_HEDLEY_UNREACHABLE +#undef JSON_HEDLEY_UNREACHABLE_RETURN +#undef JSON_HEDLEY_VERSION +#undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#undef JSON_HEDLEY_VERSION_DECODE_MINOR +#undef JSON_HEDLEY_VERSION_DECODE_REVISION +#undef JSON_HEDLEY_VERSION_ENCODE +#undef JSON_HEDLEY_WARNING +#undef JSON_HEDLEY_WARN_UNUSED_RESULT +#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#undef JSON_HEDLEY_FALL_THROUGH + + + +#endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/NativeScript/runtime/android/napi/instrumentation/ManualInstrumentation.cpp b/NativeScript/runtime/android/napi/instrumentation/ManualInstrumentation.cpp new file mode 100644 index 000000000..88f832860 --- /dev/null +++ b/NativeScript/runtime/android/napi/instrumentation/ManualInstrumentation.cpp @@ -0,0 +1,8 @@ +// +// Created by Panayot Cankov on 26/05/2017. +// + +#include "ManualInstrumentation.h" + +bool tns::instrumentation::Frame::disabled = true; +const std::chrono::system_clock::time_point tns::instrumentation::Frame::disabled_time = std::chrono::system_clock::time_point(); diff --git a/NativeScript/runtime/android/napi/instrumentation/ManualInstrumentation.h b/NativeScript/runtime/android/napi/instrumentation/ManualInstrumentation.h new file mode 100644 index 000000000..b9b84f06c --- /dev/null +++ b/NativeScript/runtime/android/napi/instrumentation/ManualInstrumentation.h @@ -0,0 +1,74 @@ +// +// Created by Panayot Cankov on 26/05/2017. +// + +#ifndef MANUALINSTRUMENTATION_H +#define MANUALINSTRUMENTATION_H + +#import +#import +#include + +namespace tns { +namespace instrumentation { +class Frame { + public: + inline Frame() : Frame("") { } + inline Frame(std::string name) : name(name), start(disabled ? disabled_time : std::chrono::system_clock::now()) {} + + inline ~Frame() { + if (!name.empty() && check()) { + log(name); + } + } + + inline bool check() { + if (disabled) { + return false; + } + std::chrono::system_clock::time_point end = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(std::chrono::operator-(end, start)).count(); + return duration >= 16000; + } + + inline void log(const char* message) { + if (disabled) { + return; + } + std::chrono::system_clock::time_point end = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(std::chrono::operator-(end, start)).count(); + auto startMilis = std::chrono::time_point_cast(start).time_since_epoch().count() / 1000.0; + auto endMilis = std::chrono::time_point_cast(end).time_since_epoch().count() / 1000.0; + __android_log_print(ANDROID_LOG_DEBUG, "JS", "Timeline: %.3fms: Runtime: %s (%.3fms - %.3fms)", duration / 1000.0, message, startMilis, endMilis); + } + + inline void log(const std::string& message) { + log(message.c_str()); + } + + static inline void enable() { + disabled = false; + } + static inline void disable() { + disabled = true; + } + + private: + static bool disabled; + static const std::chrono::system_clock::time_point disabled_time; // Couldn't find reasonable constant + + const std::chrono::system_clock::time_point start; + const std::string name; + + Frame(const Frame&) = delete; + Frame& operator=(const Frame&) = delete; +}; +}; +}; + +/** + * Place at the start of a method. Will log to android using the "JS" tag methods that execute relatively slow. + */ +#define TNSPERF() tns::instrumentation::Frame __tns_manual_instrumentation(__func__) + +#endif //MANUALINSTRUMENTATION_H diff --git a/NativeScript/runtime/android/napi/messageloop/MessageLoopTimer.cpp b/NativeScript/runtime/android/napi/messageloop/MessageLoopTimer.cpp new file mode 100644 index 000000000..6fbc8bdd2 --- /dev/null +++ b/NativeScript/runtime/android/napi/messageloop/MessageLoopTimer.cpp @@ -0,0 +1,121 @@ +#include "MessageLoopTimer.h" +#include +#include +#include +#include +#include +#include "NativeScriptAssert.h" +#include "Runtime.h" +#include "native_api_util.h" + +using namespace tns; + +static const int SLEEP_INTERVAL_MS = 100; + +void MessageLoopTimer::Init(napi_env env) { + this->RegisterStartStopFunctions(env); +} + +void MessageLoopTimer::RegisterStartStopFunctions(napi_env env) { + + napi_status status; + napi_value timer_start; + napi_value timer_stop; + + const char * timer_start_name = "__messageLoopTimerStart"; + const char * timer_stop_name = "__messageLoopTimerStop"; + + NAPI_GUARD(napi_create_function(env, timer_start_name, strlen(timer_start_name), MessageLoopTimer::StartCallback, + this, &timer_start)) { + return; + } + NAPI_GUARD(napi_create_function(env, timer_stop_name, strlen(timer_stop_name), MessageLoopTimer::StopCallback, + this, &timer_stop)) { + return; + } + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, global, timer_start_name, timer_start)) { + return; + } + NAPI_GUARD(napi_set_named_property(env, global, timer_stop_name, timer_stop)) {} +} + +napi_value MessageLoopTimer::StartCallback(napi_env env, napi_callback_info info) { + + napi_status status; + void * data = nullptr; + NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data)) { + return nullptr; + } + + auto self = static_cast(data); + + if (self->m_isRunning) { + return nullptr; + } + + self->m_isRunning = true; + + auto looper = ALooper_forThread(); + if (looper == nullptr) { + __android_log_print(ANDROID_LOG_ERROR, "NAPI", "Unable to get looper for the current thread"); + return nullptr; + } + + int pipeStatus = pipe(self->m_fd); + if (pipeStatus != 0) { + __android_log_print(ANDROID_LOG_ERROR, "NAPI", "Unable to create a pipe: %s", strerror(errno)); + return nullptr; + } + + ALooper_addFd(looper, self->m_fd[0], 0, ALOOPER_EVENT_INPUT, MessageLoopTimer::PumpMessageLoopCallback, env); + + std::thread worker(MessageLoopTimer::WorkerThreadRun, self); + + worker.detach(); + + return nullptr; +} + +napi_value MessageLoopTimer::StopCallback(napi_env env, napi_callback_info info) { + napi_status status; + void * data = nullptr; + NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data)) { + return nullptr; + } + auto self = static_cast(data); + + + if (!self->m_isRunning) { + return nullptr; + } + + self->m_isRunning = false; + + return nullptr; +} + +int MessageLoopTimer::PumpMessageLoopCallback(int fd, int events, void* data) { + uint8_t msg; + read(fd, &msg, sizeof(uint8_t)); + auto env = (napi_env) data; + + js_execute_pending_jobs(env); + + return 1; +} + +void MessageLoopTimer::WorkerThreadRun(MessageLoopTimer* timer) { + while (timer->m_isRunning) { + uint8_t msg = 1; + write(timer->m_fd[1], &msg, sizeof(uint8_t)); + std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_MS)); + } + + uint8_t msg = 0; + write(timer->m_fd[1], &msg, sizeof(uint8_t)); +} diff --git a/NativeScript/runtime/android/napi/messageloop/MessageLoopTimer.h b/NativeScript/runtime/android/napi/messageloop/MessageLoopTimer.h new file mode 100644 index 000000000..67d7570b3 --- /dev/null +++ b/NativeScript/runtime/android/napi/messageloop/MessageLoopTimer.h @@ -0,0 +1,24 @@ +#ifndef MESSAGELOOPTIMER_H +#define MESSAGELOOPTIMER_H + +#include "js_native_api.h" + +namespace tns { + +class MessageLoopTimer { +public: + void Init(napi_env env); +private: + bool m_isRunning; + int m_fd[2]; + + void RegisterStartStopFunctions(napi_env env); + static napi_value StartCallback(napi_env, napi_callback_info info); + static napi_value StopCallback(napi_env env, napi_callback_info info); + static int PumpMessageLoopCallback(int fd, int events, void* data); + static void WorkerThreadRun(MessageLoopTimer* timer); +}; + +} + +#endif //MESSAGELOOPTIMER_H diff --git a/NativeScript/runtime/android/napi/modules/AndroidRuntimeModules.h b/NativeScript/runtime/android/napi/modules/AndroidRuntimeModules.h new file mode 100644 index 000000000..688970c5d --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/AndroidRuntimeModules.h @@ -0,0 +1,27 @@ +// +// Created by Ammar Ahmed on 01/03/2025. +// + +#ifndef TEST_APP_ANDROID_RUNTIME_MODULES_H +#define TEST_APP_ANDROID_RUNTIME_MODULES_H + +#include "js_native_api.h" +#include "url/URL.h" +#include "url/URLPattern.h" +#include "url/URLSearchParams.h" + +namespace tns { + class AndroidRuntimeModules { + public: + static void Init(napi_env env, napi_value global) { + nativescript::URL::Init(env, global); + nativescript::URLSearchParams::Init(env, global); + // URLPattern is android-only: its regex provider defers napi_ref + // teardown through tns::PostFinalizer, which lives in ffi/jni and is + // not part of the Apple build. + URLPattern::Init(env, global); + } + }; +} + +#endif //TEST_APP_ANDROID_RUNTIME_MODULES_H diff --git a/NativeScript/runtime/android/napi/modules/console/Console.cpp b/NativeScript/runtime/android/napi/modules/console/Console.cpp new file mode 100644 index 000000000..cda294c73 --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/console/Console.cpp @@ -0,0 +1,698 @@ +// +// Created by pkanev on 12/8/2017. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ArgConverter.h" +#include "Console.h" + +using namespace tns; + +#include "Console.h" +#include "ArgConverter.h" +#include "JEnv.h" +#include "NativeScriptException.h" +#include +#include + +using namespace std; + +#ifdef __V8__ +using namespace v8_inspector; +#endif + + +const char *Console::LOG_TAG = "JS"; +std::map> Console::s_envToConsoleTimersMap; +int Console::m_maxLogcatObjectSize; + +void Console::createConsole(napi_env env, ConsoleCallback callback, const int maxLogcatObjectSize, const bool forceLog) { + m_callback = callback; + m_maxLogcatObjectSize = maxLogcatObjectSize; + + napi_status status; + + napi_value console; + NAPI_GUARD(napi_create_object(env, &console)) { + return; + } + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + + napi_value assertFunc, errorFunc, infoFunc, logFunc, warnFunc, dirFunc, traceFunc, timeFunc, timeEndFunc; + NAPI_GUARD(napi_create_function(env, "assert", strlen("assert"), assertCallback, nullptr, &assertFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "error", strlen("error"), errorCallback, nullptr, &errorFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "info", strlen("info"), infoCallback, nullptr, &infoFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "log", strlen("log"), logCallback, nullptr, &logFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "warn", strlen("warn"), warnCallback, nullptr, &warnFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "dir", strlen("dir"), dirCallback, nullptr, &dirFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "trace", strlen("trace"), traceCallback, nullptr, &traceFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "time", strlen("time"), timeCallback, nullptr, &timeFunc)) { + return; + } + NAPI_GUARD(napi_create_function(env, "timeEnd", strlen("timeEnd"), timeEndCallback, nullptr, &timeEndFunc)) { + return; + } + + NAPI_GUARD(napi_set_named_property(env, console, "assert", assertFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "error", errorFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "info", infoFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "log", logFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "warn", warnFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "dir", dirFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "trace", traceFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "time", timeFunc)) {} + NAPI_GUARD(napi_set_named_property(env, console, "timeEnd", timeEndFunc)) {} + NAPI_GUARD(napi_set_named_property(env, global, "console", console)) {} +} + +std::string transformJSObject(napi_env env, napi_value object) { + napi_status status; + napi_value toStringFunc; + bool hasToString = false; + + // Check if the object has a toString method + NAPI_GUARD(napi_has_named_property(env, object, "toString", &hasToString)) {} + + if (hasToString) { + NAPI_GUARD(napi_get_named_property(env, object, "toString", &toStringFunc)) {} + if (napi_util::is_of_type(env, toStringFunc, napi_function)) { + napi_value result; + NAPI_GUARD(napi_call_function(env, object, toStringFunc, 0, nullptr, &result)) {} + auto value = ArgConverter::ConvertToString(env, result); + + bool is_error = false; + NAPI_GUARD(napi_is_error(env, object, &is_error)) {} + if (is_error) { + napi_value stack; + status = napi_get_named_property(env, object, "stack", &stack); + if (status == napi_ok && !napi_util::is_null_or_undefined(env, stack)) { + auto stack_value = ArgConverter::ConvertToString(env, stack); + if (!stack_value.empty() && value.find(stack_value) == std::string::npos) { + value += "\n" + stack_value; + } + } + } + + auto hasCustomToStringImplementation = value.find("[object Object]") == std::string::npos; + if (hasCustomToStringImplementation) return value; + } + } + // If no custom toString method, stringify the object + return JsonStringifyObject(env, object, false); +} + +std::string buildStringFromArg(napi_env env, napi_value val) { + napi_status status; + napi_valuetype type; + NAPI_GUARD(napi_typeof(env, val, &type)) {} + + if (type == napi_function) { + napi_value funcString; + NAPI_GUARD(napi_coerce_to_string(env, val, &funcString)) {} + return napi_util::get_string_value(env, funcString); + } else if (napi_util::is_array(env, val)) { + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) {} + return JsonStringifyObject(env, val, false); + } else if (type == napi_object) { + return transformJSObject(env, val); + } else if (type == napi_symbol) { + // A Symbol has no ToString coercion (napi_coerce_to_string throws per + // spec), so invoke Symbol.prototype.toString explicitly, which yields + // "Symbol(description)". + napi_value toStringFn = nullptr, symString = nullptr; + NAPI_GUARD(napi_get_named_property(env, val, "toString", &toStringFn)) {} + NAPI_GUARD(napi_call_function(env, val, toStringFn, 0, nullptr, &symString)) {} + return ArgConverter::ConvertToString(env, symString); + } else { + napi_value defaultToString; + NAPI_GUARD(napi_coerce_to_string(env, val, &defaultToString)) {} + return napi_util::get_string_value(env, defaultToString); + } +} + +std::string buildLogString(napi_env env, napi_callback_info info, int startingIndex = 0) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(16) + + std::stringstream ss; + + if (argc) { + for (size_t i = startingIndex; i < argc; i++) { + // separate args with a space + if (i != 0) { + ss << " "; + } + + std::string argString = buildStringFromArg(env, argv[i]); + ss << argString; + } + } else { + ss << std::endl; + } + + return ss.str(); +} + +napi_value Console::assertCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + bool expressionPasses = false; + + if (argc > 0) { + if (napi_util::is_of_type(env, args[0], napi_boolean)) { + NAPI_GUARD(napi_get_value_bool(env, args[0], &expressionPasses)) {} + } else { + napi_value boolValue; + NAPI_GUARD(napi_coerce_to_bool(env, args[0], &boolValue)) {} + NAPI_GUARD(napi_get_value_bool(env, boolValue, &expressionPasses)) {} + } + } + + if (!expressionPasses) { + std::stringstream assertionError; + assertionError << "Assertion failed: "; + + if (argc > 1) { + assertionError << buildLogString(env, info, 1); + } else { + assertionError << "console.assert"; + } + + std::string log = assertionError.str(); + sendToADBLogcat(log, ANDROID_LOG_ERROR); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kAssert, info); +#endif + } + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value Console::errorCallback(napi_env env, napi_callback_info info) { + try { + std::string log = "CONSOLE ERROR: "; + log += buildLogString(env, info); + sendToADBLogcat(log, ANDROID_LOG_ERROR); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kError, info); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value Console::infoCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::string log = "CONSOLE INFO: "; + log += buildLogString(env, info); + + sendToADBLogcat(log, ANDROID_LOG_INFO); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kInfo, info); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value Console::logCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::string log = "CONSOLE LOG: "; + log += buildLogString(env, info); + + sendToADBLogcat(log, ANDROID_LOG_INFO); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kLog, info); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value Console::warnCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::string log = "CONSOLE WARN: "; + log += buildLogString(env, info); + + sendToADBLogcat(log, ANDROID_LOG_WARN); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kWarning, info); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +napi_value Console::dirCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::stringstream ss; + + if (argc > 0) { + napi_value arg = args[0]; + if (napi_util::is_object(env, arg)) { + ss << "==== object dump start ====" << std::endl; + + napi_value propNames; + NAPI_GUARD(napi_get_property_names(env, arg, &propNames)) { + return nullptr; + } + + uint32_t propertiesLen; + NAPI_GUARD(napi_get_array_length(env, propNames, &propertiesLen)) { + return nullptr; + } + + for (uint32_t i = 0; i < propertiesLen; i++) { + napi_value propertyName; + NAPI_GUARD(napi_get_element(env, propNames, i, &propertyName)) { + return nullptr; + } + + napi_value propertyValue; + NAPI_GUARD(napi_get_property(env, arg, propertyName, &propertyValue)) { + return nullptr; + } + + bool propIsFunction = napi_util::is_of_type(env, propertyValue, napi_function); + + ss << napi_util::get_string_value(env, propertyName); + + if (propIsFunction) { + ss << "()"; + } else if (napi_util::is_array(env, propertyValue)) { + std::string jsonStringifiedArray = buildStringFromArg(env, propertyValue); + ss << ": " << jsonStringifiedArray; + } else if (napi_util::is_of_type(env, propertyValue, napi_object)) { + std::string jsonStringifiedObject = transformJSObject(env, propertyValue); + // if object prints out as the error string for circular references, replace with #CR instead for brevity + if (jsonStringifiedObject.find("circular structure") != std::string::npos) { + jsonStringifiedObject = "#CR"; + } + ss << ": " << jsonStringifiedObject; + } else { + ss << ": \"" << napi_util::get_string_value(env, propertyValue) << "\""; + } + + ss << std::endl; + } + + ss << "==== object dump end ====" << std::endl; + } else { + std::string logString = buildLogString(env, info); + ss << logString; + } + } else { + ss << std::endl; + } + + std::string log = ss.str(); + + sendToADBLogcat(log, ANDROID_LOG_INFO); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kDir, info); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception &e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value Console::traceCallback(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::stringstream ss; + + std::string logString = buildLogString(env, info); + + if (logString.compare("\n") == 0) { + ss << "Trace"; + } else { + ss << "Trace: " << logString; + } + + ss << std::endl; + + // Create an error object to get the stack trace + napi_value error; + napi_value errorMessage; + NAPI_GUARD(napi_create_string_utf8(env, "Trace", NAPI_AUTO_LENGTH, &errorMessage)) { + return nullptr; + } + NAPI_GUARD(napi_create_error(env, nullptr, errorMessage, &error)) { + return nullptr; + } + + napi_value stack; + NAPI_GUARD(napi_get_named_property(env, error, "stack", &stack)) { + return nullptr; + } + + size_t stackLength; + NAPI_GUARD(napi_get_value_string_utf8(env, stack, nullptr, 0, &stackLength)) { + return nullptr; + } + std::string stackStr(stackLength + 1, '\0'); + NAPI_GUARD(napi_get_value_string_utf8(env, stack, &stackStr[0], stackLength + 1, &stackLength)) { + return nullptr; + } + + ss << stackStr << std::endl; + + std::string log = ss.str(); + __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, log.c_str()); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kTrace, info); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value Console::timeCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::string label = "default"; + + if (argc > 0 && napi_util::is_of_type(env, args[0], napi_string)) { + label = napi_util::get_string_value(env, args[0]); + } + + auto it = Console::s_envToConsoleTimersMap.find(env); + if (it == Console::s_envToConsoleTimersMap.end()) { + // throw? + } + + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double timeStamp = nano.time_since_epoch().count(); + + it->second.insert(std::make_pair(label, timeStamp)); + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +napi_value Console::timeEndCallback(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { + return nullptr; + } + + try { + std::string label = "default"; + + if (argc > 0 && napi_util::is_of_type(env, args[0], napi_string)) { + label = napi_util::get_string_value(env, args[0]); + } + + auto it = Console::s_envToConsoleTimersMap.find(env); + if (it == Console::s_envToConsoleTimersMap.end()) { + // throw? + } + + std::map timersMap = it->second; + + auto itTimersMap = timersMap.find(label); + if (itTimersMap == timersMap.end()) { + std::string warning = std::string( + "No such label '" + label + "' for console.timeEnd()"); + + __android_log_write(ANDROID_LOG_WARN, LOG_TAG, warning.c_str()); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kWarning, warning); +#endif + + return nullptr; + } + + auto nano = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + double endTimeStamp = nano.time_since_epoch().count(); + double startTimeStamp = itTimersMap->second; + + it->second.erase(label); + + double diffMicroseconds = endTimeStamp - startTimeStamp; + double diffMilliseconds = diffMicroseconds / 1000.0; + + std::stringstream ss; + ss << "CONSOLE TIME: " << label << ": " << std::fixed << std::setprecision(3) + << diffMilliseconds << "ms"; + std::string log = ss.str(); + + __android_log_write(ANDROID_LOG_INFO, LOG_TAG, log.c_str()); +#ifdef __V8__ + sendToDevToolsFrontEnd(env, ConsoleAPIType::kTimeEnd, log); +#endif + } + catch (NativeScriptException &e) { + e.ReThrowToNapi(env); + } + catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + + return nullptr; +} + +void Console::onDisposeEnv(napi_env env) { + s_envToConsoleTimersMap.erase(env); +} + +void Console::sendToADBLogcat(const std::string &message, android_LogPriority logPriority) { + // limit the size of the message that we send to logcat using the predefined value in package.json + auto messageToLog = message; + if (messageToLog.length() > m_maxLogcatObjectSize) { + messageToLog = messageToLog.erase(m_maxLogcatObjectSize, std::string::npos); + messageToLog = messageToLog + "..."; + } + + // split strings into chunks of 4000 characters + // __android_log_write can't send more than 4000 to the stdout at a time + auto messageLength = messageToLog.length(); + int maxStringLength = 4000; + + if (messageLength < maxStringLength) { + __android_log_write(logPriority, Console::LOG_TAG, messageToLog.c_str()); + } else { + for (int i = 0; i < messageLength; i += maxStringLength) { + auto messagePart = messageToLog.substr(i, maxStringLength); + + __android_log_write(logPriority, Console::LOG_TAG, messagePart.c_str()); + } + } +} + +#ifdef __V8__ +int Console::sendToDevToolsFrontEnd(napi_env env, ConsoleAPIType method, napi_callback_info info) { + if (!m_callback) { + return 0; + } + NAPI_CALLBACK_BEGIN_VARGS() + + v8::Local *v8_args = reinterpret_cast*>(const_cast(argv.data())); + + std::vector> arg_vector; + unsigned nargs = argc; + arg_vector.reserve(nargs); + for (unsigned ix = 0; ix < nargs; ix++) + arg_vector.push_back(v8_args[ix]); + + m_callback(env, method, arg_vector); + return 0; +} + +void Console::sendToDevToolsFrontEnd(napi_env env, ConsoleAPIType method, const std::string &message) { + if (!m_callback) { + return; + } + + std::vector> args{tns::ConvertToV8String(env->isolate, message)}; + m_callback(env, method, args); +} +#endif + +ConsoleCallback Console::m_callback = nullptr; diff --git a/NativeScript/runtime/android/napi/modules/console/Console.h b/NativeScript/runtime/android/napi/modules/console/Console.h new file mode 100644 index 000000000..01f422cd8 --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/console/Console.h @@ -0,0 +1,58 @@ +// +// Created by pkanev on 12/8/2017. +// + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include "js_native_api.h" +#include "jsr.h" +#include +#include +#include +#include + +#ifdef __V8__ +#include +#endif + +#ifdef __V8__ +typedef void (*ConsoleCallback)(napi_env env, v8_inspector::ConsoleAPIType method, const std::vector>& args); +#else +typedef void (*ConsoleCallback)(napi_env env, napi_callback_info info); +#endif + +namespace tns { + class Console { + public: + static void createConsole(napi_env env, ConsoleCallback callback, int maxLogcatObjectSize, bool forceLog); + + static napi_value assertCallback(napi_env env, napi_callback_info info); + static napi_value errorCallback(napi_env env, napi_callback_info info); + static napi_value infoCallback(napi_env env, napi_callback_info info); + static napi_value logCallback(napi_env env, napi_callback_info info); + static napi_value warnCallback(napi_env env, napi_callback_info info); + static napi_value dirCallback(napi_env env, napi_callback_info info); + static napi_value traceCallback(napi_env env, napi_callback_info info); + static napi_value timeCallback(napi_env env, napi_callback_info info); + static napi_value timeEndCallback(napi_env env, napi_callback_info info); + + static void onDisposeEnv(napi_env env); + + private: + + static int m_maxLogcatObjectSize; + static ConsoleCallback m_callback; + static const char* LOG_TAG; + static std::map> s_envToConsoleTimersMap; + + static void sendToADBLogcat(const std::string& log, android_LogPriority logPriority); +#ifdef __V8__ + static int sendToDevToolsFrontEnd(napi_env env, v8_inspector::ConsoleAPIType method, napi_callback_info info); + static void sendToDevToolsFrontEnd(napi_env env, v8_inspector::ConsoleAPIType method, const std::string& args); +#endif + }; + +} + +#endif //CONSOLE_H \ No newline at end of file diff --git a/NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp b/NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp new file mode 100644 index 000000000..44549a386 --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp @@ -0,0 +1,608 @@ +#include "ModuleInternal.h" +#include "File.h" +#include "JniLocalRef.h" +#include "ArgConverter.h" +#include "NativeScriptAssert.h" +#include "Constants.h" +#include "NativeScriptException.h" +#include "Util.h" +#include "CallbackHandlers.h" +#include "Runtime.h" +#include +#include +#include +#include +#include +#include +#include "GlobalHelpers.h" +#include + + + +using namespace tns; +using namespace std; + +namespace { +void ThrowFallbackRequireError(napi_env env, const char* message) { + bool pendingException = false; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value ignored; + napi_get_and_clear_last_exception(env, &ignored); + } + + napi_throw_error(env, nullptr, message); +} + +void ReThrowRequireError(napi_env env, NativeScriptException& exception) { + try { + exception.ReThrowToNapi(env); + } catch (NativeScriptException&) { + ThrowFallbackRequireError(env, "Error rethrowing NativeScript require exception."); + } catch (std::exception& nestedException) { + ThrowFallbackRequireError(env, nestedException.what()); + } catch (...) { + ThrowFallbackRequireError(env, "Error rethrowing NativeScript require exception."); + } +} +} + +ModuleInternal::ModuleInternal() + : m_env(nullptr), m_requireFunction(nullptr), m_requireFactoryFunction(nullptr) { +} + +void ModuleInternal::DeInit() { + napi_status status; + if (m_env != nullptr) { + NAPI_GUARD(napi_delete_reference(m_env, this->m_requireFunction)) {} + NAPI_GUARD(napi_delete_reference(m_env, this->m_requireFactoryFunction)) {} + } + + for (const auto& pair: this->m_requireCache) { + if (m_env != nullptr) { + NAPI_GUARD(napi_delete_reference(m_env, pair.second)) {} + } + } + this->m_requireCache.clear(); +} + +void ModuleInternal::Init(napi_env env, const std::string& baseDir) { + napi_status status; + JEnv jenv; + + if (MODULE_CLASS == nullptr) { + MODULE_CLASS = jenv.FindClass("com/tns/Module"); + assert(MODULE_CLASS != nullptr); + + RESOLVE_PATH_METHOD_ID = jenv.GetStaticMethodID(MODULE_CLASS, "resolvePath", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); + assert(RESOLVE_PATH_METHOD_ID != nullptr); + } + + m_env = env; + + const char *requireFactoryScript = R"( + (function () { + return function require_factory(requireInternal, dirName) { + return function require(modulePath) { + if(typeof global.__requireOverride !== "undefined") { + var result = global.__requireOverride(modulePath, dirName); + if(result) { + return result; + } + } + return requireInternal(modulePath, dirName); + } + } +})(); +)"; + + napi_value source; + NAPI_GUARD(napi_create_string_utf8(env, requireFactoryScript, NAPI_AUTO_LENGTH, &source)) { + return; + } + + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return; + } + + napi_value result; + status = js_execute_script(env, source, "", &result); + assert(status == napi_ok); + + m_requireFactoryFunction = napi_util::make_ref(m_env, result); + + napi_value requireFunction = napi_util::napi_set_function(env, global, "__nativeRequire", RequireCallback, this); + m_requireFunction = napi_util::make_ref(m_env, requireFunction); + + napi_value globalRequire = GetRequireFunction(env, baseDir.empty() ? Constants::APP_ROOT_FOLDER_PATH : baseDir); + status = napi_set_named_property(env, global, "require", globalRequire); + assert(status == napi_ok); +} + +napi_value ModuleInternal::GetRequireFunction(napi_env env, const std::string& dirName) { + napi_value requireFunc; + + auto itFound = m_requireCache.find(dirName); + + if (itFound != m_requireCache.end()) { + requireFunc = napi_util::get_ref_value(env, itFound->second); + } else { + napi_status status; + napi_value requireFuncFactory = napi_util::get_ref_value(env, m_requireFactoryFunction); + + napi_value requireInternalFunc = napi_util::get_ref_value(env, m_requireFunction); + + napi_value args[2]; + args[0] = requireInternalFunc; + NAPI_GUARD(napi_create_string_utf8(env, dirName.c_str(), NAPI_AUTO_LENGTH, &args[1])) { + return nullptr; + } + + napi_value thiz; + NAPI_GUARD(napi_create_object(env, &thiz)) { + return nullptr; + } + + napi_value result; + status = napi_call_function(env, thiz, requireFuncFactory, 2, args, &result); + assert(status == napi_ok && result != nullptr); + + bool isFunction = napi_util::is_of_type(env, result, napi_function); + assert(isFunction); + + requireFunc = result; + + napi_ref poFunc = napi_util::make_ref(env, requireFunc); + m_requireCache.emplace(dirName, poFunc); + } + + return requireFunc; +} + +napi_value ModuleInternal::RequireCallback(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + auto thiz = static_cast(data); + return thiz->RequireCallbackImpl(env, info); + } catch (NativeScriptException& e) { + ReThrowRequireError(env, e); + } catch (std::exception& e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + ReThrowRequireError(env, nsEx); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + ReThrowRequireError(env, nsEx); + } + + return nullptr; +} + +napi_value ModuleInternal::RequireCallbackImpl(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(4) + + if (argc != 2) { + throw NativeScriptException(string("require should be called with two parameters")); + } + if (!napi_util::is_of_type(env, argv[0], napi_string)) { + throw NativeScriptException(string("require's first parameter should be string")); + } + if (!napi_util::is_of_type(env, argv[1], napi_string)) { + throw NativeScriptException(string("require's second parameter should be string")); + } + + string moduleName = ArgConverter::ConvertToString(env, argv[0]); + string callingModuleDirName = ArgConverter::ConvertToString(env, argv[1]); + + auto isData = false; + + auto moduleObj = LoadImpl(env, moduleName, callingModuleDirName, isData); + if (moduleObj == nullptr) { + return nullptr; + } + + if (isData) { + assert(!napi_util::is_null_or_undefined(env, moduleObj)); + return moduleObj; + } else { + napi_value exports; + // Throw rather than return nullptr so a failed require surfaces as a JS + // exception instead of silently evaluating to undefined. + NAPI_GUARD(napi_get_named_property(env, moduleObj, "exports", &exports)) { + throw NativeScriptException("Failed to read exports for module: " + moduleName); + } + assert(!napi_util::is_null_or_undefined(env, exports)); + return exports; + } +} + +napi_value ModuleInternal::RequireNativeCallback(napi_env env, napi_callback_info info) { + napi_status status; + void* data = nullptr; + NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data)) { + return nullptr; + } + auto cb = reinterpret_cast(data); + napi_value exports; + NAPI_GUARD(napi_create_object(env, &exports)) { + return nullptr; + } + return cb(env, exports); +} + +napi_status ModuleInternal::Load(napi_env env, const std::string& path) { + napi_status status; + napi_value global; + NAPI_GUARD(napi_get_global(env, &global)) { + return status; + } + + napi_value require; + NAPI_GUARD(napi_get_named_property(env, global, "require", &require)) { + return status; + } + + napi_value args[1]; + NAPI_GUARD(napi_create_string_utf8(env, path.c_str(), path.size(), &args[0])) { + return status; + } + + napi_value result; + status = napi_call_function(env, global, require, 1, args, &result); + return status; +} + +void ModuleInternal::LoadWorker(napi_env env, const string& path) { + napi_status status; + Load(env, path); + bool hasPendingException; + NAPI_GUARD(napi_is_exception_pending(env, &hasPendingException)) { + return; + } + + if (hasPendingException) { + napi_value error; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &error)) { + return; + } + CallbackHandlers::CallWorkerScopeOnErrorHandle(env, error); + } +} + +void ModuleInternal::CheckFileExists(napi_env env, const std::string& path, const std::string& baseDir) { + JEnv jEnv; + JniLocalRef jsModulename(jEnv.NewStringUTF(path.c_str())); + JniLocalRef jsBaseDir(jEnv.NewStringUTF(baseDir.c_str())); + jEnv.CallStaticObjectMethod(MODULE_CLASS, RESOLVE_PATH_METHOD_ID, (jstring) jsModulename, (jstring) jsBaseDir); +} + +napi_value ModuleInternal::LoadInternalModule(napi_env env, const std::string& moduleName) { + if (moduleName == "url") { + napi_status status; + napi_value moduleObj; + NAPI_GUARD(napi_create_object(env, &moduleObj)) { + return nullptr; + } + napi_value url; + napi_value exports; + NAPI_GUARD(napi_create_object(env, &exports)) { + return nullptr; + } + NAPI_GUARD(napi_get_named_property(env, napi_util::global(env), "URL", &url)) { + return nullptr; + } + NAPI_GUARD(napi_set_named_property(env, exports, "URL", url)) { + return nullptr; + } + NAPI_GUARD(napi_set_named_property(env, moduleObj, "exports", exports)) { + return nullptr; + } + napi_util::napi_set_function(env, exports, "pathToFileURL", [](napi_env env, napi_callback_info info) -> napi_value { + return ArgConverter::convertToJsString(env, "file://"); + }); + return moduleObj; + } + return nullptr; +} + +napi_value ModuleInternal::LoadImpl(napi_env env, const std::string& moduleName, const std::string& baseDir, bool& isData) { + auto pathKind = GetModulePathKind(moduleName); + auto cachePathKey = (pathKind == ModulePathKind::Global) ? moduleName : (baseDir + "*" + moduleName); + + napi_value result; + + DEBUG_WRITE(">>LoadImpl cachePathKey=%s", cachePathKey.c_str()); + + auto it = m_loadedModules.find(cachePathKey); + + /** + * Load internal modules like url,fs etc directly if someone does + * require('url'); + */ + napi_value moduleObj = ModuleInternal::LoadInternalModule(env, moduleName); + if (moduleObj) return moduleObj; + + if (it == m_loadedModules.end()) { + std::string path; + + // Search App System libs + std::string sys_lib("system_lib://"); + if (moduleName.rfind(sys_lib, 0) == 0) { + auto pos = moduleName.find(sys_lib); + path = std::string(moduleName); + path.replace(pos, sys_lib.length(), ""); + } else if (Util::EndsWith(moduleName, ".so")) { + path = "lib" + moduleName; + } else if (Util::EndsWith(moduleName, ".node")) { + std::string libName = moduleName; + Util::ReplaceAll(libName, ".node", ""); + path = "lib" + libName + ".so"; + } else { + JEnv jenv; + JniLocalRef jsModulename(jenv.NewStringUTF(moduleName.c_str())); + JniLocalRef jsBaseDir(jenv.NewStringUTF(baseDir.c_str())); + JniLocalRef jsModulePath( + jenv.CallStaticObjectMethod(MODULE_CLASS, RESOLVE_PATH_METHOD_ID, + (jstring) jsModulename, (jstring) jsBaseDir)); + + path = ArgConverter::jstringToString((jstring) jsModulePath); + } + + auto it2 = m_loadedModules.find(path); + + if (it2 == m_loadedModules.end()) { + if (Util::EndsWith(path, ".js") || Util::EndsWith(path, ".so")) { + isData = false; + result = LoadModule(env, path, cachePathKey); + } else if (Util::EndsWith(path, ".json")) { + isData = true; + result = LoadData(env, path); + } else { + std::string errMsg = "Unsupported file extension: " + path; + throw NativeScriptException(errMsg); + } + } else { + auto& cacheEntry = it2->second; + isData = cacheEntry.isData; + result = napi_util::get_ref_value(env, cacheEntry.obj); + } + } else { + auto& cacheEntry = it->second; + isData = cacheEntry.isData; + result = napi_util::get_ref_value(env, cacheEntry.obj); + } + + return result; +} + +std::string ModuleInternal::EnsureFileProtocol(const std::string& path) { + const std::string protocol = "file://"; + if (path.compare(0, protocol.length(), protocol) != 0) { + return protocol + path; + } + return path; +} + +napi_value ModuleInternal::LoadModule(napi_env env, const std::string& modulePath, const std::string& moduleCacheKey) { + napi_status status; + napi_value result; + + // A failure setting up the module scaffold must propagate as an exception: + // require() returning nullptr would surface to JS as `undefined` with no + // error, unlike every other failure path in this function which throws. + napi_value context; + NAPI_GUARD(napi_get_global(env, &context)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_value moduleObj; + NAPI_GUARD(napi_create_object(env, &moduleObj)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_value exportsObj; + NAPI_GUARD(napi_create_object(env, &exportsObj)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + NAPI_GUARD(napi_set_named_property(env, moduleObj, "exports", exportsObj)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_value fullRequiredModulePath; + NAPI_GUARD(napi_create_string_utf8(env, modulePath.c_str(), modulePath.size(), &fullRequiredModulePath)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + NAPI_GUARD(napi_set_named_property(env, moduleObj, "filename", fullRequiredModulePath)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_ref poModuleObj = napi_util::make_ref(env, moduleObj); + TempModule tempModule(this, modulePath, moduleCacheKey, poModuleObj); + + napi_value moduleFunc; + + if (Util::EndsWith(modulePath, ".js")) { + DEBUG_WRITE("%s", modulePath.c_str()); + + // Fast path: if the build compiled this module to engine bytecode, run it + // directly. This peeks the file header only — the source is never read or + // wrapped for a bytecode module. Bytecode is the compiled form of the + // *wrapped* module content, so it yields the same wrapper function. + status = js_run_bytecode_file(env, EnsureFileProtocol(modulePath).c_str(), &moduleFunc); + if (status == napi_cannot_run_js) { + // Not bytecode — compile and run the wrapped source as usual. + napi_value script = LoadScript(env, modulePath, fullRequiredModulePath); + status = js_execute_script(env, script, EnsureFileProtocol(modulePath).c_str(), &moduleFunc); + } + if (status != napi_ok) { + bool pendingException; + NAPI_GUARD(napi_is_exception_pending(env, &pendingException)) {} + napi_value error = nullptr; + if (pendingException) { + NAPI_GUARD(napi_get_and_clear_last_exception(env, &error)) {} + } + if (error) { + throw NativeScriptException(env, error, "Error running script " + modulePath); + } else { + throw NativeScriptException("Error running script " + modulePath); + } + } + } else if (Util::EndsWith(modulePath, ".so")) { + auto handle = dlopen(modulePath.c_str(), RTLD_NOW); + if (handle == nullptr) { + auto error = dlerror(); + std::string errMsg(error); + throw NativeScriptException(errMsg); + } + auto func = dlsym(handle, "napi_register_module_v1"); + + if (func == nullptr) { + std::string errMsg("Cannot find 'napi_register_module_v1' in " + modulePath); + throw NativeScriptException(errMsg); + } + + auto cb = reinterpret_cast(func); + napi_value exports; + NAPI_GUARD(napi_create_object(env, &exports)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + napi_value result = cb(env, exports); + NAPI_GUARD(napi_set_named_property(env, moduleObj, "exports", result)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + tempModule.SaveToCache(); + return moduleObj; + } else { + std::string errMsg = "Unsupported file extension: " + modulePath; + throw NativeScriptException(errMsg); + } + + napi_value fileName; + NAPI_GUARD(napi_create_string_utf8(env, modulePath.c_str(), modulePath.size(), &fileName)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + char pathcopy[1024]; + strcpy(pathcopy, modulePath.c_str()); + std::string strDirName(dirname(pathcopy)); + + napi_value dirName; + NAPI_GUARD(napi_create_string_utf8(env, strDirName.c_str(), strDirName.size(), &dirName)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_value require = GetRequireFunction(env, strDirName); + + napi_value requireArgs[5] = { moduleObj, exportsObj, require, fileName, dirName }; + + NAPI_GUARD(napi_set_named_property(env, moduleObj, "require", require)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + napi_util::define_property(env, moduleObj, "id", fileName); + + napi_value thiz; + NAPI_GUARD(napi_create_object(env, &thiz)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_value globalExtends; + NAPI_GUARD(napi_get_named_property(env, context, "__extends", &globalExtends)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + NAPI_GUARD(napi_set_named_property(env, thiz, "__extends", globalExtends)) { + throw NativeScriptException("Failed to load module: " + modulePath); + } + + napi_value callResult; + // Keep the module-call status in its own variable: NAPI_GUARD below reassigns + // the shared `status`, which would otherwise mask a failed module invocation. + napi_status callStatus = napi_call_function(env, thiz, moduleFunc, 5, requireArgs, &callResult); + bool pendingException = false; + NAPI_GUARD(napi_is_exception_pending(env, &pendingException)) {} + if (callStatus != napi_ok || pendingException) { + napi_value exception = nullptr; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &exception)) {} + if (exception) { + throw NativeScriptException(env, exception, "Error calling module function: "); + } else { + throw NativeScriptException("Error calling module function: " + modulePath); + } + } + + tempModule.SaveToCache(); + result = moduleObj; + + return result; +} + +napi_value ModuleInternal::LoadScript(napi_env env, const std::string& path, napi_value fullRequiredModulePath) { + napi_value scriptText = ModuleInternal::WrapModuleContent(env, path); + return scriptText; +} + +napi_value ModuleInternal::LoadData(napi_env env, const std::string& path) { + napi_status status; + std::string jsonData = Runtime::GetRuntime(m_env)->ReadFileText(path); + napi_value json = JsonParseString(env, jsonData); + + if (!napi_util::is_of_type(env, json, napi_object)) { + bool pendingException; + NAPI_GUARD(napi_is_exception_pending(env, &pendingException)) {} + if (pendingException) { + napi_value error; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &error)) {} + throw NativeScriptException(env, error, "JSON is not valid, file=" + path); + } else { + throw NativeScriptException("JSON is not valid, file=" + path); + } + } + + napi_ref poObj = napi_util::make_ref(env, json); + m_loadedModules.emplace(path, ModuleCacheEntry(poObj, true /* isData */)); + return json; +} + +napi_value ModuleInternal::WrapModuleContent(napi_env env, const std::string& path) { + + std::string content = Runtime::GetRuntime(m_env)->ReadFileText(path); + + // TODO: Use statically allocated buffer for better performance + std::string result(MODULE_PROLOGUE); + result.reserve(content.length() + 1024); + result += content; + result += MODULE_EPILOGUE; + + napi_status status; + napi_value wrappedContent; + NAPI_GUARD(napi_create_string_utf8(env, result.c_str(), result.size(), &wrappedContent)) { + return nullptr; + } + + return wrappedContent; +} + +ModuleInternal::ModulePathKind ModuleInternal::GetModulePathKind(const std::string& path) { + ModulePathKind kind; + switch (path[0]) { + case '.': + kind = ModulePathKind::Relative; + break; + case '/': + kind = ModulePathKind::Absolute; + break; + default: + kind = ModulePathKind::Global; + break; + } + return kind; +} + +jclass ModuleInternal::MODULE_CLASS = nullptr; +jmethodID ModuleInternal::RESOLVE_PATH_METHOD_ID = nullptr; + +const char* ModuleInternal::MODULE_PROLOGUE = "(function(module, exports, require, __filename, __dirname){ "; +const char* ModuleInternal::MODULE_EPILOGUE = "\n})"; +int ModuleInternal::MODULE_PROLOGUE_LENGTH = std::string(ModuleInternal::MODULE_PROLOGUE).length(); diff --git a/NativeScript/runtime/android/napi/modules/module/ModuleInternal.h b/NativeScript/runtime/android/napi/modules/module/ModuleInternal.h new file mode 100644 index 000000000..fc048ed56 --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/module/ModuleInternal.h @@ -0,0 +1,122 @@ +#ifndef JNI_MODULE_H_ +#define JNI_MODULE_H_ + +#include "JEnv.h" +#include "js_native_api.h" +#include +#include +#include "robin_hood.h" + +typedef napi_value napi_register_module_v(napi_env env, napi_value exports); + +namespace tns { +class ModuleInternal { + public: + ModuleInternal(); + + void Init(napi_env env, const std::string& baseDir = ""); + + napi_status Load(napi_env env, const std::string& path); + + /* + * Reuses `Load` logic and adds TryCatch exception handling to push any unhandled exceptions + * during script's initial load through the worker scope's `onerror` handler (if implemented before the exception was thrown) + */ + void LoadWorker(napi_env env, const std::string& path); + + /* + * Checks if target script exists, will throw if negative + * Used before initializing workers, to ensure a thread will not be created, when the file doesn't exist + */ + static void CheckFileExists(napi_env env, const std::string& path, const std::string& baseDir); + static std::string EnsureFileProtocol(const std::string& path); + + static int MODULE_PROLOGUE_LENGTH; + void DeInit(); + private: + enum class ModulePathKind { + Global, + Relative, + Absolute + }; + + struct ModuleCacheEntry { + ModuleCacheEntry(napi_ref _obj) + : obj(_obj), isData(false) { + } + + ModuleCacheEntry(napi_ref _obj, bool _isData) + : obj(_obj), isData(_isData) { + } + + bool isData; + napi_ref obj; + }; + + static napi_value RequireCallback(napi_env env, napi_callback_info info); + static napi_value LoadInternalModule(napi_env env, const std::string& moduleName); + + static napi_value RequireNativeCallback(napi_env env, napi_callback_info info); + + static napi_value RequireNativeV8Callback(napi_env env, napi_callback_info info); + + napi_value RequireCallbackImpl(napi_env env, napi_callback_info info); + + napi_value WrapModuleContent(napi_env env, const std::string& path); + + napi_value LoadImpl(napi_env env, const std::string& moduleName, const std::string& baseDir, bool& isData); + + napi_value LoadModule(napi_env env, const std::string& path, const std::string& moduleCacheKey); + + napi_value LoadData(napi_env env, const std::string& path); + + napi_value LoadScript(napi_env env, const std::string& modulePath, napi_value fullRequiredModulePath); + + napi_value GetRequireFunction(napi_env env, const std::string& dirName); + + // void SaveScriptCache(napi_env env, napi_value script, const std::string& path); + + ModulePathKind GetModulePathKind(const std::string& path); + + static jclass MODULE_CLASS; + static jmethodID RESOLVE_PATH_METHOD_ID; + static const char* MODULE_PROLOGUE; + static const char* MODULE_EPILOGUE; + + napi_env m_env; + napi_ref m_requireFunction; + napi_ref m_requireFactoryFunction; + robin_hood::unordered_map m_requireCache; + robin_hood::unordered_map m_loadedModules; + + class TempModule { + public: + TempModule(ModuleInternal* module, const std::string& modulePath, const std::string& cacheKey, napi_ref poModuleObj) + :m_module(module), m_dispose(true), m_modulePath(modulePath), m_cacheKey(cacheKey), m_poModuleObj(poModuleObj) { + m_module->m_loadedModules.emplace(m_modulePath, ModuleCacheEntry(m_poModuleObj)); + m_module->m_loadedModules.emplace(m_cacheKey, ModuleCacheEntry(m_poModuleObj)); + } + + ~TempModule() { + if (m_dispose) { + m_module->m_loadedModules.erase(m_modulePath); + m_module->m_loadedModules.erase(m_cacheKey); + } + } + + void SaveToCache() { + m_dispose = false; + } + + private: + bool m_dispose; + ModuleInternal* m_module; + std::string m_modulePath; + std::string m_cacheKey; + napi_ref m_poModuleObj; + }; + +}; +} + +#endif /* JNI_MODULE_H_ */ \ No newline at end of file diff --git a/NativeScript/runtime/android/napi/modules/performance/Performance.h b/NativeScript/runtime/android/napi/modules/performance/Performance.h new file mode 100644 index 000000000..aec7ed235 --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/performance/Performance.h @@ -0,0 +1,38 @@ +// +// Created by Ammar Ahmed on 21/09/2024. +// + +#ifndef TESTAPPNAPI_PERFORMANCE_H +#define TESTAPPNAPI_PERFORMANCE_H +#include +#include "js_native_api.h" + +napi_value Now(napi_env env, napi_callback_info info) { + auto now = std::chrono::high_resolution_clock::now(); + auto ms = std::chrono::duration_cast(now.time_since_epoch()).count(); + + napi_value result; + napi_create_double(env, static_cast(ms), &result); + return result; +} + +namespace tns { + + class Performance { + public: + static void createPerformance(napi_env env, napi_value global) { + bool isInstalled = false; + napi_has_named_property(env, global, "performance", &isInstalled); + if (!isInstalled) { + napi_value performance; + napi_create_object(env, &performance); + napi_util::napi_set_function(env, performance, "now", Now); + napi_set_named_property(env, global, "performance", performance); + } + + } + }; + +} // tns + +#endif //TESTAPPNAPI_PERFORMANCE_H diff --git a/NativeScript/runtime/android/napi/modules/timers/Timers.cpp b/NativeScript/runtime/android/napi/modules/timers/Timers.cpp new file mode 100644 index 000000000..57d6471c5 --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/timers/Timers.cpp @@ -0,0 +1,345 @@ +#include "Timers.h" +#include "ArgConverter.h" +#include "Runtime.h" +#include "NativeScriptException.h" +#include "JEnv.h" +#include +#include +#include "Util.h" +#include "NativeScriptAssert.h" + +/** + * Timers ride the runtime thread's Java MessageQueue via a per-runtime + * com.tns.TimerHandler bound to the isolate's Looper: + * - each scheduled timer enqueues one anonymous "due token" message via + * sendMessageAtTime, so timers share a single queue with Handler.post/ + * postDelayed and fire in exact MessageQueue order; + * - a native list (sortedTimers_) sorted by exact (sub-millisecond) due time + * picks the earliest-due timer per token, preserving the relative ordering of + * JS timers despite the millisecond-quantized Java queue. + * + * Everything below runs on the runtime thread (Init, the setTimeout/clear + * callbacks, and FireTimer via TimerHandler.handleMessage), so no locking is + * needed — sortedTimers_/timerMap_ are only touched there. + */ + +// Takes a value and transform into a positive number +// returns a negative number if the number is negative or invalid +inline static double ToMaybePositiveValue(napi_env env, napi_value v) { + double value = -1; + if (napi_util::is_null_or_undefined(env, v)) { + return -1; + } + napi_value numberValue; + napi_status status = napi_coerce_to_number(env, v, &numberValue); + if (status == napi_ok) { + status = napi_get_value_double(env, numberValue, &value); + if (status != napi_ok || isnan(value)) { + value = -1; + } + } + return value; +} + +static double now_ms() { + struct timespec res; + clock_gettime(CLOCK_MONOTONIC, &res); + return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6; +} + +using 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(napi_env env, napi_value global) { + env_ = env; + // TODO: remove the __ns__ prefix once this is validated + napi_util::napi_set_function(env, global, "__ns__setTimeout", SetTimeoutCallback, this); + napi_util::napi_set_function(env, global, "__ns__setInterval", SetIntervalCallback, this); + napi_util::napi_set_function(env, global, "__ns__clearTimeout", ClearTimer, this); + napi_util::napi_set_function(env, global, "__ns__clearInterval", ClearTimer, this); + napi_util::napi_set_function(env, global, "setTimeout", SetTimeoutCallback, this); + napi_util::napi_set_function(env, global, "setInterval", SetIntervalCallback, this); + napi_util::napi_set_function(env, global, "clearTimeout", ClearTimer, this); + napi_util::napi_set_function(env, global, "clearInterval", ClearTimer, this); + + napi_status status; + // Non-fatal to timer operation if this fails (only affects GC cleanup of + // `this`), so log for diagnostics but continue with handler setup. + NAPI_GUARD(napi_add_finalizer(env, global, this, + [](napi_env env, void *finalizeData, void *finalizeHint) { + auto thiz = reinterpret_cast(finalizeData); + delete thiz; + }, nullptr, nullptr)) {} + + JEnv jEnv; + if (TIMER_HANDLER_CLASS == nullptr) { + TIMER_HANDLER_CLASS = jEnv.FindClass("com/tns/TimerHandler"); + assert(TIMER_HANDLER_CLASS != nullptr); + TIMER_HANDLER_CTOR = jEnv.GetMethodID(TIMER_HANDLER_CLASS, "", "(J)V"); + TIMER_HANDLER_POST = jEnv.GetMethodID(TIMER_HANDLER_CLASS, "post", "(J)V"); + TIMER_HANDLER_RELEASE = jEnv.GetMethodID(TIMER_HANDLER_CLASS, "release", "()V"); + } + + // Bind a TimerHandler to the current (runtime) thread's Looper. + jobject localHandler = jEnv.NewObject(TIMER_HANDLER_CLASS, TIMER_HANDLER_CTOR, + reinterpret_cast(this)); + handler_ = jEnv.NewGlobalRef(localHandler); + stopped_ = false; +} + +void Timers::postTimer(const std::shared_ptr &task, double now) { + // Due-now timers post at (long)now so they tie (FIFO) with a same-ms + // postDelayed(0); future timers post at ceil(dueTime) so they never fire early. + jlong when = task->dueTime_ <= now ? (jlong) now : (jlong) std::ceil(task->dueTime_); + JEnv jEnv; + jEnv.CallVoidMethod(handler_, TIMER_HANDLER_POST, when); +} + +void Timers::addTask(const std::shared_ptr& task) { + if (task->queued_) { + return; + } + auto now = now_ms(); + task->nestingLevel_ = nesting + 1; + task->queued_ = true; + // 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; + } + timerMap_.emplace(task->id_, task); + 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::removeTask(const std::shared_ptr &task) { + removeTask(task->id_); +} + +void Timers::removeTask(const int &taskId) { + auto it = timerMap_.find(taskId); + if (it == timerMap_.end()) { + return; + } + auto task = it->second; + if (task->queued_) { + // Remove the pending due-token reference (matched by id at its dueTime). + auto lo = std::lower_bound(sortedTimers_.begin(), sortedTimers_.end(), task->dueTime_, + [](const TimerReference &ref, const double &value) { + return ref.dueTime < value; + }); + for (auto ref = lo; ref != sortedTimers_.end() && ref->dueTime == task->dueTime_; ++ref) { + if (ref->id == taskId) { + sortedTimers_.erase(ref); + break; + } + } + // A token already enqueued in the Java queue is left to no-op in FireTimer. + } + task->Unschedule(); + timerMap_.erase(it); +} + +void Timers::FireTimer() { + if (stopped_ || env_ == nullptr) { + return; + } + + NapiScope scope(env_); + + if (sortedTimers_.empty()) { + return; // leftover token + } + auto ref = sortedTimers_.front(); + if (ref.dueTime > now_ms()) { + return; // front not due yet → leftover token + } + sortedTimers_.erase(sortedTimers_.begin()); + + auto it = timerMap_.find(ref.id); + if (it == timerMap_.end()) { + return; + } + auto task = it->second; + task->queued_ = false; + nesting = task->nestingLevel_; + + if (task->repeats_) { + // Follow chromium's non-drifting interval scheduling: anchor the next + // fire to the ideal dueTime rather than "now". + task->startTime_ = task->dueTime_; + addTask(task); + } + + napi_value cb = napi_util::get_ref_value(env_, task->callback_); + napi_value recv = napi_util::get_ref_value(env_, task->thisArg); + size_t argc = task->args_ == nullptr ? 0 : task->args_->size(); + napi_status status; + if (argc > 0) { + std::vector argv(argc); + for (size_t i = 0; i < argc; i++) { + argv[i] = napi_util::get_ref_value(env_, task->args_->at(i)); + } + NAPI_GUARD(napi_call_function(env_, recv, cb, argc, argv.data(), nullptr)) {} + } else { + NAPI_GUARD(napi_call_function(env_, recv, cb, 0, nullptr, nullptr)) {} + } + + // task is not queued, so it's either a setTimeout or a cleared setInterval: + // remove it (which releases its JS references via Unschedule). + if (!task->queued_) { + removeTask(task); + } + + nesting = 0; + + // A pending exception left on the env fails every subsequent napi call + // (NAPI_PREAMBLE returns napi_pending_exception), which would silently stop + // all timer dispatch. Clear it and surface it to Java instead — cleanup + // above must run first so the task's references are released. + bool pendingException = false; + napi_is_exception_pending(env_, &pendingException); + if (pendingException) { + napi_value error = nullptr; + NAPI_GUARD(napi_get_and_clear_last_exception(env_, &error)) {} + if (error != nullptr) { + throw NativeScriptException(env_, error, "Error in timer callback"); + } + } +} + +void Timers::Destroy() { + if (stopped_) { + return; + } + stopped_ = true; + + if (handler_ != nullptr) { + JEnv jEnv; + jEnv.CallVoidMethod(handler_, TIMER_HANDLER_RELEASE); + jEnv.DeleteGlobalRef(handler_); + handler_ = nullptr; + } + + // Release any references still held by pending tasks. + for (auto &entry: timerMap_) { + entry.second->Unschedule(); + } + timerMap_.clear(); + sortedTimers_.clear(); + env_ = nullptr; +} + +Timers::~Timers() { + Destroy(); +} + +napi_value Timers::SetTimeoutCallback(napi_env env, napi_callback_info info) { + return Timers::SetTimer(env, info, false); +} + +napi_value Timers::SetIntervalCallback(napi_env env, napi_callback_info info) { + return Timers::SetTimer(env, info, true); +} + +napi_value Timers::ClearTimer(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + void *data = nullptr; + napi_status status; + NAPI_GUARD(napi_get_cb_info(env, info, &argc, args, nullptr, &data)) { + return nullptr; + } + + int id = -1; + if (argc > 0) { + id = (int) ToMaybePositiveValue(env, args[0]); + } + // ids start at 1 + if (id > 0) { + auto thiz = reinterpret_cast(data); + thiz->removeTask(id); + } + + return nullptr; +} + +napi_value Timers::SetTimer(napi_env env, napi_callback_info info, bool repeatable) { + NAPI_CALLBACK_BEGIN_VARGS_FAST(8) + + auto thiz = reinterpret_cast(data); + + int id = ++thiz->currentTimerId; + if (argc >= 1) { + + if (!napi_util::is_of_type(env, argv[0], napi_function)) { + napi_value result; + NAPI_GUARD(napi_get_undefined(env, &result)) { + return nullptr; + } + return result; + } + + napi_value handler = argv[0]; + long timeout = 0; + if (argc >= 2) { + timeout = (long) ToMaybePositiveValue(env, argv[1]); + if (timeout < 0) { + timeout = 0; + } + } + + std::shared_ptr> argArray; + if (argc >= 3) { + auto otherArgLength = argc - 2; + argArray = std::make_shared>(otherArgLength); + for (size_t i = 0; i < otherArgLength; i++) { + (*argArray)[i] = napi_util::make_ref(env, argv[i + 2]); + } + } + + auto task = std::make_shared(env, napi_util::make_ref(env, handler), timeout, + repeatable, argArray, + napi_util::make_ref(env, jsThis), id, now_ms()); + thiz->addTask(task); + } + napi_value result; + NAPI_GUARD(napi_create_int32(env, id, &result)) { + return nullptr; + } + return result; +} + +void Timers::InitStatic(napi_env env, napi_value global) { + auto timers = new Timers(); + timers->Init(env, global); +} + +// Reverse native for com.tns.TimerHandler.nativeFireTimer (bound by symbol name). +extern "C" JNIEXPORT void JNICALL +Java_com_tns_TimerHandler_nativeFireTimer(JNIEnv* env, jclass clazz, jlong timersPtr) { + try { + reinterpret_cast(timersPtr)->FireTimer(); + } catch (NativeScriptException& e) { + e.ReThrowToJava(nullptr); + } catch (std::exception& e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} diff --git a/NativeScript/runtime/android/napi/modules/timers/Timers.h b/NativeScript/runtime/android/napi/modules/timers/Timers.h new file mode 100644 index 000000000..214e7883a --- /dev/null +++ b/NativeScript/runtime/android/napi/modules/timers/Timers.h @@ -0,0 +1,149 @@ +#ifndef TEST_APP_TIMERS_H +#define TEST_APP_TIMERS_H + +#include +#include "js_native_api.h" +#include "ObjectManager.h" +#include "robin_hood.h" + +namespace tns { + /** + * A Timer Task + * this class is used to store the persistent values and context + * once Unschedule is called everything is released + */ + class TimerTask { + public: + inline TimerTask(napi_env env, napi_ref callback, double frequency, + bool repeats, + const std::shared_ptr> &args, + napi_ref _thisArg, + int id, double startTime) + : env_(env), callback_(callback), thisArg(_thisArg), + frequency_(frequency), repeats_(repeats), args_(args), id_(id), + startTime_(startTime) { + + } + + inline double NextTime(double targetTime) { + if (frequency_ <= 0) { + return targetTime; + } + auto timeDiff = targetTime - startTime_; + auto div = std::div((long) timeDiff, (long) frequency_); + return startTime_ + frequency_ * (div.quot + 1); + } + + // Releases the JS references held by this task. Requires a live napi_env, + // so it is called from the runtime thread (FireTimer / removeTask). + inline void Unschedule() { + if (env_ != nullptr) { + if (callback_ != nullptr) { + napi_delete_reference(env_, callback_); + } + if (thisArg != nullptr) { + napi_delete_reference(env_, thisArg); + } + if (args_ != nullptr) { + for (auto ref: *args_) { + napi_delete_reference(env_, ref); + } + } + } + callback_ = nullptr; + thisArg = nullptr; + args_.reset(); + env_ = nullptr; + queued_ = false; + } + + int nestingLevel_ = 0; + napi_env env_; + napi_ref callback_; + std::shared_ptr> args_; + napi_ref thisArg; + bool repeats_ = false; + /** + * 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 + */ + bool queued_ = false; + double frequency_ = 0; + double dueTime_ = -1; + double startTime_ = -1; + int id_; + }; + + struct TimerReference { + int id; + double dueTime; + }; + + class Timers { + public: + /** + * Initializes the global functions setTimeout, setInterval, clearTimeout and clearInterval + * and binds a Java TimerHandler to the executing thread's Looper. + * @param env target environment + * @param global global object + */ + void Init(napi_env env, napi_value global); + + static void InitStatic(napi_env env, napi_value global); + + /** + * Fires the earliest-due timer. Invoked from Java (TimerHandler.handleMessage) + * on the runtime thread, once per posted "due token". + */ + void FireTimer(); + + /** + * Disposes the timers, releasing all references and the Java handler. + * MUST be called on the thread Init was called on. Idempotent. + */ + void Destroy(); + + ~Timers(); + + private: + static napi_value SetTimeoutCallback(napi_env env, napi_callback_info info); + + static napi_value SetIntervalCallback(napi_env env, napi_callback_info info); + + static napi_value SetTimer(napi_env env, napi_callback_info info, bool repeatable); + + static napi_value ClearTimer(napi_env env, napi_callback_info info); + + void addTask(const std::shared_ptr& task); + + void removeTask(const std::shared_ptr &task); + + void removeTask(const int &taskId); + + // Enqueues one "due token" on the Java MessageQueue for this task. Due-now + // timers post at (long)now so they tie (FIFO) with a same-ms postDelayed(0); + // future timers post at ceil(dueTime) so they never fire early. + void postTimer(const std::shared_ptr &task, double now); + + napi_env env_ = nullptr; + int currentTimerId = 0; + int nesting = 0; + // stores the map of timer tasks + robin_hood::unordered_map> timerMap_; + // sorted by exact (sub-millisecond) dueTime; touched only on the runtime thread + std::vector sortedTimers_; + // global ref to the com.tns.TimerHandler bound to this thread's Looper + jobject handler_ = nullptr; + bool stopped_ = false; + + // Cached (process-wide) TimerHandler JNI ids. + static jclass TIMER_HANDLER_CLASS; + static jmethodID TIMER_HANDLER_CTOR; + static jmethodID TIMER_HANDLER_POST; + static jmethodID TIMER_HANDLER_RELEASE; + }; + +} + +#endif //TEST_APP_TIMERS_H diff --git a/NativeScript/runtime/android/napi/profiler/SimpleProfiler.cpp b/NativeScript/runtime/android/napi/profiler/SimpleProfiler.cpp new file mode 100644 index 000000000..de3b47942 --- /dev/null +++ b/NativeScript/runtime/android/napi/profiler/SimpleProfiler.cpp @@ -0,0 +1,71 @@ +#include "SimpleProfiler.h" +#include "NativeScriptException.h" +#include "NativeScriptAssert.h" +#include +#include + +using namespace tns; +using namespace std; + +SimpleProfiler::SimpleProfiler(char* fileName, int lineNumber) + : + m_frame(nullptr), m_time(0) { + for (auto& f : s_frames) { + if ((f.fileName == fileName) && (f.lineNumber == lineNumber)) { + m_frame = &f; + break; + } + } + if (m_frame == nullptr) { + FrameEntry entry(fileName, lineNumber); + s_frames.push_back(entry); + m_frame = &s_frames.back(); + } + ++m_frame->stackCount; + if (m_frame->stackCount == 1) { + struct timespec nowt; + clock_gettime(CLOCK_MONOTONIC, &nowt); + m_time = (int64_t) nowt.tv_sec * 1000000000LL + nowt.tv_nsec; + } +} + +SimpleProfiler::~SimpleProfiler() { + --m_frame->stackCount; + if (m_frame->stackCount == 0) { + struct timespec nowt; + clock_gettime(CLOCK_MONOTONIC, &nowt); + auto time = (int64_t) nowt.tv_sec * 1000000000LL + nowt.tv_nsec; + m_frame->time += (time - m_time) / 1000000; + } +} + +void SimpleProfiler::Init(napi_env env, napi_value global) { + s_frames.reserve(10000); + napi_util::napi_set_function(env, global, "__printProfilerData", PrintProfilerDataCallback, nullptr); +} + +napi_value SimpleProfiler::PrintProfilerDataCallback(napi_env env, napi_callback_info info) { + try { + PrintProfilerData(); + } catch (NativeScriptException& e) { + e.ReThrowToNapi(env); + } catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToNapi(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToNapi(env); + } + return nullptr; +} + +void SimpleProfiler::PrintProfilerData() { + std::sort(s_frames.begin(), s_frames.end()); + for (auto& f : s_frames) { + __android_log_print(ANDROID_LOG_DEBUG, "TNS.Native.Profiler", "Time: %lld, File: %s, Line: %d", (long long)f.time, f.fileName, f.lineNumber); + } +} + +std::vector SimpleProfiler::s_frames; diff --git a/NativeScript/runtime/android/napi/profiler/SimpleProfiler.h b/NativeScript/runtime/android/napi/profiler/SimpleProfiler.h new file mode 100644 index 000000000..6ae18fd45 --- /dev/null +++ b/NativeScript/runtime/android/napi/profiler/SimpleProfiler.h @@ -0,0 +1,49 @@ +#ifndef SIMPLEPROFILER_H_ +#define SIMPLEPROFILER_H_ + +#include "js_native_api.h" +#include "native_api_util.h" +#include +#include + +namespace tns { +#ifndef SIMPLE_PROFILER +#define SET_PROFILER_FRAME() ((void)0) +#else +#define SET_PROFILER_FRAME() SimpleProfiler __frame(__FILE__, __LINE__) +#endif + +class SimpleProfiler { + public: + SimpleProfiler(char* fileName, int lineNumber); + + ~SimpleProfiler(); + + static void Init(napi_env env, napi_value global); + + static void PrintProfilerData(); + + private: + struct FrameEntry { + FrameEntry(char* _fileName, int _lineNumer) + : + fileName(_fileName), lineNumber(_lineNumer), time(0), stackCount(0) { + } + bool operator<(const FrameEntry& rhs) const { + return time < rhs.time; + } + char* fileName; + int lineNumber; + int64_t time; + int stackCount; + }; + + static napi_value PrintProfilerDataCallback(napi_env env, napi_callback_info info); + + FrameEntry* m_frame; + int64_t m_time; + static std::vector s_frames; +}; +} + +#endif /* SIMPLEPROFILER_H_ */ diff --git a/NativeScript/runtime/android/napi/sighandler/SIGHandler.h b/NativeScript/runtime/android/napi/sighandler/SIGHandler.h new file mode 100644 index 000000000..fa3d2a8cd --- /dev/null +++ b/NativeScript/runtime/android/napi/sighandler/SIGHandler.h @@ -0,0 +1,26 @@ +#ifndef SIGHANDLER_H +#define SIGHANDLER_H +#include +#include "NativeScriptException.h" +using namespace tns; + +void SIGHandler(int sigNumber) { + std::stringstream msg; + msg << "JNI Exception occurred ("; + switch (sigNumber) { + case SIGABRT: + msg << "SIGABRT"; + break; + case SIGSEGV: + msg << "SIGSEGV"; + break; + default: + // Shouldn't happen, but for completeness + msg << "Signal #" << sigNumber; + break; + } + msg << ").\n=======\nCheck the 'adb logcat' for additional information about the error.\n=======\n"; + throw NativeScriptException(msg.str()); +} + +#endif //SIGHANDLER_H \ No newline at end of file diff --git a/NativeScript/runtime/android/napi/util/Util.cpp b/NativeScript/runtime/android/napi/util/Util.cpp new file mode 100644 index 000000000..f64904531 --- /dev/null +++ b/NativeScript/runtime/android/napi/util/Util.cpp @@ -0,0 +1,148 @@ +#include "Util.h" +#include +#include +#include + +using namespace std; +namespace tns { + + + string Util::JniClassPathToCanonicalName(const string &jniClassPath) { + std::string canonicalName; + + const char prefix = jniClassPath[0]; + + std::string rest; + int lastIndex; + + switch (prefix) { + case 'L': + canonicalName = jniClassPath.substr(1, jniClassPath.size() - 2); + std::replace(canonicalName.begin(), canonicalName.end(), '/', '.'); + std::replace(canonicalName.begin(), canonicalName.end(), '$', '.'); + break; + + case '[': + canonicalName = jniClassPath; + lastIndex = canonicalName.find_last_of('['); + rest = canonicalName.substr(lastIndex + 1); + canonicalName = canonicalName.substr(0, lastIndex + 1); + canonicalName.append(JniClassPathToCanonicalName(rest)); + break; + + default: + // TODO: + canonicalName = jniClassPath; + break; + } + return canonicalName; + } + + void Util::SplitString(const string &str, const string &delimiters, vector &tokens) { + string::size_type delimPos = 0, tokenPos = 0, pos = 0; + + if (str.length() < 1) { + return; + } + + while (true) { + delimPos = str.find_first_of(delimiters, pos); + tokenPos = str.find_first_not_of(delimiters, pos); + + if (string::npos != delimPos) { + if (string::npos != tokenPos) { + if (tokenPos < delimPos) { + tokens.push_back(str.substr(pos, delimPos - pos)); + } else { + tokens.emplace_back(""); + } + } else { + tokens.emplace_back(""); + } + pos = delimPos + 1; + } else { + if (string::npos != tokenPos) { + tokens.push_back(str.substr(pos)); + } else { + tokens.emplace_back(""); + } + break; + } + } + } + + bool Util::EndsWith(const string &str, const string &suffix) { + bool res = false; + if (str.size() > suffix.size()) { + res = equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + return res; + } + + bool Util::Contains(const string &str, const string &sequence) { + return str.find(sequence) != string::npos; + } + + string Util::ConvertFromJniToCanonicalName(const string &name) { + string converted = name; + replace(converted.begin(), converted.end(), '/', '.'); + return converted; + } + + string Util::ConvertFromCanonicalToJniName(const string &name) { + string converted = name; + replace(converted.begin(), converted.end(), '.', '/'); + return converted; + } + + string Util::ReplaceAll(string &str, const string &from, const string &to) { + if (from.empty()) { + return str; + } + + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; + } + + u16string Util::ConvertFromUtf8ToUtf16(const string &str) { + auto utf16String = + std::wstring_convert, char16_t>().from_bytes(str); + + return utf16String; + } + + void Util::JoinString(const std::vector &list, const std::string &delimiter, + std::string &out) { + out.clear(); + + stringstream ss; + + for (auto it = list.begin(); it != list.end(); ++it) { + ss << *it; + + if (it != list.end() - 1) { + ss << delimiter; + } + } + + out = ss.str(); + } + + std::vector Util::ToVector(const std::string &value) { +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // FIXME: std::codecvt_utf8_utf16 is deprecated + std::wstring_convert, char16_t> convert; + std::u16string value16 = convert.from_bytes(value); + + const uint16_t *begin = reinterpret_cast(value16.data()); + const uint16_t *end = reinterpret_cast(value16.data() + value16.size()); + std::vector vector(begin, end); + return vector; + } + +}; diff --git a/NativeScript/runtime/android/napi/util/Util.h b/NativeScript/runtime/android/napi/util/Util.h new file mode 100644 index 000000000..71227083e --- /dev/null +++ b/NativeScript/runtime/android/napi/util/Util.h @@ -0,0 +1,58 @@ +#ifndef UTIL_H_ +#define UTIL_H_ + +#include +#include +#ifdef __V8__ +#include +#endif + +namespace tns { +class Util { + public: + static std::string JniClassPathToCanonicalName(const std::string& jniClassPath); + + static void SplitString(const std::string& str, const std::string& delimiters, std::vector& tokens); + + static void JoinString(const std::vector& list, const std::string& delimiter, std::string& out); + + static bool EndsWith(const std::string& str, const std::string& suffix); + static bool Contains(const std::string &str, const std::string &sequence); + + static std::string ConvertFromJniToCanonicalName(const std::string& name); + + static std::string ConvertFromCanonicalToJniName(const std::string& name); + + static std::string ReplaceAll(std::string& str, const std::string& from, const std::string& to); + + static std::u16string ConvertFromUtf8ToUtf16(const std::string& str); + + // static std::uint16_t* ConvertFromUtf8ToProtocolUtf16(const std::string& str); + static std::vector ToVector(const std::string &value); + +#ifdef __V8__ + inline static std::string ToString(v8::Isolate *isolate, const v8::Local &value) { + if (value.IsEmpty()) { + return std::string(); + } + + if (value->IsStringObject()) { + v8::Local obj = value.As()->ValueOf(); + return ToString(isolate, obj); + } + + v8::String::Utf8Value result(isolate, value); + + const char *val = *result; + if (val == nullptr) { + return std::string(); + } + + return std::string(*result, result.length()); + } +#endif +}; + +} + +#endif /* UTIL_H_ */ \ No newline at end of file diff --git a/NativeScript/runtime/android/napi/version/Version.h b/NativeScript/runtime/android/napi/version/Version.h new file mode 100644 index 000000000..102265992 --- /dev/null +++ b/NativeScript/runtime/android/napi/version/Version.h @@ -0,0 +1,7 @@ +#ifndef VERSION_H +#define VERSION_H + +#define NATIVE_SCRIPT_RUNTIME_VERSION "9.0.0" +#define NATIVE_SCRIPT_RUNTIME_COMMIT_SHA "no commit sha was provided by build.gradle build" + +#endif //VERSION_H \ No newline at end of file diff --git a/NativeScript/runtime/android/napi/workers/ConcurrentQueue.cpp b/NativeScript/runtime/android/napi/workers/ConcurrentQueue.cpp new file mode 100644 index 000000000..9c68a7cef --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/ConcurrentQueue.cpp @@ -0,0 +1,98 @@ +#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 + 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_. + { + std::unique_lock mlock(this->mutex_); + std::queue> empty; + this->messagesQueue_.swap(empty); + } +} + +} // namespace tns diff --git a/NativeScript/runtime/android/napi/workers/ConcurrentQueue.h b/NativeScript/runtime/android/napi/workers/ConcurrentQueue.h new file mode 100644 index 000000000..148d3d9ac --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/ConcurrentQueue.h @@ -0,0 +1,37 @@ +#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; 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/NativeScript/runtime/android/napi/workers/LooperTasks.cpp b/NativeScript/runtime/android/napi/workers/LooperTasks.cpp new file mode 100644 index 000000000..0040d03bc --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/LooperTasks.cpp @@ -0,0 +1,100 @@ +#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. + 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. + 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. + try { + task(); + } catch (NativeScriptException& ex) { + ex.ReThrowToJava(nullptr); + } 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/NativeScript/runtime/android/napi/workers/LooperTasks.h b/NativeScript/runtime/android/napi/workers/LooperTasks.h new file mode 100644 index 000000000..2c1394fb7 --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/LooperTasks.h @@ -0,0 +1,40 @@ +#ifndef LOOPERTASKS_H_ +#define LOOPERTASKS_H_ + +#include +#include +#include +#include + +namespace tns { + +/* + * A task queue bound to one runtime's looper. Each Runtime (main or worker) + * owns one; child workers post their outbound messages, errors and cleanup + * notifications onto 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/NativeScript/runtime/android/napi/workers/WorkerMessage.h b/NativeScript/runtime/android/napi/workers/WorkerMessage.h new file mode 100644 index 000000000..3d262f86e --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/WorkerMessage.h @@ -0,0 +1,53 @@ +#ifndef WORKER_MESSAGE_H_ +#define WORKER_MESSAGE_H_ + +#include + +namespace tns { +namespace worker { + +// A worker message carried across the C++ inbox/task rails. +// +// This napi (multi-engine) port uses JSON string payloads (matching the fork's +// existing worker semantics) rather than V8's structured clone. Data messages +// hold a JSON string; Error messages carry the fields the parent's onerror +// handler needs. +enum class MessageKind { Data, Error }; + +struct Message { + MessageKind kind = MessageKind::Data; + + // Data payload (JSON string produced by JsonStringifyObject / consumed by JSON.parse). + std::string data; + + // Error fields (kind == Error). + std::string errorMessage; + std::string errorStackTrace; + std::string errorFilename; + int errorLineNo = 0; + + Message() = default; + + static Message MakeData(std::string json) { + Message m; + m.kind = MessageKind::Data; + m.data = std::move(json); + return m; + } + + static Message MakeError(std::string message, std::string stackTrace, + std::string filename, int lineNo) { + Message m; + m.kind = MessageKind::Error; + m.errorMessage = std::move(message); + m.errorStackTrace = std::move(stackTrace); + m.errorFilename = std::move(filename); + m.errorLineNo = lineNo; + return m; + } +}; + +} // namespace worker +} // namespace tns + +#endif /* WORKER_MESSAGE_H_ */ diff --git a/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp b/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp new file mode 100644 index 000000000..c16efaa1e --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/WorkerWrapper.cpp @@ -0,0 +1,692 @@ +#include "WorkerWrapper.h" + +#include +#include +#include + +#include +#include + +#include "ArgConverter.h" +#include "CallbackHandlers.h" +#include "GlobalHelpers.h" +#include "JEnv.h" +#include "JniLocalRef.h" +#include "LooperTasks.h" +#include "NativeScriptAssert.h" +#include "NativeScriptException.h" +#include "Runtime.h" +#include "jsr.h" +#include "native_api_util.h" + +#if defined(__V8__) && defined(APPLICATION_IN_DEBUG) +#include "JsV8InspectorClient.h" +#include "WorkerInspectorClient.h" +#endif + +namespace tns { + +WorkerWrapper::WorkerWrapper(napi_env parentEnv, int workerId, std::string workerPath, + std::string callingDir, int priority, napi_value workerObject) + : parentEnv_(parentEnv), + // runs on the parent's thread, where the parent runtime is alive + parentTasks_(Runtime::GetRuntime(parentEnv)->GetLooperTasks()), + workerEnv_(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_(nullptr), + isClosing_(false), + isTerminating_(false), + isDisposed_(false), + javaLooperRef_(nullptr) { + napi_status status; + NAPI_GUARD(napi_create_reference(parentEnv, workerObject, 1, &poWorker_)) {} +} + +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; + } + +#if defined(__V8__) && defined(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 the cooperative looper + // quit below can take effect. + std::lock_guard lock(inspectorMutex_); + if (inspector_ != nullptr) { + inspector_->NotifyTerminating(); + } + } +#endif + + // Cooperative: there is no cross-thread "interrupt running JS" primitive in + // napi (v8::TerminateExecution has no equivalent), so we simply quit the + // worker's Java looper. Any JS already running finishes its current turn; + // once the callback unwinds, Looper.loop() returns and the thread shuts + // down. A worker stuck in a synchronous busy-loop will NOT be preempted. + 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() { + napi_env env = workerEnv_.load(); + if (env == nullptr || isTerminating_) { + return; + } + + auto messages = queue_.PopAll(); + if (messages.empty()) { + return; + } + + NapiScope scope(env); + napi_status status; + napi_value globalObject; + NAPI_GUARD(napi_get_global(env, &globalObject)) { + return; + } + + for (auto& message : messages) { + if (isTerminating_ || isClosing_) { + break; + } + + napi_value callback; + NAPI_GUARD(napi_get_named_property(env, globalObject, "onmessage", &callback)) {} + if (!napi_util::is_of_type(env, callback, napi_function)) { + DEBUG_WRITE( + "WORKER: couldn't fire a worker's `onmessage` callback because it isn't implemented!"); + continue; + } + + napi_value event; + NAPI_GUARD(napi_create_object(env, &event)) {} + napi_value data = tns::JsonParseString(env, message->data); + if (!napi_util::is_null_or_undefined(env, data)) { + NAPI_GUARD(napi_set_named_property(env, event, "data", data)) {} + } + + napi_value args[1] = {event}; + napi_value result; + status = napi_call_function(env, globalObject, callback, 1, args, &result); + if (status == napi_pending_exception && !isTerminating_) { + napi_value error; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &error)) {} + CallbackHandlers::CallWorkerScopeOnErrorHandle(env, error); + } + } +} + +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; + } + + napi_env env = wrapper->parentEnv_; + NapiScope scope(env); + + if (wrapper->poWorker_ == nullptr) { + DEBUG_WRITE( + "MAIN: couldn't fire a worker(id=%d) object's `onmessage` callback because the worker has been cleared.", + workerId); + return; + } + + napi_value worker = napi_util::get_ref_value(env, wrapper->poWorker_); + if (napi_util::is_null_or_undefined(env, worker)) { + return; + } + + napi_status status; + napi_value callback; + NAPI_GUARD(napi_get_named_property(env, worker, "onmessage", &callback)) {} + if (!napi_util::is_of_type(env, callback, napi_function)) { + DEBUG_WRITE( + "MAIN: couldn't fire a worker(id=%d) object's `onmessage` callback because it isn't implemented.", + workerId); + return; + } + + napi_value event; + NAPI_GUARD(napi_create_object(env, &event)) {} + napi_value data = tns::JsonParseString(env, message->data); + NAPI_GUARD(napi_set_named_property(env, event, "data", data)) {} + + napi_value args[1] = {event}; + napi_value result; + status = napi_call_function(env, worker, callback, 1, args, &result); + if (status == napi_pending_exception) { + napi_value error; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &error)) {} + // Surface to Java; LooperTasks::Drain wraps this in a try/catch. + throw NativeScriptException(env, error, "Error calling onmessage on Worker object"); + } +} + +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_; + + parentTasks->Post([workerId, message, filename, stackTrace, lineno, threadName]() { + 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; + } + + napi_env env = wrapper->parentEnv_; + NapiScope scope(env); + + if (wrapper->poWorker_ == nullptr) { + DEBUG_WRITE( + "MAIN: couldn't fire a worker(id=%d) object's `onerror` callback because the worker has been cleared.", + workerId); + return; + } + + napi_value worker = napi_util::get_ref_value(env, wrapper->poWorker_); + if (napi_util::is_null_or_undefined(env, worker)) { + return; + } + + napi_status status; + napi_value callback; + NAPI_GUARD(napi_get_named_property(env, worker, "onerror", &callback)) {} + + if (napi_util::is_of_type(env, callback, napi_function)) { + napi_value errEvent; + napi_value msgValue; + NAPI_GUARD(napi_create_string_utf8(env, message.c_str(), NAPI_AUTO_LENGTH, &msgValue)) {} + napi_value codeValue; + NAPI_GUARD(napi_create_string_utf8(env, "", 0, &codeValue)) {} + NAPI_GUARD(napi_create_error(env, codeValue, msgValue, &errEvent)) {} + + // Combine the worker-side stack trace with the Worker object's captured + // construction stack (main thread), mirroring the old fork behavior. + napi_value mainStackValue; + NAPI_GUARD(napi_get_named_property(env, worker, "__stack__", &mainStackValue)) {} + std::string fullStack = stackTrace; + if (napi_util::is_of_type(env, mainStackValue, napi_string)) { + std::string mainStack = ArgConverter::ConvertToString(env, mainStackValue); + auto nl = mainStack.find_first_of("\n"); + if (nl != std::string::npos) { + fullStack = stackTrace + "\n" + mainStack.substr(nl + 1); + } + } + + napi_value fullStackValue; + NAPI_GUARD(napi_create_string_utf8(env, fullStack.c_str(), fullStack.size(), &fullStackValue)) {} + NAPI_GUARD(napi_set_named_property(env, errEvent, "stack", fullStackValue)) {} + + napi_value args[1] = {errEvent}; + napi_value result; + status = napi_call_function(env, worker, callback, 1, args, &result); + + if (status == napi_pending_exception) { + napi_value exception; + NAPI_GUARD(napi_get_and_clear_last_exception(env, &exception)) {} + throw NativeScriptException(env, exception, "Error calling onerror on Worker object"); + } + + // If the handler returns a truthy value, the exception is handled. + if (!napi_util::is_null_or_undefined(env, result)) { + bool handled = false; + NAPI_GUARD(napi_get_value_bool(env, result, &handled)) {} + if (handled) { + 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()); +} + +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 env on this + // thread via initNativeScript). + 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); + + napi_env napiEnv = runtime_->GetNapiEnv(); + workerEnv_.store(napiEnv); + { + std::lock_guard lock(registryMutex_); + envRegistry_[napiEnv] = this; + } + + if (!isTerminating_) { + NapiScope scope(napiEnv); + +#if defined(__V8__) && defined(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(napiEnv); +#endif + + runtime_->RunWorker(workerPath_); + + napi_status status; + bool pending = false; + NAPI_GUARD(napi_is_exception_pending(napiEnv, &pending)) {} + if (pending) { + napi_value error; + NAPI_GUARD(napi_get_and_clear_last_exception(napiEnv, &error)) {} + + std::string emsg; + std::string estack; + if (napi_util::is_of_type(napiEnv, error, napi_object)) { + napi_value m; + napi_value s; + NAPI_GUARD(napi_get_named_property(napiEnv, error, "message", &m)) {} + NAPI_GUARD(napi_get_named_property(napiEnv, error, "stack", &s)) {} + if (napi_util::is_of_type(napiEnv, m, napi_string)) { + emsg = ArgConverter::ConvertToString(napiEnv, m); + } + if (napi_util::is_of_type(napiEnv, s, napi_string)) { + estack = ArgConverter::ConvertToString(napiEnv, s); + } + } else { + napi_value m; + NAPI_GUARD(napi_coerce_to_string(napiEnv, error, &m)) {} + emsg = ArgConverter::ConvertToString(napiEnv, m); + } + + if (!isTerminating_) { + PassUncaughtExceptionFromWorkerToParent(emsg, workerPath_, estack, 0); + } + } + } + + // Deliver messages that were posted before the worker was ready. + 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(std::string(ex.what()), 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 refs live in this env, so they must be released before the env is + // disposed below. Each child cascades to its own children during shutdown. + { + napi_env napiEnv = workerEnv_.load(); + if (napiEnv != nullptr) { + TerminateChildren(napiEnv); + } + } + +#if defined(__V8__) && defined(APPLICATION_IN_DEBUG) + // The inspector must be gone before the Runtime (and with it the env/isolate) + // is disposed 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) { + napi_env napiEnv = workerEnv_.load(); + + try { + // Java-side detach (GcListener.unsubscribe + runtimeCache.remove) + // must happen before the env is disposed. + 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.what()); + } + + { + std::lock_guard lock(registryMutex_); + if (napiEnv != nullptr) { + envRegistry_.erase(napiEnv); + } + } + + workerEnv_.store(nullptr); + + // Enters the engine scope, tears down the runtime and frees the env, + // then deletes the Runtime (which frees the underlying engine runtime). + Runtime::DisposeRuntime(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 ref 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) { + NapiScope scope(wrapper->parentEnv_); + napi_status status; + NAPI_GUARD(napi_delete_reference(wrapper->parentEnv_, wrapper->poWorker_)) {} + wrapper->poWorker_ = nullptr; + } +} + +void WorkerWrapper::TerminateChildren(napi_env parentEnv) { + std::vector> children; + { + std::lock_guard lock(registryMutex_); + for (auto& entry : registry_) { + if (entry.second->parentEnv_ == parentEnv) { + 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::FromEnv(napi_env env) { + std::lock_guard lock(registryMutex_); + auto it = envRegistry_.find(env); + return it != envRegistry_.end() ? it->second : nullptr; +} + +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, "detachRuntime", "(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"); +} + +#if defined(__V8__) && defined(APPLICATION_IN_DEBUG) +void WorkerWrapper::CreateInspector(napi_env env) { + // 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_, env, 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::map WorkerWrapper::envRegistry_; +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/NativeScript/runtime/android/napi/workers/WorkerWrapper.h b/NativeScript/runtime/android/napi/workers/WorkerWrapper.h new file mode 100644 index 000000000..29fc71952 --- /dev/null +++ b/NativeScript/runtime/android/napi/workers/WorkerWrapper.h @@ -0,0 +1,216 @@ +#ifndef WORKERWRAPPER_H_ +#define WORKERWRAPPER_H_ + +#include + +#include +#include +#include +#include +#include + +#if defined(__V8__) && defined(APPLICATION_IN_DEBUG) +#include + +#include "v8.h" +#include +#endif + +#include "ConcurrentQueue.h" +#include "WorkerMessage.h" +#include "js_native_api.h" + +namespace tns { + +class LooperTasks; +class Runtime; +#if defined(__V8__) && defined(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). + * + * This is the napi (multi-engine) port of the old V8 WorkerWrapper. It uses: + * - napi_env instead of v8::Isolate* + * - JSON string payloads (worker::Message) instead of V8 ValueSerializer + * - a cooperative Terminate() (quit the looper) instead of + * v8::TerminateExecution (which has no napi equivalent) + * + * Messaging: + * - 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(napi_env parentEnv, int workerId, std::string workerPath, + std::string callingDir, int priority, napi_value 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 JSON 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. Cooperatively interrupts the worker by + * quitting its Java looper (no v8::TerminateExecution equivalent exists in + * napi, so a runaway busy-loop in the worker is NOT preempted; the loop + * stops once the current JS callback unwinds). + */ + 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 napi handles from the worker env. + */ + 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 napi_ref and removes the + * wrapper from the registry. Idempotent. + */ + static void ClearWorkerOnParent(int workerId); + + /* + * Terminates and clears all workers whose parent is the given env. + * Must run on the parent's thread, before the parent env is disposed + * (the children's Worker object refs live in that env). + * Cascades: each child terminates its own children during shutdown. + */ + static void TerminateChildren(napi_env parentEnv); + + /* + * Resolves the wrapper of a worker env via the env->wrapper registry. + * Returns nullptr on the main env. + */ + static WorkerWrapper* FromEnv(napi_env env); + + /* + * 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(); + +#if defined(__V8__) && defined(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); + + napi_env parentEnv_; + // 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 workerEnv_; + Runtime* runtime_; + + const int workerId_; + const std::string workerPath_; + const std::string callingDir_; + const std::string threadName_; + const int priority_; + + napi_ref poWorker_; + + std::atomic_bool isClosing_; + std::atomic_bool isTerminating_; + std::atomic_bool isDisposed_; + + ConcurrentQueue queue_; + + std::mutex looperMutex_; + jobject javaLooperRef_; + +#if defined(__V8__) && defined(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 env/isolate is disposed. + */ + void CreateInspector(napi_env env); + void DestroyInspector(); + + WorkerInspectorClient* inspector_ = nullptr; + std::mutex inspectorMutex_; +#endif + + static std::mutex registryMutex_; + static std::map> registry_; + static std::map envRegistry_; + 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/NativeScript/runtime/Bundle.h b/NativeScript/runtime/apple/Bundle.h similarity index 100% rename from NativeScript/runtime/Bundle.h rename to NativeScript/runtime/apple/Bundle.h diff --git a/NativeScript/runtime/Bundle.mm b/NativeScript/runtime/apple/Bundle.mm similarity index 100% rename from NativeScript/runtime/Bundle.mm rename to NativeScript/runtime/apple/Bundle.mm diff --git a/NativeScript/runtime/NativeScript.mm b/NativeScript/runtime/apple/NativeScript.mm similarity index 98% rename from NativeScript/runtime/NativeScript.mm rename to NativeScript/runtime/apple/NativeScript.mm index ca61cf81b..9f026a241 100644 --- a/NativeScript/runtime/NativeScript.mm +++ b/NativeScript/runtime/apple/NativeScript.mm @@ -1,10 +1,10 @@ #include "NativeScript.h" #include "Runtime.h" #include "RuntimeConfig.h" -#include "ffi/shared/Tasks.h" +#include "runtime/apple/NativeScriptException.h" +#include "ffi/objc/shared/Tasks.h" #include "js_native_api.h" #include "jsr.h" -#include "runtime/NativeScriptException.h" using namespace nativescript; diff --git a/NativeScript/runtime/NativeScriptException.h b/NativeScript/runtime/apple/NativeScriptException.h similarity index 100% rename from NativeScript/runtime/NativeScriptException.h rename to NativeScript/runtime/apple/NativeScriptException.h diff --git a/NativeScript/runtime/NativeScriptException.mm b/NativeScript/runtime/apple/NativeScriptException.mm similarity index 98% rename from NativeScript/runtime/NativeScriptException.mm rename to NativeScript/runtime/apple/NativeScriptException.mm index 6fe9e178f..217b329ee 100644 --- a/NativeScript/runtime/NativeScriptException.mm +++ b/NativeScript/runtime/apple/NativeScriptException.mm @@ -1,4 +1,4 @@ -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #import #include #include "js_native_api.h" @@ -7,7 +7,7 @@ #include "jsr.h" #endif #include "native_api_util.h" -#include "runtime/Runtime.h" +#include "runtime/apple/Runtime.h" namespace nativescript { diff --git a/NativeScript/runtime/apple/Runtime.cpp b/NativeScript/runtime/apple/Runtime.cpp new file mode 100644 index 000000000..97c55e0a3 --- /dev/null +++ b/NativeScript/runtime/apple/Runtime.cpp @@ -0,0 +1,942 @@ +#include "native_api_util.h" +#include "runtime/apple/SpinLock.h" +#ifdef ENABLE_JS_RUNTIME + +#include "NativeScriptException.h" +#include "Runtime.h" +#include "RuntimeConfig.h" +#include "js_native_api.h" +#include "js_native_api_types.h" +#include "jsr.h" +#include "jsr_common.h" +#include "runtime/apple/Util.h" +#include "runtime/apple/modules/RuntimeModules.h" +#ifdef TARGET_ENGINE_V8 +#include "ffi/objc/v8/NativeApiV8.h" +#include "v8-api.h" +#endif // TARGET_ENGINE_V8 +#ifdef TARGET_ENGINE_HERMES +#include "ffi/objc/hermes/NativeApiJsi.h" +#endif // TARGET_ENGINE_HERMES +#ifdef TARGET_ENGINE_JSC +#include "ffi/objc/jsc/NativeApiJSC.h" +#endif // TARGET_ENGINE_JSC +#ifdef TARGET_ENGINE_QUICKJS +#include "ffi/objc/quickjs/NativeApiQuickJS.h" +#endif // TARGET_ENGINE_QUICKJS +#include +#include +#include +#include +#include +#include +#include + +#include "NativeScript.h" +#include "robin_hood.h" + +extern "C" { +void node_module_register(const char* name, napi_module_init init) { + nativescript::napiModuleRegistry[name] = init; +} +} + +namespace nativescript { + +std::unordered_map napiModuleRegistry; + +static robin_hood::unordered_map runtimes_; + +std::atomic Runtime::nextIsolateId{0}; + +void unregisterRuntimePromiseRunLoop(CFRunLoopRef runLoop); + +Runtime* Runtime::GetRuntime(napi_env env) { + auto it = runtimes_.find(env); + if (it != runtimes_.end()) { + return it->second; + } + return nullptr; +} + +#ifdef TARGET_ENGINE_HERMES +class HermesRuntimeUnlockScope final { + public: + explicit HermesRuntimeUnlockScope(napi_env env) { + jsr_ = JSR::FromEnv(env); + if (jsr_ == nullptr) { + return; + } + + unlockedDepth_ = js_current_env_lock_depth(env); + for (int i = 0; i < unlockedDepth_; i++) { + jsr_->unlock(); + } + if (unlockedDepth_ == 0 && jsr_->runtime != nullptr) { + jsr_->runtime->unlock(); + unlockedRuntime_ = true; + } + } + + ~HermesRuntimeUnlockScope() { + if (unlockedRuntime_ && jsr_ != nullptr && jsr_->runtime != nullptr) { + jsr_->runtime->lock(); + } + if (jsr_ != nullptr) { + for (int i = 0; i < unlockedDepth_; i++) { + jsr_->lock(); + } + } + } + + HermesRuntimeUnlockScope(const HermesRuntimeUnlockScope&) = delete; + HermesRuntimeUnlockScope& operator=(const HermesRuntimeUnlockScope&) = delete; + + private: + JSR* jsr_ = nullptr; + int unlockedDepth_ = 0; + bool unlockedRuntime_ = false; +}; + +void InvokeWithUnlockedHermesRuntime(napi_env env, + const std::function& task) { + HermesRuntimeUnlockScope scope(env); + task(); +} +#endif // TARGET_ENGINE_HERMES + +Runtime::Runtime() { + currentRuntime_ = this; + workerId_ = -1; + runtimeLoop_ = nullptr; + microtaskObserver_ = nullptr; + // workerCache_ = Caches::Workers; +} + +Runtime::~Runtime() { + currentRuntime_ = nullptr; + if (microtaskObserver_ != nullptr) { + if (runtimeLoop_ != nullptr) { + CFRunLoopRemoveObserver(runtimeLoop_, microtaskObserver_, + kCFRunLoopCommonModes); + } + CFRelease(microtaskObserver_); + microtaskObserver_ = nullptr; + } + if (runtimeLoop_ != nullptr) { + unregisterRuntimePromiseRunLoop(runtimeLoop_); + runtimeLoop_ = nullptr; + } + + // Tearing down the env runs JS (env cleanup hooks, finalizers), and on a JSI + // engine a failing job is reported by throwing a C++ jsi::JSError. A + // destructor is implicitly noexcept, so letting that escape calls + // std::terminate instead of finishing teardown. Swallow it here: the runtime + // is being destroyed, so there is nothing left to report it to. + try { + if (env_) { + { + // Enter isolate/context for deinit work without creating another + // temporary N-API handle scope. We must close the long-lived + // `globalScope_` in LIFO order. + NapiScope scope(env_, false); + + modules_.DeInit(); + napi_close_handle_scope(env_, globalScope_); + js_free_napi_env(env_); + } + + js_free_runtime(runtime_); + } else { + modules_.DeInit(); + } + } catch (...) { + } + + { + SpinLock lock(envsMutex_); + runtimes_.erase(env_); + } +} + +napi_value drainMicrotasks(napi_env env, napi_callback_info cbinfo) { + js_execute_pending_jobs(env); + return nullptr; +} + +// Leaked, never-destroyed singletons: the Runtime destructor can run during +// process teardown after file-scope statics are destroyed, so a destroyed +// mutex would fail to lock (std::system_error). Heap-allocating and never +// freeing avoids the static-destruction-order fiasco. +std::mutex& gRuntimePromiseRunLoopMutex() { + static std::mutex* mutex = new std::mutex(); + return *mutex; +} +std::unordered_map& gRuntimePromiseRunLoops() { + static auto* runLoops = new std::unordered_map(); + return *runLoops; +} + +std::string runtimePromiseRunLoopToken(CFRunLoopRef runLoop) { + char buffer[(sizeof(void*) * 2) + 3] = {}; + std::snprintf(buffer, sizeof(buffer), "%p", runLoop); + return buffer; +} + +std::string registerRuntimePromiseRunLoop(CFRunLoopRef runLoop) { + if (runLoop == nullptr) { + return ""; + } + + std::string token = runtimePromiseRunLoopToken(runLoop); + std::lock_guard lock(gRuntimePromiseRunLoopMutex()); + if (gRuntimePromiseRunLoops().find(token) == gRuntimePromiseRunLoops().end()) { + gRuntimePromiseRunLoops().emplace(token, (CFRunLoopRef)CFRetain(runLoop)); + } + return token; +} + +void unregisterRuntimePromiseRunLoop(CFRunLoopRef runLoop) { + if (runLoop == nullptr) { + return; + } + + std::string token = runtimePromiseRunLoopToken(runLoop); + std::lock_guard lock(gRuntimePromiseRunLoopMutex()); + auto it = gRuntimePromiseRunLoops().find(token); + if (it == gRuntimePromiseRunLoops().end()) { + return; + } + CFRelease(it->second); + gRuntimePromiseRunLoops().erase(it); +} + +CFRunLoopRef copyRuntimePromiseRunLoop(const std::string& token) { + std::lock_guard lock(gRuntimePromiseRunLoopMutex()); + auto it = gRuntimePromiseRunLoops().find(token); + if (it == gRuntimePromiseRunLoops().end()) { + return nullptr; + } + return (CFRunLoopRef)CFRetain(it->second); +} + +bool readRuntimeStringArgument(napi_env env, napi_value value, + std::string* result) { + if (result == nullptr) { + return false; + } + + napi_valuetype type = napi_undefined; + if (napi_typeof(env, value, &type) != napi_ok || type != napi_string) { + return false; + } + + size_t length = 0; + if (napi_get_value_string_utf8(env, value, nullptr, 0, &length) != napi_ok) { + return false; + } + + std::string buffer(length + 1, '\0'); + size_t copied = 0; + if (napi_get_value_string_utf8(env, value, buffer.data(), buffer.size(), + &copied) != napi_ok) { + return false; + } + buffer.resize(copied); + *result = std::move(buffer); + return true; +} + +napi_value runtimeCurrentRunLoopToken(napi_env env, napi_callback_info) { + std::string token = registerRuntimePromiseRunLoop(CFRunLoopGetCurrent()); + napi_value result; + napi_create_string_utf8(env, token.c_str(), token.size(), &result); + return result; +} + +napi_value runtimeIsCurrentRunLoopToken(napi_env env, napi_callback_info cbinfo) { + napi_value argv[1]; + size_t argc = 1; + napi_value jsThis; + void* data; + if (napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, &data) != napi_ok || + argc < 1) { + napi_throw_type_error(env, nullptr, "Expected a run loop token."); + return nullptr; + } + + std::string token; + if (!readRuntimeStringArgument(env, argv[0], &token)) { + napi_throw_type_error(env, nullptr, "Expected a run loop token string."); + return nullptr; + } + + napi_value result; + napi_get_boolean(env, + token == runtimePromiseRunLoopToken(CFRunLoopGetCurrent()), + &result); + return result; +} + +napi_value runtimeScheduleOnRunLoop(napi_env env, napi_callback_info cbinfo) { + napi_value argv[2]; + size_t argc = 2; + napi_value jsThis; + void* data; + if (napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, &data) != napi_ok || + argc < 2) { + napi_throw_type_error(env, nullptr, + "Expected a run loop token and callback."); + return nullptr; + } + + std::string token; + if (!readRuntimeStringArgument(env, argv[0], &token)) { + napi_throw_type_error(env, nullptr, "Expected a run loop token string."); + return nullptr; + } + + napi_valuetype callbackType = napi_undefined; + if (napi_typeof(env, argv[1], &callbackType) != napi_ok || + callbackType != napi_function) { + napi_throw_type_error(env, nullptr, "Expected a callback function."); + return nullptr; + } + + CFRunLoopRef runLoop = copyRuntimePromiseRunLoop(token); + if (runLoop == nullptr) { + napi_throw_error(env, nullptr, "Run loop token is no longer valid."); + return nullptr; + } + + napi_ref callbackRef = nullptr; + if (napi_create_reference(env, argv[1], 1, &callbackRef) != napi_ok) { + CFRelease(runLoop); + napi_throw_error(env, nullptr, "Unable to retain scheduled callback."); + return nullptr; + } + + CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{ + NapiScope scope(env); + napi_value callback = nullptr; + napi_value global = nullptr; + if (napi_get_reference_value(env, callbackRef, &callback) == napi_ok && + callback != nullptr && napi_get_global(env, &global) == napi_ok) { + // A throwing callback leaves an exception pending on the env. Draining + // the microtask queue on top of that is not just untidy: on Hermes, + // drainJobs() sees the already-thrown value, reports EXCEPTION, and JSI + // turns that into a C++ throw out of this block. Report it here and + // clear it, so the queue is drained against a clean runtime. + if (napi_call_function(env, global, callback, 0, nullptr, nullptr) != + napi_ok) { + napi_value exception = nullptr; + if (napi_get_and_clear_last_exception(env, &exception) == napi_ok && + !napi_util::is_null_or_undefined(env, exception)) { + NativeScriptException::OnUncaughtError(env, exception); + } + } + js_execute_pending_jobs(env); + } + napi_delete_reference(env, callbackRef); + CFRelease(runLoop); + }); + CFRunLoopWakeUp(runLoop); + return nullptr; +} + +void Runtime::Init(bool isWorker) { + js_set_runtime_flags(""); + + auto now = std::chrono::steady_clock::now(); + startTime_ = std::chrono::duration(now.time_since_epoch()).count(); + + auto sysNow = std::chrono::system_clock::now(); + realtimeOrigin_ = std::chrono::duration_cast( + sysNow.time_since_epoch()) + .count(); + + js_create_runtime(&runtime_); + js_create_napi_env(&env_, runtime_); + + runtimeLoop_ = CFRunLoopGetCurrent(); + { + Runtime* runtime = this; + microtaskObserver_ = CFRunLoopObserverCreateWithHandler( + kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, + ^(CFRunLoopObserverRef, CFRunLoopActivity) { + napi_env env = runtime->env_; + if (env == nullptr || !Runtime::IsAlive(env)) { + return; + } + + NapiScope scope(env); + js_execute_pending_jobs(env); + }); + if (microtaskObserver_ != nullptr) { + CFRunLoopAddObserver(runtimeLoop_, microtaskObserver_, + kCFRunLoopCommonModes); + } + } + + { + SpinLock lock(envsMutex_); + runtimes_[env_] = this; + } + +#ifdef TARGET_ENGINE_V8 + v8::Locker locker(env_->isolate); + v8::Isolate::Scope isolate_scope(env_->isolate); + v8::Context::Scope context_scope(env_->context()); +#endif // TARGET_ENGINE_V8 + + napi_open_handle_scope(env_, &globalScope_); + + napi_handle_scope scope; + napi_open_handle_scope(env_, &scope); + + napi_value global; + napi_get_global(env_, &global); + napi_set_named_property(env_, global, "global", global); + + const char* CompatScript = R"( + if (typeof globalThis.__extends !== "function") { + var __extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function(d, b) { d.__proto__ = b; }) || + function(d, b) { + for (var p in b) { + if (Object.prototype.hasOwnProperty.call(b, p)) { + d[p] = b[p]; + } + } + }; + globalThis.__extends = function(d, b) { + if (typeof b !== "function" && b !== null) { + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + } + __extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + } + + if (typeof globalThis.__decorate !== "function") { + globalThis.__decorate = function(decorators, target, key, desc) { + var c = arguments.length; + var r = c < 3 ? target : (desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc); + var d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { + r = Reflect.decorate(decorators, target, key, desc); + } else { + for (var i = decorators.length - 1; i >= 0; i--) { + d = decorators[i]; + if (d) { + r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + } + } + } + if (c > 3 && r) { + Object.defineProperty(target, key, r); + } + return r; + }; + } + + if (typeof globalThis.__param !== "function") { + globalThis.__param = function(paramIndex, decorator) { + return function(target, key) { decorator(target, key, paramIndex); }; + }; + } + + if (typeof globalThis.ObjCClass !== "function") { + globalThis.ObjCClass = function ObjCClass(...protocols) { + return function(constructor) { + constructor.ObjCProtocols = protocols; + }; + }; + } + + if (typeof WeakRef === "function") { + if (!WeakRef.prototype.get && typeof WeakRef.prototype.deref === "function") { + WeakRef.prototype.get = WeakRef.prototype.deref; + } + + if (!WeakRef.prototype.clear) { + WeakRef.prototype.clear = function() { + console.warn("WeakRef.clear() is non-standard and has been deprecated. It does nothing and the call can be safely removed."); + }; + } + } + + if (!globalThis.__time) { + globalThis.__time = function() { + if (globalThis.performance && typeof performance.now === "function") { + return performance.now(); + } + return Date.now(); + }; + } + + if (globalThis.performance && typeof performance.now === "function" && + typeof performance.timeOrigin !== "number") { + const now = performance.now(); + const origin = Date.now() - now; + try { + Object.defineProperty(performance, "timeOrigin", { + configurable: true, + enumerable: true, + writable: false, + value: origin + }); + } catch (_) { + performance.timeOrigin = origin; + } + } + + if (!globalThis.__collect) { + globalThis.__collect = function() { + gc(); + }; + } + + if (!globalThis.__dynamicImport) { + globalThis.__dynamicImport = function(specifier) { + return Promise.resolve().then(function() { + const runtimeRequire = + typeof globalThis.require === "function" ? globalThis.require : null; + if (!runtimeRequire) { + throw new ReferenceError("require is not available"); + } + + const loaded = runtimeRequire(specifier); + if (loaded !== null && + (typeof loaded === "object" || typeof loaded === "function")) { + if (loaded.__esModule) { + return loaded; + } + return Object.assign({ default: loaded }, loaded); + } + + return { default: loaded }; + }); + }; + } + + if (!globalThis.gc) { + globalThis.gc = function() { + console.warn('gc() is not exposed'); + }; + } + + if (typeof globalThis.URLPattern !== "function") { + globalThis.URLPattern = class URLPattern { + constructor(input, baseURL) { + if (typeof input !== "string") { + throw new TypeError("Failed to construct 'URLPattern': input must be a string"); + } + + let url; + if (baseURL === undefined || baseURL === null) { + url = new URL(input); + } else { + url = new URL(input, baseURL); + } + + const normalizeProtocol = (value) => value.endsWith(":") ? value.slice(0, -1) : value; + + this.protocol = normalizeProtocol(url.protocol); + this.username = url.username || "*"; + this.password = url.password || "*"; + this.hostname = url.hostname || "*"; + this.port = url.port || ""; + this.pathname = url.pathname || "/"; + this.search = url.search || "*"; + this.hash = url.hash || "*"; + this.hasRegExpGroups = false; + } + }; + } + )"; + + napi_value compatScript, result; + napi_create_string_utf8(env_, CompatScript, NAPI_AUTO_LENGTH, &compatScript); + napi_run_script(env_, compatScript, &result); + + napi_property_descriptor runtimeProps[] = { + napi_util::desc("__drainMicrotaskQueue", drainMicrotasks, nullptr), + napi_util::desc("__runtimeCurrentRunLoopToken", + runtimeCurrentRunLoopToken, nullptr), + napi_util::desc("__runtimeIsCurrentRunLoopToken", + runtimeIsCurrentRunLoopToken, nullptr), + napi_util::desc("__runtimeScheduleOnRunLoop", runtimeScheduleOnRunLoop, + nullptr), + }; + napi_define_properties(env_, global, + sizeof(runtimeProps) / sizeof(runtimeProps[0]), + runtimeProps); + +#if defined(TARGET_ENGINE_V8) || defined(TARGET_ENGINE_HERMES) + const char* PromiseProxyScript = R"( + // Ensure that Promise callbacks are executed on the + // same thread on which they were created + (() => { + const currentRunLoopToken = globalThis.__runtimeCurrentRunLoopToken; + const isCurrentRunLoop = globalThis.__runtimeIsCurrentRunLoopToken; + const scheduleRunLoop = globalThis.__runtimeScheduleOnRunLoop; + if (typeof currentRunLoopToken !== "function" || + typeof isCurrentRunLoop !== "function" || + typeof scheduleRunLoop !== "function") { + return; + } + + const runLoopQueues = []; + + function getRunLoopQueue(runloop) { + for (let i = 0; i < runLoopQueues.length; i++) { + if (runLoopQueues[i].runloop === runloop) { + return runLoopQueues[i]; + } + } + + const queue = { + runloop, + pending: false, + callbacks: [], + drain() { + queue.pending = false; + const callbacks = queue.callbacks.splice(0); + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](); + } + if (queue.callbacks.length > 0 && !queue.pending) { + queue.pending = true; + scheduleRunLoop(queue.runloop, queue.drain); + } + } + }; + runLoopQueues.push(queue); + return queue; + } + + function scheduleOnRunLoop(queue, callback) { + queue.callbacks.push(callback); + if (queue.pending) { + return; + } + queue.pending = true; + scheduleRunLoop(queue.runloop, queue.drain); + } + + global.Promise = new Proxy(global.Promise, { + construct: function(target, args) { + let origFunc = args[0]; + let runloop = currentRunLoopToken(); + let runloopQueue = getRunLoopQueue(runloop); + + let promise = new target(function(resolve, reject) { + function isFulfilled() { + return !resolve; + } + function markFulfilled() { + origFunc = null; + resolve = null; + reject = null; + } + origFunc(value => { + if (isFulfilled()) { + return; + } + const resolveFn = resolve; + const resolveCall = function() { + resolveFn(value); + }; + if (isCurrentRunLoop(runloop)) { + markFulfilled(); + resolveCall(); + } else { + markFulfilled(); + scheduleOnRunLoop(runloopQueue, resolveCall); + } + }, reason => { + if (isFulfilled()) { + return; + } + const rejectFn = reject; + const rejectCall = function() { + rejectFn(reason); + }; + if (isCurrentRunLoop(runloop)) { + markFulfilled(); + rejectCall(); + } else { + markFulfilled(); + scheduleOnRunLoop(runloopQueue, rejectCall); + } + }); + }); + + return new Proxy(promise, { + get: function(target, name) { + let orig = target[name]; + if (name === "then" || name === "catch" || name === "finally") { + return orig.bind(target); + } + return typeof orig === 'function' ? function(x) { + if (isCurrentRunLoop(runloop)) { + orig.bind(target, x)(); + return target; + } + scheduleRunLoop(runloop, orig.bind(target, x)); + return target; + } : orig; + } + }); + } + }); + })(); + )"; + + napi_value promiseProxyScript; + napi_create_string_utf8(env_, PromiseProxyScript, NAPI_AUTO_LENGTH, + &promiseProxyScript); + napi_run_script(env_, promiseProxyScript, &result); +#endif + + if (isWorker) { + napi_property_descriptor prop = napi_util::desc("self", global); + napi_define_properties(env_, global, 1, &prop); + } + modules_.Init(env_, global); + +#ifdef TARGET_ENGINE_HERMES + const char* HermesGlobalBootstrap = R"( + if (typeof globalThis.Console === "function" && typeof globalThis.console === "undefined") { + globalThis.console = Object.create(globalThis.Console.prototype); + } + if (typeof globalThis.Performance === "function" && typeof globalThis.performance === "undefined") { + globalThis.performance = Object.create(globalThis.Performance.prototype); + } + )"; + + napi_value hermesGlobalBootstrapScript; + napi_create_string_utf8(env_, HermesGlobalBootstrap, NAPI_AUTO_LENGTH, + &hermesGlobalBootstrapScript); + napi_run_script(env_, hermesGlobalBootstrapScript, &result); +#endif + + const char* metadata_path = std::getenv("NS_METADATA_PATH"); +#if NS_FFI_BACKEND_NAPI + nativescript_init(env_, metadata_path, RuntimeConfig.MetadataPtr); +#endif + +#if NS_FFI_BACKEND_V8 && defined(TARGET_ENGINE_V8) + { + NativeApiConfig nativeApiV8Config; + nativeApiV8Config.metadataPath = metadata_path; + nativeApiV8Config.metadataPtr = RuntimeConfig.MetadataPtr; + nativeApiV8Config.installGlobalSymbols = true; + nativeApiV8Config.nativeCallbackInvoker = + [env = env_](std::function task) { + NapiScope scope(env); + task(); + }; + nativeApiV8Config.jsThreadCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + false); + }; + nativeApiV8Config.jsThreadAsyncCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + true); + }; + InstallNativeApi(env_->isolate, env_->context(), nativeApiV8Config); + } +#endif // NS_FFI_BACKEND_V8 && TARGET_ENGINE_V8 + +#if NS_FFI_BACKEND_HERMES && defined(TARGET_ENGINE_HERMES) + if (auto* jsiRuntime = js_get_jsi_runtime(env_)) { + NativeApiJsiConfig nativeApiJsiConfig; + nativeApiJsiConfig.metadataPath = metadata_path; + nativeApiJsiConfig.metadataPtr = RuntimeConfig.MetadataPtr; + nativeApiJsiConfig.installGlobalSymbols = true; + nativeApiJsiConfig.invokeCallbacksOnNativeCallerThread = true; + nativeApiJsiConfig.nativeInvocationInvoker = + [env = env_](std::function task) { + InvokeWithUnlockedHermesRuntime(env, task); + }; + nativeApiJsiConfig.jsThreadCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + false); + }; + nativeApiJsiConfig.jsThreadAsyncCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + true); + }; + InstallNativeApiJSI(*jsiRuntime, nativeApiJsiConfig); + } +#endif // NS_FFI_BACKEND_HERMES && TARGET_ENGINE_HERMES + +#if NS_FFI_BACKEND_JSC && defined(TARGET_ENGINE_JSC) + { + NativeApiConfig nativeApiJSCConfig; + nativeApiJSCConfig.metadataPath = metadata_path; + nativeApiJSCConfig.metadataPtr = RuntimeConfig.MetadataPtr; + nativeApiJSCConfig.installGlobalSymbols = true; + nativeApiJSCConfig.nativeCallbackInvoker = + [env = env_](std::function task) { + NapiScope scope(env); + task(); + }; + nativeApiJSCConfig.jsThreadCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + false); + }; + nativeApiJSCConfig.jsThreadAsyncCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + true); + }; + InstallNativeApi(env_->context, nativeApiJSCConfig); + } +#endif // NS_FFI_BACKEND_JSC && TARGET_ENGINE_JSC + +#if NS_FFI_BACKEND_QUICKJS && defined(TARGET_ENGINE_QUICKJS) + { + NativeApiConfig nativeApiQuickJSConfig; + nativeApiQuickJSConfig.metadataPath = metadata_path; + nativeApiQuickJSConfig.metadataPtr = RuntimeConfig.MetadataPtr; + nativeApiQuickJSConfig.installGlobalSymbols = true; + nativeApiQuickJSConfig.nativeCallbackInvoker = + [env = env_](std::function task) { + NapiScope scope(env); + task(); + }; + nativeApiQuickJSConfig.jsThreadCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + false); + }; + nativeApiQuickJSConfig.jsThreadAsyncCallbackInvoker = + [env = env_, runLoop = runtimeLoop_](std::function task) { + ExecuteOnRunLoop( + runLoop, + [env, task = std::move(task)]() mutable { + NapiScope scope(env); + task(); + }, + true); + }; + InstallNativeApi(qjs_get_context(env_), nativeApiQuickJSConfig); + } +#endif // NS_FFI_BACKEND_QUICKJS && TARGET_ENGINE_QUICKJS + + napi_close_handle_scope(env_, scope); +} + +const int Runtime::WorkerId() { return this->workerId_; } + +void Runtime::SetWorkerId(int workerId) { this->workerId_ = workerId; } + +void Runtime::RunScript(std::string& scriptSrc, std::string file) { + NapiScope scope(env_); + + napi_value script, result; + napi_create_string_utf8(env_, scriptSrc.c_str(), scriptSrc.length(), &script); + js_execute_script(env_, script, file.c_str(), &result); +} + +napi_value Runtime::RunModule(std::string spec) { + NapiScope scope(env_); + return modules_.module.Require(env_, spec, RuntimeConfig.BaseDir); +} + +void Runtime::RunMainModule() { napi_value result = RunModule("./"); } + +void Runtime::RunLoop() { + // Keep the runtime alive while asynchronous main-thread work is pending, but + // still exit once the loop has been idle for a short period. + constexpr CFTimeInterval kPollSeconds = 0.1; + constexpr int kIdlePollsBeforeExit = 10; // ~1s idle window + + int idlePolls = 0; + while (true) { + js_execute_pending_jobs(env_); + + const auto result = + CFRunLoopRunInMode(kCFRunLoopDefaultMode, kPollSeconds, true); + + if (result == kCFRunLoopRunHandledSource) { + idlePolls = 0; + continue; + } + + if (result == kCFRunLoopRunTimedOut) { +#ifdef __APPLE__ + if (Timers::HasActiveTimers()) { + idlePolls = 0; + continue; + } +#endif + idlePolls++; + if (idlePolls >= kIdlePollsBeforeExit) { + break; + } + continue; + } + + if (result == kCFRunLoopRunStopped || result == kCFRunLoopRunFinished) { + break; + } + } +} + +bool Runtime::IsAlive(napi_env env) { + SpinLock lock(envsMutex_); + auto it = runtimes_.find(env); + return it != runtimes_.end(); +} + +thread_local Runtime* Runtime::currentRuntime_ = nullptr; +SpinMutex Runtime::envsMutex_; + +} // namespace nativescript + +#endif // ENABLE_JS_RUNTIME diff --git a/NativeScript/runtime/apple/Runtime.h b/NativeScript/runtime/apple/Runtime.h new file mode 100644 index 000000000..c1b71b9f1 --- /dev/null +++ b/NativeScript/runtime/apple/Runtime.h @@ -0,0 +1,81 @@ +#ifndef RUNTIME_H +#define RUNTIME_H + +#include + +#include + +#include "js_native_api_types.h" +// jsr_ns_runtime (used for runtime_ below) moved out of js_native_api_types.h +// and into jsr_common.h. +#include "jsr_common.h" +#include "runtime/apple/SpinLock.h" +#include "runtime/apple/modules/RuntimeModules.h" + +typedef napi_value (*napi_module_init)(napi_env env, napi_value exports); + +extern "C" { +void node_module_register(const char* name, napi_module_init init); +} + +namespace nativescript { + +extern std::unordered_map napiModuleRegistry; + +class Runtime { + public: + Runtime(); + ~Runtime(); + + void Init(bool isWorker = false); + + inline napi_env GetEnv() { return env_; } + static Runtime* GetRuntime(napi_env env); + + void RunScript(std::string& script, std::string file = ""); + napi_value RunModule(std::string spec); + void RunMainModule(); + + const int WorkerId(); + void SetWorkerId(int workerId); + inline bool IsRuntimeWorker() { return workerId_ > 0; } + + static bool IsWorker() { + if (currentRuntime_ == nullptr) { + return false; + } + + return currentRuntime_->IsRuntimeWorker(); + } + + inline CFRunLoopRef RuntimeLoop() { return runtimeLoop_; } + + void RunLoop(); + + static Runtime* GetCurrentRuntime() { return currentRuntime_; } + + static bool IsAlive(napi_env env); + + private: + int workerId_; + CFRunLoopRef runtimeLoop_; + CFRunLoopObserverRef microtaskObserver_; + double startTime_; + double realtimeOrigin_; + + jsr_ns_runtime runtime_; + napi_env env_ = nullptr; + napi_handle_scope globalScope_; + RuntimeModules modules_ = RuntimeModules(); + + static thread_local Runtime* currentRuntime_; + static SpinMutex envsMutex_; + static std::atomic nextIsolateId; + + // std::shared_ptr>> + // workerCache_; +}; + +} // namespace nativescript + +#endif // RUNTIME_H diff --git a/NativeScript/runtime/RuntimeConfig.cpp b/NativeScript/runtime/apple/RuntimeConfig.cpp similarity index 100% rename from NativeScript/runtime/RuntimeConfig.cpp rename to NativeScript/runtime/apple/RuntimeConfig.cpp diff --git a/NativeScript/runtime/RuntimeConfig.h b/NativeScript/runtime/apple/RuntimeConfig.h similarity index 100% rename from NativeScript/runtime/RuntimeConfig.h rename to NativeScript/runtime/apple/RuntimeConfig.h diff --git a/NativeScript/runtime/SpinLock.h b/NativeScript/runtime/apple/SpinLock.h similarity index 100% rename from NativeScript/runtime/SpinLock.h rename to NativeScript/runtime/apple/SpinLock.h diff --git a/NativeScript/runtime/ThreadSafeFunction.mm b/NativeScript/runtime/apple/ThreadSafeFunction.mm similarity index 95% rename from NativeScript/runtime/ThreadSafeFunction.mm rename to NativeScript/runtime/apple/ThreadSafeFunction.mm index df2587599..eaba44454 100644 --- a/NativeScript/runtime/ThreadSafeFunction.mm +++ b/NativeScript/runtime/apple/ThreadSafeFunction.mm @@ -15,13 +15,14 @@ #include "js_native_api.h" #include "js_native_tsfn.h" #include "jsr.h" -#include "runtime/Runtime.h" +#include "runtime/apple/Runtime.h" #ifdef TARGET_ENGINE_V8 #include "v8-api.h" #endif typedef struct napi_async_context__* napi_async_context; + typedef struct napi_callback_scope__* napi_callback_scope; typedef napi_env node_api_basic_env; @@ -98,10 +99,19 @@ typedef void(NAPI_CDECL* napi_async_cleanup_hook)( bool draining_async_hooks = false; }; -static std::mutex g_cleanup_hooks_mutex; -static std::condition_variable g_cleanup_hooks_cv; -static std::unordered_map - g_cleanup_hooks; +// Leaked and never destroyed, for the reason spelled out above +// gRuntimePromiseRunLoopMutex() in Runtime.cpp: these are reached from +// ~Runtime -> js_free_napi_env -> the env cleanup hooks, and ~Runtime runs +// from the destructor of the global `runtime_` unique_ptr in NativeScript.mm, +// i.e. during static destruction at exit(). Destruction order across +// translation units is unspecified and this one lost: locking the +// already-destroyed mutex threw std::system_error("mutex lock failed: Invalid +// argument") out of a noexcept destructor and terminated the process on every +// run, after the test suite had already reported its results. +static std::mutex& g_cleanup_hooks_mutex = *new std::mutex(); +static std::condition_variable& g_cleanup_hooks_cv = *new std::condition_variable(); +static std::unordered_map& g_cleanup_hooks = + *new std::unordered_map(); static bool IsCleanupStateEmpty(const EnvCleanupState& state) { return state.env_hooks.empty() && state.async_hooks.empty() && diff --git a/NativeScript/runtime/apple/Util.h b/NativeScript/runtime/apple/Util.h new file mode 100644 index 000000000..c5b7057ec --- /dev/null +++ b/NativeScript/runtime/apple/Util.h @@ -0,0 +1,150 @@ + +#include +#include +#include "runtime/apple/NativeScriptException.h" +#include "jsr_common.h" +#include "native_api_util.h" +#include "robin_hood.h" + +namespace nativescript { + +static robin_hood::unordered_map envToPersistentSmartJSONStringify = + robin_hood::unordered_map(); + +inline napi_value GetSmartJSONStringifyFunction(napi_env env) { + auto it = envToPersistentSmartJSONStringify.find(env); + if (it != envToPersistentSmartJSONStringify.end()) { + napi_value smartStringifyFunction; + napi_get_reference_value(env, it->second, &smartStringifyFunction); + return smartStringifyFunction; + } + + const char* smartStringifyFunctionScript = R"( + (function () { + function smartStringify(object, handleCirculars) { + if (!handleCirculars) { + return JSON.stringify(object, null, 2); + } + + const seen = []; + var replacer = function (key, value) { + if (value != null && typeof value == "object") { + if (seen.indexOf(value) >= 0) { + if (key) { + return "[Circular]"; + } + return; + } + seen.push(value); + } + return value; + }; + return JSON.stringify(object, replacer, 2); + } + return smartStringify; +})(); +)"; + + napi_value source; + napi_create_string_utf8(env, smartStringifyFunctionScript, strlen(smartStringifyFunctionScript), + &source); + + napi_value global; + napi_get_global(env, &global); + + napi_value result; + napi_status status = js_execute_script(env, source, "", &result); + if (status != napi_ok) { + return nullptr; + } + + if (!napi_util::is_of_type(env, result, napi_function)) { + return nullptr; + } + + napi_ref smartStringifyPersistentFunction; + napi_create_reference(env, result, 1, &smartStringifyPersistentFunction); + + envToPersistentSmartJSONStringify.emplace(env, smartStringifyPersistentFunction); + + return result; +} + +inline std::string JsonStringifyObject(napi_env env, napi_value value, + bool handleCircularReferences) { + if (value == nullptr) { + return ""; + } + + napi_value smartJSONStringifyFunction = GetSmartJSONStringifyFunction(env); + std::string result; + if (smartJSONStringifyFunction != nullptr) { + napi_value resultValue; + napi_value args[2]; + args[0] = value; + args[1] = handleCircularReferences ? napi_util::get_true(env) : napi_util::get_false(env); + napi_status status = napi_call_function(env, napi_util::global(env), smartJSONStringifyFunction, + 2, args, &resultValue); + if (status != napi_ok) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + if (!napi_util::is_null_or_undefined(env, exception)) { + throw NativeScriptException(env, exception, "Error converting object to json"); + } else { + throw NativeScriptException("Error converting object to json"); + } + } + result = napi_util::get_cxx_string(env, resultValue); + } + + return result; +} + +inline napi_value JsonParse(napi_env env, const std::string& json) { + napi_value result = nullptr; + napi_value parseFunction; + napi_value global; + napi_get_global(env, &global); + napi_get_named_property(env, global, "JSON", &parseFunction); + napi_get_named_property(env, parseFunction, "parse", &parseFunction); + napi_value args[1]; + napi_create_string_utf8(env, json.c_str(), json.size(), &args[0]); + napi_status status = + napi_call_function(env, global, parseFunction, 1, args, &result); + if (status != napi_ok) { + return nullptr; + } + return result; +} + +struct LockAndCV { + std::mutex m; + std::condition_variable cv; +}; + +inline void ExecuteOnRunLoop(CFRunLoopRef queue, std::function func, bool async) { + if (!async) { + bool __block finished = false; + auto state = std::make_shared(); + std::unique_lock lock(state->m); + CFRunLoopPerformBlock(queue, kCFRunLoopCommonModes, ^(void) { + func(); + { + std::lock_guard lk(state->m); + finished = true; + } + state->cv.notify_all(); + }); + CFRunLoopWakeUp(queue); + while (!finished) { + state->cv.wait(lock); + } + } else { + CFRunLoopPerformBlock(queue, kCFRunLoopCommonModes, ^(void) { + func(); + }); + CFRunLoopWakeUp(queue); + } +} + +} // namespace nativescript diff --git a/NativeScript/runtime/modules/RuntimeModules.h b/NativeScript/runtime/apple/modules/RuntimeModules.h similarity index 79% rename from NativeScript/runtime/modules/RuntimeModules.h rename to NativeScript/runtime/apple/modules/RuntimeModules.h index df292230d..6c7809467 100644 --- a/NativeScript/runtime/modules/RuntimeModules.h +++ b/NativeScript/runtime/apple/modules/RuntimeModules.h @@ -5,11 +5,11 @@ #include "js_native_api_types.h" #include "node/Node.h" #include "performance/Performance.h" -#include "runtime/RuntimeConfig.h" -#include "runtime/modules/module/ModuleInternal.h" -#include "runtime/modules/worker/Worker.h" -#include "url/URL.h" -#include "url/URLSearchParams.h" +#include "runtime/apple/RuntimeConfig.h" +#include "runtime/apple/modules/module/ModuleInternal.h" +#include "runtime/apple/modules/worker/Worker.h" +#include "runtime/modules/url/URL.h" +#include "runtime/modules/url/URLSearchParams.h" #include "web/Web.h" #ifdef __APPLE__ #include "app/App.h" diff --git a/NativeScript/runtime/modules/app/App.h b/NativeScript/runtime/apple/modules/app/App.h similarity index 100% rename from NativeScript/runtime/modules/app/App.h rename to NativeScript/runtime/apple/modules/app/App.h diff --git a/NativeScript/runtime/modules/app/App.mm b/NativeScript/runtime/apple/modules/app/App.mm similarity index 100% rename from NativeScript/runtime/modules/app/App.mm rename to NativeScript/runtime/apple/modules/app/App.mm diff --git a/NativeScript/runtime/apple/modules/console/Console.cpp b/NativeScript/runtime/apple/modules/console/Console.cpp new file mode 100644 index 000000000..4bd1b910b --- /dev/null +++ b/NativeScript/runtime/apple/modules/console/Console.cpp @@ -0,0 +1,484 @@ +#include "Console.h" + +#include +#include +#include + +#include "runtime/apple/NativeScriptException.h" +#include "js_native_api.h" +#include "native_api_util.h" +#include "runtime/apple/RuntimeConfig.h" +#include "runtime/apple/Util.h" +#ifdef TARGET_ENGINE_V8 +#include "v8-api.h" +#endif + +#ifdef __APPLE__ + +#include +extern "C" void NSLog(CFStringRef format, ...); + +#if defined(__has_include) +#if __has_include() +#include +#define TNS_HAVE_OS_LOG 1 +#else +#define TNS_HAVE_OS_LOG 0 +#endif +#else +#define TNS_HAVE_OS_LOG 0 +#endif + +#else +#include +#endif + +namespace nativescript { + +namespace { + +bool readStringValue(napi_env env, napi_value value, std::string& result) { + if (value == nullptr) { + return false; + } + + size_t length = 0; + if (napi_get_value_string_utf8(env, value, nullptr, 0, &length) != napi_ok) { + return false; + } + + std::vector buffer(length + 1); + size_t copied = 0; + if (napi_get_value_string_utf8(env, value, buffer.data(), buffer.size(), + &copied) != napi_ok) { + return false; + } + + result.assign(buffer.data(), copied); + return true; +} + +bool throwPendingException(napi_env env, const std::string& message) { + bool isPending = false; + if (napi_is_exception_pending(env, &isPending) != napi_ok || !isPending) { + return false; + } + + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + + if (exception != nullptr && + napi_util::is_of_type(env, exception, napi_object)) { + throw NativeScriptException(env, exception, message); + } + + throw NativeScriptException(env, message); +} + +bool coerceToString(napi_env env, napi_value value, std::string& result) { + napi_value stringValue = nullptr; + if (napi_coerce_to_string(env, value, &stringValue) != napi_ok || + stringValue == nullptr) { + throwPendingException(env, "Error converting console argument to string"); + return false; + } + + if (!readStringValue(env, stringValue, result)) { + throwPendingException(env, "Error reading console argument string"); + return false; + } + + return true; +} + +} // namespace + +JS_CLASS_INIT(Console::Init) { + napi_value Console, console; + + const napi_property_descriptor properties[] = { + napi_util::desc("log", Log, (void*)kConsoleLogTypeLog), + napi_util::desc("error", Log, (void*)kConsoleLogTypeError), + napi_util::desc("warn", Log, (void*)kConsoleLogTypeWarn), + napi_util::desc("info", Log, (void*)kConsoleLogTypeInfo), + napi_util::desc("assert", Log, (void*)kConsoleLogTypeAssert), + napi_util::desc("time", Time, nullptr), + napi_util::desc("timeEnd", TimeEnd, nullptr), + napi_util::desc("dir", Dir, nullptr), + napi_util::desc("trace", Trace, nullptr), + }; + + napi_define_class(env, "Console", NAPI_AUTO_LENGTH, Console::Constructor, + nullptr, 9, properties, &Console); + + napi_new_instance(env, Console, 0, nullptr, &console); + + napi_set_named_property(env, global, "Console", Console); + napi_set_named_property(env, global, "console", console); +} + +JS_METHOD(Console::Constructor) { + napi_value thisArg; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); + return thisArg; +} + +std::string transformJSObject(napi_env env, napi_value object) { + napi_value toStringFunc = nullptr; + bool hasToString = false; + + // Check if the object has a toString method + if (napi_has_named_property(env, object, "toString", &hasToString) != + napi_ok) { + throwPendingException(env, "Error reading console object toString"); + return "[object Object]"; + } + + if (hasToString) { + if (napi_get_named_property(env, object, "toString", &toStringFunc) != + napi_ok) { + throwPendingException(env, "Error reading console object toString"); + return "[object Object]"; + } + + if (toStringFunc != nullptr && + napi_util::is_of_type(env, toStringFunc, napi_function)) { + napi_value result = nullptr; + if (napi_call_function(env, object, toStringFunc, 0, nullptr, &result) != + napi_ok || + result == nullptr) { + throwPendingException(env, "Error converting console object to string"); + return "[object Object]"; + } + + std::string value; + if (!coerceToString(env, result, value)) { + return "[object Object]"; + } + + auto hasCustomToStringImplementation = + value.find("[object Object]") == std::string::npos; + if (hasCustomToStringImplementation) return value; + } + } + // If no custom toString method, stringify the object + return JsonStringifyObject(env, object, false); +} + +std::string buildStringFromArg(napi_env env, napi_value val, + napi_value inspectSymbol) { + napi_valuetype type; + if (napi_typeof(env, val, &type) != napi_ok) { + throwPendingException(env, "Error reading console argument type"); + return ""; + } + + if (type == napi_function) { + std::string funcString; + if (coerceToString(env, val, funcString)) { + return funcString; + } + return ""; + } else if (napi_util::is_array(env, val)) { + napi_value cachedSelf = val; + + // Get array length + uint32_t arrayLength = 0; + if (napi_get_array_length(env, val, &arrayLength) != napi_ok) { + throwPendingException(env, "Error reading console array length"); + return "[]"; + } + + std::stringstream arrayStr; + arrayStr << "["; + + for (uint32_t i = 0; i < arrayLength; i++) { + napi_value propertyValue = nullptr; + if (napi_get_element(env, val, i, &propertyValue) != napi_ok || + propertyValue == nullptr) { + throwPendingException(env, "Error reading console array element"); + arrayStr << ""; + if (i != arrayLength - 1) { + arrayStr << ", "; + } + continue; + } + + // Check for circular reference + bool isStrictEqual = false; + if (napi_strict_equals(env, propertyValue, cachedSelf, &isStrictEqual) != + napi_ok) { + throwPendingException(env, "Error comparing console array element"); + } + + if (isStrictEqual) { + arrayStr << "[Circular]"; + } else { + std::string elementString = + buildStringFromArg(env, propertyValue, inspectSymbol); + arrayStr << elementString; + } + + if (i != arrayLength - 1) { + arrayStr << ", "; + } + } + + arrayStr << "]"; + return arrayStr.str(); + } else if (type == napi_object) { + napi_value inspectFunc = nullptr; + napi_status getInspectStatus = + napi_get_property(env, val, inspectSymbol, &inspectFunc); + if (getInspectStatus == napi_ok && + inspectFunc != nullptr && + napi_util::is_of_type(env, inspectFunc, napi_function)) { + napi_value inspectedValue = nullptr; + if (napi_call_function(env, val, inspectFunc, 0, nullptr, + &inspectedValue) != napi_ok || + inspectedValue == nullptr) { + throwPendingException(env, "Error inspecting console object"); + return "[object Object]"; + } + return buildStringFromArg(env, inspectedValue, inspectSymbol); + } else if (getInspectStatus != napi_ok) { + throwPendingException(env, "Error reading console inspect function"); + } + return transformJSObject(env, val); + } else if (type == napi_symbol) { + std::string symString; + if (coerceToString(env, val, symString)) { + return symString; + } + return "Symbol()"; + } else { + std::string defaultToString; + if (coerceToString(env, val, defaultToString)) { + return defaultToString; + } + return ""; + } +} + +std::string buildLogString(napi_env env, napi_callback_info info, + int startingIndex = 0) { + NAPI_CALLBACK_BEGIN_VARGS() + + napi_value global, Symbol, SymbolFor, symbolDescription, inspectSymbol; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &Symbol); + napi_get_named_property(env, Symbol, "for", &SymbolFor); + napi_create_string_utf8(env, "nodejs.util.inspect.custom", NAPI_AUTO_LENGTH, + &symbolDescription); + napi_call_function(env, global, SymbolFor, 1, &symbolDescription, + &inspectSymbol); + + std::stringstream ss; + + if (argc) { + for (size_t i = startingIndex; i < argc; i++) { + // separate args with a space + if (i != 0) { + ss << " "; + } + + std::string argString = buildStringFromArg(env, argv[i], inspectSymbol); + ss << argString; + } + } else { + ss << std::endl; + } + + return ss.str(); +} + +JS_METHOD(Console::Log) { + try { + if (!RuntimeConfig.LogToSystemConsole) { + return UNDEFINED; + } + + size_t argc = 0; + ConsoleLogType stream; + void* data = nullptr; + + napi_get_cb_info(env, cbinfo, &argc, nullptr, nullptr, &data); + + stream = ConsoleLogType((unsigned long)data); + + size_t initialArg = 0; + + if (stream == kConsoleLogTypeAssert) { + bool passes = false; + + if (argc > 0) { + napi_value firstArg = nullptr; + napi_get_cb_info(env, cbinfo, &argc, &firstArg, nullptr, nullptr); + napi_coerce_to_bool(env, firstArg, &firstArg); + napi_get_value_bool(env, firstArg, &passes); + } + + if (!passes) { + initialArg = 1; + } else { + return UNDEFINED; + } + } + + napi_value argv[argc]; + napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr); + + std::stringstream log; + + // TODO(dj): what if we made this pretty? + + log << "CONSOLE"; + switch (stream) { + case kConsoleLogTypeLog: + log << " LOG"; + break; + case kConsoleLogTypeError: + log << " ERROR"; + break; + case kConsoleLogTypeWarn: + log << " WARN"; + break; + case kConsoleLogTypeInfo: + log << " INFO"; + break; + case kConsoleLogTypeAssert: + log << " ASSERT FAILED"; + break; + } + log << ": "; + + log << buildLogString(env, cbinfo, initialArg); + + log << "\n"; + + std::string logString = log.str(); + +#ifdef __APPLE__ +#if TARGET_OS_OSX + NSLog(CFSTR("%s"), logString.c_str()); +#elif TNS_HAVE_OS_LOG + os_log(OS_LOG_DEFAULT, "%{public}s", logString.c_str()); +#else + NSLog(CFSTR("%s"), logString.c_str()); +#endif +#else + switch (stream) { + case kConsoleLogTypeLog: + case kConsoleLogTypeInfo: + std::cout << logString; + break; + case kConsoleLogTypeError: + case kConsoleLogTypeWarn: + case kConsoleLogTypeAssert: + std::cerr << logString; + break; + } +#endif + + if (RuntimeConfig.CustomLogCallback) { + RuntimeConfig.CustomLogCallback(logString.c_str()); + } + +#ifdef TARGET_ENGINE_V8 + v8_inspector::ConsoleAPIType method; + switch (stream) { + case kConsoleLogTypeLog: + method = v8_inspector::ConsoleAPIType::kLog; + break; + case kConsoleLogTypeError: + method = v8_inspector::ConsoleAPIType::kError; + break; + case kConsoleLogTypeWarn: + method = v8_inspector::ConsoleAPIType::kWarning; + break; + case kConsoleLogTypeInfo: + method = v8_inspector::ConsoleAPIType::kInfo; + break; + case kConsoleLogTypeAssert: + method = v8_inspector::ConsoleAPIType::kAssert; + break; + default: + break; + } + + sendToDevToolsFrontEnd(env, method, logString); +#endif + } catch (NativeScriptException& e) { + e.ReThrowToJS(env); + } + + return UNDEFINED; +} + +JS_METHOD(Console::Time) { + napi_value thisArg; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); + return thisArg; +} + +JS_METHOD(Console::TimeEnd) { + napi_value thisArg; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); + return thisArg; +} + +JS_METHOD(Console::Dir) { + napi_value thisArg; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); + return thisArg; +} + +JS_METHOD(Console::Trace) { + napi_value thisArg; + napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); + return thisArg; +} + +#ifdef TARGET_ENGINE_V8 +int Console::sendToDevToolsFrontEnd(napi_env env, + v8_inspector::ConsoleAPIType method, + napi_callback_info info) { + if (!m_callback) { + return 0; + } + NAPI_CALLBACK_BEGIN_VARGS() + + v8::Local* v8_args = reinterpret_cast*>( + const_cast(argv.data())); + + std::vector> arg_vector; + unsigned nargs = argc; + arg_vector.reserve(nargs); + for (unsigned ix = 0; ix < nargs; ix++) arg_vector.push_back(v8_args[ix]); + + m_callback(env, method, arg_vector); + return 0; +} + +void Console::sendToDevToolsFrontEnd(napi_env env, + v8_inspector::ConsoleAPIType method, + const std::string& message) { + if (!m_callback) { + return; + } + + v8::Local v8_message = + v8::String::NewFromUtf8(env->isolate, message.c_str(), + v8::NewStringType::kNormal, message.length()) + .ToLocalChecked(); + + std::vector> args{ + v8_message, + }; + m_callback(env, method, args); +} +#endif + +ConsoleCallback Console::m_callback = nullptr; + +} // namespace nativescript diff --git a/NativeScript/runtime/modules/console/Console.h b/NativeScript/runtime/apple/modules/console/Console.h similarity index 100% rename from NativeScript/runtime/modules/console/Console.h rename to NativeScript/runtime/apple/modules/console/Console.h diff --git a/NativeScript/runtime/apple/modules/module/ModuleInternal.cpp b/NativeScript/runtime/apple/modules/module/ModuleInternal.cpp new file mode 100644 index 000000000..97179926b --- /dev/null +++ b/NativeScript/runtime/apple/modules/module/ModuleInternal.cpp @@ -0,0 +1,2119 @@ +#include "ModuleInternal.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "runtime/apple/NativeScriptException.h" +#include "native_api_util.h" +#include "runtime/apple/RuntimeConfig.h" +#include "runtime/apple/Util.h" +#include "runtime/apple/modules/node/Node.h" +#include "runtime/apple/modules/web/Web.h" + +#ifdef TARGET_ENGINE_V8 +// vendor/v8 is on the include path for V8 builds (see NativeScript/CMakeLists). +#include "v8-module-loader.h" +#elif defined(TARGET_ENGINE_QUICKJS) +#include "quickjs.h" +#include "quicks-runtime.h" +#endif + +typedef napi_value (*napi_module_init)(napi_env env, napi_value exports); + +namespace nativescript { +extern std::unordered_map napiModuleRegistry; +} + +using namespace nativescript; +using namespace std; + +namespace { + +// Cache for package.json \"type\" field lookups. +// +// Deliberately leaked rather than held in a namespace-scope object. The only +// caller of DeInit() is ~Runtime, which runs from the destructor of the global +// `runtime_` unique_ptr in NativeScript.mm -- i.e. during static destruction at +// exit(). Destruction order between two translation units in the same image is +// unspecified, and here this map was being destroyed first: DeInit() then +// called clear() on a dead unordered_map and freed its already-freed nodes, +// aborting every run with +// "___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED" after +// the suite had finished. A function-local pointer that is never deleted has no +// destruction order to get wrong. +std::unordered_map& modulePackageTypeCache() { + static auto* cache = new std::unordered_map(); + return *cache; +} + +// Strip shebang line from source code (e.g., #!/usr/bin/env node) +std::string StripShebang(const std::string& source) { + if (source.size() >= 2 && source[0] == '#' && source[1] == '!') { + size_t lineEnd = source.find('\n'); + if (lineEnd != std::string::npos) { + return source.substr(lineEnd + 1); + } + return ""; // Entire file is just a shebang + } + return source; +} + +#if defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_JSC) +std::string RewriteCommonJSDynamicImportsForFallbackEngines( + const std::string& source) { + static const std::regex kDynamicImportPattern( + R"((^|[^A-Za-z0-9_$\.])import\s*\()", + std::regex::ECMAScript | std::regex::multiline); + return std::regex_replace(source, kDynamicImportPattern, + "$1__dynamicImport("); +} + +std::string TrimFallbackESMToken(const std::string& value) { + const auto begin = value.find_first_not_of(" \t\r\n"); + if (begin == std::string::npos) { + return ""; + } + const auto end = value.find_last_not_of(" \t\r\n"); + return value.substr(begin, end - begin + 1); +} + +std::vector SplitFallbackESMList(const std::string& value) { + std::vector parts; + std::stringstream stream(value); + std::string part; + while (std::getline(stream, part, ',')) { + part = TrimFallbackESMToken(part); + if (!part.empty()) { + parts.push_back(part); + } + } + return parts; +} + +std::string EscapeFallbackESMSpecifier(const std::string& specifier) { + std::string escaped; + escaped.reserve(specifier.size()); + for (char c : specifier) { + switch (c) { + case '\\': + escaped += "\\\\"; + break; + case '\'': + escaped += "\\'"; + break; + case '\n': + escaped += "\\n"; + break; + case '\r': + escaped += "\\r"; + break; + default: + escaped += c; + break; + } + } + return escaped; +} + +std::string FallbackESMRequireExpression(const std::string& specifier) { + return "require('" + EscapeFallbackESMSpecifier(specifier) + "')"; +} + +std::string RewriteFallbackESMImportBindings(const std::string& bindings) { + std::string result; + for (const auto& part : SplitFallbackESMList(bindings)) { + static const std::regex kAliasPattern( + R"(^([A-Za-z_$][A-Za-z0-9_$]*)\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)$)"); + std::smatch alias; + if (std::regex_match(part, alias, kAliasPattern)) { + result += (result.empty() ? "" : ", "); + result += alias[1].str() + ": " + alias[2].str(); + } else { + result += (result.empty() ? "" : ", "); + result += part; + } + } + return result; +} + +std::string FallbackESMExportAssignments(const std::string& exports, + const std::string& sourceObject = "") { + std::string result; + for (const auto& part : SplitFallbackESMList(exports)) { + static const std::regex kAliasPattern( + R"(^([A-Za-z_$][A-Za-z0-9_$]*)\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)$)"); + std::smatch alias; + std::string local = part; + std::string exported = part; + if (std::regex_match(part, alias, kAliasPattern)) { + local = alias[1].str(); + exported = alias[2].str(); + } + + if (!result.empty()) { + result += "\n"; + } + result += "exports." + exported + " = "; + result += sourceObject.empty() ? local : sourceObject + "." + local; + result += ";"; + } + return result; +} + +template +std::string RegexReplaceWithFallbackESMCallback(const std::string& source, + const std::regex& pattern, + Callback callback) { + std::string result; + std::sregex_iterator it(source.begin(), source.end(), pattern); + std::sregex_iterator end; + size_t last = 0; + for (; it != end; ++it) { + const std::smatch& match = *it; + result.append(source, last, match.position() - last); + result += callback(match); + last = match.position() + match.length(); + } + result.append(source, last, std::string::npos); + return result; +} + +std::string TransformESModuleForFallbackEngines(const std::string& source) { + std::string result = source; + int tempIndex = 0; + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*import[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]*,[ \t]*\*[ \t]+as[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [&](const std::smatch& match) { + std::string module = "__esm_import_" + std::to_string(tempIndex++); + return "const " + module + " = " + + FallbackESMRequireExpression(match[3].str()) + ";\nconst " + + match[1].str() + " = " + module + ".default;\nconst " + + match[2].str() + " = " + module + ";"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*import[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]*,[ \t]*\{([^}]*)\}[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [&](const std::smatch& match) { + std::string module = "__esm_import_" + std::to_string(tempIndex++); + return "const " + module + " = " + + FallbackESMRequireExpression(match[3].str()) + ";\nconst " + + match[1].str() + " = " + module + ".default;\nconst {" + + RewriteFallbackESMImportBindings(match[2].str()) + "} = " + + module + ";"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*import[ \t]+\{([^}]*)\}[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "const {" + RewriteFallbackESMImportBindings(match[1].str()) + + "} = " + FallbackESMRequireExpression(match[2].str()) + ";"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*import[ \t]+\*[ \t]+as[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "const " + match[1].str() + " = " + + FallbackESMRequireExpression(match[2].str()) + ";"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*import[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "const " + match[1].str() + " = " + + FallbackESMRequireExpression(match[2].str()) + ".default;"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex(R"(^[ \t]*import[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return FallbackESMRequireExpression(match[1].str()) + ";"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*export[ \t]+\*[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "Object.assign(exports, " + + FallbackESMRequireExpression(match[1].str()) + ");"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*export[ \t]+\{([^}]*)\}[ \t]+from[ \t]+['"]([^'"]+)['"][ \t]*;?)", + std::regex::ECMAScript | std::regex::multiline), + [&](const std::smatch& match) { + std::string module = "__esm_export_" + std::to_string(tempIndex++); + return "const " + module + " = " + + FallbackESMRequireExpression(match[2].str()) + ";\n" + + FallbackESMExportAssignments(match[1].str(), module); + }); + + result = std::regex_replace( + result, + std::regex(R"(^[ \t]*export[ \t]+default[ \t]+function[ \t]*)", + std::regex::ECMAScript | std::regex::multiline), + "exports.default = function "); + + result = std::regex_replace( + result, + std::regex(R"(^[ \t]*export[ \t]+default[ \t]+class[ \t]*)", + std::regex::ECMAScript | std::regex::multiline), + "exports.default = class "); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*export[ \t]+function[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]*\()", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "exports." + match[1].str() + " = function " + match[1].str() + + "("; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*export[ \t]+class[ \t]+([A-Za-z_$][A-Za-z0-9_$]*))", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "exports." + match[1].str() + " = class " + match[1].str(); + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex( + R"(^[ \t]*export[ \t]+(const|let|var)[ \t]+([A-Za-z_$][A-Za-z0-9_$]*)[ \t]*=[ \t]*([^;\r\n]*);?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return match[1].str() + " " + match[2].str() + " = " + + match[3].str() + ";\nexports." + match[2].str() + " = " + + match[2].str() + ";"; + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex(R"(^[ \t]*export[ \t]*\{([^}]*)\}[ \t]*;)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return FallbackESMExportAssignments(match[1].str()); + }); + + result = RegexReplaceWithFallbackESMCallback( + result, + std::regex(R"(^[ \t]*export[ \t]+default[ \t]+([^;\r\n]*);?)", + std::regex::ECMAScript | std::regex::multiline), + [](const std::smatch& match) { + return "exports.default = " + match[1].str() + ";"; + }); + + return RewriteCommonJSDynamicImportsForFallbackEngines(result); +} +#endif + +// Check if path has .cjs extension (explicitly CommonJS) +bool IsCJSModule(const std::string& path) { + return path.size() >= 4 && path.compare(path.size() - 4, 4, ".cjs") == 0; +} + +// Find nearest package.json by walking up from directory +std::string FindNearestPackageJson(const std::filesystem::path& startDir) { + std::filesystem::path current = startDir; + + while (!current.empty() && current != current.root_path()) { + std::filesystem::path packagePath = current / "package.json"; + std::error_code ec; + if (std::filesystem::exists(packagePath, ec) && !ec) { + return packagePath.string(); + } + current = current.parent_path(); + } + + return ""; +} + +// Check if package.json has "type": "module" +bool IsPackageTypeModule(const std::string& packageJsonPath) { + auto& cache = modulePackageTypeCache(); + auto cacheIt = cache.find(packageJsonPath); + if (cacheIt != cache.end()) { + return cacheIt->second; + } + + bool isModule = false; + + std::ifstream file(packageJsonPath); + if (file.is_open()) { + std::string content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + file.close(); + + // Simple JSON parsing for "type": "module" + size_t typePos = content.find("\"type\""); + if (typePos != std::string::npos) { + size_t colonPos = content.find(':', typePos + 6); + if (colonPos != std::string::npos) { + size_t valueStart = content.find('"', colonPos + 1); + if (valueStart != std::string::npos) { + size_t valueEnd = content.find('"', valueStart + 1); + if (valueEnd != std::string::npos) { + std::string typeValue = + content.substr(valueStart + 1, valueEnd - valueStart - 1); + isModule = (typeValue == "module"); + } + } + } + } + } + + cache[packageJsonPath] = isModule; + return isModule; +} + +// Determine if a .js file should be treated as ESM based on nearest +// package.json +bool ShouldTreatJsAsESModule(const std::string& path) { + std::filesystem::path filePath(path); + std::string packageJson = FindNearestPackageJson(filePath.parent_path()); + + if (!packageJson.empty()) { + return IsPackageTypeModule(packageJson); + } + + return false; // Default to CommonJS +} + +bool PathExistsWithExactCase(const std::filesystem::path& path) { + std::error_code ec; + if (!std::filesystem::exists(path, ec) || ec) { + return false; + } + + std::filesystem::path normalized = path.lexically_normal(); + std::filesystem::path current; + + for (const auto& component : normalized) { + if (component == normalized.root_name() || + component == normalized.root_directory()) { + current /= component; + continue; + } + + if (component == ".") { + continue; + } + + if (component == "..") { + current = current.parent_path(); + continue; + } + + std::filesystem::path parent = current.empty() ? "." : current; + bool matched = false; + std::error_code iterEc; + for (std::filesystem::directory_iterator it(parent, iterEc), end; + !iterEc && it != end; it.increment(iterEc)) { + if (it->path().filename() == component) { + matched = true; + break; + } + } + + if (iterEc) { + // On device the sandbox forbids enumerating everything above the app + // container (/private/var/...), so case verification is only possible + // for listable levels; exists() already vouched for the full path. + current /= component; + continue; + } + if (!matched) { + return false; + } + + current /= component; + } + + return true; +} + +bool IsRegularFileWithExactCase(const std::filesystem::path& path) { + std::error_code ec; + return PathExistsWithExactCase(path) && + std::filesystem::is_regular_file(path, ec) && !ec; +} + +bool IsDirectoryWithExactCase(const std::filesystem::path& path) { + std::error_code ec; + return PathExistsWithExactCase(path) && + std::filesystem::is_directory(path, ec) && !ec; +} + +napi_value LoadRegisteredNapiModule(napi_env env, + const std::string& moduleName) { + const auto loadByName = [&](const std::string& name) -> napi_value { + auto it = nativescript::napiModuleRegistry.find(name); + if (it == nativescript::napiModuleRegistry.end() || it->second == nullptr) { + return nullptr; + } + + napi_value moduleObj; + napi_create_object(env, &moduleObj); + + napi_value exports; + napi_create_object(env, &exports); + + napi_value moduleExports = it->second(env, exports); + + bool hasPendingException = false; + napi_is_exception_pending(env, &hasPendingException); + if (hasPendingException) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + throw NativeScriptException( + env, exception, "Error initializing native module '" + name + "'"); + } + + if (moduleExports == nullptr) { + moduleExports = exports; + } + + napi_set_named_property(env, moduleObj, "exports", moduleExports); + return moduleObj; + }; + + napi_value moduleObj = loadByName(moduleName); + if (moduleObj != nullptr) { + return moduleObj; + } + + if (moduleName.rfind("node:", 0) == 0) { + return loadByName(moduleName.substr(5)); + } + + return nullptr; +} + +std::string ModulePathToURL(const std::string& modulePath) { + if (modulePath.rfind("file://", 0) == 0) { + return modulePath; + } + + if (modulePath.rfind("nativescript:", 0) == 0 || + modulePath.rfind("node:", 0) == 0) { + return "nativescript:" + modulePath; + } + + if (!modulePath.empty() && modulePath[0] == '/') { + return "file://" + modulePath; + } + + return "nativescript:" + modulePath; +} + +bool IsNodeBuiltinSpecifier(const std::string& specifier) { + static const std::unordered_set kBuiltins = { + "url", "node:url", "fs", "node:fs", + "fs/promises", "node:fs/promises", "path", "node:path", + "vm", "node:vm", "web", "node:web", + "stream/web", "node:stream/web"}; + return kBuiltins.contains(specifier); +} + +std::string NormalizeNodeBuiltinSpecifier(const std::string& specifier) { + if (specifier.rfind("node:", 0) == 0) { + return specifier.substr(5); + } + return specifier; +} + +std::string EscapeForSingleQuotedJsString(const std::string& value) { + std::string escaped; + escaped.reserve(value.size()); + + for (char c : value) { + switch (c) { + case '\\': + escaped += "\\\\"; + break; + case '\'': + escaped += "\\'"; + break; + case '\n': + escaped += "\\n"; + break; + case '\r': + escaped += "\\r"; + break; + default: + escaped += c; + break; + } + } + + return escaped; +} + +std::string NormalizeRegisteredNapiModuleSpecifier( + const std::string& specifier) { + auto it = nativescript::napiModuleRegistry.find(specifier); + if (it != nativescript::napiModuleRegistry.end() && it->second != nullptr) { + return specifier; + } + + if (specifier.rfind("node:", 0) == 0) { + std::string withoutPrefix = specifier.substr(5); + it = nativescript::napiModuleRegistry.find(withoutPrefix); + if (it != nativescript::napiModuleRegistry.end() && it->second != nullptr) { + return withoutPrefix; + } + } + + return ""; +} + +std::string GetRegisteredNapiESModuleSource(const std::string& specifier) { + std::string normalized = NormalizeRegisteredNapiModuleSpecifier(specifier); + if (normalized.empty()) { + return ""; + } + + std::string escapedSpecifier = EscapeForSingleQuotedJsString(normalized); + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load native module '${name}'`); +}; +const __nativeModule = __load(')" + + escapedSpecifier + R"('); +export default __nativeModule; +)"; +} + +std::string GetBuiltinESModuleSource(const std::string& specifier) { + const auto builtinName = NormalizeNodeBuiltinSpecifier(specifier); + + if (builtinName == "url") { + return R"( +const __toURL = (input) => input instanceof URL ? input : new URL(String(input)); +export const URL = globalThis.URL; +export const URLSearchParams = globalThis.URLSearchParams; +export function pathToFileURL(path) { + const value = String(path); + return new URL(value.startsWith("/") ? `file://${value}` : `file:///${value}`); +} +export function fileURLToPath(value) { + const u = __toURL(value); + if (u.protocol !== "file:") { + throw new TypeError("The URL must be of scheme file:"); + } + return decodeURIComponent(u.pathname); +} +export default { URL, URLSearchParams, pathToFileURL, fileURLToPath }; +)"; + } + + if (builtinName == "fs") { + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load builtin module '${name}'`); +}; +const __fs = __load("node:fs"); +export const readFileSync = __fs.readFileSync; +export const writeFileSync = __fs.writeFileSync; +export const existsSync = __fs.existsSync; +export const mkdirSync = __fs.mkdirSync; +export const readdirSync = __fs.readdirSync; +export const statSync = __fs.statSync; +export const lstatSync = __fs.lstatSync; +export const unlinkSync = __fs.unlinkSync; +export const rmSync = __fs.rmSync; +export const readFile = __fs.readFile; +export const writeFile = __fs.writeFile; +export const constants = __fs.constants; +export const promises = __fs.promises; +export default __fs; +)"; + } + + if (builtinName == "fs/promises") { + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load builtin module '${name}'`); +}; +const __fsp = __load("node:fs").promises; +export const readFile = __fsp.readFile; +export const writeFile = __fsp.writeFile; +export const mkdir = __fsp.mkdir; +export const readdir = __fsp.readdir; +export const stat = __fsp.stat; +export const lstat = __fsp.lstat; +export const unlink = __fsp.unlink; +export const rm = __fsp.rm; +export default __fsp; +)"; + } + + if (builtinName == "path") { + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load builtin module '${name}'`); +}; +const __path = __load("node:path"); +export const basename = __path.basename; +export const dirname = __path.dirname; +export const extname = __path.extname; +export const isAbsolute = __path.isAbsolute; +export const join = __path.join; +export const normalize = __path.normalize; +export const parse = __path.parse; +export const format = __path.format; +export const relative = __path.relative; +export const resolve = __path.resolve; +export const toNamespacedPath = __path.toNamespacedPath; +export const sep = __path.sep; +export const delimiter = __path.delimiter; +export const posix = __path.posix; +export const win32 = __path.win32; +export default __path; +)"; + } + + if (builtinName == "vm") { + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load builtin module '${name}'`); +}; +const __vm = __load("node:vm"); +export const Script = __vm.Script; +export const Module = __vm.Module; +export const SourceTextModule = __vm.SourceTextModule; +export const SyntheticModule = __vm.SyntheticModule; +export const compileFunction = __vm.compileFunction; +export const constants = __vm.constants; +export const createContext = __vm.createContext; +export const isContext = __vm.isContext; +export const measureMemory = __vm.measureMemory; +export const runInContext = __vm.runInContext; +export const runInNewContext = __vm.runInNewContext; +export const runInThisContext = __vm.runInThisContext; +export default __vm; +)"; + } + + if (builtinName == "web") { + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load builtin module '${name}'`); +}; +const __web = __load("web"); +export const fetch = __web.fetch; +export const Headers = __web.Headers; +export const Request = __web.Request; +export const Response = __web.Response; +export const WebSocket = __web.WebSocket; +export const ReadableStream = __web.ReadableStream; +export const WritableStream = __web.WritableStream; +export const TransformStream = __web.TransformStream; +export default __web; +)"; + } + + if (builtinName == "stream/web") { + return R"( +const __load = (name) => { + if (typeof globalThis.require === "function") { + return globalThis.require(name); + } + if (typeof globalThis.__nativeRequire === "function") { + const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; + return globalThis.__nativeRequire(name, dir); + } + throw new Error(`Cannot load builtin module '${name}'`); +}; +const __streamWeb = __load("stream/web"); +export const ReadableStream = __streamWeb.ReadableStream; +export const ReadableStreamDefaultReader = __streamWeb.ReadableStreamDefaultReader; +export const WritableStream = __streamWeb.WritableStream; +export const TransformStream = __streamWeb.TransformStream; +export const ByteLengthQueuingStrategy = __streamWeb.ByteLengthQueuingStrategy; +export const CountQueuingStrategy = __streamWeb.CountQueuingStrategy; +export default __streamWeb; +)"; + } + + return ""; +} +} // namespace + +ModuleInternal::ModuleInternal() + : m_env(nullptr), + m_requireFunction(nullptr), + m_requireFactoryFunction(nullptr) {} + +void ModuleInternal::DeInit() { +#ifdef TARGET_ENGINE_V8 + for (auto& kv : v8impl::g_moduleRegistry) { + kv.second.Reset(); + } + v8impl::g_moduleRegistry.clear(); +#endif + + // Clear the package.json type cache + modulePackageTypeCache().clear(); + + if (m_env != nullptr) { + napi_delete_reference(m_env, this->m_requireFunction); + napi_delete_reference(m_env, this->m_requireFactoryFunction); + } + + for (const auto& pair : this->m_requireCache) { + if (m_env != nullptr) { + napi_delete_reference(m_env, pair.second); + } + } + this->m_requireCache.clear(); +} + +void ModuleInternal::Init(napi_env env, const std::string& baseDir) { + napi_status status; + + m_env = env; + +#ifdef TARGET_ENGINE_V8 + // Bootstrap V8 ES module hooks early so dynamic import() from CommonJS + // settles even before the first explicit .mjs module load. + napi_value bootstrapSource; + if (napi_create_string_utf8(env, "export default 0;", NAPI_AUTO_LENGTH, + &bootstrapSource) == napi_ok) { + const std::string bootstrapModulePath = + RuntimeConfig.ApplicationPath + "/app/.nativescript-esm-bootstrap.mjs"; + napi_value bootstrapNamespace; + napi_status bootstrapStatus = napi_run_script_as_module( + env, bootstrapSource, bootstrapModulePath.c_str(), &bootstrapNamespace); + if (bootstrapStatus != napi_ok) { + bool pendingException = false; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value ignored; + napi_get_and_clear_last_exception(env, &ignored); + } + } + } +#endif + + const char* requireFactoryScript = R"( + (function () { + return function require_factory(requireInternal, dirName) { + return function require(modulePath) { + if(typeof global.__requireOverride !== "undefined") { + var result = global.__requireOverride(modulePath, dirName); + if(result) { + return result; + } + } + return requireInternal(modulePath, dirName); + } + } +})(); +)"; + + napi_value source; + napi_create_string_utf8(env, requireFactoryScript, NAPI_AUTO_LENGTH, &source); + + napi_value global; + napi_get_global(env, &global); + + napi_value globalEnv; + napi_create_external(env, env, nullptr, nullptr, &globalEnv); + napi_set_named_property(env, global, "__globalEnv", globalEnv); + + napi_value result; + status = napi_run_script(env, source, &result); + assert(status == napi_ok); + + m_requireFactoryFunction = napi_util::make_ref(m_env, result); + + napi_value requireFunction = napi_util::napi_set_function( + env, global, "__nativeRequire", RequireCallback, this); + m_requireFunction = napi_util::make_ref(m_env, requireFunction); + + napi_value globalRequire = GetRequireFunction( + env, baseDir.empty() ? RuntimeConfig.ApplicationPath : baseDir); + status = napi_set_named_property(env, global, "require", globalRequire); + assert(status == napi_ok); + +#if defined(TARGET_ENGINE_QUICKJS) + InitQuickJSESModuleLoader(env); +#endif +} + +napi_value ModuleInternal::GetRequireFunction(napi_env env, + const std::string& dirName) { + napi_value requireFunc; + + auto itFound = m_requireCache.find(dirName); + + if (itFound != m_requireCache.end()) { + requireFunc = napi_util::get_ref_value(env, itFound->second); + } else { + napi_value requireFuncFactory = + napi_util::get_ref_value(env, m_requireFactoryFunction); + + napi_value requireInternalFunc = + napi_util::get_ref_value(env, m_requireFunction); + + napi_value args[2]; + args[0] = requireInternalFunc; + napi_create_string_utf8(env, dirName.c_str(), NAPI_AUTO_LENGTH, &args[1]); + + napi_value thiz; + napi_create_object(env, &thiz); + + napi_value result; + napi_status status = + napi_call_function(env, thiz, requireFuncFactory, 2, args, &result); + assert(status == napi_ok && result != nullptr); + + bool isFunction = napi_util::is_of_type(env, result, napi_function); + assert(isFunction); + + requireFunc = result; + + napi_ref poFunc = napi_util::make_ref(env, requireFunc); + m_requireCache.emplace(dirName, poFunc); + } + + return requireFunc; +} + +napi_value ModuleInternal::RequireCallback(napi_env env, + napi_callback_info info) { + NAPI_CALLBACK_BEGIN(0) + try { + auto thiz = static_cast(data); + return thiz->RequireCallbackImpl(env, info); + } catch (NativeScriptException& e) { + e.ReThrowToJS(env); + } catch (std::exception& e) { + stringstream ss; + ss << "Error: C++ Exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJS(env); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJS(env); + } + + return nullptr; +} + +napi_value ModuleInternal::Require(napi_env env, const std::string& moduleName, + const std::string& callingModuleDirName) { + auto isData = false; + + napi_value moduleObj = + LoadImpl(env, moduleName, callingModuleDirName, isData); + + if (isData) { + assert(!napi_util::is_null_or_undefined(env, moduleObj)); + return moduleObj; + } else { + // Check if this is an ES module by looking for __esModule property + bool hasEsModuleProp; + napi_status status = + napi_has_named_property(env, moduleObj, "__esModule", &hasEsModuleProp); + + bool isEsModule = false; + if (status == napi_ok && hasEsModuleProp) { + napi_value esModuleFlag; + napi_get_named_property(env, moduleObj, "__esModule", &esModuleFlag); + napi_get_value_bool(env, esModuleFlag, &isEsModule); + } + + if (isEsModule) { + // For ES modules, return the module namespace directly + return moduleObj; + } else { + // For CommonJS modules, return the exports + napi_value exports; + napi_get_named_property(env, moduleObj, "exports", &exports); + assert(!napi_util::is_null_or_undefined(env, exports)); + return exports; + } + } +} + +napi_value ModuleInternal::RequireCallbackImpl(napi_env env, + napi_callback_info info) { + NAPI_CALLBACK_BEGIN_VARGS() + + if (argc != 2) { + throw NativeScriptException( + string("require should be called with two parameters")); + } + if (!napi_util::is_of_type(env, argv[0], napi_string)) { + throw NativeScriptException( + string("require's first parameter should be string")); + } + if (!napi_util::is_of_type(env, argv[1], napi_string)) { + throw NativeScriptException( + string("require's second parameter should be string")); + } + + string moduleName = napi_util::get_cxx_string(env, argv[0]); + string callingModuleDirName = napi_util::get_cxx_string(env, argv[1]); + + try { + return Require(env, moduleName, callingModuleDirName); + } catch (NativeScriptException& e) { + e.ReThrowToJS(env); + return nullptr; + } +} + +napi_value ModuleInternal::RequireNativeCallback(napi_env env, + napi_callback_info info) { + void* data; + napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); + auto cb = reinterpret_cast(data); + napi_value exports; + napi_create_object(env, &exports); + return cb(env, exports); +} + +napi_status ModuleInternal::Load(napi_env env, const std::string& path) { + napi_value global; + napi_get_global(env, &global); + + napi_value require; + napi_get_named_property(env, global, "require", &require); + + napi_value args[1]; + napi_create_string_utf8(env, path.c_str(), path.size(), &args[0]); + + napi_value result; + napi_status status = + napi_call_function(env, global, require, 1, args, &result); + return status; +} + +void ModuleInternal::LoadWorker(napi_env env, const string& path) { + Load(env, path); + bool hasPendingException; + napi_is_exception_pending(env, &hasPendingException); + + if (hasPendingException) { + napi_value error; + napi_get_and_clear_last_exception(env, &error); + // TODO + // CallbackHandlers::CallWorkerScopeOnErrorHandle(env, error); + } +} + +void ModuleInternal::CheckFileExists(napi_env env, const std::string& path, + const std::string& baseDir) { + struct stat buffer; + if (stat(path.c_str(), &buffer) != 0) { + std::string errMsg = "Module not found: " + path; + throw NativeScriptException(errMsg); + } + if (baseDir != "") { + std::string fullPath = baseDir + "/" + path; + if (stat(fullPath.c_str(), &buffer) != 0) { + std::string errMsg = "Module not found: " + fullPath; + throw NativeScriptException(errMsg); + } + } +} + +napi_value ModuleInternal::LoadInternalModule(napi_env env, + const std::string& moduleName) { + auto nodeModule = Node::LoadInternalModule(env, moduleName); + if (nodeModule != nullptr) { + return nodeModule; + } + +#ifdef __APPLE__ + auto webModule = Web::LoadInternalModule(env, moduleName); + if (webModule != nullptr) { + return webModule; + } +#endif + + auto napiModule = LoadRegisteredNapiModule(env, moduleName); + if (napiModule != nullptr) { + return napiModule; + } + + if (moduleName == "url" || moduleName == "node:url") { + napi_value moduleObj; + napi_create_object(env, &moduleObj); + napi_value url; + napi_value exports; + napi_create_object(env, &exports); + napi_get_named_property(env, napi_util::global(env), "URL", &url); + napi_set_named_property(env, exports, "URL", url); + napi_set_named_property(env, moduleObj, "exports", exports); + napi_util::napi_set_function( + env, exports, "pathToFileURL", + [](napi_env env, napi_callback_info info) -> napi_value { + return napi_util::to_js_string(env, "file://"); + }); + return moduleObj; + } + return nullptr; +} + +std::string ModuleInternal::ResolvePathFromPackageJson( + napi_env env, const std::string& packageJsonPath, bool& error) { + error = false; + if (!IsRegularFileWithExactCase(packageJsonPath)) { + return ""; + } + + std::ifstream packageJsonFile(packageJsonPath); + if (!packageJsonFile.is_open()) { + // Missing package.json is not fatal for directory resolution. + error = false; + return ""; + } + std::string line; + std::stringstream packageJsonStream; + while (std::getline(packageJsonFile, line)) { + packageJsonStream << line; + } + packageJsonFile.close(); + std::string packageJson = packageJsonStream.str(); + napi_value obj = JsonParse(env, packageJson); + if (obj == nullptr) { + bool hasPendingException = false; + napi_is_exception_pending(env, &hasPendingException); + if (hasPendingException) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + } + error = true; + return ""; + } + bool hasMain = false; + napi_status hasMainStatus = + napi_has_named_property(env, obj, "main", &hasMain); + if (hasMainStatus != napi_ok || !hasMain) { + // package.json without "main" should fall back to + // index.js/index.mjs/index.cjs + error = false; + return ""; + } + napi_value mainValue; + napi_get_named_property(env, obj, "main", &mainValue); + if (mainValue == nullptr) { + error = false; + return ""; + } + + napi_valuetype type; + napi_typeof(env, mainValue, &type); + if (type != napi_string) { + error = false; + return ""; + } + + std::string main = napi_util::get_cxx_string(env, mainValue); + if (main.empty()) { + error = false; + return ""; + } + + std::filesystem::path packageJsonDir(packageJsonPath); + std::filesystem::path packageJsonDirName = + packageJsonDir.parent_path().string(); + std::filesystem::path mainPath = packageJsonDirName / main; + + if (IsDirectoryWithExactCase(mainPath)) { + mainPath = mainPath / "package.json"; + if (IsRegularFileWithExactCase(mainPath)) { + return ResolvePathFromPackageJson(env, mainPath.string(), error); + } + + std::filesystem::path indexMjs = mainPath.parent_path() / "index.mjs"; + if (IsRegularFileWithExactCase(indexMjs)) { + return indexMjs.string(); + } + + std::filesystem::path indexJs = mainPath.parent_path() / "index.js"; + if (IsRegularFileWithExactCase(indexJs)) { + return indexJs.string(); + } + + std::filesystem::path indexCjs = mainPath.parent_path() / "index.cjs"; + if (IsRegularFileWithExactCase(indexCjs)) { + return indexCjs.string(); + } + + error = false; + return ""; + } + + if (IsRegularFileWithExactCase(mainPath)) { + return mainPath.string(); + } + + // Support extensionless "main" entries (e.g. "bundle") by resolving to + // modern ESM/CJS bundle outputs. + if (!mainPath.has_extension()) { + std::filesystem::path mjsPath = mainPath; + mjsPath.replace_extension(".mjs"); + if (IsRegularFileWithExactCase(mjsPath)) { + return mjsPath.string(); + } + + std::filesystem::path jsPath = mainPath; + jsPath.replace_extension(".js"); + if (IsRegularFileWithExactCase(jsPath)) { + return jsPath.string(); + } + + std::filesystem::path cjsPath = mainPath; + cjsPath.replace_extension(".cjs"); + if (IsRegularFileWithExactCase(cjsPath)) { + return cjsPath.string(); + } + } + + // Unresolvable "main" should fall back to index.js/index.mjs/index.cjs. + error = false; + return ""; +} + +std::string ModuleInternal::ResolvePath(napi_env env, + const std::string& baseDir, + const std::string& moduleName) { + std::string moduleNameCopy = moduleName; + + if (moduleName.starts_with("~")) { + moduleNameCopy = RuntimeConfig.ApplicationPath + moduleNameCopy.substr(1); + } + + std::filesystem::path baseDirPath(baseDir); + std::filesystem::path moduleNamePath(moduleNameCopy); + std::filesystem::path fullPath = baseDirPath / moduleNamePath; + + // Normalize the path to remove redundant ./ sequences + fullPath = fullPath.lexically_normal(); + + bool exists = PathExistsWithExactCase(fullPath); + bool isDirectory = exists && IsDirectoryWithExactCase(fullPath); + bool isNonRelativeModule = + !moduleNameCopy.starts_with("./") && !moduleNameCopy.starts_with("../") && + !moduleNameCopy.starts_with("/") && !moduleNameCopy.starts_with("~"); + + if (exists == true && isDirectory == true) { + // Try .mjs first for ES modules + std::filesystem::path mjsFile = fullPath; + mjsFile = mjsFile.replace_extension(".mjs"); + if (IsRegularFileWithExactCase(mjsFile)) { + return mjsFile.string(); + } + + // Then try .js for CommonJS + std::filesystem::path jsFile = fullPath; + jsFile = jsFile.replace_extension(".js"); + if (IsRegularFileWithExactCase(jsFile)) { + return jsFile.string(); + } + + // Then try .cjs for explicit CommonJS + std::filesystem::path cjsFile = fullPath; + cjsFile = cjsFile.replace_extension(".cjs"); + if (IsRegularFileWithExactCase(cjsFile)) { + return cjsFile.string(); + } + } + + if (exists == false) { + std::filesystem::path appModulesPath; + bool hasAppModulesBase = false; + + if (isNonRelativeModule) { + appModulesPath = std::filesystem::path(RuntimeConfig.ApplicationPath) / + "tns_modules" / moduleNameCopy; + hasAppModulesBase = true; + if (PathExistsWithExactCase(appModulesPath)) { + fullPath = appModulesPath; + exists = true; + isDirectory = IsDirectoryWithExactCase(fullPath); + } else { + // Preserve legacy runtime behavior for extensionless non-relative + // requires by probing app/tns_modules before caller-relative paths. + std::filesystem::path appModulesMjs = appModulesPath.string() + ".mjs"; + if (IsRegularFileWithExactCase(appModulesMjs)) { + return appModulesMjs.string(); + } + + std::filesystem::path appModulesJs = appModulesPath.string() + ".js"; + if (IsRegularFileWithExactCase(appModulesJs)) { + return appModulesJs.string(); + } + + std::filesystem::path appModulesJson = + appModulesPath.string() + ".json"; + if (IsRegularFileWithExactCase(appModulesJson)) { + return appModulesJson.string(); + } + + std::filesystem::path appModulesCjs = appModulesPath.string() + ".cjs"; + if (IsRegularFileWithExactCase(appModulesCjs)) { + return appModulesCjs.string(); + } + } + } + + // Try .mjs extension first (ES modules have priority) + std::filesystem::path mjsBase = + hasAppModulesBase ? appModulesPath : fullPath; + std::filesystem::path mjsPath = mjsBase.string() + ".mjs"; + if (PathExistsWithExactCase(mjsPath)) { + exists = true; + isDirectory = IsDirectoryWithExactCase(mjsPath); + if (!isDirectory) { + return mjsPath.string(); + } + } + + // Try .js extension by appending, preserving dotted filenames + std::filesystem::path jsBase = + hasAppModulesBase ? appModulesPath : fullPath; + std::filesystem::path jsPath = jsBase.string() + ".js"; + if (PathExistsWithExactCase(jsPath)) { + exists = true; + isDirectory = IsDirectoryWithExactCase(jsPath); + fullPath = jsPath; + } + + // Try .cjs extension for explicit CommonJS + if (!exists) { + std::filesystem::path cjsBase = + hasAppModulesBase ? appModulesPath : fullPath; + std::filesystem::path cjsPath = cjsBase.string() + ".cjs"; + if (PathExistsWithExactCase(cjsPath)) { + exists = true; + isDirectory = IsDirectoryWithExactCase(cjsPath); + fullPath = cjsPath; + } + } + } + + if (exists == false) { + throw NativeScriptException("The specified module does not exist: " + + moduleName); + } + + if (isDirectory == false) { + return fullPath.string(); + } + + std::filesystem::path packageJson = fullPath / "package.json"; + if (std::filesystem::is_regular_file(packageJson)) { + bool error = false; + std::string entry = + this->ResolvePathFromPackageJson(env, packageJson.string(), error); + if (error) { + throw NativeScriptException("Unable to locate main entry in " + + packageJson.string()); + } + + if (!entry.empty()) { + fullPath = entry; + } + } + + exists = PathExistsWithExactCase(fullPath); + isDirectory = exists && IsDirectoryWithExactCase(fullPath); + if (exists == true && isDirectory == false) { + return fullPath.string(); + } + + if (exists == false) { + // Try index.mjs first + std::filesystem::path indexMjs = fullPath.parent_path() / "index.mjs"; + if (IsRegularFileWithExactCase(indexMjs)) { + return indexMjs.string(); + } + + // Then try path + ".js" (preserves dotted filenames like "file.name") + std::filesystem::path jsCandidate = fullPath.string() + ".js"; + if (IsRegularFileWithExactCase(jsCandidate)) { + return jsCandidate.string(); + } + + // Finally try path + ".cjs" for explicit CommonJS entry files. + fullPath = fullPath.string() + ".cjs"; + } else { + // Try index.mjs first in directory + std::filesystem::path indexMjs = fullPath / "index.mjs"; + if (IsRegularFileWithExactCase(indexMjs)) { + return indexMjs.string(); + } + + // Then try index.js + std::filesystem::path indexJs = fullPath / "index.js"; + if (IsRegularFileWithExactCase(indexJs)) { + fullPath = indexJs; + } else { + // Then try index.cjs + std::filesystem::path indexCjs = fullPath / "index.cjs"; + if (IsRegularFileWithExactCase(indexCjs)) { + return indexCjs.string(); + } + fullPath /= "index.js"; + } + } + + exists = PathExistsWithExactCase(fullPath); + if (exists == false) { + throw NativeScriptException("The specified module does not exist: " + + moduleName); + } + return fullPath.string(); +} + +napi_value ModuleInternal::LoadImpl(napi_env env, const std::string& moduleName, + const std::string& baseDir, bool& isData) { + auto pathKind = GetModulePathKind(moduleName); + auto cachePathKey = (pathKind == ModulePathKind::Global) + ? moduleName + : (baseDir + "*" + moduleName); + + napi_value result; + + // DEBUG_WRITE(">>LoadImpl cachePathKey=%s", cachePathKey.c_str()); + + auto it = m_loadedModules.find(cachePathKey); + + if (it == m_loadedModules.end()) { + /** + * Load internal modules like url, fs and statically registered + * N-API modules directly for global requires. + */ + napi_value moduleObj = ModuleInternal::LoadInternalModule(env, moduleName); + if (moduleObj != nullptr) { + auto poModuleObj = napi_util::make_ref(env, moduleObj); + m_loadedModules.emplace(cachePathKey, ModuleCacheEntry(poModuleObj)); + return moduleObj; + } + + std::string path; + + // Search App System libs + std::string sys_lib("system_lib://"); + if (moduleName.rfind(sys_lib, 0) == 0) { + auto pos = moduleName.find(sys_lib); + path = std::string(moduleName); + path.replace(pos, sys_lib.length(), ""); + } else if (moduleName.ends_with(".so") || moduleName.ends_with(".dylib") || + moduleName.ends_with(".node")) { + path = moduleName; + } else { + path = ResolvePath(env, baseDir, moduleName); + } + + auto it2 = m_loadedModules.find(path); + + if (it2 == m_loadedModules.end()) { + if (path.ends_with(".js") || path.ends_with(".mjs") || + path.ends_with(".cjs") || path.ends_with(".so") || + path.ends_with(".dylib") || path.ends_with(".node")) { + isData = false; + result = LoadModule(env, path, cachePathKey); + } else if (path.ends_with(".json")) { + isData = true; + result = LoadData(env, path); + } else { + std::filesystem::path filePath(path); + + if (std::filesystem::is_directory(filePath)) { + std::filesystem::path packageJson = filePath / "package.json"; + if (std::filesystem::is_regular_file(packageJson)) { + bool error = false; + std::string packageMain = + ResolvePathFromPackageJson(env, packageJson.string(), error); + if (error) { + throw NativeScriptException("Unable to locate main entry in " + + packageJson.string()); + } + if (!packageMain.empty() && packageMain != path) { + return LoadImpl(env, packageMain, baseDir, isData); + } + } + } + + std::filesystem::path fileWithIndexJs = filePath / "index.js"; + std::filesystem::path fileWithIndexMjs = filePath / "index.mjs"; + std::filesystem::path fileWithIndexCjs = filePath / "index.cjs"; + if (IsRegularFileWithExactCase(fileWithIndexMjs)) { + return LoadImpl(env, fileWithIndexMjs.string(), baseDir, isData); + } else if (IsRegularFileWithExactCase(fileWithIndexJs)) { + return LoadImpl(env, fileWithIndexJs.string(), baseDir, isData); + } else if (IsRegularFileWithExactCase(fileWithIndexCjs)) { + return LoadImpl(env, fileWithIndexCjs.string(), baseDir, isData); + } + std::string errMsg = "Unsupported file extension: " + path; + throw NativeScriptException(errMsg); + } + } else { + auto& cacheEntry = it2->second; + isData = cacheEntry.isData; + result = napi_util::get_ref_value(env, cacheEntry.obj); + } + } else { + auto& cacheEntry = it->second; + isData = cacheEntry.isData; + result = napi_util::get_ref_value(env, cacheEntry.obj); + } + + return result; +} + +std::string ModuleInternal::EnsureFileProtocol(const std::string& path) { + const std::string protocol = "file://"; + if (path.compare(0, protocol.length(), protocol) != 0) { + return protocol + path; + } + return path; +} + +napi_value ModuleInternal::LoadModule(napi_env env, + const std::string& modulePath, + const std::string& moduleCacheKey) { + napi_value result; + + napi_value context; + napi_get_global(env, &context); + + napi_value moduleObj; + napi_create_object(env, &moduleObj); + + napi_value exportsObj; + napi_create_object(env, &exportsObj); + + napi_set_named_property(env, moduleObj, "exports", exportsObj); + + napi_value fullRequiredModulePath; + napi_create_string_utf8(env, modulePath.c_str(), modulePath.size(), + &fullRequiredModulePath); + napi_set_named_property(env, moduleObj, "filename", fullRequiredModulePath); + + napi_ref poModuleObj = napi_util::make_ref(env, moduleObj); + TempModule tempModule(this, modulePath, moduleCacheKey, poModuleObj); + + napi_value moduleFunc; + + if (IsESModule(modulePath)) { + // Handle ES modules + napi_value esModuleResult = LoadESModule(env, modulePath); + + // Store the namespace as module.exports so the public require() result is + // the namespace while the cache shape stays compatible with CommonJS. + napi_set_named_property(env, moduleObj, "exports", esModuleResult); + tempModule.SaveToCache(); + return moduleObj; + } else if (modulePath.ends_with(".js") || modulePath.ends_with(".cjs")) { + napi_value script = LoadScript(env, modulePath, fullRequiredModulePath); + // DEBUG_WRITE("%s", modulePath.c_str()); + + // napi_status status = js_execute_script( + // env, script, EnsureFileProtocol(modulePath).c_str(), &moduleFunc); + napi_status status = + napi_run_script_source(env, script, modulePath.c_str(), &moduleFunc); + if (status != napi_ok) { + bool pendingException; + napi_is_exception_pending(env, &pendingException); + napi_value error = nullptr; + if (pendingException) { + napi_get_and_clear_last_exception(env, &error); + } + if (error) { + throw NativeScriptException(env, error, + "Error running script " + modulePath); + } else { + throw NativeScriptException("Error running script " + modulePath); + } + } + } else if (modulePath.ends_with(".so") || modulePath.ends_with(".dylib") || + modulePath.ends_with(".node")) { + auto handle = dlopen(modulePath.c_str(), RTLD_NOW); + if (handle == nullptr) { + auto error = dlerror(); + std::string errMsg(error); + throw NativeScriptException(errMsg); + } + auto func = dlsym(handle, "napi_register_module_v1"); + + if (func == nullptr) { + std::string errMsg("Cannot find 'napi_register_module_v1' in " + + modulePath); + throw NativeScriptException(errMsg); + } + + auto cb = reinterpret_cast(func); + napi_value exports; + napi_create_object(env, &exports); + napi_value result = cb(env, exports); + napi_set_named_property(env, moduleObj, "exports", result); + tempModule.SaveToCache(); + return moduleObj; + } else { + std::string errMsg = "Unsupported file extension: " + modulePath; + throw NativeScriptException(errMsg); + } + + napi_value fileName; + napi_create_string_utf8(env, modulePath.c_str(), modulePath.size(), + &fileName); + + char pathcopy[1024]; + strcpy(pathcopy, modulePath.c_str()); + std::string strDirName(dirname(pathcopy)); + + napi_value dirName; + napi_create_string_utf8(env, strDirName.c_str(), strDirName.size(), &dirName); + + napi_value require = GetRequireFunction(env, strDirName); + + napi_value requireArgs[5] = {moduleObj, exportsObj, require, fileName, + dirName}; + + napi_set_named_property(env, moduleObj, "require", require); + napi_util::define_property(env, moduleObj, "id", fileName); + + napi_value thiz; + napi_create_object(env, &thiz); + + napi_value globalExtends; + napi_get_named_property(env, context, "__extends", &globalExtends); + napi_set_named_property(env, thiz, "__extends", globalExtends); + + napi_value callResult; + napi_status status = + napi_call_function(env, thiz, moduleFunc, 5, requireArgs, &callResult); + bool pendingException; + napi_is_exception_pending(env, &pendingException); + if (status != napi_ok || pendingException) { + napi_value exception; + napi_get_and_clear_last_exception(env, &exception); + if (exception) { + throw NativeScriptException(env, exception, + "Error calling module function: "); + } else { + throw NativeScriptException("Error calling module function: " + + modulePath); + } + } + + tempModule.SaveToCache(); + result = moduleObj; + + return result; +} + +napi_value ModuleInternal::LoadScript(napi_env env, const std::string& path, + napi_value fullRequiredModulePath) { + napi_value scriptText = ModuleInternal::WrapModuleContent(env, path); + return scriptText; +} + +napi_value ModuleInternal::LoadData(napi_env env, const std::string& path) { + std::string jsonData; + std::ifstream jsonFile(path); + if (!jsonFile.is_open()) { + throw NativeScriptException("Unable to open JSON file: " + path); + } + + std::stringstream buffer; + buffer << jsonFile.rdbuf(); + jsonData = buffer.str(); + napi_value json = JsonParse(env, jsonData); + + if (!napi_util::is_of_type(env, json, napi_object)) { + bool pendingException; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value error; + napi_get_and_clear_last_exception(env, &error); + throw NativeScriptException(env, error, + "JSON is not valid, file=" + path); + } else { + throw NativeScriptException("JSON is not valid, file=" + path); + } + } + + napi_ref poObj = napi_util::make_ref(env, json); + m_loadedModules.emplace(path, ModuleCacheEntry(poObj, true /* isData */)); + return json; +} + +// ES Module support functions +bool ModuleInternal::IsESModule(const std::string& path) { + // .mjs is always ESM + if (path.size() >= 4 && path.compare(path.size() - 4, 4, ".mjs") == 0) { + return true; + } + + // .cjs is always CommonJS + if (IsCJSModule(path)) { + return false; + } + + // .js files: check package.json "type" field + if (path.size() >= 3 && path.compare(path.size() - 3, 3, ".js") == 0) { + return ShouldTreatJsAsESModule(path); + } + + return false; +} + +napi_value ModuleInternal::LoadESModule(napi_env env, const std::string& path) { +#if defined(TARGET_ENGINE_V8) || defined(TARGET_ENGINE_QUICKJS) + try { + // Use canonicalized module paths so the top-level ESM module identity + // matches dependency resolution cache keys. + std::error_code pathError; + auto absPathFs = std::filesystem::absolute(path, pathError); + if (pathError) { + pathError.clear(); + absPathFs = std::filesystem::path(path); + } + absPathFs = absPathFs.lexically_normal(); + auto canonicalPath = + std::filesystem::weakly_canonical(absPathFs, pathError); + if (!pathError) { + absPathFs = canonicalPath; + } + std::string absPath = absPathFs.string(); + + // Read the ES module source + napi_value scriptContent = WrapModuleContent(env, absPath); + + // Use the new napi_run_script_as_module function + napi_value moduleNamespace; + napi_status status = napi_run_script_as_module( + env, scriptContent, absPath.c_str(), &moduleNamespace); + + if (status != napi_ok) { + bool pendingException; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value error; + napi_get_and_clear_last_exception(env, &error); + throw NativeScriptException(env, error, + "Failed to load ES module " + absPath); + } else { + throw NativeScriptException("Failed to load ES module " + absPath); + } + } + + return moduleNamespace; + + } catch (const std::exception& e) { + throw NativeScriptException("Failed to load ES module " + path + ": " + + e.what()); + } +#elif defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_JSC) + try { + std::error_code pathError; + auto absPathFs = std::filesystem::absolute(path, pathError); + if (pathError) { + pathError.clear(); + absPathFs = std::filesystem::path(path); + } + absPathFs = absPathFs.lexically_normal(); + auto canonicalPath = + std::filesystem::weakly_canonical(absPathFs, pathError); + if (!pathError) { + absPathFs = canonicalPath; + } + std::string absPath = absPathFs.string(); + + std::ifstream file(absPath); + if (!file.is_open()) { + throw NativeScriptException("Unable to open file: " + absPath); + } + std::stringstream buffer; + buffer << file.rdbuf(); + std::string transformed = + TransformESModuleForFallbackEngines(StripShebang(buffer.str())); + file.close(); + + std::string wrapped; + wrapped.reserve(transformed.length() + 1024); + wrapped += MODULE_PROLOGUE; + wrapped += transformed; + wrapped += MODULE_EPILOGUE; + + napi_value scriptContent = nullptr; + napi_create_string_utf8(env, wrapped.c_str(), wrapped.size(), + &scriptContent); + + napi_value moduleFunc = nullptr; + napi_status status = + napi_run_script_source(env, scriptContent, absPath.c_str(), &moduleFunc); + if (status != napi_ok) { + bool pendingException = false; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value error = nullptr; + napi_get_and_clear_last_exception(env, &error); + throw NativeScriptException(env, error, + "Failed to transform ES module " + absPath); + } + throw NativeScriptException("Failed to transform ES module " + absPath); + } + + napi_value moduleObj = nullptr; + napi_create_object(env, &moduleObj); + + napi_value exportsObj = nullptr; + napi_create_object(env, &exportsObj); + napi_set_named_property(env, moduleObj, "exports", exportsObj); + + napi_value esModuleMarker = nullptr; + napi_get_boolean(env, true, &esModuleMarker); + napi_util::define_property(env, exportsObj, "__esModule", esModuleMarker); + + napi_value fileName = nullptr; + napi_create_string_utf8(env, absPath.c_str(), absPath.size(), &fileName); + napi_set_named_property(env, moduleObj, "filename", fileName); + + std::string dirNameString = + std::filesystem::path(absPath).parent_path().string(); + napi_value dirName = nullptr; + napi_create_string_utf8(env, dirNameString.c_str(), dirNameString.size(), + &dirName); + + napi_value require = GetRequireFunction(env, dirNameString); + napi_set_named_property(env, moduleObj, "require", require); + napi_util::define_property(env, moduleObj, "id", fileName); + + napi_value thisArg = nullptr; + napi_create_object(env, &thisArg); + + napi_value global = nullptr; + napi_get_global(env, &global); + napi_value globalExtends = nullptr; + napi_get_named_property(env, global, "__extends", &globalExtends); + napi_set_named_property(env, thisArg, "__extends", globalExtends); + + napi_value args[5] = {moduleObj, exportsObj, require, fileName, dirName}; + napi_value callResult = nullptr; + status = napi_call_function(env, thisArg, moduleFunc, 5, args, &callResult); + bool pendingException = false; + napi_is_exception_pending(env, &pendingException); + if (status != napi_ok || pendingException) { + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + if (exception != nullptr) { + throw NativeScriptException(env, exception, + "Error evaluating ES module " + absPath); + } + throw NativeScriptException("Error evaluating ES module " + absPath); + } + + return exportsObj; + } catch (const std::exception& e) { + throw NativeScriptException("Failed to load ES module " + path + ": " + + e.what()); + } +#else + throw NativeScriptException("ES Modules are not supported in this runtime."); +#endif +} + +#if defined(TARGET_ENGINE_QUICKJS) +void ModuleInternal::InitQuickJSESModuleLoader(napi_env env) { + JSRuntime* runtime = qjs_get_runtime(env); + if (runtime == nullptr) { + return; + } + + JS_SetModuleLoaderFunc( + runtime, + [](JSContext* ctx, const char* base_name, const char* name, + void* opaque) -> char* { + auto* modules = static_cast(opaque); + if (modules == nullptr) { + return js_strdup(ctx, name != nullptr ? name : ""); + } + + std::string normalized = modules->NormalizeQuickJSImportSpecifier( + base_name != nullptr ? base_name : "", name != nullptr ? name : ""); + return js_strdup(ctx, normalized.c_str()); + }, + [](JSContext* ctx, const char* module_name, + void* opaque) -> JSModuleDef* { + auto* modules = static_cast(opaque); + if (modules == nullptr) { + return nullptr; + } + + return modules->LoadQuickJSImportModule( + ctx, module_name != nullptr ? module_name : ""); + }, + this); +} + +std::string ModuleInternal::NormalizeQuickJSImportSpecifier( + const std::string& baseName, const std::string& moduleName) { + if (moduleName.empty()) { + return moduleName; + } + + if (IsNodeBuiltinSpecifier(moduleName) || + !NormalizeRegisteredNapiModuleSpecifier(moduleName).empty()) { + return moduleName; + } + + if (moduleName.rfind("file://", 0) == 0) { + return moduleName.substr(std::string("file://").size()); + } + + std::string normalizedBase = baseName; + if (normalizedBase.rfind("file://", 0) == 0) { + normalizedBase = normalizedBase.substr(std::string("file://").size()); + } + + std::string baseDir = RuntimeConfig.ApplicationPath; + if (!normalizedBase.empty() && normalizedBase[0] == '/') { + std::filesystem::path basePath(normalizedBase); + baseDir = basePath.parent_path().string(); + } else if (!normalizedBase.empty() && + normalizedBase.rfind("nativescript:", 0) != 0 && + normalizedBase.rfind("node:", 0) != 0) { + std::filesystem::path basePath(normalizedBase); + baseDir = basePath.parent_path().string(); + } + + try { + std::string resolved = ResolvePath(m_env, baseDir, moduleName); + std::error_code ec; + auto absolutePath = std::filesystem::absolute(resolved, ec); + if (ec) { + ec.clear(); + absolutePath = std::filesystem::path(resolved); + } + + absolutePath = absolutePath.lexically_normal(); + auto canonicalPath = std::filesystem::weakly_canonical(absolutePath, ec); + if (!ec) { + absolutePath = canonicalPath; + } + + return absolutePath.string(); + } catch (...) { + return moduleName; + } +} + +JSModuleDef* ModuleInternal::LoadQuickJSImportModule( + JSContext* ctx, const std::string& moduleName) { + std::string source = GetBuiltinESModuleSource(moduleName); + if (source.empty()) { + source = GetRegisteredNapiESModuleSource(moduleName); + } + + std::string resolvedModuleName = moduleName; + if (source.empty()) { + resolvedModuleName = NormalizeQuickJSImportSpecifier("", moduleName); + if (!IsESModule(resolvedModuleName)) { + JS_ThrowReferenceError(ctx, "could not load module '%s'", + moduleName.c_str()); + return nullptr; + } + + std::ifstream file(resolvedModuleName); + if (!file.is_open()) { + JS_ThrowReferenceError(ctx, "could not load module '%s'", + resolvedModuleName.c_str()); + return nullptr; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + source = StripShebang(buffer.str()); + } + + JSValue moduleValue = + JS_Eval(ctx, source.c_str(), source.size(), resolvedModuleName.c_str(), + JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); + if (JS_IsException(moduleValue)) { + return nullptr; + } + + JSModuleDef* module = + static_cast(JS_VALUE_GET_PTR(moduleValue)); + JSValue meta = JS_GetImportMeta(ctx, module); + if (JS_IsException(meta)) { + JS_FreeValue(ctx, moduleValue); + return nullptr; + } + + std::string moduleURL = ModulePathToURL(resolvedModuleName); + if (JS_DefinePropertyValueStr(ctx, meta, "url", + JS_NewString(ctx, moduleURL.c_str()), + JS_PROP_C_W_E) < 0 || + JS_DefinePropertyValueStr(ctx, meta, "main", JS_FALSE, JS_PROP_C_W_E) < + 0) { + JS_FreeValue(ctx, meta); + JS_FreeValue(ctx, moduleValue); + return nullptr; + } + + JS_FreeValue(ctx, meta); + JS_FreeValue(ctx, moduleValue); + return module; +} +#endif + +napi_value ModuleInternal::WrapModuleContent(napi_env env, + const std::string& path) { + std::string content; + std::ifstream file(path); + if (!file.is_open()) { + throw NativeScriptException("Unable to open file: " + path); + } + std::stringstream buffer; + buffer << file.rdbuf(); + content = StripShebang(buffer.str()); + file.close(); + + std::string result; + + if (IsESModule(path)) { + // For ES modules, return content as-is to preserve import/export syntax + result = content; + } else { +#if defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_JSC) + content = RewriteCommonJSDynamicImportsForFallbackEngines(content); +#endif + // For CommonJS modules, wrap in factory function + result.reserve(content.length() + 1024); + result += MODULE_PROLOGUE; + result += content; + result += MODULE_EPILOGUE; + } + + napi_value wrappedContent; + napi_create_string_utf8(env, result.c_str(), result.size(), &wrappedContent); + + return wrappedContent; +} + +ModuleInternal::ModulePathKind ModuleInternal::GetModulePathKind( + const std::string& path) { + ModulePathKind kind; + switch (path[0]) { + case '.': + kind = ModulePathKind::Relative; + break; + case '/': + kind = ModulePathKind::Absolute; + break; + default: + kind = ModulePathKind::Global; + break; + } + return kind; +} + +#if defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_JSC) +const char* ModuleInternal::MODULE_PROLOGUE = + "(function(module, exports, require, __filename, __dirname){ " + "const __dynamicImport = (specifier) => Promise.resolve().then(() => { " + "const __loaded = require(specifier); " + "if (__loaded !== null && (typeof __loaded === 'object' || typeof __loaded " + "=== 'function')) { " + "if (__loaded.__esModule) { return __loaded; } " + "return Object.assign({ default: __loaded }, __loaded); " + "} " + "return { default: __loaded }; " + "}); "; +#else +const char* ModuleInternal::MODULE_PROLOGUE = + "(function(module, exports, require, __filename, __dirname){ "; +#endif +const char* ModuleInternal::MODULE_EPILOGUE = "\n})"; +int ModuleInternal::MODULE_PROLOGUE_LENGTH = + std::string(ModuleInternal::MODULE_PROLOGUE).length(); diff --git a/NativeScript/runtime/modules/module/ModuleInternal.h b/NativeScript/runtime/apple/modules/module/ModuleInternal.h similarity index 100% rename from NativeScript/runtime/modules/module/ModuleInternal.h rename to NativeScript/runtime/apple/modules/module/ModuleInternal.h diff --git a/NativeScript/runtime/modules/node/FS.cpp b/NativeScript/runtime/apple/modules/node/FS.cpp similarity index 100% rename from NativeScript/runtime/modules/node/FS.cpp rename to NativeScript/runtime/apple/modules/node/FS.cpp diff --git a/NativeScript/runtime/modules/node/FS.h b/NativeScript/runtime/apple/modules/node/FS.h similarity index 100% rename from NativeScript/runtime/modules/node/FS.h rename to NativeScript/runtime/apple/modules/node/FS.h diff --git a/NativeScript/runtime/modules/node/Node.cpp b/NativeScript/runtime/apple/modules/node/Node.cpp similarity index 100% rename from NativeScript/runtime/modules/node/Node.cpp rename to NativeScript/runtime/apple/modules/node/Node.cpp diff --git a/NativeScript/runtime/modules/node/Node.h b/NativeScript/runtime/apple/modules/node/Node.h similarity index 100% rename from NativeScript/runtime/modules/node/Node.h rename to NativeScript/runtime/apple/modules/node/Node.h diff --git a/NativeScript/runtime/modules/node/Path.cpp b/NativeScript/runtime/apple/modules/node/Path.cpp similarity index 100% rename from NativeScript/runtime/modules/node/Path.cpp rename to NativeScript/runtime/apple/modules/node/Path.cpp diff --git a/NativeScript/runtime/modules/node/Path.h b/NativeScript/runtime/apple/modules/node/Path.h similarity index 100% rename from NativeScript/runtime/modules/node/Path.h rename to NativeScript/runtime/apple/modules/node/Path.h diff --git a/NativeScript/runtime/modules/node/Process.cpp b/NativeScript/runtime/apple/modules/node/Process.cpp similarity index 99% rename from NativeScript/runtime/modules/node/Process.cpp rename to NativeScript/runtime/apple/modules/node/Process.cpp index ef77ba055..f570314e0 100644 --- a/NativeScript/runtime/modules/node/Process.cpp +++ b/NativeScript/runtime/apple/modules/node/Process.cpp @@ -17,8 +17,8 @@ #include "js_native_api.h" #include "jsr.h" #include "native_api_util.h" -#include "runtime/Runtime.h" -#include "runtime/RuntimeConfig.h" +#include "runtime/apple/Runtime.h" +#include "runtime/apple/RuntimeConfig.h" #if defined(__APPLE__) #include diff --git a/NativeScript/runtime/modules/node/Process.h b/NativeScript/runtime/apple/modules/node/Process.h similarity index 100% rename from NativeScript/runtime/modules/node/Process.h rename to NativeScript/runtime/apple/modules/node/Process.h diff --git a/NativeScript/runtime/modules/node/VM.cpp b/NativeScript/runtime/apple/modules/node/VM.cpp similarity index 99% rename from NativeScript/runtime/modules/node/VM.cpp rename to NativeScript/runtime/apple/modules/node/VM.cpp index 93f2234f7..4675238c5 100644 --- a/NativeScript/runtime/modules/node/VM.cpp +++ b/NativeScript/runtime/apple/modules/node/VM.cpp @@ -983,7 +983,7 @@ bool ExtractV8DependencySpecifiers(v8::Isolate* isolate, for (int index = 0; index < length; ++index) { v8::Local entry = - requests->Get(isolate->GetCurrentContext(), index); + requests->Get(index); v8::Local specifier = v8::ModuleRequest::Cast(*entry)->GetSpecifier(); v8::String::Utf8Value text(isolate, specifier); @@ -1791,7 +1791,7 @@ JSValue CloneQuickJSValue(JSContext* sourceContext, return result; } - if (JS_IsBigInt(sourceContext, value)) { + if (JS_IsBigInt(value)) { int64_t bigintValue = 0; if (JS_ToBigInt64(sourceContext, &bigintValue, value) < 0) { JS_ThrowTypeError(destinationContext, diff --git a/NativeScript/runtime/modules/node/VM.h b/NativeScript/runtime/apple/modules/node/VM.h similarity index 100% rename from NativeScript/runtime/modules/node/VM.h rename to NativeScript/runtime/apple/modules/node/VM.h diff --git a/NativeScript/runtime/modules/performance/Performance.cpp b/NativeScript/runtime/apple/modules/performance/Performance.cpp similarity index 100% rename from NativeScript/runtime/modules/performance/Performance.cpp rename to NativeScript/runtime/apple/modules/performance/Performance.cpp diff --git a/NativeScript/runtime/modules/performance/Performance.h b/NativeScript/runtime/apple/modules/performance/Performance.h similarity index 100% rename from NativeScript/runtime/modules/performance/Performance.h rename to NativeScript/runtime/apple/modules/performance/Performance.h diff --git a/NativeScript/runtime/modules/timers/Timers.h b/NativeScript/runtime/apple/modules/timers/Timers.h similarity index 100% rename from NativeScript/runtime/modules/timers/Timers.h rename to NativeScript/runtime/apple/modules/timers/Timers.h diff --git a/NativeScript/runtime/modules/timers/Timers.mm b/NativeScript/runtime/apple/modules/timers/Timers.mm similarity index 99% rename from NativeScript/runtime/modules/timers/Timers.mm rename to NativeScript/runtime/apple/modules/timers/Timers.mm index f9fe7a075..fdeeb3390 100644 --- a/NativeScript/runtime/modules/timers/Timers.mm +++ b/NativeScript/runtime/apple/modules/timers/Timers.mm @@ -9,7 +9,7 @@ #include #include #include "Timers.h" -#include "ffi/napi/CallbackThreading.h" +#include "ffi/objc/napi/CallbackThreading.h" static std::atomic gActiveTimers{0}; struct TimerToken; diff --git a/NativeScript/runtime/modules/web/Web.h b/NativeScript/runtime/apple/modules/web/Web.h similarity index 100% rename from NativeScript/runtime/modules/web/Web.h rename to NativeScript/runtime/apple/modules/web/Web.h diff --git a/NativeScript/runtime/modules/web/Web.mm b/NativeScript/runtime/apple/modules/web/Web.mm similarity index 99% rename from NativeScript/runtime/modules/web/Web.mm rename to NativeScript/runtime/apple/modules/web/Web.mm index 475f8d732..0bba29b02 100644 --- a/NativeScript/runtime/modules/web/Web.mm +++ b/NativeScript/runtime/apple/modules/web/Web.mm @@ -18,8 +18,8 @@ #include "js_native_api.h" #include "jsr.h" #include "native_api_util.h" -#include "runtime/Runtime.h" -#include "runtime/Util.h" +#include "runtime/apple/Runtime.h" +#include "runtime/apple/Util.h" namespace nativescript { diff --git a/NativeScript/runtime/modules/worker/ConcurrentMap.h b/NativeScript/runtime/apple/modules/worker/ConcurrentMap.h similarity index 100% rename from NativeScript/runtime/modules/worker/ConcurrentMap.h rename to NativeScript/runtime/apple/modules/worker/ConcurrentMap.h diff --git a/NativeScript/runtime/modules/worker/ConcurrentQueue.cpp b/NativeScript/runtime/apple/modules/worker/ConcurrentQueue.cpp similarity index 100% rename from NativeScript/runtime/modules/worker/ConcurrentQueue.cpp rename to NativeScript/runtime/apple/modules/worker/ConcurrentQueue.cpp diff --git a/NativeScript/runtime/apple/modules/worker/ConcurrentQueue.h b/NativeScript/runtime/apple/modules/worker/ConcurrentQueue.h new file mode 100644 index 000000000..ff303ea74 --- /dev/null +++ b/NativeScript/runtime/apple/modules/worker/ConcurrentQueue.h @@ -0,0 +1,33 @@ +#ifndef ConcurrentQueue_h +#define ConcurrentQueue_h + +#include + +#include +#include +#include + +#include "runtime/apple/modules/worker/Message.h" + +namespace nativescript { + +struct ConcurrentQueue { + public: + void Initialize(CFRunLoopRef runLoop, void (*performWork)(void*), void* info); + void Push(std::shared_ptr message); + std::vector> PopAll(); + void Terminate(); + + private: + std::queue> messagesQueue_; + CFRunLoopSourceRef runLoopTasksSource_ = nullptr; + CFRunLoopRef runLoop_ = nullptr; + bool terminated = false; + std::mutex mutex_; + std::mutex initializationMutex_; + void SignalAndWakeUp(); +}; + +} // namespace nativescript + +#endif /* ConcurrentQueue_h */ diff --git a/NativeScript/runtime/modules/worker/Message.h b/NativeScript/runtime/apple/modules/worker/Message.h similarity index 100% rename from NativeScript/runtime/modules/worker/Message.h rename to NativeScript/runtime/apple/modules/worker/Message.h diff --git a/NativeScript/runtime/apple/modules/worker/MessageJSON.cpp b/NativeScript/runtime/apple/modules/worker/MessageJSON.cpp new file mode 100644 index 000000000..629eba6f5 --- /dev/null +++ b/NativeScript/runtime/apple/modules/worker/MessageJSON.cpp @@ -0,0 +1,1191 @@ +// Message.cpp +// NativeScript +// +// Created by Eduardo Speroni on 11/22/23. +// Copyright © 2023 Progress. All rights reserved. + +#ifndef TARGET_ENGINE_V8 + +#include "MessageJSON.h" + +#include +#include +#include +#include +#include +#include + +#include "js_native_api.h" +#include "runtime/apple/NativeScriptException.h" + +#ifdef TARGET_ENGINE_QUICKJS +#include "quickjs.h" +#include "quicks-runtime.h" +#endif + +namespace nativescript { +namespace { + +enum class ValueTag : uint8_t { + Undefined = 0, + Null = 1, + Boolean = 2, + Number = 3, + String = 4, + BigInt = 5, + Reference = 6, + Array = 7, + Object = 8, + Date = 9, + ArrayBuffer = 10, + TypedArray = 11, + DataView = 12, + Map = 13, + Set = 14, + RegExp = 15, + Error = 16, + SharedArrayBuffer = 17, +}; + +void Check(napi_env env, napi_status status, const std::string& message) { + if (status == napi_ok) { + return; + } + + bool pendingException = false; + napi_is_exception_pending(env, &pendingException); + if (pendingException) { + napi_value exception = nullptr; + napi_get_and_clear_last_exception(env, &exception); + if (exception != nullptr) { + throw NativeScriptException(env, exception, message); + } + } + + throw NativeScriptException(env, message, "DataCloneError"); +} + +void ThrowDataCloneError(napi_env env, const std::string& message) { + throw NativeScriptException(env, message, "DataCloneError"); +} + +napi_value Global(napi_env env) { + napi_value global = nullptr; + Check(env, napi_get_global(env, &global), "Unable to read global object"); + return global; +} + +napi_value Undefined(napi_env env) { + napi_value value = nullptr; + Check(env, napi_get_undefined(env, &value), "Unable to create undefined"); + return value; +} + +napi_value Boolean(napi_env env, bool value) { + napi_value result = nullptr; + Check(env, napi_get_boolean(env, value, &result), "Unable to create boolean"); + return result; +} + +napi_value String(napi_env env, const std::string& value) { + napi_value result = nullptr; + Check(env, + napi_create_string_utf8(env, value.data(), value.size(), &result), + "Unable to create string"); + return result; +} + +std::string ToString(napi_env env, napi_value value) { + size_t length = 0; + Check(env, napi_get_value_string_utf8(env, value, nullptr, 0, &length), + "Unable to read string length"); + + std::vector buffer(length + 1); + size_t written = 0; + Check(env, + napi_get_value_string_utf8(env, value, buffer.data(), buffer.size(), + &written), + "Unable to read string"); + return std::string(buffer.data(), written); +} + +napi_value GetNamed(napi_env env, napi_value object, const char* name) { + napi_value result = nullptr; + Check(env, napi_get_named_property(env, object, name, &result), + std::string("Unable to read property ") + name); + return result; +} + +bool IsFunction(napi_env env, napi_value value) { + napi_valuetype type = napi_undefined; + Check(env, napi_typeof(env, value, &type), "Unable to inspect value type"); + return type == napi_function; +} + +napi_value Call(napi_env env, napi_value receiver, napi_value function, + const std::vector& args, + const std::string& message) { + napi_value result = nullptr; + Check(env, + napi_call_function(env, receiver, function, args.size(), + args.empty() ? nullptr : args.data(), &result), + message); + return result; +} + +napi_value Construct(napi_env env, const char* constructorName, + const std::vector& args) { + napi_value ctor = GetNamed(env, Global(env), constructorName); + if (!IsFunction(env, ctor)) { + ThrowDataCloneError(env, + std::string("Missing constructor ") + constructorName); + } + + napi_value result = nullptr; + Check(env, + napi_new_instance(env, ctor, args.size(), + args.empty() ? nullptr : args.data(), &result), + std::string("Unable to construct ") + constructorName); + return result; +} + +napi_value GetStructuredCloneHelper(napi_env env) { + static std::unordered_map helperRefs; + + auto it = helperRefs.find(env); + if (it != helperRefs.end()) { + napi_value helper = nullptr; + Check(env, napi_get_reference_value(env, it->second, &helper), + "Unable to read structured clone helper"); + return helper; + } + + static const char* kHelperSource = R"( + (function () { + const hasCtor = (name) => typeof globalThis[name] === 'function'; + return { + isMap(value) { + return hasCtor('Map') && value instanceof Map; + }, + isSet(value) { + return hasCtor('Set') && value instanceof Set; + }, + isRegExp(value) { + return hasCtor('RegExp') && value instanceof RegExp; + }, + isSharedArrayBuffer(value) { + return hasCtor('SharedArrayBuffer') && value instanceof SharedArrayBuffer; + }, + mapEntries(value) { + return Array.from(value.entries()); + }, + setValues(value) { + return Array.from(value.values()); + }, + regexpInfo(value) { + return { source: value.source, flags: value.flags }; + }, + errorInfo(value) { + return { name: value.name, message: value.message, stack: value.stack }; + }, + bigintToString(value) { + return value.toString(); + }, + makeBigInt(value) { + return BigInt(value); + }, + ownEnumerableKeys(value) { + return Object.keys(value); + }, + makeMap() { + return new Map(); + }, + mapSet(map, key, value) { + map.set(key, value); + return map; + }, + makeSet() { + return new Set(); + }, + setAdd(set, value) { + set.add(value); + return set; + }, + makeRegExp(source, flags) { + return new RegExp(source, flags); + }, + makeError(name, message, stack) { + const Ctor = typeof globalThis[name] === 'function' ? globalThis[name] : Error; + const error = new Ctor(message); + try { error.name = name; } catch (_) {} + if (stack !== undefined) { + try { error.stack = stack; } catch (_) {} + } + return error; + }, + }; + })(); + )"; + + napi_value source = nullptr; + Check(env, + napi_create_string_utf8(env, kHelperSource, NAPI_AUTO_LENGTH, &source), + "Unable to create structured clone helper source"); + + napi_value helper = nullptr; + Check(env, napi_run_script(env, source, &helper), + "Unable to install structured clone helper"); + + napi_ref ref = nullptr; + Check(env, napi_create_reference(env, helper, 1, &ref), + "Unable to retain structured clone helper"); + helperRefs.emplace(env, ref); + return helper; +} + +napi_value CallHelper(napi_env env, const char* name, + const std::vector& args) { + napi_value helper = GetStructuredCloneHelper(env); + napi_value function = GetNamed(env, helper, name); + return Call(env, helper, function, args, + std::string("Unable to call structured clone helper ") + name); +} + +bool CallHelperBool(napi_env env, const char* name, napi_value value) { + napi_value result = CallHelper(env, name, {value}); + bool boolResult = false; + Check(env, napi_get_value_bool(env, result, &boolResult), + std::string("Structured clone helper did not return boolean: ") + + name); + return boolResult; +} + +class BufferWriter { + public: + void WriteU8(uint8_t value) { bytes_.push_back(value); } + + void WriteU32(uint32_t value) { + for (int i = 0; i < 4; i++) { + WriteU8(static_cast((value >> (i * 8)) & 0xff)); + } + } + + void WriteU64(uint64_t value) { + for (int i = 0; i < 8; i++) { + WriteU8(static_cast((value >> (i * 8)) & 0xff)); + } + } + + void WriteDouble(double value) { + uint64_t bits = 0; + static_assert(sizeof(bits) == sizeof(value), "double size mismatch"); + std::memcpy(&bits, &value, sizeof(value)); + WriteU64(bits); + } + + void WriteBytes(const void* data, size_t size) { + const auto* begin = static_cast(data); + bytes_.insert(bytes_.end(), begin, begin + size); + } + + void WriteString(const std::string& value) { + WriteU32(static_cast(value.size())); + WriteBytes(value.data(), value.size()); + } + + MallocedBuffer Release() { + MallocedBuffer result; + result.size = bytes_.size(); + result.data = static_cast(std::malloc(bytes_.size())); + if (!bytes_.empty()) { + std::memcpy(result.data, bytes_.data(), bytes_.size()); + } + return result; + } + + private: + std::vector bytes_; +}; + +class BufferReader { + public: + BufferReader(const char* data, size_t size) + : current_(reinterpret_cast(data)), + end_(reinterpret_cast(data) + size) {} + + uint8_t ReadU8() { + Ensure(1); + return *current_++; + } + + uint32_t ReadU32() { + uint32_t value = 0; + for (int i = 0; i < 4; i++) { + value |= static_cast(ReadU8()) << (i * 8); + } + return value; + } + + uint64_t ReadU64() { + uint64_t value = 0; + for (int i = 0; i < 8; i++) { + value |= static_cast(ReadU8()) << (i * 8); + } + return value; + } + + double ReadDouble() { + uint64_t bits = ReadU64(); + double value = 0; + static_assert(sizeof(bits) == sizeof(value), "double size mismatch"); + std::memcpy(&value, &bits, sizeof(value)); + return value; + } + + std::vector ReadBytes(size_t size) { + Ensure(size); + std::vector result(current_, current_ + size); + current_ += size; + return result; + } + + std::string ReadString() { + uint32_t size = ReadU32(); + Ensure(size); + std::string result(reinterpret_cast(current_), size); + current_ += size; + return result; + } + + private: + void Ensure(size_t size) { + if (static_cast(end_ - current_) < size) { + throw NativeScriptException("Malformed worker message."); + } + } + + const uint8_t* current_; + const uint8_t* end_; +}; + +#ifdef TARGET_ENGINE_QUICKJS +JSValue QuickJSValueFromNapi(napi_value value); +#endif + +class Serializer { + public: + Serializer(napi_env env, BufferWriter& writer, + std::vector* quickjsSharedArrayBuffers = nullptr) + : env_(env), + writer_(writer), + quickjs_shared_array_buffers_(quickjsSharedArrayBuffers) {} + + void Write(napi_value value) { + napi_valuetype type = napi_undefined; + Check(env_, napi_typeof(env_, value, &type), "Unable to inspect value"); + + switch (type) { + case napi_undefined: + WriteTag(ValueTag::Undefined); + return; + case napi_null: + WriteTag(ValueTag::Null); + return; + case napi_boolean: + WriteBoolean(value); + return; + case napi_number: + WriteNumber(value); + return; + case napi_string: + WriteStringValue(value); + return; + case napi_bigint: + WriteBigInt(value); + return; + case napi_symbol: + case napi_function: + case napi_external: + ThrowDataCloneError(env_, "Value cannot be cloned."); + return; + case napi_object: + WriteObjectLike(value); + return; + } + } + + private: + void WriteTag(ValueTag tag) { writer_.WriteU8(static_cast(tag)); } + + void WriteBoolean(napi_value value) { + bool boolValue = false; + Check(env_, napi_get_value_bool(env_, value, &boolValue), + "Unable to read boolean"); + WriteTag(ValueTag::Boolean); + writer_.WriteU8(boolValue ? 1 : 0); + } + + void WriteNumber(napi_value value) { + double number = 0; + Check(env_, napi_get_value_double(env_, value, &number), + "Unable to read number"); + WriteTag(ValueTag::Number); + writer_.WriteDouble(number); + } + + void WriteStringValue(napi_value value) { + WriteTag(ValueTag::String); + writer_.WriteString(ToString(env_, value)); + } + + void WriteBigInt(napi_value value) { + napi_value text = CallHelper(env_, "bigintToString", {value}); + WriteTag(ValueTag::BigInt); + writer_.WriteString(ToString(env_, text)); + } + + bool WriteReferenceIfSeen(napi_value value, uint32_t* idOut) { + for (uint32_t i = 0; i < seen_.size(); i++) { + bool equal = false; + Check(env_, napi_strict_equals(env_, seen_[i], value, &equal), + "Unable to compare object identity"); + if (equal) { + WriteTag(ValueTag::Reference); + writer_.WriteU32(i); + return true; + } + } + + *idOut = static_cast(seen_.size()); + seen_.push_back(value); + return false; + } + + void WriteObjectHeader(ValueTag tag, uint32_t id) { + WriteTag(tag); + writer_.WriteU32(id); + } + + void WriteObjectLike(napi_value value) { + bool isDate = false; + Check(env_, napi_is_date(env_, value, &isDate), "Unable to inspect Date"); + if (isDate) { + WriteDate(value); + return; + } + + bool isArrayBuffer = false; + Check(env_, napi_is_arraybuffer(env_, value, &isArrayBuffer), + "Unable to inspect ArrayBuffer"); + if (isArrayBuffer) { +#ifdef TARGET_ENGINE_QUICKJS + if (CallHelperBool(env_, "isSharedArrayBuffer", value)) { + WriteSharedArrayBuffer(value); + return; + } +#else + if (CallHelperBool(env_, "isSharedArrayBuffer", value)) { + ThrowDataCloneError( + env_, "SharedArrayBuffer cloning is not supported by this engine."); + } +#endif + WriteArrayBuffer(value); + return; + } + + bool isDataView = false; + Check(env_, napi_is_dataview(env_, value, &isDataView), + "Unable to inspect DataView"); + if (isDataView) { + WriteDataView(value); + return; + } + + bool isTypedArray = false; + Check(env_, napi_is_typedarray(env_, value, &isTypedArray), + "Unable to inspect TypedArray"); + if (isTypedArray) { + WriteTypedArray(value); + return; + } + + bool isArray = false; + Check(env_, napi_is_array(env_, value, &isArray), "Unable to inspect Array"); + if (isArray) { + WriteArray(value); + return; + } + + if (CallHelperBool(env_, "isMap", value)) { + WriteMap(value); + return; + } + + if (CallHelperBool(env_, "isSet", value)) { + WriteSet(value); + return; + } + + if (CallHelperBool(env_, "isRegExp", value)) { + WriteRegExp(value); + return; + } + + bool isError = false; + Check(env_, napi_is_error(env_, value, &isError), "Unable to inspect Error"); + if (isError) { + WriteError(value); + return; + } + + WritePlainObject(value); + } + + void WriteDate(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + double time = 0; + Check(env_, napi_get_date_value(env_, value, &time), "Unable to read Date"); + WriteObjectHeader(ValueTag::Date, id); + writer_.WriteDouble(time); + } + + void WriteArrayBuffer(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + void* data = nullptr; + size_t byteLength = 0; + Check(env_, napi_get_arraybuffer_info(env_, value, &data, &byteLength), + "Unable to read ArrayBuffer"); + WriteObjectHeader(ValueTag::ArrayBuffer, id); + writer_.WriteU32(static_cast(byteLength)); + writer_.WriteBytes(data, byteLength); + } + +#ifdef TARGET_ENGINE_QUICKJS + void WriteSharedArrayBuffer(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + if (quickjs_shared_array_buffers_ == nullptr) { + ThrowDataCloneError(env_, "SharedArrayBuffer cannot be retained."); + } + + JSContext* context = qjs_get_context(env_); + if (context == nullptr) { + throw NativeScriptException("QuickJS context is not available."); + } + + size_t byteLength = 0; + JSSABTab sabTab = {nullptr, 0}; + uint8_t* bytes = JS_WriteObject2( + context, &byteLength, QuickJSValueFromNapi(value), + JS_WRITE_OBJ_REFERENCE | JS_WRITE_OBJ_SAB, &sabTab); + if (bytes == nullptr) { + JSValue exception = JS_GetException(context); + napi_value error = nullptr; + qjs_create_scoped_value(env_, exception, &error); + throw NativeScriptException(env_, error, + "Unable to serialize SharedArrayBuffer"); + } + + WriteObjectHeader(ValueTag::SharedArrayBuffer, id); + writer_.WriteU32(static_cast(byteLength)); + writer_.WriteBytes(bytes, byteLength); + js_free(context, bytes); + + if (sabTab.tab != nullptr) { + for (size_t i = 0; i < sabTab.len; i++) { + qjs_shared_array_buffer_data_retain(sabTab.tab[i]); + quickjs_shared_array_buffers_->push_back(sabTab.tab[i]); + } + js_free(context, sabTab.tab); + } + } +#endif + + void WriteTypedArray(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + napi_typedarray_type type = napi_int8_array; + size_t length = 0; + napi_value arrayBuffer = nullptr; + size_t byteOffset = 0; + Check(env_, + napi_get_typedarray_info(env_, value, &type, &length, nullptr, + &arrayBuffer, &byteOffset), + "Unable to read TypedArray"); + WriteObjectHeader(ValueTag::TypedArray, id); + writer_.WriteU32(static_cast(type)); + writer_.WriteU32(static_cast(length)); + writer_.WriteU32(static_cast(byteOffset)); + Write(arrayBuffer); + } + + void WriteDataView(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + size_t byteLength = 0; + napi_value arrayBuffer = nullptr; + size_t byteOffset = 0; + Check(env_, + napi_get_dataview_info(env_, value, &byteLength, nullptr, + &arrayBuffer, &byteOffset), + "Unable to read DataView"); + WriteObjectHeader(ValueTag::DataView, id); + writer_.WriteU32(static_cast(byteLength)); + writer_.WriteU32(static_cast(byteOffset)); + Write(arrayBuffer); + } + + void WriteArray(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + uint32_t length = 0; + Check(env_, napi_get_array_length(env_, value, &length), + "Unable to read Array length"); + WriteObjectHeader(ValueTag::Array, id); + writer_.WriteU32(length); + for (uint32_t i = 0; i < length; i++) { + napi_value element = nullptr; + Check(env_, napi_get_element(env_, value, i, &element), + "Unable to read Array element"); + Write(element); + } + } + + void WriteMap(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + napi_value entries = CallHelper(env_, "mapEntries", {value}); + uint32_t length = 0; + Check(env_, napi_get_array_length(env_, entries, &length), + "Unable to read Map entries"); + WriteObjectHeader(ValueTag::Map, id); + writer_.WriteU32(length); + for (uint32_t i = 0; i < length; i++) { + napi_value pair = nullptr; + napi_value key = nullptr; + napi_value itemValue = nullptr; + Check(env_, napi_get_element(env_, entries, i, &pair), + "Unable to read Map pair"); + Check(env_, napi_get_element(env_, pair, 0, &key), + "Unable to read Map key"); + Check(env_, napi_get_element(env_, pair, 1, &itemValue), + "Unable to read Map value"); + Write(key); + Write(itemValue); + } + } + + void WriteSet(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + napi_value values = CallHelper(env_, "setValues", {value}); + uint32_t length = 0; + Check(env_, napi_get_array_length(env_, values, &length), + "Unable to read Set values"); + WriteObjectHeader(ValueTag::Set, id); + writer_.WriteU32(length); + for (uint32_t i = 0; i < length; i++) { + napi_value itemValue = nullptr; + Check(env_, napi_get_element(env_, values, i, &itemValue), + "Unable to read Set value"); + Write(itemValue); + } + } + + void WriteRegExp(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + napi_value info = CallHelper(env_, "regexpInfo", {value}); + WriteObjectHeader(ValueTag::RegExp, id); + writer_.WriteString(ToString(env_, GetNamed(env_, info, "source"))); + writer_.WriteString(ToString(env_, GetNamed(env_, info, "flags"))); + } + + void WriteError(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + napi_value info = CallHelper(env_, "errorInfo", {value}); + WriteObjectHeader(ValueTag::Error, id); + writer_.WriteString(ToString(env_, GetNamed(env_, info, "name"))); + writer_.WriteString(ToString(env_, GetNamed(env_, info, "message"))); + + napi_value stack = GetNamed(env_, info, "stack"); + napi_valuetype stackType = napi_undefined; + Check(env_, napi_typeof(env_, stack, &stackType), "Unable to inspect stack"); + writer_.WriteU8(stackType == napi_string ? 1 : 0); + if (stackType == napi_string) { + writer_.WriteString(ToString(env_, stack)); + } + } + + void WritePlainObject(napi_value value) { + uint32_t id = 0; + if (WriteReferenceIfSeen(value, &id)) { + return; + } + + napi_value keys = CallHelper(env_, "ownEnumerableKeys", {value}); + uint32_t length = 0; + Check(env_, napi_get_array_length(env_, keys, &length), + "Unable to read object keys"); + WriteObjectHeader(ValueTag::Object, id); + writer_.WriteU32(length); + for (uint32_t i = 0; i < length; i++) { + napi_value key = nullptr; + napi_value itemValue = nullptr; + Check(env_, napi_get_element(env_, keys, i, &key), + "Unable to read object key"); + Check(env_, napi_get_property(env_, value, key, &itemValue), + "Unable to read object property"); + writer_.WriteString(ToString(env_, key)); + Write(itemValue); + } + } + + napi_env env_; + BufferWriter& writer_; + std::vector seen_; + std::vector* quickjs_shared_array_buffers_; +}; + +class Deserializer { + public: + Deserializer(napi_env env, BufferReader& reader) + : env_(env), reader_(reader) {} + + napi_value Read() { + ValueTag tag = static_cast(reader_.ReadU8()); + switch (tag) { + case ValueTag::Undefined: + return Undefined(env_); + case ValueTag::Null: + return Null(); + case ValueTag::Boolean: + return Boolean(env_, reader_.ReadU8() != 0); + case ValueTag::Number: + return Number(); + case ValueTag::String: + return String(env_, reader_.ReadString()); + case ValueTag::BigInt: + return CallHelper(env_, "makeBigInt", {String(env_, reader_.ReadString())}); + case ValueTag::Reference: + return Reference(); + case ValueTag::Array: + return Array(); + case ValueTag::Object: + return Object(); + case ValueTag::Date: + return Date(); + case ValueTag::ArrayBuffer: + return ArrayBuffer(); + case ValueTag::TypedArray: + return TypedArray(); + case ValueTag::DataView: + return DataView(); + case ValueTag::Map: + return Map(); + case ValueTag::Set: + return Set(); + case ValueTag::RegExp: + return RegExp(); + case ValueTag::Error: + return Error(); + case ValueTag::SharedArrayBuffer: +#ifdef TARGET_ENGINE_QUICKJS + return SharedArrayBuffer(); +#else + throw NativeScriptException( + "SharedArrayBuffer worker messages are not supported by this engine."); +#endif + } + + throw NativeScriptException("Malformed worker message."); + } + + private: + napi_value Null() { + napi_value value = nullptr; + Check(env_, napi_get_null(env_, &value), "Unable to create null"); + return value; + } + + napi_value Number() { + napi_value value = nullptr; + Check(env_, napi_create_double(env_, reader_.ReadDouble(), &value), + "Unable to create number"); + return value; + } + + napi_value Reference() { + uint32_t id = reader_.ReadU32(); + if (id >= refs_.size() || refs_[id] == nullptr) { + throw NativeScriptException("Malformed worker message reference."); + } + return refs_[id]; + } + + void StoreRef(uint32_t id, napi_value value) { + if (id >= refs_.size()) { + refs_.resize(id + 1, nullptr); + } + refs_[id] = value; + } + + napi_value Array() { + uint32_t id = reader_.ReadU32(); + uint32_t length = reader_.ReadU32(); + napi_value array = nullptr; + Check(env_, napi_create_array_with_length(env_, length, &array), + "Unable to create Array"); + StoreRef(id, array); + for (uint32_t i = 0; i < length; i++) { + napi_value element = Read(); + Check(env_, napi_set_element(env_, array, i, element), + "Unable to set Array element"); + } + return array; + } + + napi_value Object() { + uint32_t id = reader_.ReadU32(); + uint32_t length = reader_.ReadU32(); + napi_value object = nullptr; + Check(env_, napi_create_object(env_, &object), "Unable to create Object"); + StoreRef(id, object); + for (uint32_t i = 0; i < length; i++) { + std::string key = reader_.ReadString(); + napi_value value = Read(); + Check(env_, napi_set_named_property(env_, object, key.c_str(), value), + "Unable to set object property"); + } + return object; + } + + napi_value Date() { + uint32_t id = reader_.ReadU32(); + napi_value date = nullptr; + Check(env_, napi_create_date(env_, reader_.ReadDouble(), &date), + "Unable to create Date"); + StoreRef(id, date); + return date; + } + + napi_value ArrayBuffer() { + uint32_t id = reader_.ReadU32(); + uint32_t byteLength = reader_.ReadU32(); + std::vector bytes = reader_.ReadBytes(byteLength); + void* data = nullptr; + napi_value arrayBuffer = nullptr; + Check(env_, napi_create_arraybuffer(env_, byteLength, &data, &arrayBuffer), + "Unable to create ArrayBuffer"); + if (byteLength != 0) { + std::memcpy(data, bytes.data(), byteLength); + } + StoreRef(id, arrayBuffer); + return arrayBuffer; + } + +#ifdef TARGET_ENGINE_QUICKJS + napi_value SharedArrayBuffer() { + uint32_t id = reader_.ReadU32(); + uint32_t byteLength = reader_.ReadU32(); + std::vector bytes = reader_.ReadBytes(byteLength); + + JSContext* context = qjs_get_context(env_); + if (context == nullptr) { + throw NativeScriptException("QuickJS context is not available."); + } + + JSValue value = + JS_ReadObject(context, bytes.data(), bytes.size(), + JS_READ_OBJ_REFERENCE | JS_READ_OBJ_SAB); + if (JS_IsException(value)) { + JSValue exception = JS_GetException(context); + napi_value error = nullptr; + qjs_create_scoped_value(env_, exception, &error); + throw NativeScriptException(env_, error, + "Unable to deserialize SharedArrayBuffer"); + } + + napi_value result = nullptr; + Check(env_, qjs_create_scoped_value(env_, value, &result), + "Unable to create SharedArrayBuffer value"); + StoreRef(id, result); + return result; + } +#endif + + napi_value TypedArray() { + uint32_t id = reader_.ReadU32(); + auto type = static_cast(reader_.ReadU32()); + uint32_t length = reader_.ReadU32(); + uint32_t byteOffset = reader_.ReadU32(); + napi_value arrayBuffer = Read(); + napi_value typedArray = nullptr; + Check(env_, + napi_create_typedarray(env_, type, length, arrayBuffer, byteOffset, + &typedArray), + "Unable to create TypedArray"); + StoreRef(id, typedArray); + return typedArray; + } + + napi_value DataView() { + uint32_t id = reader_.ReadU32(); + uint32_t byteLength = reader_.ReadU32(); + uint32_t byteOffset = reader_.ReadU32(); + napi_value arrayBuffer = Read(); + napi_value dataView = nullptr; + Check(env_, + napi_create_dataview(env_, byteLength, arrayBuffer, byteOffset, + &dataView), + "Unable to create DataView"); + StoreRef(id, dataView); + return dataView; + } + + napi_value Map() { + uint32_t id = reader_.ReadU32(); + uint32_t length = reader_.ReadU32(); + napi_value map = CallHelper(env_, "makeMap", {}); + StoreRef(id, map); + for (uint32_t i = 0; i < length; i++) { + napi_value key = Read(); + napi_value value = Read(); + CallHelper(env_, "mapSet", {map, key, value}); + } + return map; + } + + napi_value Set() { + uint32_t id = reader_.ReadU32(); + uint32_t length = reader_.ReadU32(); + napi_value set = CallHelper(env_, "makeSet", {}); + StoreRef(id, set); + for (uint32_t i = 0; i < length; i++) { + napi_value value = Read(); + CallHelper(env_, "setAdd", {set, value}); + } + return set; + } + + napi_value RegExp() { + uint32_t id = reader_.ReadU32(); + napi_value regexp = CallHelper(env_, "makeRegExp", + {String(env_, reader_.ReadString()), + String(env_, reader_.ReadString())}); + StoreRef(id, regexp); + return regexp; + } + + napi_value Error() { + uint32_t id = reader_.ReadU32(); + std::string name = reader_.ReadString(); + std::string message = reader_.ReadString(); + napi_value stack = Undefined(env_); + if (reader_.ReadU8() != 0) { + stack = String(env_, reader_.ReadString()); + } + napi_value error = + CallHelper(env_, "makeError", {String(env_, name), String(env_, message), stack}); + StoreRef(id, error); + return error; + } + + napi_env env_; + BufferReader& reader_; + std::vector refs_; +}; + +#ifdef TARGET_ENGINE_QUICKJS +JSValue QuickJSValueFromNapi(napi_value value) { + return *reinterpret_cast(value); +} + +MallocedBuffer CopyToMallocedBuffer(const uint8_t* data, size_t size) { + MallocedBuffer result; + result.size = size; + result.data = static_cast(std::malloc(size)); + if (size != 0) { + std::memcpy(result.data, data, size); + } + return result; +} + +void ClearQuickJSException(JSContext* context) { + JSValue exception = JS_GetException(context); + JS_FreeValue(context, exception); +} +#endif + +} // namespace + +namespace worker { + +bool Message::Serialize(napi_env env, napi_value input) { +#ifdef TARGET_ENGINE_QUICKJS + ReleaseQuickJSSharedArrayBuffers(); + + JSContext* context = qjs_get_context(env); + if (context != nullptr) { + size_t byteLength = 0; + JSSABTab sabTab = {nullptr, 0}; + uint8_t* bytes = JS_WriteObject2( + context, &byteLength, QuickJSValueFromNapi(input), + JS_WRITE_OBJ_REFERENCE | JS_WRITE_OBJ_SAB, &sabTab); + if (bytes != nullptr) { + main_message_buf_ = CopyToMallocedBuffer(bytes, byteLength); + js_free(context, bytes); + if (sabTab.tab != nullptr && sabTab.len != 0) { + quickjs_shared_array_buffers_.assign(sabTab.tab, + sabTab.tab + sabTab.len); + for (uint8_t* sharedArrayBuffer : quickjs_shared_array_buffers_) { + qjs_shared_array_buffer_data_retain(sharedArrayBuffer); + } + } else { + quickjs_shared_array_buffers_.clear(); + } + if (sabTab.tab != nullptr) { + js_free(context, sabTab.tab); + } + is_quickjs_native_message_ = true; + return true; + } + + ClearQuickJSException(context); + } + + is_quickjs_native_message_ = false; + quickjs_shared_array_buffers_.clear(); +#endif + + BufferWriter writer; +#ifdef TARGET_ENGINE_QUICKJS + Serializer serializer(env, writer, &quickjs_shared_array_buffers_); +#else + Serializer serializer(env, writer); +#endif + serializer.Write(input); + main_message_buf_ = writer.Release(); + return true; +} + +napi_value Message::Deserialize(napi_env env) { +#ifdef TARGET_ENGINE_QUICKJS + if (is_quickjs_native_message_) { + JSContext* context = qjs_get_context(env); + if (context == nullptr) { + throw NativeScriptException("QuickJS context is not available."); + } + + JSValue value = JS_ReadObject( + context, reinterpret_cast(main_message_buf_.data), + main_message_buf_.size, JS_READ_OBJ_REFERENCE | JS_READ_OBJ_SAB); + if (JS_IsException(value)) { + JSValue exception = JS_GetException(context); + napi_value error = nullptr; + qjs_create_scoped_value(env, exception, &error); + throw NativeScriptException(env, error, "Unable to deserialize worker message"); + } + + napi_value result = nullptr; + Check(env, qjs_create_scoped_value(env, value, &result), + "Unable to create worker message value"); + ReleaseQuickJSSharedArrayBuffers(); + return result; + } +#endif + + BufferReader reader(main_message_buf_.data, main_message_buf_.size); + Deserializer deserializer(env, reader); + napi_value result = deserializer.Read(); +#ifdef TARGET_ENGINE_QUICKJS + ReleaseQuickJSSharedArrayBuffers(); +#endif + return result; +} + +Message::Message(MallocedBuffer&& payload) + : main_message_buf_(std::move(payload)) {} + +Message::Message(Message&& other) noexcept + : main_message_buf_(std::move(other.main_message_buf_)) { +#ifdef TARGET_ENGINE_QUICKJS + is_quickjs_native_message_ = other.is_quickjs_native_message_; + quickjs_shared_array_buffers_ = + std::move(other.quickjs_shared_array_buffers_); + other.is_quickjs_native_message_ = false; + other.quickjs_shared_array_buffers_.clear(); +#endif +} + +Message& Message::operator=(Message&& other) noexcept { + if (this == &other) { + return *this; + } + +#ifdef TARGET_ENGINE_QUICKJS + ReleaseQuickJSSharedArrayBuffers(); +#endif + main_message_buf_ = std::move(other.main_message_buf_); +#ifdef TARGET_ENGINE_QUICKJS + is_quickjs_native_message_ = other.is_quickjs_native_message_; + quickjs_shared_array_buffers_ = + std::move(other.quickjs_shared_array_buffers_); + other.is_quickjs_native_message_ = false; + other.quickjs_shared_array_buffers_.clear(); +#endif + return *this; +} + +Message::~Message() { +#ifdef TARGET_ENGINE_QUICKJS + ReleaseQuickJSSharedArrayBuffers(); +#endif +} + +#ifdef TARGET_ENGINE_QUICKJS +void Message::ReleaseQuickJSSharedArrayBuffers() { + for (uint8_t* sharedArrayBuffer : quickjs_shared_array_buffers_) { + qjs_shared_array_buffer_data_release(sharedArrayBuffer); + } + quickjs_shared_array_buffers_.clear(); +} +#endif + +}; // namespace worker +}; // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/runtime/modules/worker/MessageJSON.h b/NativeScript/runtime/apple/modules/worker/MessageJSON.h similarity index 88% rename from NativeScript/runtime/modules/worker/MessageJSON.h rename to NativeScript/runtime/apple/modules/worker/MessageJSON.h index 05ffba9a5..b1fd0d2aa 100644 --- a/NativeScript/runtime/modules/worker/MessageJSON.h +++ b/NativeScript/runtime/apple/modules/worker/MessageJSON.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "js_native_api_types.h" namespace nativescript { @@ -82,8 +83,9 @@ namespace worker { class Message { public: Message(MallocedBuffer&& payload = MallocedBuffer()); - Message(Message&& other) = default; - Message& operator=(Message&& other) = default; + Message(Message&& other) noexcept; + Message& operator=(Message&& other) noexcept; + ~Message(); Message& operator=(const Message&) = delete; Message(const Message&) = delete; bool Serialize(napi_env env, napi_value input); @@ -91,6 +93,11 @@ class Message { private: MallocedBuffer main_message_buf_; +#ifdef TARGET_ENGINE_QUICKJS + bool is_quickjs_native_message_ = false; + std::vector quickjs_shared_array_buffers_; + void ReleaseQuickJSSharedArrayBuffers(); +#endif }; }; // namespace worker } // namespace nativescript diff --git a/NativeScript/runtime/modules/worker/MessageV8.cpp b/NativeScript/runtime/apple/modules/worker/MessageV8.cpp similarity index 99% rename from NativeScript/runtime/modules/worker/MessageV8.cpp rename to NativeScript/runtime/apple/modules/worker/MessageV8.cpp index f32c97c40..fb8883417 100644 --- a/NativeScript/runtime/modules/worker/MessageV8.cpp +++ b/NativeScript/runtime/apple/modules/worker/MessageV8.cpp @@ -10,7 +10,7 @@ #include "MessageV8.h" -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "v8-api.h" using namespace v8; diff --git a/NativeScript/runtime/modules/worker/MessageV8.h b/NativeScript/runtime/apple/modules/worker/MessageV8.h similarity index 100% rename from NativeScript/runtime/modules/worker/MessageV8.h rename to NativeScript/runtime/apple/modules/worker/MessageV8.h diff --git a/NativeScript/runtime/apple/modules/worker/Worker.h b/NativeScript/runtime/apple/modules/worker/Worker.h new file mode 100644 index 000000000..9a0c5f8a4 --- /dev/null +++ b/NativeScript/runtime/apple/modules/worker/Worker.h @@ -0,0 +1,36 @@ +#ifndef WORKER_H +#define WORKER_H + +#include +#include +#include + +#include "native_api_util.h" +#include "runtime/apple/modules/worker/Message.h" + +namespace nativescript { + +class Worker { + public: + static void Init(napi_env env, napi_value global, bool isWorkerThread); + static void Init(napi_env env, napi_value global); + + private: + static JS_METHOD(Constructor); + static void Finalize(napi_env env, void* nativeObject, + void* finalize_hint); + static JS_METHOD(PostMessage); + static JS_METHOD(Terminate); + static void OnMessage(napi_env env, napi_value jsThis, + std::shared_ptr); + static JS_METHOD(PostMessageToMain); + static JS_METHOD(CloseWorker); + + static napi_value Serialize(napi_env env, napi_value value); + static void SetWorkerId(napi_env env, int workerId); + static int GetWorkerId(napi_env env, napi_value global); +}; + +} // namespace nativescript + +#endif // WORKER_H diff --git a/NativeScript/runtime/modules/worker/Worker.mm b/NativeScript/runtime/apple/modules/worker/Worker.mm similarity index 92% rename from NativeScript/runtime/modules/worker/Worker.mm rename to NativeScript/runtime/apple/modules/worker/Worker.mm index 039f86f00..223ae6d2d 100644 --- a/NativeScript/runtime/modules/worker/Worker.mm +++ b/NativeScript/runtime/apple/modules/worker/Worker.mm @@ -2,14 +2,14 @@ #include #include #include -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api.h" #include "js_native_api_types.h" #include "jsr.h" #include "native_api_util.h" -#include "runtime/Runtime.h" -#include "runtime/Util.h" -#include "runtime/modules/worker/WorkerImpl.h" +#include "runtime/apple/Runtime.h" +#include "runtime/apple/Util.h" +#include "runtime/apple/modules/worker/WorkerImpl.h" namespace nativescript { @@ -25,18 +25,19 @@ napi_define_properties(env, global, 2, globalProperties); } + napi_value Worker; +#ifdef TARGET_ENGINE_HERMES + napi_create_function(env, "Worker", NAPI_AUTO_LENGTH, Constructor, nullptr, + &Worker); +#else napi_property_descriptor properties[] = { napi_util::desc("postMessage", PostMessage), napi_util::desc("terminate", Terminate), }; - - napi_value Worker; napi_define_class(env, "Worker", NAPI_AUTO_LENGTH, Constructor, nullptr, 2, properties, &Worker); +#endif - napi_property_descriptor globalProperties[] = { - napi_util::desc("Worker", Worker), - }; - napi_define_properties(env, global, 1, globalProperties); + napi_set_named_property(env, global, "Worker", Worker); } JS_METHOD(Worker::Constructor) { @@ -46,6 +47,18 @@ napi_value jsThis; napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, nullptr); +#ifdef TARGET_ENGINE_HERMES + if (!napi_util::is_of_type(env, jsThis, napi_object)) { + napi_create_object(env, &jsThis); + } + + napi_property_descriptor properties[] = { + napi_util::desc("postMessage", PostMessage), + napi_util::desc("terminate", Terminate), + }; + napi_define_properties(env, jsThis, 2, properties); +#endif + std::string workerPath = napi_util::get_cxx_string(env, argv[0]); WorkerImpl* worker = new WorkerImpl(env, Worker::OnMessage); diff --git a/NativeScript/runtime/modules/worker/WorkerImpl.h b/NativeScript/runtime/apple/modules/worker/WorkerImpl.h similarity index 91% rename from NativeScript/runtime/modules/worker/WorkerImpl.h rename to NativeScript/runtime/apple/modules/worker/WorkerImpl.h index 90c23d017..3dbd35f11 100644 --- a/NativeScript/runtime/modules/worker/WorkerImpl.h +++ b/NativeScript/runtime/apple/modules/worker/WorkerImpl.h @@ -5,12 +5,12 @@ #include #include -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api_types.h" #include "native_api_util.h" -#include "runtime/modules/worker/ConcurrentMap.h" -#include "runtime/modules/worker/ConcurrentQueue.h" -#include "runtime/modules/worker/Message.h" +#include "runtime/apple/modules/worker/ConcurrentMap.h" +#include "runtime/apple/modules/worker/ConcurrentQueue.h" +#include "runtime/apple/modules/worker/Message.h" namespace nativescript { diff --git a/NativeScript/runtime/modules/worker/WorkerImpl.mm b/NativeScript/runtime/apple/modules/worker/WorkerImpl.mm similarity index 97% rename from NativeScript/runtime/modules/worker/WorkerImpl.mm rename to NativeScript/runtime/apple/modules/worker/WorkerImpl.mm index 4fa77f345..de3de3185 100644 --- a/NativeScript/runtime/modules/worker/WorkerImpl.mm +++ b/NativeScript/runtime/apple/modules/worker/WorkerImpl.mm @@ -1,16 +1,16 @@ #include #include #include -#include "runtime/NativeScriptException.h" +#include "runtime/apple/NativeScriptException.h" #include "js_native_api.h" #include "js_native_api_types.h" #include "jsr.h" #include "native_api_util.h" #include "WorkerImpl.h" -#include "runtime/Runtime.h" -#include "runtime/Util.h" -#include "runtime/modules/worker/ConcurrentMap.h" +#include "runtime/apple/Runtime.h" +#include "runtime/apple/Util.h" +#include "runtime/apple/modules/worker/ConcurrentMap.h" namespace nativescript { diff --git a/NativeScript/runtime/modules/console/Console.cpp b/NativeScript/runtime/modules/console/Console.cpp deleted file mode 100644 index f8a07a22c..000000000 --- a/NativeScript/runtime/modules/console/Console.cpp +++ /dev/null @@ -1,484 +0,0 @@ -#include "Console.h" - -#include -#include -#include - -#include "runtime/NativeScriptException.h" -#include "js_native_api.h" -#include "native_api_util.h" -#include "runtime/RuntimeConfig.h" -#include "runtime/Util.h" -#ifdef TARGET_ENGINE_V8 -#include "v8-api.h" -#endif - -#ifdef __APPLE__ - -#include -extern "C" void NSLog(CFStringRef format, ...); - -#if defined(__has_include) -#if __has_include() -#include -#define TNS_HAVE_OS_LOG 1 -#else -#define TNS_HAVE_OS_LOG 0 -#endif -#else -#define TNS_HAVE_OS_LOG 0 -#endif - -#else -#include -#endif - -namespace nativescript { - -namespace { - -bool readStringValue(napi_env env, napi_value value, std::string& result) { - if (value == nullptr) { - return false; - } - - size_t length = 0; - if (napi_get_value_string_utf8(env, value, nullptr, 0, &length) != napi_ok) { - return false; - } - - std::vector buffer(length + 1); - size_t copied = 0; - if (napi_get_value_string_utf8(env, value, buffer.data(), buffer.size(), - &copied) != napi_ok) { - return false; - } - - result.assign(buffer.data(), copied); - return true; -} - -bool throwPendingException(napi_env env, const std::string& message) { - bool isPending = false; - if (napi_is_exception_pending(env, &isPending) != napi_ok || !isPending) { - return false; - } - - napi_value exception = nullptr; - napi_get_and_clear_last_exception(env, &exception); - - if (exception != nullptr && - napi_util::is_of_type(env, exception, napi_object)) { - throw NativeScriptException(env, exception, message); - } - - throw NativeScriptException(env, message); -} - -bool coerceToString(napi_env env, napi_value value, std::string& result) { - napi_value stringValue = nullptr; - if (napi_coerce_to_string(env, value, &stringValue) != napi_ok || - stringValue == nullptr) { - throwPendingException(env, "Error converting console argument to string"); - return false; - } - - if (!readStringValue(env, stringValue, result)) { - throwPendingException(env, "Error reading console argument string"); - return false; - } - - return true; -} - -} // namespace - -JS_CLASS_INIT(Console::Init) { - napi_value Console, console; - - const napi_property_descriptor properties[] = { - napi_util::desc("log", Log, (void*)kConsoleLogTypeLog), - napi_util::desc("error", Log, (void*)kConsoleLogTypeError), - napi_util::desc("warn", Log, (void*)kConsoleLogTypeWarn), - napi_util::desc("info", Log, (void*)kConsoleLogTypeInfo), - napi_util::desc("assert", Log, (void*)kConsoleLogTypeAssert), - napi_util::desc("time", Time, nullptr), - napi_util::desc("timeEnd", TimeEnd, nullptr), - napi_util::desc("dir", Dir, nullptr), - napi_util::desc("trace", Trace, nullptr), - }; - - napi_define_class(env, "Console", NAPI_AUTO_LENGTH, Console::Constructor, - nullptr, 9, properties, &Console); - - napi_new_instance(env, Console, 0, nullptr, &console); - - napi_set_named_property(env, global, "Console", Console); - napi_set_named_property(env, global, "console", console); -} - -JS_METHOD(Console::Constructor) { - napi_value thisArg; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); - return thisArg; -} - -std::string transformJSObject(napi_env env, napi_value object) { - napi_value toStringFunc = nullptr; - bool hasToString = false; - - // Check if the object has a toString method - if (napi_has_named_property(env, object, "toString", &hasToString) != - napi_ok) { - throwPendingException(env, "Error reading console object toString"); - return "[object Object]"; - } - - if (hasToString) { - if (napi_get_named_property(env, object, "toString", &toStringFunc) != - napi_ok) { - throwPendingException(env, "Error reading console object toString"); - return "[object Object]"; - } - - if (toStringFunc != nullptr && - napi_util::is_of_type(env, toStringFunc, napi_function)) { - napi_value result = nullptr; - if (napi_call_function(env, object, toStringFunc, 0, nullptr, &result) != - napi_ok || - result == nullptr) { - throwPendingException(env, "Error converting console object to string"); - return "[object Object]"; - } - - std::string value; - if (!coerceToString(env, result, value)) { - return "[object Object]"; - } - - auto hasCustomToStringImplementation = - value.find("[object Object]") == std::string::npos; - if (hasCustomToStringImplementation) return value; - } - } - // If no custom toString method, stringify the object - return JsonStringifyObject(env, object, false); -} - -std::string buildStringFromArg(napi_env env, napi_value val, - napi_value inspectSymbol) { - napi_valuetype type; - if (napi_typeof(env, val, &type) != napi_ok) { - throwPendingException(env, "Error reading console argument type"); - return ""; - } - - if (type == napi_function) { - std::string funcString; - if (coerceToString(env, val, funcString)) { - return funcString; - } - return ""; - } else if (napi_util::is_array(env, val)) { - napi_value cachedSelf = val; - - // Get array length - uint32_t arrayLength = 0; - if (napi_get_array_length(env, val, &arrayLength) != napi_ok) { - throwPendingException(env, "Error reading console array length"); - return "[]"; - } - - std::stringstream arrayStr; - arrayStr << "["; - - for (uint32_t i = 0; i < arrayLength; i++) { - napi_value propertyValue = nullptr; - if (napi_get_element(env, val, i, &propertyValue) != napi_ok || - propertyValue == nullptr) { - throwPendingException(env, "Error reading console array element"); - arrayStr << ""; - if (i != arrayLength - 1) { - arrayStr << ", "; - } - continue; - } - - // Check for circular reference - bool isStrictEqual = false; - if (napi_strict_equals(env, propertyValue, cachedSelf, &isStrictEqual) != - napi_ok) { - throwPendingException(env, "Error comparing console array element"); - } - - if (isStrictEqual) { - arrayStr << "[Circular]"; - } else { - std::string elementString = - buildStringFromArg(env, propertyValue, inspectSymbol); - arrayStr << elementString; - } - - if (i != arrayLength - 1) { - arrayStr << ", "; - } - } - - arrayStr << "]"; - return arrayStr.str(); - } else if (type == napi_object) { - napi_value inspectFunc = nullptr; - napi_status getInspectStatus = - napi_get_property(env, val, inspectSymbol, &inspectFunc); - if (getInspectStatus == napi_ok && - inspectFunc != nullptr && - napi_util::is_of_type(env, inspectFunc, napi_function)) { - napi_value inspectedValue = nullptr; - if (napi_call_function(env, val, inspectFunc, 0, nullptr, - &inspectedValue) != napi_ok || - inspectedValue == nullptr) { - throwPendingException(env, "Error inspecting console object"); - return "[object Object]"; - } - return buildStringFromArg(env, inspectedValue, inspectSymbol); - } else if (getInspectStatus != napi_ok) { - throwPendingException(env, "Error reading console inspect function"); - } - return transformJSObject(env, val); - } else if (type == napi_symbol) { - std::string symString; - if (coerceToString(env, val, symString)) { - return symString; - } - return "Symbol()"; - } else { - std::string defaultToString; - if (coerceToString(env, val, defaultToString)) { - return defaultToString; - } - return ""; - } -} - -std::string buildLogString(napi_env env, napi_callback_info info, - int startingIndex = 0) { - NAPI_CALLBACK_BEGIN_VARGS() - - napi_value global, Symbol, SymbolFor, symbolDescription, inspectSymbol; - napi_get_global(env, &global); - napi_get_named_property(env, global, "Symbol", &Symbol); - napi_get_named_property(env, Symbol, "for", &SymbolFor); - napi_create_string_utf8(env, "nodejs.util.inspect.custom", NAPI_AUTO_LENGTH, - &symbolDescription); - napi_call_function(env, global, SymbolFor, 1, &symbolDescription, - &inspectSymbol); - - std::stringstream ss; - - if (argc) { - for (size_t i = startingIndex; i < argc; i++) { - // separate args with a space - if (i != 0) { - ss << " "; - } - - std::string argString = buildStringFromArg(env, argv[i], inspectSymbol); - ss << argString; - } - } else { - ss << std::endl; - } - - return ss.str(); -} - -JS_METHOD(Console::Log) { - try { - if (!RuntimeConfig.LogToSystemConsole) { - return UNDEFINED; - } - - size_t argc = 0; - ConsoleLogType stream; - void* data = nullptr; - - napi_get_cb_info(env, cbinfo, &argc, nullptr, nullptr, &data); - - stream = ConsoleLogType((unsigned long)data); - - size_t initialArg = 0; - - if (stream == kConsoleLogTypeAssert) { - bool passes = false; - - if (argc > 0) { - napi_value firstArg = nullptr; - napi_get_cb_info(env, cbinfo, &argc, &firstArg, nullptr, nullptr); - napi_coerce_to_bool(env, firstArg, &firstArg); - napi_get_value_bool(env, firstArg, &passes); - } - - if (!passes) { - initialArg = 1; - } else { - return UNDEFINED; - } - } - - napi_value argv[argc]; - napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr); - - std::stringstream log; - - // TODO(dj): what if we made this pretty? - - log << "CONSOLE"; - switch (stream) { - case kConsoleLogTypeLog: - log << " LOG"; - break; - case kConsoleLogTypeError: - log << " ERROR"; - break; - case kConsoleLogTypeWarn: - log << " WARN"; - break; - case kConsoleLogTypeInfo: - log << " INFO"; - break; - case kConsoleLogTypeAssert: - log << " ASSERT FAILED"; - break; - } - log << ": "; - - log << buildLogString(env, cbinfo, initialArg); - - log << "\n"; - - std::string logString = log.str(); - -#ifdef __APPLE__ -#if TARGET_OS_OSX - NSLog(CFSTR("%s"), logString.c_str()); -#elif TNS_HAVE_OS_LOG - os_log(OS_LOG_DEFAULT, "%{public}s", logString.c_str()); -#else - NSLog(CFSTR("%s"), logString.c_str()); -#endif -#else - switch (stream) { - case kConsoleLogTypeLog: - case kConsoleLogTypeInfo: - std::cout << logString; - break; - case kConsoleLogTypeError: - case kConsoleLogTypeWarn: - case kConsoleLogTypeAssert: - std::cerr << logString; - break; - } -#endif - - if (RuntimeConfig.CustomLogCallback) { - RuntimeConfig.CustomLogCallback(logString.c_str()); - } - -#ifdef TARGET_ENGINE_V8 - v8_inspector::ConsoleAPIType method; - switch (stream) { - case kConsoleLogTypeLog: - method = v8_inspector::ConsoleAPIType::kLog; - break; - case kConsoleLogTypeError: - method = v8_inspector::ConsoleAPIType::kError; - break; - case kConsoleLogTypeWarn: - method = v8_inspector::ConsoleAPIType::kWarning; - break; - case kConsoleLogTypeInfo: - method = v8_inspector::ConsoleAPIType::kInfo; - break; - case kConsoleLogTypeAssert: - method = v8_inspector::ConsoleAPIType::kAssert; - break; - default: - break; - } - - sendToDevToolsFrontEnd(env, method, logString); -#endif - } catch (NativeScriptException& e) { - e.ReThrowToJS(env); - } - - return UNDEFINED; -} - -JS_METHOD(Console::Time) { - napi_value thisArg; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); - return thisArg; -} - -JS_METHOD(Console::TimeEnd) { - napi_value thisArg; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); - return thisArg; -} - -JS_METHOD(Console::Dir) { - napi_value thisArg; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); - return thisArg; -} - -JS_METHOD(Console::Trace) { - napi_value thisArg; - napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisArg, nullptr); - return thisArg; -} - -#ifdef TARGET_ENGINE_V8 -int Console::sendToDevToolsFrontEnd(napi_env env, - v8_inspector::ConsoleAPIType method, - napi_callback_info info) { - if (!m_callback) { - return 0; - } - NAPI_CALLBACK_BEGIN_VARGS() - - v8::Local* v8_args = reinterpret_cast*>( - const_cast(argv.data())); - - std::vector> arg_vector; - unsigned nargs = argc; - arg_vector.reserve(nargs); - for (unsigned ix = 0; ix < nargs; ix++) arg_vector.push_back(v8_args[ix]); - - m_callback(env, method, arg_vector); - return 0; -} - -void Console::sendToDevToolsFrontEnd(napi_env env, - v8_inspector::ConsoleAPIType method, - const std::string& message) { - if (!m_callback) { - return; - } - - v8::Local v8_message = - v8::String::NewFromUtf8(env->isolate, message.c_str(), - v8::NewStringType::kNormal, message.length()) - .ToLocalChecked(); - - std::vector> args{ - v8_message, - }; - m_callback(env, method, args); -} -#endif - -ConsoleCallback Console::m_callback = nullptr; - -} // namespace nativescript diff --git a/NativeScript/runtime/modules/module/ModuleInternal.cpp b/NativeScript/runtime/modules/module/ModuleInternal.cpp deleted file mode 100644 index 6d565d955..000000000 --- a/NativeScript/runtime/modules/module/ModuleInternal.cpp +++ /dev/null @@ -1,1739 +0,0 @@ -#include "ModuleInternal.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "native_api_util.h" -#include "runtime/NativeScriptException.h" -#include "runtime/RuntimeConfig.h" -#include "runtime/Util.h" -#include "runtime/modules/node/Node.h" -#include "runtime/modules/web/Web.h" - -#ifdef TARGET_ENGINE_V8 -#include "../../napi/v8/v8-module-loader.h" -#elif defined(TARGET_ENGINE_QUICKJS) -#include "quickjs.h" -#include "quicks-runtime.h" -#endif - -typedef napi_value (*napi_module_init)(napi_env env, napi_value exports); - -namespace nativescript { -extern std::unordered_map napiModuleRegistry; -} - -using namespace nativescript; -using namespace std; - -namespace { - -// Cache for package.json \"type\" field lookups -std::unordered_map g_modulePackageTypeCache; - -// Strip shebang line from source code (e.g., #!/usr/bin/env node) -std::string StripShebang(const std::string& source) { - if (source.size() >= 2 && source[0] == '#' && source[1] == '!') { - size_t lineEnd = source.find('\n'); - if (lineEnd != std::string::npos) { - return source.substr(lineEnd + 1); - } - return ""; // Entire file is just a shebang - } - return source; -} - -#if defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_JSC) -std::string RewriteCommonJSDynamicImportsForFallbackEngines( - const std::string& source) { - static const std::regex kDynamicImportPattern( - R"((^|[^A-Za-z0-9_$\.])import\s*\()", - std::regex::ECMAScript | std::regex::multiline); - return std::regex_replace(source, kDynamicImportPattern, - "$1__dynamicImport("); -} -#endif - -// Check if path has .cjs extension (explicitly CommonJS) -bool IsCJSModule(const std::string& path) { - return path.size() >= 4 && path.compare(path.size() - 4, 4, ".cjs") == 0; -} - -// Find nearest package.json by walking up from directory -std::string FindNearestPackageJson(const std::filesystem::path& startDir) { - std::filesystem::path current = startDir; - - while (!current.empty() && current != current.root_path()) { - std::filesystem::path packagePath = current / "package.json"; - std::error_code ec; - if (std::filesystem::exists(packagePath, ec) && !ec) { - return packagePath.string(); - } - current = current.parent_path(); - } - - return ""; -} - -// Check if package.json has "type": "module" -bool IsPackageTypeModule(const std::string& packageJsonPath) { - auto cacheIt = g_modulePackageTypeCache.find(packageJsonPath); - if (cacheIt != g_modulePackageTypeCache.end()) { - return cacheIt->second; - } - - bool isModule = false; - - std::ifstream file(packageJsonPath); - if (file.is_open()) { - std::string content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - file.close(); - - // Simple JSON parsing for "type": "module" - size_t typePos = content.find("\"type\""); - if (typePos != std::string::npos) { - size_t colonPos = content.find(':', typePos + 6); - if (colonPos != std::string::npos) { - size_t valueStart = content.find('"', colonPos + 1); - if (valueStart != std::string::npos) { - size_t valueEnd = content.find('"', valueStart + 1); - if (valueEnd != std::string::npos) { - std::string typeValue = - content.substr(valueStart + 1, valueEnd - valueStart - 1); - isModule = (typeValue == "module"); - } - } - } - } - } - - g_modulePackageTypeCache[packageJsonPath] = isModule; - return isModule; -} - -// Determine if a .js file should be treated as ESM based on nearest -// package.json -bool ShouldTreatJsAsESModule(const std::string& path) { - std::filesystem::path filePath(path); - std::string packageJson = FindNearestPackageJson(filePath.parent_path()); - - if (!packageJson.empty()) { - return IsPackageTypeModule(packageJson); - } - - return false; // Default to CommonJS -} - -bool PathExistsWithExactCase(const std::filesystem::path& path) { - std::error_code ec; - if (!std::filesystem::exists(path, ec) || ec) { - return false; - } - - std::filesystem::path normalized = path.lexically_normal(); - std::filesystem::path current; - - for (const auto& component : normalized) { - if (component == normalized.root_name() || - component == normalized.root_directory()) { - current /= component; - continue; - } - - if (component == ".") { - continue; - } - - if (component == "..") { - current = current.parent_path(); - continue; - } - - std::filesystem::path parent = current.empty() ? "." : current; - bool matched = false; - std::error_code iterEc; - for (std::filesystem::directory_iterator it(parent, iterEc), end; - !iterEc && it != end; it.increment(iterEc)) { - if (it->path().filename() == component) { - matched = true; - break; - } - } - - if (iterEc) { - // On device the sandbox forbids enumerating everything above the app - // container (/private/var/...), so case verification is only possible - // for listable levels; exists() already vouched for the full path. - current /= component; - continue; - } - if (!matched) { - return false; - } - - current /= component; - } - - return true; -} - -bool IsRegularFileWithExactCase(const std::filesystem::path& path) { - std::error_code ec; - return PathExistsWithExactCase(path) && - std::filesystem::is_regular_file(path, ec) && !ec; -} - -bool IsDirectoryWithExactCase(const std::filesystem::path& path) { - std::error_code ec; - return PathExistsWithExactCase(path) && - std::filesystem::is_directory(path, ec) && !ec; -} - -napi_value LoadRegisteredNapiModule(napi_env env, - const std::string& moduleName) { - const auto loadByName = [&](const std::string& name) -> napi_value { - auto it = nativescript::napiModuleRegistry.find(name); - if (it == nativescript::napiModuleRegistry.end() || it->second == nullptr) { - return nullptr; - } - - napi_value moduleObj; - napi_create_object(env, &moduleObj); - - napi_value exports; - napi_create_object(env, &exports); - - napi_value moduleExports = it->second(env, exports); - - bool hasPendingException = false; - napi_is_exception_pending(env, &hasPendingException); - if (hasPendingException) { - napi_value exception; - napi_get_and_clear_last_exception(env, &exception); - throw NativeScriptException( - env, exception, "Error initializing native module '" + name + "'"); - } - - if (moduleExports == nullptr) { - moduleExports = exports; - } - - napi_set_named_property(env, moduleObj, "exports", moduleExports); - return moduleObj; - }; - - napi_value moduleObj = loadByName(moduleName); - if (moduleObj != nullptr) { - return moduleObj; - } - - if (moduleName.rfind("node:", 0) == 0) { - return loadByName(moduleName.substr(5)); - } - - return nullptr; -} - -std::string ModulePathToURL(const std::string& modulePath) { - if (modulePath.rfind("file://", 0) == 0) { - return modulePath; - } - - if (modulePath.rfind("nativescript:", 0) == 0 || - modulePath.rfind("node:", 0) == 0) { - return "nativescript:" + modulePath; - } - - if (!modulePath.empty() && modulePath[0] == '/') { - return "file://" + modulePath; - } - - return "nativescript:" + modulePath; -} - -bool IsNodeBuiltinSpecifier(const std::string& specifier) { - static const std::unordered_set kBuiltins = { - "url", "node:url", "fs", "node:fs", - "fs/promises", "node:fs/promises", "path", "node:path", - "vm", "node:vm", "web", "node:web", - "stream/web", "node:stream/web"}; - return kBuiltins.contains(specifier); -} - -std::string NormalizeNodeBuiltinSpecifier(const std::string& specifier) { - if (specifier.rfind("node:", 0) == 0) { - return specifier.substr(5); - } - return specifier; -} - -std::string EscapeForSingleQuotedJsString(const std::string& value) { - std::string escaped; - escaped.reserve(value.size()); - - for (char c : value) { - switch (c) { - case '\\': - escaped += "\\\\"; - break; - case '\'': - escaped += "\\'"; - break; - case '\n': - escaped += "\\n"; - break; - case '\r': - escaped += "\\r"; - break; - default: - escaped += c; - break; - } - } - - return escaped; -} - -std::string NormalizeRegisteredNapiModuleSpecifier( - const std::string& specifier) { - auto it = nativescript::napiModuleRegistry.find(specifier); - if (it != nativescript::napiModuleRegistry.end() && it->second != nullptr) { - return specifier; - } - - if (specifier.rfind("node:", 0) == 0) { - std::string withoutPrefix = specifier.substr(5); - it = nativescript::napiModuleRegistry.find(withoutPrefix); - if (it != nativescript::napiModuleRegistry.end() && it->second != nullptr) { - return withoutPrefix; - } - } - - return ""; -} - -std::string GetRegisteredNapiESModuleSource(const std::string& specifier) { - std::string normalized = NormalizeRegisteredNapiModuleSpecifier(specifier); - if (normalized.empty()) { - return ""; - } - - std::string escapedSpecifier = EscapeForSingleQuotedJsString(normalized); - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load native module '${name}'`); -}; -const __nativeModule = __load(')" + - escapedSpecifier + R"('); -export default __nativeModule; -)"; -} - -std::string GetBuiltinESModuleSource(const std::string& specifier) { - const auto builtinName = NormalizeNodeBuiltinSpecifier(specifier); - - if (builtinName == "url") { - return R"( -const __toURL = (input) => input instanceof URL ? input : new URL(String(input)); -export const URL = globalThis.URL; -export const URLSearchParams = globalThis.URLSearchParams; -export function pathToFileURL(path) { - const value = String(path); - return new URL(value.startsWith("/") ? `file://${value}` : `file:///${value}`); -} -export function fileURLToPath(value) { - const u = __toURL(value); - if (u.protocol !== "file:") { - throw new TypeError("The URL must be of scheme file:"); - } - return decodeURIComponent(u.pathname); -} -export default { URL, URLSearchParams, pathToFileURL, fileURLToPath }; -)"; - } - - if (builtinName == "fs") { - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load builtin module '${name}'`); -}; -const __fs = __load("node:fs"); -export const readFileSync = __fs.readFileSync; -export const writeFileSync = __fs.writeFileSync; -export const existsSync = __fs.existsSync; -export const mkdirSync = __fs.mkdirSync; -export const readdirSync = __fs.readdirSync; -export const statSync = __fs.statSync; -export const lstatSync = __fs.lstatSync; -export const unlinkSync = __fs.unlinkSync; -export const rmSync = __fs.rmSync; -export const readFile = __fs.readFile; -export const writeFile = __fs.writeFile; -export const constants = __fs.constants; -export const promises = __fs.promises; -export default __fs; -)"; - } - - if (builtinName == "fs/promises") { - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load builtin module '${name}'`); -}; -const __fsp = __load("node:fs").promises; -export const readFile = __fsp.readFile; -export const writeFile = __fsp.writeFile; -export const mkdir = __fsp.mkdir; -export const readdir = __fsp.readdir; -export const stat = __fsp.stat; -export const lstat = __fsp.lstat; -export const unlink = __fsp.unlink; -export const rm = __fsp.rm; -export default __fsp; -)"; - } - - if (builtinName == "path") { - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load builtin module '${name}'`); -}; -const __path = __load("node:path"); -export const basename = __path.basename; -export const dirname = __path.dirname; -export const extname = __path.extname; -export const isAbsolute = __path.isAbsolute; -export const join = __path.join; -export const normalize = __path.normalize; -export const parse = __path.parse; -export const format = __path.format; -export const relative = __path.relative; -export const resolve = __path.resolve; -export const toNamespacedPath = __path.toNamespacedPath; -export const sep = __path.sep; -export const delimiter = __path.delimiter; -export const posix = __path.posix; -export const win32 = __path.win32; -export default __path; -)"; - } - - if (builtinName == "vm") { - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load builtin module '${name}'`); -}; -const __vm = __load("node:vm"); -export const Script = __vm.Script; -export const Module = __vm.Module; -export const SourceTextModule = __vm.SourceTextModule; -export const SyntheticModule = __vm.SyntheticModule; -export const compileFunction = __vm.compileFunction; -export const constants = __vm.constants; -export const createContext = __vm.createContext; -export const isContext = __vm.isContext; -export const measureMemory = __vm.measureMemory; -export const runInContext = __vm.runInContext; -export const runInNewContext = __vm.runInNewContext; -export const runInThisContext = __vm.runInThisContext; -export default __vm; -)"; - } - - if (builtinName == "web") { - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load builtin module '${name}'`); -}; -const __web = __load("web"); -export const fetch = __web.fetch; -export const Headers = __web.Headers; -export const Request = __web.Request; -export const Response = __web.Response; -export const WebSocket = __web.WebSocket; -export const ReadableStream = __web.ReadableStream; -export const WritableStream = __web.WritableStream; -export const TransformStream = __web.TransformStream; -export default __web; -)"; - } - - if (builtinName == "stream/web") { - return R"( -const __load = (name) => { - if (typeof globalThis.require === "function") { - return globalThis.require(name); - } - if (typeof globalThis.__nativeRequire === "function") { - const dir = typeof globalThis.__approot === "string" ? `${globalThis.__approot}/app` : ""; - return globalThis.__nativeRequire(name, dir); - } - throw new Error(`Cannot load builtin module '${name}'`); -}; -const __streamWeb = __load("stream/web"); -export const ReadableStream = __streamWeb.ReadableStream; -export const ReadableStreamDefaultReader = __streamWeb.ReadableStreamDefaultReader; -export const WritableStream = __streamWeb.WritableStream; -export const TransformStream = __streamWeb.TransformStream; -export const ByteLengthQueuingStrategy = __streamWeb.ByteLengthQueuingStrategy; -export const CountQueuingStrategy = __streamWeb.CountQueuingStrategy; -export default __streamWeb; -)"; - } - - return ""; -} -} // namespace - -ModuleInternal::ModuleInternal() - : m_env(nullptr), - m_requireFunction(nullptr), - m_requireFactoryFunction(nullptr) {} - -void ModuleInternal::DeInit() { -#ifdef TARGET_ENGINE_V8 - for (auto& kv : v8impl::g_moduleRegistry) { - kv.second.Reset(); - } - v8impl::g_moduleRegistry.clear(); -#endif - - // Clear the package.json type cache - g_modulePackageTypeCache.clear(); - - if (m_env != nullptr) { - napi_delete_reference(m_env, this->m_requireFunction); - napi_delete_reference(m_env, this->m_requireFactoryFunction); - } - - for (const auto& pair : this->m_requireCache) { - if (m_env != nullptr) { - napi_delete_reference(m_env, pair.second); - } - } - this->m_requireCache.clear(); -} - -void ModuleInternal::Init(napi_env env, const std::string& baseDir) { - napi_status status; - - m_env = env; - -#ifdef TARGET_ENGINE_V8 - // Bootstrap V8 ES module hooks early so dynamic import() from CommonJS - // settles even before the first explicit .mjs module load. - napi_value bootstrapSource; - if (napi_create_string_utf8(env, "export default 0;", NAPI_AUTO_LENGTH, - &bootstrapSource) == napi_ok) { - const std::string bootstrapModulePath = - RuntimeConfig.ApplicationPath + "/app/.nativescript-esm-bootstrap.mjs"; - napi_value bootstrapNamespace; - napi_status bootstrapStatus = napi_run_script_as_module( - env, bootstrapSource, bootstrapModulePath.c_str(), &bootstrapNamespace); - if (bootstrapStatus != napi_ok) { - bool pendingException = false; - napi_is_exception_pending(env, &pendingException); - if (pendingException) { - napi_value ignored; - napi_get_and_clear_last_exception(env, &ignored); - } - } - } -#endif - - const char* requireFactoryScript = R"( - (function () { - return function require_factory(requireInternal, dirName) { - return function require(modulePath) { - if(typeof global.__requireOverride !== "undefined") { - var result = global.__requireOverride(modulePath, dirName); - if(result) { - return result; - } - } - return requireInternal(modulePath, dirName); - } - } -})(); -)"; - - napi_value source; - napi_create_string_utf8(env, requireFactoryScript, NAPI_AUTO_LENGTH, &source); - - napi_value global; - napi_get_global(env, &global); - - napi_value globalEnv; - napi_create_external(env, env, nullptr, nullptr, &globalEnv); - napi_set_named_property(env, global, "__globalEnv", globalEnv); - - napi_value result; - status = napi_run_script(env, source, &result); - assert(status == napi_ok); - - m_requireFactoryFunction = napi_util::make_ref(m_env, result); - - napi_value requireFunction = napi_util::napi_set_function( - env, global, "__nativeRequire", RequireCallback, this); - m_requireFunction = napi_util::make_ref(m_env, requireFunction); - - napi_value globalRequire = GetRequireFunction( - env, baseDir.empty() ? RuntimeConfig.ApplicationPath : baseDir); - status = napi_set_named_property(env, global, "require", globalRequire); - assert(status == napi_ok); - -#if defined(TARGET_ENGINE_QUICKJS) - InitQuickJSESModuleLoader(env); -#endif -} - -napi_value ModuleInternal::GetRequireFunction(napi_env env, - const std::string& dirName) { - napi_value requireFunc; - - auto itFound = m_requireCache.find(dirName); - - if (itFound != m_requireCache.end()) { - requireFunc = napi_util::get_ref_value(env, itFound->second); - } else { - napi_value requireFuncFactory = - napi_util::get_ref_value(env, m_requireFactoryFunction); - - napi_value requireInternalFunc = - napi_util::get_ref_value(env, m_requireFunction); - - napi_value args[2]; - args[0] = requireInternalFunc; - napi_create_string_utf8(env, dirName.c_str(), NAPI_AUTO_LENGTH, &args[1]); - - napi_value thiz; - napi_create_object(env, &thiz); - - napi_value result; - napi_status status = - napi_call_function(env, thiz, requireFuncFactory, 2, args, &result); - assert(status == napi_ok && result != nullptr); - - bool isFunction = napi_util::is_of_type(env, result, napi_function); - assert(isFunction); - - requireFunc = result; - - napi_ref poFunc = napi_util::make_ref(env, requireFunc); - m_requireCache.emplace(dirName, poFunc); - } - - return requireFunc; -} - -napi_value ModuleInternal::RequireCallback(napi_env env, - napi_callback_info info) { - NAPI_CALLBACK_BEGIN(0) - try { - auto thiz = static_cast(data); - return thiz->RequireCallbackImpl(env, info); - } catch (NativeScriptException& e) { - e.ReThrowToJS(env); - } catch (std::exception& e) { - stringstream ss; - ss << "Error: C++ Exception: " << e.what() << endl; - NativeScriptException nsEx(ss.str()); - nsEx.ReThrowToJS(env); - } catch (...) { - NativeScriptException nsEx(std::string("Error: c++ exception!")); - nsEx.ReThrowToJS(env); - } - - return nullptr; -} - -napi_value ModuleInternal::Require(napi_env env, const std::string& moduleName, - const std::string& callingModuleDirName) { - auto isData = false; - - napi_value moduleObj = - LoadImpl(env, moduleName, callingModuleDirName, isData); - - if (isData) { - assert(!napi_util::is_null_or_undefined(env, moduleObj)); - return moduleObj; - } else { - // Check if this is an ES module by looking for __esModule property - bool hasEsModuleProp; - napi_status status = - napi_has_named_property(env, moduleObj, "__esModule", &hasEsModuleProp); - - bool isEsModule = false; - if (status == napi_ok && hasEsModuleProp) { - napi_value esModuleFlag; - napi_get_named_property(env, moduleObj, "__esModule", &esModuleFlag); - napi_get_value_bool(env, esModuleFlag, &isEsModule); - } - - if (isEsModule) { - // For ES modules, return the module namespace directly - return moduleObj; - } else { - // For CommonJS modules, return the exports - napi_value exports; - napi_get_named_property(env, moduleObj, "exports", &exports); - assert(!napi_util::is_null_or_undefined(env, exports)); - return exports; - } - } -} - -napi_value ModuleInternal::RequireCallbackImpl(napi_env env, - napi_callback_info info) { - NAPI_CALLBACK_BEGIN_VARGS() - - if (argc != 2) { - throw NativeScriptException( - string("require should be called with two parameters")); - } - if (!napi_util::is_of_type(env, argv[0], napi_string)) { - throw NativeScriptException( - string("require's first parameter should be string")); - } - if (!napi_util::is_of_type(env, argv[1], napi_string)) { - throw NativeScriptException( - string("require's second parameter should be string")); - } - - string moduleName = napi_util::get_cxx_string(env, argv[0]); - string callingModuleDirName = napi_util::get_cxx_string(env, argv[1]); - - try { - return Require(env, moduleName, callingModuleDirName); - } catch (NativeScriptException& e) { - e.ReThrowToJS(env); - return nullptr; - } -} - -napi_value ModuleInternal::RequireNativeCallback(napi_env env, - napi_callback_info info) { - void* data; - napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data); - auto cb = reinterpret_cast(data); - napi_value exports; - napi_create_object(env, &exports); - return cb(env, exports); -} - -napi_status ModuleInternal::Load(napi_env env, const std::string& path) { - napi_value global; - napi_get_global(env, &global); - - napi_value require; - napi_get_named_property(env, global, "require", &require); - - napi_value args[1]; - napi_create_string_utf8(env, path.c_str(), path.size(), &args[0]); - - napi_value result; - napi_status status = - napi_call_function(env, global, require, 1, args, &result); - return status; -} - -void ModuleInternal::LoadWorker(napi_env env, const string& path) { - Load(env, path); - bool hasPendingException; - napi_is_exception_pending(env, &hasPendingException); - - if (hasPendingException) { - napi_value error; - napi_get_and_clear_last_exception(env, &error); - // TODO - // CallbackHandlers::CallWorkerScopeOnErrorHandle(env, error); - } -} - -void ModuleInternal::CheckFileExists(napi_env env, const std::string& path, - const std::string& baseDir) { - struct stat buffer; - if (stat(path.c_str(), &buffer) != 0) { - std::string errMsg = "Module not found: " + path; - throw NativeScriptException(errMsg); - } - if (baseDir != "") { - std::string fullPath = baseDir + "/" + path; - if (stat(fullPath.c_str(), &buffer) != 0) { - std::string errMsg = "Module not found: " + fullPath; - throw NativeScriptException(errMsg); - } - } -} - -napi_value ModuleInternal::LoadInternalModule(napi_env env, - const std::string& moduleName) { - auto nodeModule = Node::LoadInternalModule(env, moduleName); - if (nodeModule != nullptr) { - return nodeModule; - } - -#ifdef __APPLE__ - auto webModule = Web::LoadInternalModule(env, moduleName); - if (webModule != nullptr) { - return webModule; - } -#endif - - auto napiModule = LoadRegisteredNapiModule(env, moduleName); - if (napiModule != nullptr) { - return napiModule; - } - - if (moduleName == "url" || moduleName == "node:url") { - napi_value moduleObj; - napi_create_object(env, &moduleObj); - napi_value url; - napi_value exports; - napi_create_object(env, &exports); - napi_get_named_property(env, napi_util::global(env), "URL", &url); - napi_set_named_property(env, exports, "URL", url); - napi_set_named_property(env, moduleObj, "exports", exports); - napi_util::napi_set_function( - env, exports, "pathToFileURL", - [](napi_env env, napi_callback_info info) -> napi_value { - return napi_util::to_js_string(env, "file://"); - }); - return moduleObj; - } - return nullptr; -} - -std::string ModuleInternal::ResolvePathFromPackageJson( - napi_env env, const std::string& packageJsonPath, bool& error) { - error = false; - if (!IsRegularFileWithExactCase(packageJsonPath)) { - return ""; - } - - std::ifstream packageJsonFile(packageJsonPath); - if (!packageJsonFile.is_open()) { - // Missing package.json is not fatal for directory resolution. - error = false; - return ""; - } - std::string line; - std::stringstream packageJsonStream; - while (std::getline(packageJsonFile, line)) { - packageJsonStream << line; - } - packageJsonFile.close(); - std::string packageJson = packageJsonStream.str(); - napi_value obj = JsonParse(env, packageJson); - if (obj == nullptr) { - bool hasPendingException = false; - napi_is_exception_pending(env, &hasPendingException); - if (hasPendingException) { - napi_value exception; - napi_get_and_clear_last_exception(env, &exception); - } - error = true; - return ""; - } - bool hasMain = false; - napi_status hasMainStatus = - napi_has_named_property(env, obj, "main", &hasMain); - if (hasMainStatus != napi_ok || !hasMain) { - // package.json without "main" should fall back to - // index.js/index.mjs/index.cjs - error = false; - return ""; - } - napi_value mainValue; - napi_get_named_property(env, obj, "main", &mainValue); - if (mainValue == nullptr) { - error = false; - return ""; - } - - napi_valuetype type; - napi_typeof(env, mainValue, &type); - if (type != napi_string) { - error = false; - return ""; - } - - std::string main = napi_util::get_cxx_string(env, mainValue); - if (main.empty()) { - error = false; - return ""; - } - - std::filesystem::path packageJsonDir(packageJsonPath); - std::filesystem::path packageJsonDirName = - packageJsonDir.parent_path().string(); - std::filesystem::path mainPath = packageJsonDirName / main; - - if (IsDirectoryWithExactCase(mainPath)) { - mainPath = mainPath / "package.json"; - if (IsRegularFileWithExactCase(mainPath)) { - return ResolvePathFromPackageJson(env, mainPath.string(), error); - } - - std::filesystem::path indexMjs = mainPath.parent_path() / "index.mjs"; - if (IsRegularFileWithExactCase(indexMjs)) { - return indexMjs.string(); - } - - std::filesystem::path indexJs = mainPath.parent_path() / "index.js"; - if (IsRegularFileWithExactCase(indexJs)) { - return indexJs.string(); - } - - std::filesystem::path indexCjs = mainPath.parent_path() / "index.cjs"; - if (IsRegularFileWithExactCase(indexCjs)) { - return indexCjs.string(); - } - - error = false; - return ""; - } - - if (IsRegularFileWithExactCase(mainPath)) { - return mainPath.string(); - } - - // Support extensionless "main" entries (e.g. "bundle") by resolving to - // modern ESM/CJS bundle outputs. - if (!mainPath.has_extension()) { - std::filesystem::path mjsPath = mainPath; - mjsPath.replace_extension(".mjs"); - if (IsRegularFileWithExactCase(mjsPath)) { - return mjsPath.string(); - } - - std::filesystem::path jsPath = mainPath; - jsPath.replace_extension(".js"); - if (IsRegularFileWithExactCase(jsPath)) { - return jsPath.string(); - } - - std::filesystem::path cjsPath = mainPath; - cjsPath.replace_extension(".cjs"); - if (IsRegularFileWithExactCase(cjsPath)) { - return cjsPath.string(); - } - } - - // Unresolvable "main" should fall back to index.js/index.mjs/index.cjs. - error = false; - return ""; -} - -std::string ModuleInternal::ResolvePath(napi_env env, - const std::string& baseDir, - const std::string& moduleName) { - std::string moduleNameCopy = moduleName; - - if (moduleName.starts_with("~")) { - moduleNameCopy = RuntimeConfig.ApplicationPath + moduleNameCopy.substr(1); - } - - std::filesystem::path baseDirPath(baseDir); - std::filesystem::path moduleNamePath(moduleNameCopy); - std::filesystem::path fullPath = baseDirPath / moduleNamePath; - - // Normalize the path to remove redundant ./ sequences - fullPath = fullPath.lexically_normal(); - - bool exists = PathExistsWithExactCase(fullPath); - bool isDirectory = exists && IsDirectoryWithExactCase(fullPath); - bool isNonRelativeModule = - !moduleNameCopy.starts_with("./") && !moduleNameCopy.starts_with("../") && - !moduleNameCopy.starts_with("/") && !moduleNameCopy.starts_with("~"); - - if (exists == true && isDirectory == true) { - // Try .mjs first for ES modules - std::filesystem::path mjsFile = fullPath; - mjsFile = mjsFile.replace_extension(".mjs"); - if (IsRegularFileWithExactCase(mjsFile)) { - return mjsFile.string(); - } - - // Then try .js for CommonJS - std::filesystem::path jsFile = fullPath; - jsFile = jsFile.replace_extension(".js"); - if (IsRegularFileWithExactCase(jsFile)) { - return jsFile.string(); - } - - // Then try .cjs for explicit CommonJS - std::filesystem::path cjsFile = fullPath; - cjsFile = cjsFile.replace_extension(".cjs"); - if (IsRegularFileWithExactCase(cjsFile)) { - return cjsFile.string(); - } - } - - if (exists == false) { - std::filesystem::path appModulesPath; - bool hasAppModulesBase = false; - - if (isNonRelativeModule) { - appModulesPath = std::filesystem::path(RuntimeConfig.ApplicationPath) / - "tns_modules" / moduleNameCopy; - hasAppModulesBase = true; - if (PathExistsWithExactCase(appModulesPath)) { - fullPath = appModulesPath; - exists = true; - isDirectory = IsDirectoryWithExactCase(fullPath); - } else { - // Preserve legacy runtime behavior for extensionless non-relative - // requires by probing app/tns_modules before caller-relative paths. - std::filesystem::path appModulesMjs = appModulesPath.string() + ".mjs"; - if (IsRegularFileWithExactCase(appModulesMjs)) { - return appModulesMjs.string(); - } - - std::filesystem::path appModulesJs = appModulesPath.string() + ".js"; - if (IsRegularFileWithExactCase(appModulesJs)) { - return appModulesJs.string(); - } - - std::filesystem::path appModulesJson = - appModulesPath.string() + ".json"; - if (IsRegularFileWithExactCase(appModulesJson)) { - return appModulesJson.string(); - } - - std::filesystem::path appModulesCjs = appModulesPath.string() + ".cjs"; - if (IsRegularFileWithExactCase(appModulesCjs)) { - return appModulesCjs.string(); - } - } - } - - // Try .mjs extension first (ES modules have priority) - std::filesystem::path mjsBase = - hasAppModulesBase ? appModulesPath : fullPath; - std::filesystem::path mjsPath = mjsBase.string() + ".mjs"; - if (PathExistsWithExactCase(mjsPath)) { - exists = true; - isDirectory = IsDirectoryWithExactCase(mjsPath); - if (!isDirectory) { - return mjsPath.string(); - } - } - - // Try .js extension by appending, preserving dotted filenames - std::filesystem::path jsBase = - hasAppModulesBase ? appModulesPath : fullPath; - std::filesystem::path jsPath = jsBase.string() + ".js"; - if (PathExistsWithExactCase(jsPath)) { - exists = true; - isDirectory = IsDirectoryWithExactCase(jsPath); - fullPath = jsPath; - } - - // Try .cjs extension for explicit CommonJS - if (!exists) { - std::filesystem::path cjsBase = - hasAppModulesBase ? appModulesPath : fullPath; - std::filesystem::path cjsPath = cjsBase.string() + ".cjs"; - if (PathExistsWithExactCase(cjsPath)) { - exists = true; - isDirectory = IsDirectoryWithExactCase(cjsPath); - fullPath = cjsPath; - } - } - } - - if (exists == false) { - throw NativeScriptException("The specified module does not exist: " + - moduleName); - } - - if (isDirectory == false) { - return fullPath.string(); - } - - std::filesystem::path packageJson = fullPath / "package.json"; - if (std::filesystem::is_regular_file(packageJson)) { - bool error = false; - std::string entry = - this->ResolvePathFromPackageJson(env, packageJson.string(), error); - if (error) { - throw NativeScriptException("Unable to locate main entry in " + - packageJson.string()); - } - - if (!entry.empty()) { - fullPath = entry; - } - } - - exists = PathExistsWithExactCase(fullPath); - isDirectory = exists && IsDirectoryWithExactCase(fullPath); - if (exists == true && isDirectory == false) { - return fullPath.string(); - } - - if (exists == false) { - // Try index.mjs first - std::filesystem::path indexMjs = fullPath.parent_path() / "index.mjs"; - if (IsRegularFileWithExactCase(indexMjs)) { - return indexMjs.string(); - } - - // Then try path + ".js" (preserves dotted filenames like "file.name") - std::filesystem::path jsCandidate = fullPath.string() + ".js"; - if (IsRegularFileWithExactCase(jsCandidate)) { - return jsCandidate.string(); - } - - // Finally try path + ".cjs" for explicit CommonJS entry files. - fullPath = fullPath.string() + ".cjs"; - } else { - // Try index.mjs first in directory - std::filesystem::path indexMjs = fullPath / "index.mjs"; - if (IsRegularFileWithExactCase(indexMjs)) { - return indexMjs.string(); - } - - // Then try index.js - std::filesystem::path indexJs = fullPath / "index.js"; - if (IsRegularFileWithExactCase(indexJs)) { - fullPath = indexJs; - } else { - // Then try index.cjs - std::filesystem::path indexCjs = fullPath / "index.cjs"; - if (IsRegularFileWithExactCase(indexCjs)) { - return indexCjs.string(); - } - fullPath /= "index.js"; - } - } - - exists = PathExistsWithExactCase(fullPath); - if (exists == false) { - throw NativeScriptException("The specified module does not exist: " + - moduleName); - } - return fullPath.string(); -} - -napi_value ModuleInternal::LoadImpl(napi_env env, const std::string& moduleName, - const std::string& baseDir, bool& isData) { - auto pathKind = GetModulePathKind(moduleName); - auto cachePathKey = (pathKind == ModulePathKind::Global) - ? moduleName - : (baseDir + "*" + moduleName); - - napi_value result; - - // DEBUG_WRITE(">>LoadImpl cachePathKey=%s", cachePathKey.c_str()); - - auto it = m_loadedModules.find(cachePathKey); - - if (it == m_loadedModules.end()) { - /** - * Load internal modules like url, fs and statically registered - * N-API modules directly for global requires. - */ - napi_value moduleObj = ModuleInternal::LoadInternalModule(env, moduleName); - if (moduleObj != nullptr) { - auto poModuleObj = napi_util::make_ref(env, moduleObj); - m_loadedModules.emplace(cachePathKey, ModuleCacheEntry(poModuleObj)); - return moduleObj; - } - - std::string path; - - // Search App System libs - std::string sys_lib("system_lib://"); - if (moduleName.rfind(sys_lib, 0) == 0) { - auto pos = moduleName.find(sys_lib); - path = std::string(moduleName); - path.replace(pos, sys_lib.length(), ""); - } else if (moduleName.ends_with(".so") || moduleName.ends_with(".dylib") || - moduleName.ends_with(".node")) { - path = moduleName; - } else { - path = ResolvePath(env, baseDir, moduleName); - } - - auto it2 = m_loadedModules.find(path); - - if (it2 == m_loadedModules.end()) { - if (path.ends_with(".js") || path.ends_with(".mjs") || - path.ends_with(".cjs") || path.ends_with(".so") || - path.ends_with(".dylib") || path.ends_with(".node")) { - isData = false; - result = LoadModule(env, path, cachePathKey); - } else if (path.ends_with(".json")) { - isData = true; - result = LoadData(env, path); - } else { - std::filesystem::path filePath(path); - - if (std::filesystem::is_directory(filePath)) { - std::filesystem::path packageJson = filePath / "package.json"; - if (std::filesystem::is_regular_file(packageJson)) { - bool error = false; - std::string packageMain = - ResolvePathFromPackageJson(env, packageJson.string(), error); - if (error) { - throw NativeScriptException("Unable to locate main entry in " + - packageJson.string()); - } - if (!packageMain.empty() && packageMain != path) { - return LoadImpl(env, packageMain, baseDir, isData); - } - } - } - - std::filesystem::path fileWithIndexJs = filePath / "index.js"; - std::filesystem::path fileWithIndexMjs = filePath / "index.mjs"; - std::filesystem::path fileWithIndexCjs = filePath / "index.cjs"; - if (IsRegularFileWithExactCase(fileWithIndexMjs)) { - return LoadImpl(env, fileWithIndexMjs.string(), baseDir, isData); - } else if (IsRegularFileWithExactCase(fileWithIndexJs)) { - return LoadImpl(env, fileWithIndexJs.string(), baseDir, isData); - } else if (IsRegularFileWithExactCase(fileWithIndexCjs)) { - return LoadImpl(env, fileWithIndexCjs.string(), baseDir, isData); - } - std::string errMsg = "Unsupported file extension: " + path; - throw NativeScriptException(errMsg); - } - } else { - auto& cacheEntry = it2->second; - isData = cacheEntry.isData; - result = napi_util::get_ref_value(env, cacheEntry.obj); - } - } else { - auto& cacheEntry = it->second; - isData = cacheEntry.isData; - result = napi_util::get_ref_value(env, cacheEntry.obj); - } - - return result; -} - -std::string ModuleInternal::EnsureFileProtocol(const std::string& path) { - const std::string protocol = "file://"; - if (path.compare(0, protocol.length(), protocol) != 0) { - return protocol + path; - } - return path; -} - -napi_value ModuleInternal::LoadModule(napi_env env, - const std::string& modulePath, - const std::string& moduleCacheKey) { - napi_value result; - - napi_value context; - napi_get_global(env, &context); - - napi_value moduleObj; - napi_create_object(env, &moduleObj); - - napi_value exportsObj; - napi_create_object(env, &exportsObj); - - napi_set_named_property(env, moduleObj, "exports", exportsObj); - - napi_value fullRequiredModulePath; - napi_create_string_utf8(env, modulePath.c_str(), modulePath.size(), - &fullRequiredModulePath); - napi_set_named_property(env, moduleObj, "filename", fullRequiredModulePath); - - napi_ref poModuleObj = napi_util::make_ref(env, moduleObj); - TempModule tempModule(this, modulePath, moduleCacheKey, poModuleObj); - - napi_value moduleFunc; - - if (modulePath.ends_with(".mjs")) { - // Handle ES modules - napi_value esModuleResult = LoadESModule(env, modulePath); - - // Mark the result as an ES module - napi_value isESModuleFlag; - napi_get_boolean(env, true, &isESModuleFlag); - napi_set_named_property(env, esModuleResult, "__esModule", isESModuleFlag); - - // For ES modules, we return the namespace directly, not wrapped in a module - // object - tempModule.SaveToCache(); - return esModuleResult; - } else if (modulePath.ends_with(".js") || modulePath.ends_with(".cjs")) { - napi_value script = LoadScript(env, modulePath, fullRequiredModulePath); - // DEBUG_WRITE("%s", modulePath.c_str()); - - // napi_status status = js_execute_script( - // env, script, EnsureFileProtocol(modulePath).c_str(), &moduleFunc); - napi_status status = - napi_run_script_source(env, script, modulePath.c_str(), &moduleFunc); - if (status != napi_ok) { - bool pendingException; - napi_is_exception_pending(env, &pendingException); - napi_value error = nullptr; - if (pendingException) { - napi_get_and_clear_last_exception(env, &error); - } - if (error) { - throw NativeScriptException(env, error, - "Error running script " + modulePath); - } else { - throw NativeScriptException("Error running script " + modulePath); - } - } - } else if (modulePath.ends_with(".so") || modulePath.ends_with(".dylib") || - modulePath.ends_with(".node")) { - auto handle = dlopen(modulePath.c_str(), RTLD_NOW); - if (handle == nullptr) { - auto error = dlerror(); - std::string errMsg(error); - throw NativeScriptException(errMsg); - } - auto func = dlsym(handle, "napi_register_module_v1"); - - if (func == nullptr) { - std::string errMsg("Cannot find 'napi_register_module_v1' in " + - modulePath); - throw NativeScriptException(errMsg); - } - - auto cb = reinterpret_cast(func); - napi_value exports; - napi_create_object(env, &exports); - napi_value result = cb(env, exports); - napi_set_named_property(env, moduleObj, "exports", result); - tempModule.SaveToCache(); - return moduleObj; - } else { - std::string errMsg = "Unsupported file extension: " + modulePath; - throw NativeScriptException(errMsg); - } - - napi_value fileName; - napi_create_string_utf8(env, modulePath.c_str(), modulePath.size(), - &fileName); - - char pathcopy[1024]; - strcpy(pathcopy, modulePath.c_str()); - std::string strDirName(dirname(pathcopy)); - - napi_value dirName; - napi_create_string_utf8(env, strDirName.c_str(), strDirName.size(), &dirName); - - napi_value require = GetRequireFunction(env, strDirName); - - napi_value requireArgs[5] = {moduleObj, exportsObj, require, fileName, - dirName}; - - napi_set_named_property(env, moduleObj, "require", require); - napi_util::define_property(env, moduleObj, "id", fileName); - - napi_value thiz; - napi_create_object(env, &thiz); - - napi_value globalExtends; - napi_get_named_property(env, context, "__extends", &globalExtends); - napi_set_named_property(env, thiz, "__extends", globalExtends); - - napi_value callResult; - napi_status status = - napi_call_function(env, thiz, moduleFunc, 5, requireArgs, &callResult); - bool pendingException; - napi_is_exception_pending(env, &pendingException); - if (status != napi_ok || pendingException) { - napi_value exception; - napi_get_and_clear_last_exception(env, &exception); - if (exception) { - throw NativeScriptException(env, exception, - "Error calling module function: "); - } else { - throw NativeScriptException("Error calling module function: " + - modulePath); - } - } - - tempModule.SaveToCache(); - result = moduleObj; - - return result; -} - -napi_value ModuleInternal::LoadScript(napi_env env, const std::string& path, - napi_value fullRequiredModulePath) { - napi_value scriptText = ModuleInternal::WrapModuleContent(env, path); - return scriptText; -} - -napi_value ModuleInternal::LoadData(napi_env env, const std::string& path) { - std::string jsonData; - std::ifstream jsonFile(path); - if (!jsonFile.is_open()) { - throw NativeScriptException("Unable to open JSON file: " + path); - } - - std::stringstream buffer; - buffer << jsonFile.rdbuf(); - jsonData = buffer.str(); - napi_value json = JsonParse(env, jsonData); - - if (!napi_util::is_of_type(env, json, napi_object)) { - bool pendingException; - napi_is_exception_pending(env, &pendingException); - if (pendingException) { - napi_value error; - napi_get_and_clear_last_exception(env, &error); - throw NativeScriptException(env, error, - "JSON is not valid, file=" + path); - } else { - throw NativeScriptException("JSON is not valid, file=" + path); - } - } - - napi_ref poObj = napi_util::make_ref(env, json); - m_loadedModules.emplace(path, ModuleCacheEntry(poObj, true /* isData */)); - return json; -} - -// ES Module support functions -bool ModuleInternal::IsESModule(const std::string& path) { - // .mjs is always ESM - if (path.size() >= 4 && path.compare(path.size() - 4, 4, ".mjs") == 0) { - return true; - } - - // .cjs is always CommonJS - if (IsCJSModule(path)) { - return false; - } - - // .js files: check package.json "type" field - if (path.size() >= 3 && path.compare(path.size() - 3, 3, ".js") == 0) { - return ShouldTreatJsAsESModule(path); - } - - return false; -} - -napi_value ModuleInternal::LoadESModule(napi_env env, const std::string& path) { -#ifdef TARGET_ENGINE_V8 - try { - // Use canonicalized module paths so the top-level ESM module identity - // matches dependency resolution cache keys. - std::error_code pathError; - auto absPathFs = std::filesystem::absolute(path, pathError); - if (pathError) { - pathError.clear(); - absPathFs = std::filesystem::path(path); - } - absPathFs = absPathFs.lexically_normal(); - auto canonicalPath = - std::filesystem::weakly_canonical(absPathFs, pathError); - if (!pathError) { - absPathFs = canonicalPath; - } - std::string absPath = absPathFs.string(); - - // Read the ES module source - napi_value scriptContent = WrapModuleContent(env, absPath); - - // Use the new napi_run_script_as_module function - napi_value moduleNamespace; - napi_status status = napi_run_script_as_module( - env, scriptContent, absPath.c_str(), &moduleNamespace); - - if (status != napi_ok) { - bool pendingException; - napi_is_exception_pending(env, &pendingException); - if (pendingException) { - napi_value error; - napi_get_and_clear_last_exception(env, &error); - throw NativeScriptException(env, error, - "Failed to load ES module " + absPath); - } else { - throw NativeScriptException("Failed to load ES module " + absPath); - } - } - - return moduleNamespace; - - } catch (const std::exception& e) { - throw NativeScriptException("Failed to load ES module " + path + ": " + - e.what()); - } -#else - throw NativeScriptException("ES Modules are not supported in this runtime."); -#endif -} - -#if defined(TARGET_ENGINE_QUICKJS) -void ModuleInternal::InitQuickJSESModuleLoader(napi_env env) { - JSRuntime* runtime = qjs_get_runtime(env); - if (runtime == nullptr) { - return; - } - - JS_SetModuleLoaderFunc( - runtime, - [](JSContext* ctx, const char* base_name, const char* name, - void* opaque) -> char* { - auto* modules = static_cast(opaque); - if (modules == nullptr) { - return js_strdup(ctx, name != nullptr ? name : ""); - } - - std::string normalized = modules->NormalizeQuickJSImportSpecifier( - base_name != nullptr ? base_name : "", name != nullptr ? name : ""); - return js_strdup(ctx, normalized.c_str()); - }, - [](JSContext* ctx, const char* module_name, - void* opaque) -> JSModuleDef* { - auto* modules = static_cast(opaque); - if (modules == nullptr) { - return nullptr; - } - - return modules->LoadQuickJSImportModule( - ctx, module_name != nullptr ? module_name : ""); - }, - this); -} - -std::string ModuleInternal::NormalizeQuickJSImportSpecifier( - const std::string& baseName, const std::string& moduleName) { - if (moduleName.empty()) { - return moduleName; - } - - if (IsNodeBuiltinSpecifier(moduleName) || - !NormalizeRegisteredNapiModuleSpecifier(moduleName).empty()) { - return moduleName; - } - - if (moduleName.rfind("file://", 0) == 0) { - return moduleName.substr(std::string("file://").size()); - } - - std::string normalizedBase = baseName; - if (normalizedBase.rfind("file://", 0) == 0) { - normalizedBase = normalizedBase.substr(std::string("file://").size()); - } - - std::string baseDir = RuntimeConfig.ApplicationPath; - if (!normalizedBase.empty() && normalizedBase[0] == '/') { - std::filesystem::path basePath(normalizedBase); - baseDir = basePath.parent_path().string(); - } else if (!normalizedBase.empty() && - normalizedBase.rfind("nativescript:", 0) != 0 && - normalizedBase.rfind("node:", 0) != 0) { - std::filesystem::path basePath(normalizedBase); - baseDir = basePath.parent_path().string(); - } - - try { - std::string resolved = ResolvePath(m_env, baseDir, moduleName); - std::error_code ec; - auto absolutePath = std::filesystem::absolute(resolved, ec); - if (ec) { - ec.clear(); - absolutePath = std::filesystem::path(resolved); - } - - absolutePath = absolutePath.lexically_normal(); - auto canonicalPath = std::filesystem::weakly_canonical(absolutePath, ec); - if (!ec) { - absolutePath = canonicalPath; - } - - return absolutePath.string(); - } catch (...) { - return moduleName; - } -} - -JSModuleDef* ModuleInternal::LoadQuickJSImportModule( - JSContext* ctx, const std::string& moduleName) { - std::string source = GetBuiltinESModuleSource(moduleName); - if (source.empty()) { - source = GetRegisteredNapiESModuleSource(moduleName); - } - - std::string resolvedModuleName = moduleName; - if (source.empty()) { - resolvedModuleName = NormalizeQuickJSImportSpecifier("", moduleName); - if (!IsESModule(resolvedModuleName)) { - JS_ThrowReferenceError(ctx, "could not load module '%s'", - moduleName.c_str()); - return nullptr; - } - - std::ifstream file(resolvedModuleName); - if (!file.is_open()) { - JS_ThrowReferenceError(ctx, "could not load module '%s'", - resolvedModuleName.c_str()); - return nullptr; - } - - std::stringstream buffer; - buffer << file.rdbuf(); - source = StripShebang(buffer.str()); - } - - JSValue moduleValue = - JS_Eval(ctx, source.c_str(), source.size(), resolvedModuleName.c_str(), - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - if (JS_IsException(moduleValue)) { - return nullptr; - } - - JSModuleDef* module = - static_cast(JS_VALUE_GET_PTR(moduleValue)); - JSValue meta = JS_GetImportMeta(ctx, module); - if (JS_IsException(meta)) { - JS_FreeValue(ctx, moduleValue); - return nullptr; - } - - std::string moduleURL = ModulePathToURL(resolvedModuleName); - if (JS_DefinePropertyValueStr(ctx, meta, "url", - JS_NewString(ctx, moduleURL.c_str()), - JS_PROP_C_W_E) < 0 || - JS_DefinePropertyValueStr(ctx, meta, "main", JS_FALSE, JS_PROP_C_W_E) < - 0) { - JS_FreeValue(ctx, meta); - JS_FreeValue(ctx, moduleValue); - return nullptr; - } - - JS_FreeValue(ctx, meta); - JS_FreeValue(ctx, moduleValue); - return module; -} -#endif - -napi_value ModuleInternal::WrapModuleContent(napi_env env, - const std::string& path) { - std::string content; - std::ifstream file(path); - if (!file.is_open()) { - throw NativeScriptException("Unable to open file: " + path); - } - std::stringstream buffer; - buffer << file.rdbuf(); - content = StripShebang(buffer.str()); - file.close(); - - std::string result; - - if (IsESModule(path)) { - // For ES modules, return content as-is to preserve import/export syntax - result = content; - } else { -#if defined(TARGET_ENGINE_HERMES) || defined(TARGET_ENGINE_JSC) - content = RewriteCommonJSDynamicImportsForFallbackEngines(content); -#endif - // For CommonJS modules, wrap in factory function - result.reserve(content.length() + 1024); - result += MODULE_PROLOGUE; - result += content; - result += MODULE_EPILOGUE; - } - - napi_value wrappedContent; - napi_create_string_utf8(env, result.c_str(), result.size(), &wrappedContent); - - return wrappedContent; -} - -ModuleInternal::ModulePathKind ModuleInternal::GetModulePathKind( - const std::string& path) { - ModulePathKind kind; - switch (path[0]) { - case '.': - kind = ModulePathKind::Relative; - break; - case '/': - kind = ModulePathKind::Absolute; - break; - default: - kind = ModulePathKind::Global; - break; - } - return kind; -} - -#if defined(TARGET_ENGINE_HERMES) -const char* ModuleInternal::MODULE_PROLOGUE = - "(function(module, exports, require, __filename, __dirname){ " - "const __dynamicImport = (specifier) => Promise.resolve().then(() => { " - "const __loaded = require(specifier); " - "if (__loaded !== null && (typeof __loaded === 'object' || typeof __loaded " - "=== 'function')) { " - "if (__loaded.__esModule) { return __loaded; } " - "return Object.assign({ default: __loaded }, __loaded); " - "} " - "return { default: __loaded }; " - "}); "; -#else -const char* ModuleInternal::MODULE_PROLOGUE = - "(function(module, exports, require, __filename, __dirname){ "; -#endif -const char* ModuleInternal::MODULE_EPILOGUE = "\n})"; -int ModuleInternal::MODULE_PROLOGUE_LENGTH = - std::string(ModuleInternal::MODULE_PROLOGUE).length(); diff --git a/NativeScript/runtime/modules/url/URL.cpp b/NativeScript/runtime/modules/url/URL.cpp index 6932daffd..1ada52ec4 100644 --- a/NativeScript/runtime/modules/url/URL.cpp +++ b/NativeScript/runtime/modules/url/URL.cpp @@ -74,10 +74,17 @@ bool EnsureConstructorThis(napi_env env, const char* constructorName, nullptr) == napi_ok; } +// A 2nd argument only counts as a base URL when it is actually provided and is +// neither undefined nor null. Without this check `new URL(x, undefined)` would +// coerce the base to the literal string "undefined" and fail to parse. +bool HasBaseArgument(napi_env env, size_t argc, napi_value* argv) { + return argc > 1 && !napi_util::is_null_or_undefined(env, argv[1]); +} + URL* GetInstance(napi_env env, napi_callback_info info) { NAPI_PREAMBLE napi_value jsThis; - void* data; + void* data = nullptr; NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, &data)) { return nullptr; } @@ -271,7 +278,7 @@ napi_value URL::New(napi_env env, napi_callback_info info) { ada::parse(url_input.view(), nullptr); // If the input is not absolute, retry with the provided base if available. - if (!parse_result && argc > 1) { + if (!parse_result && HasBaseArgument(env, argc, argv)) { CoercedUtf8String base_input; if (!CoerceToUtf8String(env, argv[1], base_input)) return nullptr; @@ -310,7 +317,7 @@ napi_value URL::CanParse(napi_env env, napi_callback_info info) { bool result = static_cast(ada::parse(input.view(), nullptr)); - if (!result && argc > 1) { + if (!result && HasBaseArgument(env, argc, argv)) { CoercedUtf8String base_input; if (!CoerceToUtf8String(env, argv[1], base_input)) return nullptr; diff --git a/NativeScript/runtime/modules/url/URLPattern.cpp b/NativeScript/runtime/modules/url/URLPattern.cpp new file mode 100644 index 000000000..3e2092cda --- /dev/null +++ b/NativeScript/runtime/modules/url/URLPattern.cpp @@ -0,0 +1,524 @@ +// +// napi port of URLPatternImpl (upstream nativescript/android-runtime #1830). +// + +#include "URLPattern.h" +#include + +using namespace std; +using namespace tns; +using namespace ada; + +thread_local napi_env URLPattern::s_current_env = nullptr; + +namespace { + napi_value js_str(napi_env env, std::string_view s) { + napi_value v; + napi_create_string_utf8(env, s.data(), s.size(), &v); + return v; + } + + std::string to_std_string(napi_env env, napi_value v) { + size_t len = 0; + napi_get_value_string_utf8(env, v, nullptr, 0, &len); + std::string s(len, '\0'); + napi_get_value_string_utf8(env, v, s.data(), len + 1, &len); + return s; + } + + std::optional get_str_prop(napi_env env, napi_value obj, const char *key) { + napi_value v; + if (napi_get_named_property(env, obj, key, &v) != napi_ok) { + return std::nullopt; + } + if (napi_util::is_of_type(env, v, napi_string)) { + return to_std_string(env, v); + } + return std::nullopt; + } + + void clear_pending(napi_env env) { + bool pending = false; + napi_is_exception_pending(env, &pending); + if (pending) { + napi_value e; + napi_get_and_clear_last_exception(env, &e); + } + } + + URLPattern *GetInstance(napi_env env, napi_callback_info info) { + size_t argc = 0; + napi_value jsThis; + void *data = nullptr; + if (napi_get_cb_info(env, info, &argc, nullptr, &jsThis, &data) != napi_ok) { + return nullptr; + } + URLPattern *instance = nullptr; + if (napi_unwrap(env, jsThis, reinterpret_cast(&instance)) != napi_ok) { + return nullptr; + } + return instance; + } + + napi_value GetPatternProperty(napi_env env, napi_callback_info info, + std::string_view (url_pattern::*getter)() const) { + URLPattern *instance = GetInstance(env, info); + if (instance == nullptr) { + return js_str(env, ""); + } + auto value = (instance->GetPattern()->*getter)(); + return js_str(env, value); + } +} + +// --------------------------------------------------------------------------- +// napi RegExp provider +// --------------------------------------------------------------------------- + +std::optional +napi_regex_provider::create_instance(std::string_view pattern, bool ignore_case) { + napi_env env = URLPattern::s_current_env; + if (env == nullptr) { + return std::nullopt; + } + + napi_value global; + napi_get_global(env, &global); + + napi_value regexpCtor; + if (napi_get_named_property(env, global, "RegExp", ®expCtor) != napi_ok) { + return std::nullopt; + } + + napi_value patternStr = js_str(env, pattern); + + // v8 impl always sets the unicode flag and optionally ignore-case. + std::string flags = "u"; + if (ignore_case) { + flags += "i"; + } + napi_value flagsStr = js_str(env, flags); + + napi_value argv[2] = {patternStr, flagsStr}; + napi_value regex; + if (napi_new_instance(env, regexpCtor, 2, argv, ®ex) != napi_ok) { + clear_pending(env); + return std::nullopt; + } + + napi_ref ref = napi_util::make_ref(env, regex, 1); + return NapiRegex(env, ref); +} + +std::optional>> +napi_regex_provider::regex_search(std::string_view input, const regex_type &pattern) { + napi_env env = pattern.env; + if (env == nullptr || pattern.ref == nullptr) { + return std::nullopt; + } + + napi_value regex = napi_util::get_ref_value(env, pattern.ref); + if (regex == nullptr) { + return std::nullopt; + } + + napi_value execFn; + if (napi_get_named_property(env, regex, "exec", &execFn) != napi_ok) { + return std::nullopt; + } + + napi_value inputStr = js_str(env, input); + napi_value argv[1] = {inputStr}; + napi_value result; + if (napi_call_function(env, regex, execFn, 1, argv, &result) != napi_ok) { + clear_pending(env); + return std::nullopt; + } + + if (napi_util::is_null_or_undefined(env, result)) { + return std::nullopt; + } + + std::vector> ret; + bool isArray = false; + napi_is_array(env, result, &isArray); + if (isArray) { + uint32_t len = 0; + napi_get_array_length(env, result, &len); + ret.reserve(len); + for (uint32_t i = 0; i < len; i++) { + napi_value item; + if (napi_get_element(env, result, i, &item) != napi_ok) { + return std::nullopt; + } + if (napi_util::is_undefined(env, item)) { + ret.emplace_back(std::nullopt); + } else if (napi_util::is_of_type(env, item, napi_string)) { + ret.emplace_back(to_std_string(env, item)); + } + } + } + + return ret; +} + +bool napi_regex_provider::regex_match(std::string_view input, const regex_type &pattern) { + napi_env env = pattern.env; + if (env == nullptr || pattern.ref == nullptr) { + return false; + } + + napi_value regex = napi_util::get_ref_value(env, pattern.ref); + if (regex == nullptr) { + return false; + } + + napi_value execFn; + if (napi_get_named_property(env, regex, "exec", &execFn) != napi_ok) { + return false; + } + + napi_value inputStr = js_str(env, input); + napi_value argv[1] = {inputStr}; + napi_value result; + if (napi_call_function(env, regex, execFn, 1, argv, &result) != napi_ok) { + clear_pending(env); + return false; + } + + return !napi_util::is_null_or_undefined(env, result); +} + +// --------------------------------------------------------------------------- +// URLPattern +// --------------------------------------------------------------------------- + +URLPattern::URLPattern(url_pattern pattern) + : pattern_(std::move(pattern)) {} + +url_pattern *URLPattern::GetPattern() { + return &this->pattern_; +} + +void URLPattern::Destructor(napi_env env, void *data, void *hint) { +#ifdef __HERMES__ + URLPattern *pattern = static_cast(hint); +#else + URLPattern *pattern = static_cast(data); +#endif + delete pattern; +} + +static std::optional ParseInput(napi_env env, napi_value input) { + if (!napi_util::is_object(env, input)) { + return {}; + } + + auto init = ada::url_pattern_init{}; + if (auto v = get_str_prop(env, input, "protocol")) init.protocol = *v; + if (auto v = get_str_prop(env, input, "username")) init.username = *v; + if (auto v = get_str_prop(env, input, "password")) init.password = *v; + if (auto v = get_str_prop(env, input, "hostname")) init.hostname = *v; + if (auto v = get_str_prop(env, input, "port")) init.port = *v; + if (auto v = get_str_prop(env, input, "pathname")) init.pathname = *v; + if (auto v = get_str_prop(env, input, "search")) init.search = *v; + if (auto v = get_str_prop(env, input, "hash")) init.hash = *v; + if (auto v = get_str_prop(env, input, "baseURL")) init.base_url = *v; + return init; +} + +static void SetComponent(napi_env env, napi_value object, const char *componentKey, + const ada::url_pattern_component_result &component) { + napi_value ret; + napi_create_object(env, &ret); + napi_set_named_property(env, ret, "input", js_str(env, component.input)); + + napi_value groupValue; + napi_create_object(env, &groupValue); + for (const auto &[key, value]: component.groups) { + if (value) { + napi_set_named_property(env, groupValue, key.c_str(), js_str(env, value.value())); + } else { + napi_set_named_property(env, groupValue, key.c_str(), napi_util::undefined(env)); + } + } + napi_set_named_property(env, ret, "groups", groupValue); + + napi_set_named_property(env, object, componentKey, ret); +} + +static void BuildJS(napi_env env, napi_value object, const ada::url_pattern_result &result) { + auto len = result.inputs.size(); + napi_value inputs; + napi_create_array_with_length(env, len, &inputs); + for (uint32_t i = 0; i < len; i++) { + const auto &item = result.inputs[i]; + if (std::holds_alternative(item)) { + auto view = std::get(item); + napi_set_element(env, inputs, i, js_str(env, view)); + } + } + napi_set_named_property(env, object, "inputs", inputs); + + SetComponent(env, object, "protocol", result.protocol); + SetComponent(env, object, "hash", result.hash); + SetComponent(env, object, "hostname", result.hostname); + SetComponent(env, object, "username", result.username); + SetComponent(env, object, "password", result.password); + SetComponent(env, object, "pathname", result.pathname); + SetComponent(env, object, "port", result.port); + SetComponent(env, object, "search", result.search); +} + +napi_value URLPattern::New(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(3) + s_current_env = env; + + // No-args: construct an empty pattern. + if (argc == 0 || napi_util::is_undefined(env, argv[0])) { + auto init = ada::url_pattern_init{}; + auto pattern = ada::parse_url_pattern(std::move(init)); + if (!pattern) { + napi_throw_type_error(env, nullptr, "Failed to construct URLPattern"); + return nullptr; + } + auto *impl = new URLPattern(std::move(*pattern)); + napi_wrap(env, jsThis, impl, URLPattern::Destructor, impl, nullptr); + return jsThis; + } + + std::optional init{}; + std::optional input{}; + std::optional base_url{}; + std::optional options{}; + + if (napi_util::is_of_type(env, argv[0], napi_string)) { + input = to_std_string(env, argv[0]); + } else if (napi_util::is_object(env, argv[0])) { + auto parsed = ParseInput(env, argv[0]); + if (parsed) { + init = std::move(*parsed); + } + } else { + napi_throw_type_error(env, nullptr, "Input must be an object or a string"); + return nullptr; + } + + // 2nd arg may be a base URL string or an options object. + napi_value baseOrOptions = argv[1]; + if (napi_util::is_of_type(env, baseOrOptions, napi_string)) { + base_url = to_std_string(env, baseOrOptions); + } else if (napi_util::is_object(env, baseOrOptions)) { + napi_value ignoreCase; + if (napi_get_named_property(env, baseOrOptions, "ignoreCase", &ignoreCase) == napi_ok && + napi_util::is_of_type(env, ignoreCase, napi_boolean)) { + options = ada::url_pattern_options{.ignore_case = napi_util::get_bool(env, ignoreCase)}; + } + } + + // 3rd arg is the options object when a base URL was given as 2nd arg. + napi_value opts = argv[2]; + if (napi_util::is_object(env, opts)) { + napi_value ignoreCase; + if (napi_get_named_property(env, opts, "ignoreCase", &ignoreCase) == napi_ok && + napi_util::is_of_type(env, ignoreCase, napi_boolean)) { + options = ada::url_pattern_options{.ignore_case = napi_util::get_bool(env, ignoreCase)}; + } + } + + std::string_view base_url_view{}; + if (base_url) { + base_url_view = {base_url->data(), base_url->size()}; + } + + ada::url_pattern_input arg0; + if (init.has_value()) { + arg0 = *init; + } else { + arg0 = std::string_view(*input); + } + + auto pattern = ada::parse_url_pattern( + std::move(arg0), + base_url.has_value() ? &base_url_view : nullptr, + options.has_value() ? &options.value() : nullptr); + + if (!pattern) { + napi_throw_type_error(env, nullptr, "Failed to construct URLPattern"); + return nullptr; + } + + auto *impl = new URLPattern(std::move(*pattern)); + napi_wrap(env, jsThis, impl, URLPattern::Destructor, impl, nullptr); + return jsThis; +} + +napi_value URLPattern::GetHash(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_hash); +} + +napi_value URLPattern::GetHostName(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_hostname); +} + +napi_value URLPattern::GetPassword(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_password); +} + +napi_value URLPattern::GetPathName(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_pathname); +} + +napi_value URLPattern::GetPort(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_port); +} + +napi_value URLPattern::GetProtocol(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_protocol); +} + +napi_value URLPattern::GetSearch(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_search); +} + +napi_value URLPattern::GetUserName(napi_env env, napi_callback_info info) { + return GetPatternProperty(env, info, &url_pattern::get_username); +} + +napi_value URLPattern::GetHasRegexpGroups(napi_env env, napi_callback_info info) { + URLPattern *instance = GetInstance(env, info); + napi_value result; + bool value = instance != nullptr && instance->GetPattern()->has_regexp_groups(); + napi_get_boolean(env, value, &result); + return result; +} + +napi_value URLPattern::Test(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(2) + s_current_env = env; + + URLPattern *instance = GetInstance(env, info); + if (instance == nullptr) { + napi_value result; + napi_get_boolean(env, false, &result); + return result; + } + + ada::url_pattern_input input; + std::string input_base; + + if (argc == 0 || napi_util::is_undefined(env, argv[0])) { + input = ada::url_pattern_init{}; + } else if (napi_util::is_of_type(env, argv[0], napi_string)) { + input_base = to_std_string(env, argv[0]); + input = std::string_view(input_base); + } else if (napi_util::is_object(env, argv[0])) { + auto parsed = ParseInput(env, argv[0]); + if (parsed) { + input = std::move(*parsed); + } + } else { + napi_throw_type_error(env, nullptr, "URLPattern input needs to be a string or an object"); + return nullptr; + } + + std::optional baseURL{}; + if (argc > 1 && !napi_util::is_undefined(env, argv[1])) { + if (!napi_util::is_of_type(env, argv[1], napi_string)) { + napi_throw_type_error(env, nullptr, "baseURL must be a string"); + return nullptr; + } + baseURL = to_std_string(env, argv[1]); + } + + std::optional baseURL_opt = + baseURL ? std::optional(*baseURL) : std::nullopt; + + napi_value result; + if (auto res = instance->GetPattern()->test(input, baseURL_opt ? &*baseURL_opt : nullptr)) { + napi_get_boolean(env, res.value(), &result); + } else { + napi_get_null(env, &result); + } + return result; +} + +napi_value URLPattern::Exec(napi_env env, napi_callback_info info) { + NAPI_CALLBACK_BEGIN(2) + s_current_env = env; + + URLPattern *instance = GetInstance(env, info); + if (instance == nullptr) { + return napi_util::undefined(env); + } + + ada::url_pattern_input input; + std::string input_base; + + if (argc == 0 || napi_util::is_undefined(env, argv[0])) { + input = ada::url_pattern_init{}; + } else if (napi_util::is_of_type(env, argv[0], napi_string)) { + input_base = to_std_string(env, argv[0]); + input = std::string_view(input_base); + } else if (napi_util::is_object(env, argv[0])) { + auto parsed = ParseInput(env, argv[0]); + if (parsed) { + input = std::move(*parsed); + } + } else { + napi_throw_type_error(env, nullptr, "URLPattern input needs to be a string or an object"); + return nullptr; + } + + std::optional baseURL{}; + if (argc > 1 && !napi_util::is_undefined(env, argv[1])) { + if (!napi_util::is_of_type(env, argv[1], napi_string)) { + napi_throw_type_error(env, nullptr, "baseURL must be a string"); + return nullptr; + } + baseURL = to_std_string(env, argv[1]); + } + + std::optional baseURL_opt = + baseURL ? std::optional(*baseURL) : std::nullopt; + + napi_value result; + napi_get_null(env, &result); + + if (auto res = instance->GetPattern()->exec(input, baseURL_opt ? &*baseURL_opt : nullptr)) { + auto value = res.value(); + if (value.has_value()) { + napi_create_object(env, &result); + BuildJS(env, result, value.value()); + } + } + return result; +} + +void URLPattern::Init(napi_env env, napi_value global) { + NAPI_PREAMBLE + napi_value ctor; + static const int instance_prop_count = 11; + napi_property_descriptor properties[instance_prop_count] = { + {"test", nullptr, Test, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"exec", nullptr, Exec, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"hasRegExpGroups", nullptr, nullptr, GetHasRegexpGroups, nullptr, nullptr, napi_default, nullptr}, + {"hash", nullptr, nullptr, GetHash, nullptr, nullptr, napi_default, nullptr}, + {"hostname", nullptr, nullptr, GetHostName, nullptr, nullptr, napi_default, nullptr}, + {"password", nullptr, nullptr, GetPassword, nullptr, nullptr, napi_default, nullptr}, + {"pathname", nullptr, nullptr, GetPathName, nullptr, nullptr, napi_default, nullptr}, + {"port", nullptr, nullptr, GetPort, nullptr, nullptr, napi_default, nullptr}, + {"protocol", nullptr, nullptr, GetProtocol, nullptr, nullptr, napi_default, nullptr}, + {"search", nullptr, nullptr, GetSearch, nullptr, nullptr, napi_default, nullptr}, + {"username", nullptr, nullptr, GetUserName, nullptr, nullptr, napi_default, nullptr}}; + + NAPI_GUARD(napi_define_class(env, "URLPattern", NAPI_AUTO_LENGTH, New, + nullptr, instance_prop_count, + properties, &ctor)) { + return; + } + + NAPI_GUARD(napi_set_named_property(env, global, "URLPattern", ctor)) { + return; + } +} diff --git a/NativeScript/runtime/modules/url/URLPattern.h b/NativeScript/runtime/modules/url/URLPattern.h new file mode 100644 index 000000000..3e975657f --- /dev/null +++ b/NativeScript/runtime/modules/url/URLPattern.h @@ -0,0 +1,113 @@ +// +// napi port of URLPatternImpl (upstream nativescript/android-runtime #1830). +// Backed by ada's url_pattern with a Node-API RegExp provider so it works +// across every engine the runtime supports (V8, QuickJS, Hermes, JSC, PrimJS). +// + +#ifndef TEST_APP_URLPATTERN_H +#define TEST_APP_URLPATTERN_H + +#include "native_api_util.h" +#include "FinalizerQueue.h" +#include "ada/ada.h" +#include +#include +#include + +using namespace ada; + +namespace tns { + + // Move-only RAII wrapper around a napi_ref to a JS RegExp. Mirrors the + // move-only semantics of v8::Global used by the original impl, + // and stores its own env so it can release the reference on destruction. + class NapiRegex { + public: + napi_env env = nullptr; + napi_ref ref = nullptr; + + NapiRegex() = default; + NapiRegex(napi_env e, napi_ref r) : env(e), ref(r) {} + + NapiRegex(const NapiRegex &) = delete; + NapiRegex &operator=(const NapiRegex &) = delete; + + NapiRegex(NapiRegex &&other) noexcept: env(other.env), ref(other.ref) { + other.ref = nullptr; + other.env = nullptr; + } + + NapiRegex &operator=(NapiRegex &&other) noexcept { + if (this != &other) { + if (ref != nullptr && env != nullptr) { + napi_delete_reference(env, ref); + } + env = other.env; + ref = other.ref; + other.ref = nullptr; + other.env = nullptr; + } + return *this; + } + + ~NapiRegex() { + if (ref != nullptr && env != nullptr) { + // This destructor can run from URLPattern's GC finalizer + // (URLPattern::Destructor, via napi_wrap), where deleting a napi_ref + // is unsafe on every engine. Defer it to the runtime's post-GC + // finalizer drain (which falls back to inline during teardown). + tns::PostFinalizer(env, [](napi_env env, void *d, void *) { + napi_delete_reference(env, (napi_ref) d); + }, ref, nullptr); + } + } + }; + + class napi_regex_provider { + public: + napi_regex_provider() = default; + + using regex_type = NapiRegex; + + static std::optional create_instance(std::string_view pattern, + bool ignore_case); + + static std::optional>> regex_search( + std::string_view input, const regex_type &pattern); + + static bool regex_match(std::string_view input, const regex_type &pattern); + }; + + class URLPattern { + public: + static void Init(napi_env env, napi_value global); + static void Destructor(napi_env env, void *nativeObject, void *finalize_hint); + + explicit URLPattern(url_pattern pattern); + url_pattern *GetPattern(); + + // Set at the entry of every callback that drives ada's regex provider + // (New/Test/Exec), so create_instance can reach the current env. + static thread_local napi_env s_current_env; + + private: + static napi_value New(napi_env env, napi_callback_info info); + + static napi_value GetHash(napi_env env, napi_callback_info info); + static napi_value GetHostName(napi_env env, napi_callback_info info); + static napi_value GetPassword(napi_env env, napi_callback_info info); + static napi_value GetPathName(napi_env env, napi_callback_info info); + static napi_value GetPort(napi_env env, napi_callback_info info); + static napi_value GetProtocol(napi_env env, napi_callback_info info); + static napi_value GetSearch(napi_env env, napi_callback_info info); + static napi_value GetUserName(napi_env env, napi_callback_info info); + static napi_value GetHasRegexpGroups(napi_env env, napi_callback_info info); + + static napi_value Test(napi_env env, napi_callback_info info); + static napi_value Exec(napi_env env, napi_callback_info info); + + url_pattern pattern_; + }; +} + +#endif //TEST_APP_URLPATTERN_H diff --git a/NativeScript/runtime/modules/url/URLSearchParams.cpp b/NativeScript/runtime/modules/url/URLSearchParams.cpp index ef45d8215..a57480d17 100644 --- a/NativeScript/runtime/modules/url/URLSearchParams.cpp +++ b/NativeScript/runtime/modules/url/URLSearchParams.cpp @@ -48,21 +48,369 @@ bool EnsureConstructorThis(napi_env env, const char* constructorName, nullptr) == napi_ok; } +void ThrowTypeError(napi_env env, const char* msg) { + napi_throw_type_error(env, nullptr, msg); +} + +napi_value js_str(napi_env env, std::string_view s) { + napi_value v; + napi_create_string_utf8(env, s.data(), s.length(), &v); + return v; +} + +// Brand-checked unwrap of an already-retrieved receiver. Throws "Illegal +// invocation" (TypeError) when it is not a wrapped URLSearchParams. +URLSearchParams* GetInstanceOf(napi_env env, napi_value jsThis) { + URLSearchParams* instance = nullptr; + if (napi_unwrap(env, jsThis, reinterpret_cast(&instance)) != + napi_ok || + instance == nullptr) { + ThrowTypeError(env, "Illegal invocation"); + return nullptr; + } + return instance; +} + +// Brand-checked instance retrieval: throws "Illegal invocation" (TypeError) +// when the receiver is not a wrapped URLSearchParams, matching the spec. URLSearchParams* GetInstance(napi_env env, napi_callback_info info) { - NAPI_PREAMBLE napi_value jsThis; - void* data; - NAPI_GUARD(napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, &data)) { + if (napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr) != + napi_ok) { return nullptr; } - URLSearchParams* instance; - NAPI_GUARD(napi_unwrap(env, jsThis, reinterpret_cast(&instance))) { + URLSearchParams* instance = nullptr; + if (napi_unwrap(env, jsThis, reinterpret_cast(&instance)) != + napi_ok || + instance == nullptr) { + ThrowTypeError(env, "Illegal invocation"); return nullptr; } return instance; } + +napi_value WellKnownSymbol(napi_env env, const char* name) { + napi_value global, symbolCtor, sym; + napi_get_global(env, &global); + napi_get_named_property(env, global, "Symbol", &symbolCtor); + napi_get_named_property(env, symbolCtor, name, &sym); + return sym; +} + +napi_value SymbolIterator(napi_env env) { + return WellKnownSymbol(env, "iterator"); +} + +// WebIDL USVString coercion: ToString (invokes user toString, throws for +// Symbol). Returns false leaving a pending exception on failure. +bool ValueToString(napi_env env, napi_value v, std::string& out) { + napi_value str; + if (napi_coerce_to_string(env, v, &str) != napi_ok) { + return false; + } + size_t len = 0; + if (napi_get_value_string_utf8(env, str, nullptr, 0, &len) != napi_ok) { + return false; + } + std::vector buf(len + 1); + napi_get_value_string_utf8(env, str, buf.data(), len + 1, nullptr); + out.assign(buf.data(), len); + return true; +} + +enum IterKind { ITER_KEYS = 0, ITER_VALUES = 1, ITER_ENTRIES = 2 }; + +// Per-iterator state: tracks the current index and what to yield. Freed by the +// iterator object's finalizer. +struct IterState { + uint32_t idx; + int kind; +}; + +// Internal, non-enumerable keys the iterator carries: +// - ITER_SOURCE_KEY: a strong reference to the source URLSearchParams (kept +// alive for `next`); released by ordinary property teardown. +// - ITER_BRAND_KEY: a napi_external wrapping this iterator's IterState*, used +// by `next` for the receiver brand check. +// Holding these as properties (not napi_refs) means the iterator's finalizer +// never has to delete a reference — illegal during the GC sweep on every +// engine — so it can stay a plain C++ delete (see MakeIterator). +static constexpr const char* ITER_SOURCE_KEY = "__nsSource"; +static constexpr const char* ITER_BRAND_KEY = "__nsBrand"; + +// ES IteratorClose: best-effort call to the iterator's return() on abrupt +// completion (so a generator's `finally` runs), preserving any pending +// exception. +void IteratorClose(napi_env env, napi_value iterObj) { + napi_value savedEx = nullptr; + bool pending = false; + napi_is_exception_pending(env, &pending); + if (pending) { + napi_get_and_clear_last_exception(env, &savedEx); + } + napi_value returnFn; + if (napi_get_named_property(env, iterObj, "return", &returnFn) == napi_ok && + napi_util::is_of_type(env, returnFn, napi_function)) { + napi_value res; + napi_call_function(env, iterObj, returnFn, 0, nullptr, &res); + bool p2 = false; + napi_is_exception_pending(env, &p2); + if (p2) { + napi_value e; + napi_get_and_clear_last_exception(env, &e); + } + } + if (savedEx != nullptr) { + napi_throw(env, savedEx); + } +} + +// The `next` function of a live URLSearchParams iterator. +napi_value IteratorNext(napi_env env, napi_callback_info info) { + napi_value jsThis; + if (napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr) != + napi_ok) { + ThrowTypeError(env, "Illegal invocation"); + return nullptr; + } + + // Brand check *and* state lookup in one step: the iterator state is read out + // of the receiver's own external property. A foreign receiver (next.call({})) + // carries no such property and is rejected, which is what the spec requires. + // + // The state is deliberately NOT taken from this callback's `data`, even + // though MakeIterator passes it there too: that would require the pointer to + // survive a round trip through two different Node-API mechanisms, and PrimJS + // does not hand back the same address, so every next() call failed the brand + // check and threw. + napi_value brandVal = nullptr; + void* brand = nullptr; + if (napi_get_named_property(env, jsThis, ITER_BRAND_KEY, &brandVal) != + napi_ok || + napi_util::is_null_or_undefined(env, brandVal) || + napi_get_value_external(env, brandVal, &brand) != napi_ok || + brand == nullptr) { + ThrowTypeError(env, "Illegal invocation"); + return nullptr; + } + auto* st = static_cast(brand); + + napi_value result; + napi_create_object(env, &result); + + napi_value srcObj; + URLSearchParams* self = nullptr; + if (napi_get_named_property(env, jsThis, ITER_SOURCE_KEY, &srcObj) != + napi_ok || + napi_unwrap(env, srcObj, reinterpret_cast(&self)) != napi_ok || + self == nullptr) { + napi_set_named_property(env, result, "value", napi_util::undefined(env)); + napi_set_named_property(env, result, "done", napi_util::get_true(env)); + return result; + } + + auto* p = self->GetURLSearchParams(); + if (st->idx >= p->size()) { + napi_set_named_property(env, result, "value", napi_util::undefined(env)); + napi_set_named_property(env, result, "done", napi_util::get_true(env)); + return result; + } + + auto pair = (*p)[st->idx++]; // live, duplicate-key correct + napi_value value; + if (st->kind == ITER_KEYS) { + value = js_str(env, pair.first); + } else if (st->kind == ITER_VALUES) { + value = js_str(env, pair.second); + } else { + napi_create_array_with_length(env, 2, &value); + napi_set_element(env, value, 0, js_str(env, pair.first)); + napi_set_element(env, value, 1, js_str(env, pair.second)); + } + napi_set_named_property(env, result, "value", value); + napi_set_named_property(env, result, "done", napi_util::get_false(env)); + return result; +} + +// iterator[Symbol.iterator]() -> this (iterators are iterable). +napi_value IteratorSelf(napi_env env, napi_callback_info info) { + napi_value jsThis; + napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr); + return jsThis; +} + +napi_value MakeIterator(napi_env env, napi_value jsThis, int kind) { + auto* st = new IterState{0, kind}; + + napi_value iterator; + napi_create_object(env, &iterator); + + // Attach two non-enumerable internal properties (napi_default => not + // enumerable, invisible to JS): the strong source reference kept alive for + // `next`, and a napi_external wrapping the IterState* for the brand check. + // + // The external is the SOLE owner of st and frees it when collected. Do not + // also register st with napi_add_finalizer on the iterator: that hands the + // same pointer to two independent finalization paths, and on QuickJS (where + // this runtime dispatches finalizers through a JS FinalizationRegistry) both + // fire, double-freeing IterState. The resulting heap corruption surfaced far + // away from here, as a SIGSEGV in an unrelated spec. + // + // The `next` function below also carries st as its callback data, but only as + // a non-owning alias: it is reachable only through the iterator, which holds + // the external, so st outlives every call made on it. + napi_value brandVal; + napi_create_external( + env, st, + [](napi_env, void* d, void*) { delete static_cast(d); }, + nullptr, &brandVal); + napi_property_descriptor descs[2] = {}; + descs[0].utf8name = ITER_SOURCE_KEY; + descs[0].value = jsThis; + descs[0].attributes = napi_default; + descs[1].utf8name = ITER_BRAND_KEY; + descs[1].value = brandVal; + descs[1].attributes = napi_default; + napi_define_properties(env, iterator, 2, descs); + + napi_value nextFn; + napi_create_function(env, "next", NAPI_AUTO_LENGTH, IteratorNext, st, + &nextFn); + napi_set_named_property(env, iterator, "next", nextFn); + + napi_value selfFn; + napi_create_function(env, "[Symbol.iterator]", NAPI_AUTO_LENGTH, + IteratorSelf, nullptr, &selfFn); + napi_set_property(env, iterator, SymbolIterator(env), selfFn); + + // Symbol.toStringTag = "URLSearchParams Iterator" for spec-correct + // Object.prototype.toString output. + napi_set_property(env, iterator, WellKnownSymbol(env, "toStringTag"), + js_str(env, "URLSearchParams Iterator")); + + return iterator; +} + +// Drives the ES iterator protocol on `iterable`, invoking fn(itemValue) for +// each yielded value. Returns false (with a pending exception) on protocol +// error or if fn returns false. +template +bool ForEachOfIterable(napi_env env, napi_value iterable, F&& fn) { + napi_value iterMethod; + if (napi_get_property(env, iterable, SymbolIterator(env), &iterMethod) != + napi_ok) { + return false; + } + if (!napi_util::is_of_type(env, iterMethod, napi_function)) { + ThrowTypeError(env, "value is not iterable"); + return false; + } + napi_value iterObj; + if (napi_call_function(env, iterable, iterMethod, 0, nullptr, &iterObj) != + napi_ok) { + return false; + } + if (!napi_util::is_object(env, iterObj)) { + ThrowTypeError(env, "iterator result is not an object"); + return false; + } + + napi_value nextFn; + if (napi_get_named_property(env, iterObj, "next", &nextFn) != napi_ok || + !napi_util::is_of_type(env, nextFn, napi_function)) { + ThrowTypeError(env, "iterator has no next method"); + return false; + } + + for (;;) { + napi_value step; + if (napi_call_function(env, iterObj, nextFn, 0, nullptr, &step) != + napi_ok) { + return false; + } + if (!napi_util::is_object(env, step)) { + ThrowTypeError(env, "iterator result is not an object"); + return false; + } + napi_value doneVal; + bool done = false; + if (napi_get_named_property(env, step, "done", &doneVal) != napi_ok) { + return false; + } + napi_coerce_to_bool(env, doneVal, &doneVal); + napi_get_value_bool(env, doneVal, &done); + if (done) return true; + + napi_value value; + if (napi_get_named_property(env, step, "value", &value) != napi_ok) { + return false; + } + if (!fn(value)) { + IteratorClose(env, iterObj); + return false; + } + } +} + +// sequence> form: [[k, v], [k, v], ...]. +bool BuildFromSequence(napi_env env, napi_value iterable, + url_search_params& params) { + return ForEachOfIterable(env, iterable, [&](napi_value entry) -> bool { + if (!napi_util::is_object(env, entry)) { + ThrowTypeError(env, "URLSearchParams init sequence entry is not iterable"); + return false; + } + // Collect the whole entry before validating its length: aborting early on + // the third element would skip the "exactly two elements" TypeError. + std::vector pair; + bool ok = ForEachOfIterable(env, entry, [&](napi_value item) -> bool { + std::string s; + if (!ValueToString(env, item, s)) { + return false; + } + pair.push_back(std::move(s)); + return true; + }); + if (!ok) { + return false; + } + if (pair.size() != 2) { + ThrowTypeError(env, + "URLSearchParams init sequence entry does not contain " + "exactly two elements"); + return false; + } + params.append(pair[0], pair[1]); + return true; + }); +} + +// record form. +bool BuildFromRecord(napi_env env, napi_value object, + url_search_params& params) { + napi_value keys; + if (napi_get_all_property_names(env, object, napi_key_own_only, + napi_key_enumerable, + napi_key_numbers_to_strings, + &keys) != napi_ok) { + return false; + } + uint32_t len = 0; + napi_get_array_length(env, keys, &len); + for (uint32_t i = 0; i < len; i++) { + napi_value key, value; + napi_get_element(env, keys, i, &key); + napi_get_property(env, object, key, &value); + std::string k, v; + if (!ValueToString(env, key, k) || !ValueToString(env, value, v)) { + return false; // e.g. Symbol key + } + params.append(k, v); + } + return true; +} } // namespace URLSearchParams::URLSearchParams(url_search_params params, @@ -129,25 +477,46 @@ napi_value URLSearchParams::New(napi_env env, napi_callback_info info) { url_search_params params; - if (argc > 0) { - napi_value stringValue; - NAPI_GUARD(napi_coerce_to_string(env, argv[0], &stringValue)) { - return nullptr; - } - - size_t str_size; - NAPI_GUARD( - napi_get_value_string_utf8(env, stringValue, nullptr, 0, &str_size)) { - return nullptr; - } - - std::vector buffer(str_size + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, stringValue, buffer.data(), - str_size + 1, nullptr)) { - return nullptr; + // Per spec the init argument is one of: + // USVString - query string + // sequence> - [[k, v], ...] (has Symbol.iterator) + // record - plain object (no Symbol.iterator) + // Only undefined means "no init"; null and other primitives are coerced to a + // USVString, so new URLSearchParams(null) parses as the string "null". + if (argc > 0 && !napi_util::is_undefined(env, argv[0])) { + if (napi_util::is_of_type(env, argv[0], napi_string)) { + std::string init; + if (!ValueToString(env, argv[0], init)) { + return nullptr; + } + params = url_search_params(init); + } else if (napi_util::is_object(env, argv[0])) { + napi_value iterMethod; + NAPI_GUARD( + napi_get_property(env, argv[0], SymbolIterator(env), &iterMethod)) { + return nullptr; + } + if (napi_util::is_null_or_undefined(env, iterMethod)) { + if (!BuildFromRecord(env, argv[0], params)) { + return nullptr; + } + } else if (napi_util::is_of_type(env, iterMethod, napi_function)) { + if (!BuildFromSequence(env, argv[0], params)) { + return nullptr; + } + } else { + ThrowTypeError( + env, "URLSearchParams init Symbol.iterator is not a function"); + return nullptr; + } + } else { + // number / boolean / bigint / null / symbol -> USVString + std::string init; + if (!ValueToString(env, argv[0], init)) { + return nullptr; + } + params = url_search_params(init); } - - params = url_search_params(std::string_view(buffer.data(), str_size)); } URLSearchParams* searchParams = new URLSearchParams(std::move(params)); @@ -176,59 +545,50 @@ napi_value URLSearchParams::Append(napi_env env, napi_callback_info info) { URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 2) return nullptr; - - size_t key_size, value_size; - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &key_size)) { - return nullptr; - } - NAPI_GUARD( - napi_get_value_string_utf8(env, argv[1], nullptr, 0, &value_size)) { + if (argc < 2) { + ThrowTypeError(env, "URLSearchParams.append requires 2 arguments"); return nullptr; } - std::vector key_buffer(key_size + 1); - std::vector value_buffer(value_size + 1); - - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], key_buffer.data(), - key_size + 1, nullptr)) { - return nullptr; - } - NAPI_GUARD(napi_get_value_string_utf8(env, argv[1], value_buffer.data(), - value_size + 1, nullptr)) { + std::string key, value; + if (!ValueToString(env, argv[0], key) || + !ValueToString(env, argv[1], value)) { return nullptr; } - instance->GetURLSearchParams()->append(key_buffer.data(), - value_buffer.data()); + instance->GetURLSearchParams()->append(key, value); instance->SyncParent(); return nullptr; } napi_value URLSearchParams::Has(napi_env env, napi_callback_info info) { - NAPI_CALLBACK_BEGIN(1) + NAPI_CALLBACK_BEGIN(2) URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 1) return nullptr; + if (argc < 1) return napi_util::get_false(env); - size_t str_size; - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &str_size)) { + std::string key; + if (!ValueToString(env, argv[0], key)) { return nullptr; } - std::vector buffer(str_size + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], buffer.data(), - str_size + 1, nullptr)) { - return nullptr; + // The 2-argument form matches on name *and* value; an explicit undefined + // second argument is treated as omitted. + bool has; + if (argc > 1 && !napi_util::is_undefined(env, argv[1])) { + std::string value; + if (!ValueToString(env, argv[1], value)) { + return nullptr; + } + has = instance->GetURLSearchParams()->has(key, value); + } else { + has = instance->GetURLSearchParams()->has(key); } - bool has = instance->GetURLSearchParams()->has(buffer.data()); - napi_value result; - NAPI_GUARD(napi_get_boolean(env, has, &result)) { return nullptr; } - + napi_get_boolean(env, has, &result); return result; } @@ -238,55 +598,48 @@ napi_value URLSearchParams::Get(napi_env env, napi_callback_info info) { URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 1) return nullptr; + if (argc < 1) return napi_util::null(env); - size_t str_size; - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &str_size)) { + std::string key; + if (!ValueToString(env, argv[0], key)) { return nullptr; } - std::vector buffer(str_size + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], buffer.data(), - str_size + 1, nullptr)) { - return nullptr; - } - - auto value = instance->GetURLSearchParams()->get(buffer.data()); + auto value = instance->GetURLSearchParams()->get(key); if (!value.has_value()) { - napi_value undefined; - NAPI_GUARD(napi_get_undefined(env, &undefined)) { return nullptr; } - return undefined; - } - - napi_value result; - NAPI_GUARD(napi_create_string_utf8(env, value.value().data(), - value.value().length(), &result)) { - return nullptr; + // Per spec, a missing name returns null (not undefined). + return napi_util::null(env); } - return result; + return js_str(env, value.value()); } napi_value URLSearchParams::Delete(napi_env env, napi_callback_info info) { - NAPI_CALLBACK_BEGIN(1) + NAPI_CALLBACK_BEGIN(2) URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 1) return nullptr; - - size_t str_size; - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &str_size)) { + if (argc < 1) { + ThrowTypeError(env, "URLSearchParams.delete requires 1 argument"); return nullptr; } - std::vector buffer(str_size + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], buffer.data(), - str_size + 1, nullptr)) { + std::string key; + if (!ValueToString(env, argv[0], key)) { return nullptr; } - instance->GetURLSearchParams()->remove(buffer.data()); + // The 2-argument form removes only pairs matching name *and* value. + if (argc > 1 && !napi_util::is_undefined(env, argv[1])) { + std::string value; + if (!ValueToString(env, argv[1], value)) { + return nullptr; + } + instance->GetURLSearchParams()->remove(key, value); + } else { + instance->GetURLSearchParams()->remove(key); + } instance->SyncParent(); return nullptr; } @@ -297,35 +650,24 @@ napi_value URLSearchParams::GetAll(napi_env env, napi_callback_info info) { URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 1) return nullptr; - - size_t str_size; - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &str_size)) { + if (argc < 1) { + ThrowTypeError(env, "URLSearchParams.getAll requires 1 argument"); return nullptr; } - std::vector buffer(str_size + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], buffer.data(), - str_size + 1, nullptr)) { + std::string key; + if (!ValueToString(env, argv[0], key)) { return nullptr; } - auto values = instance->GetURLSearchParams()->get_all(buffer.data()); + auto values = instance->GetURLSearchParams()->get_all(key); napi_value result; - NAPI_GUARD(napi_create_array_with_length(env, values.size(), &result)) { - return nullptr; - } - - for (size_t i = 0; i < values.size(); i++) { - napi_value item; - NAPI_GUARD(napi_create_string_utf8(env, values[i].data(), - values[i].length(), &item)) { - return nullptr; - } - NAPI_GUARD(napi_set_element(env, result, i, item)) { return nullptr; } + napi_create_array_with_length(env, values.size(), &result); + // napi_set_element takes a uint32_t index; iterate as one to avoid narrowing. + for (uint32_t i = 0; i < values.size(); i++) { + napi_set_element(env, result, i, js_str(env, values[i])); } - return result; } @@ -335,30 +677,18 @@ napi_value URLSearchParams::Set(napi_env env, napi_callback_info info) { URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 2) return nullptr; - - size_t key_size, value_size; - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], nullptr, 0, &key_size)) { + if (argc < 2) { + ThrowTypeError(env, "URLSearchParams.set requires 2 arguments"); return nullptr; } - NAPI_GUARD( - napi_get_value_string_utf8(env, argv[1], nullptr, 0, &value_size)) { - return nullptr; - } - - std::vector key_buffer(key_size + 1); - std::vector value_buffer(value_size + 1); - NAPI_GUARD(napi_get_value_string_utf8(env, argv[0], key_buffer.data(), - key_size + 1, nullptr)) { - return nullptr; - } - NAPI_GUARD(napi_get_value_string_utf8(env, argv[1], value_buffer.data(), - value_size + 1, nullptr)) { + std::string key, value; + if (!ValueToString(env, argv[0], key) || + !ValueToString(env, argv[1], value)) { return nullptr; } - instance->GetURLSearchParams()->set(key_buffer.data(), value_buffer.data()); + instance->GetURLSearchParams()->set(key, value); instance->SyncParent(); return nullptr; } @@ -405,112 +735,45 @@ napi_value URLSearchParams::ToString(napi_env env, napi_callback_info info) { } napi_value URLSearchParams::Keys(napi_env env, napi_callback_info info) { - NAPI_PREAMBLE - URLSearchParams* instance = GetInstance(env, info); - if (!instance) return nullptr; - - auto keys = instance->GetURLSearchParams()->get_keys(); - std::vector key_list; - - while (keys.has_next()) { - if (auto key = keys.next()) { - key_list.push_back(key.value()); - } - } - - napi_value result; - NAPI_GUARD(napi_create_array_with_length(env, key_list.size(), &result)) { + // Read the callback info once: PrimJS does not return the receiver from a + // second napi_get_cb_info call for the same napi_callback_info, so calling it + // again inside GetInstance made the unwrap fail and every iterator accessor + // throw "Illegal invocation". + napi_value jsThis; + if (napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr) != + napi_ok) { return nullptr; } - - for (size_t i = 0; i < key_list.size(); i++) { - napi_value item; - NAPI_GUARD(napi_create_string_utf8(env, key_list[i].data(), - key_list[i].length(), &item)) { - return nullptr; - } - NAPI_GUARD(napi_set_element(env, result, i, item)) { return nullptr; } - } - - return result; + if (!GetInstanceOf(env, jsThis)) return nullptr; + return MakeIterator(env, jsThis, ITER_KEYS); } napi_value URLSearchParams::Values(napi_env env, napi_callback_info info) { - NAPI_PREAMBLE - URLSearchParams* instance = GetInstance(env, info); - if (!instance) return nullptr; - - auto keys = instance->GetURLSearchParams()->get_keys(); - std::vector value_list; - - while (keys.has_next()) { - if (auto key = keys.next()) { - if (auto value = instance->GetURLSearchParams()->get(key.value())) { - value_list.push_back(value.value()); - } - } - } - - napi_value result; - NAPI_GUARD(napi_create_array_with_length(env, value_list.size(), &result)) { + // Read the callback info once: PrimJS does not return the receiver from a + // second napi_get_cb_info call for the same napi_callback_info, so calling it + // again inside GetInstance made the unwrap fail and every iterator accessor + // throw "Illegal invocation". + napi_value jsThis; + if (napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr) != + napi_ok) { return nullptr; } - - for (size_t i = 0; i < value_list.size(); i++) { - napi_value item; - NAPI_GUARD(napi_create_string_utf8(env, value_list[i].data(), - value_list[i].length(), &item)) { - return nullptr; - } - NAPI_GUARD(napi_set_element(env, result, i, item)) { return nullptr; } - } - - return result; + if (!GetInstanceOf(env, jsThis)) return nullptr; + return MakeIterator(env, jsThis, ITER_VALUES); } napi_value URLSearchParams::Entries(napi_env env, napi_callback_info info) { - NAPI_PREAMBLE - URLSearchParams* instance = GetInstance(env, info); - if (!instance) return nullptr; - - auto keys = instance->GetURLSearchParams()->get_keys(); - std::vector> entries; - - while (keys.has_next()) { - if (auto key = keys.next()) { - if (auto value = instance->GetURLSearchParams()->get(key.value())) { - entries.emplace_back(key.value(), value.value()); - } - } - } - - napi_value result; - NAPI_GUARD(napi_create_array_with_length(env, entries.size(), &result)) { + // Read the callback info once: PrimJS does not return the receiver from a + // second napi_get_cb_info call for the same napi_callback_info, so calling it + // again inside GetInstance made the unwrap fail and every iterator accessor + // throw "Illegal invocation". + napi_value jsThis; + if (napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr) != + napi_ok) { return nullptr; } - - for (size_t i = 0; i < entries.size(); i++) { - napi_value entry; - NAPI_GUARD(napi_create_array_with_length(env, 2, &entry)) { - return nullptr; - } - - napi_value key, value; - NAPI_GUARD(napi_create_string_utf8(env, entries[i].first.data(), - entries[i].first.length(), &key)) { - return nullptr; - } - NAPI_GUARD(napi_create_string_utf8(env, entries[i].second.data(), - entries[i].second.length(), &value)) { - return nullptr; - } - - NAPI_GUARD(napi_set_element(env, entry, 0, key)) { return nullptr; } - NAPI_GUARD(napi_set_element(env, entry, 1, value)) { return nullptr; } - NAPI_GUARD(napi_set_element(env, result, i, entry)) { return nullptr; } - } - - return result; + if (!GetInstanceOf(env, jsThis)) return nullptr; + return MakeIterator(env, jsThis, ITER_ENTRIES); } napi_value URLSearchParams::ForEach(napi_env env, napi_callback_info info) { @@ -519,34 +782,31 @@ napi_value URLSearchParams::ForEach(napi_env env, napi_callback_info info) { URLSearchParams* instance = GetInstance(env, info); if (!instance) return nullptr; - if (argc < 1) return nullptr; + if (argc < 1 || !napi_util::is_of_type(env, argv[0], napi_function)) { + ThrowTypeError(env, + "URLSearchParams.forEach requires a callback function"); + return nullptr; + } napi_value callback = argv[0]; napi_value thisArg = argc >= 2 ? argv[1] : nullptr; - auto keys = instance->GetURLSearchParams()->get_keys(); - while (keys.has_next()) { - if (auto key = keys.next()) { - if (auto value = instance->GetURLSearchParams()->get(key.value())) { - napi_value args[3]; - NAPI_GUARD(napi_create_string_utf8(env, value.value().data(), - value.value().length(), &args[0])) { - return nullptr; - } - NAPI_GUARD(napi_create_string_utf8(env, key.value().data(), - key.value().length(), &args[1])) { - return nullptr; - } - args[2] = jsThis; - - napi_value global; - NAPI_GUARD(napi_get_global(env, &global)) { return nullptr; } - - napi_value result; - NAPI_GUARD(napi_call_function(env, thisArg ? thisArg : global, callback, - 3, args, &result)) { - return nullptr; - } + napi_value global; + napi_get_global(env, &global); + + // Use get_entries() so duplicate keys (e.g. ?a=1&a=2) each yield their own + // value; get(key) would return the first value for every occurrence. + auto entries = instance->GetURLSearchParams()->get_entries(); + while (entries.has_next()) { + if (auto entry = entries.next()) { + auto& [key, value] = entry.value(); + // Per spec, forEach callback receives (value, key, searchParams). + napi_value args[3] = {js_str(env, value), js_str(env, key), jsThis}; + napi_value result; + if (napi_call_function(env, thisArg ? thisArg : global, callback, 3, + args, &result) != napi_ok) { + // If the callback throws, stop iteration. + return nullptr; } } } @@ -591,6 +851,14 @@ void URLSearchParams::Init(napi_env env, napi_value global) { return; } + // Per spec, URLSearchParams.prototype[Symbol.iterator] === prototype.entries, + // so `for (const [k, v] of params)` works. + napi_value proto, entriesFn; + if (napi_get_named_property(env, ctor, "prototype", &proto) == napi_ok && + napi_get_named_property(env, proto, "entries", &entriesFn) == napi_ok) { + napi_set_property(env, proto, SymbolIterator(env), entriesFn); + } + NAPI_GUARD(napi_set_named_property(env, global, "URLSearchParams", ctor)) { return; } diff --git a/NativeScript/runtime/modules/url/ada/ada.cpp b/NativeScript/runtime/modules/url/ada/ada.cpp index 8237277f7..72f0e3167 100644 --- a/NativeScript/runtime/modules/url/ada/ada.cpp +++ b/NativeScript/runtime/modules/url/ada/ada.cpp @@ -1,28 +1,29 @@ -/* auto-generated on 2024-07-06 17:38:56 -0400. Do not edit! */ +/* auto-generated on 2025-09-23 12:57:35 -0400. Do not edit! */ /* begin file src/ada.cpp */ #include "ada.h" /* begin file src/checkers.cpp */ #include +#include +#include namespace ada::checkers { -ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept { +ada_really_inline constexpr bool is_ipv4(std::string_view view) noexcept { // The string is not empty and does not contain upper case ASCII characters. // // Optimization. To be considered as a possible ipv4, the string must end // with 'x' or a lowercase hex character. // Most of the time, this will be false so this simple check will save a lot // of effort. - char last_char = view.back(); // If the address ends with a dot, we need to prune it (special case). - if (last_char == '.') { + if (view.ends_with('.')) { view.remove_suffix(1); if (view.empty()) { return false; } - last_char = view.back(); } + char last_char = view.back(); bool possible_ipv4 = (last_char >= '0' && last_char <= '9') || (last_char >= 'a' && last_char <= 'f') || last_char == 'x'; @@ -38,7 +39,7 @@ ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept { /** Optimization opportunity: we have basically identified the last number of the ipv4 if we return true here. We might as well parse it and have at least one number parsed when we get to parse_ipv4. */ - if (std::all_of(view.begin(), view.end(), ada::checkers::is_digit)) { + if (std::ranges::all_of(view, ada::checkers::is_digit)) { return true; } // It could be hex (0x), but not if there is a single character. @@ -46,7 +47,7 @@ ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept { return false; } // It must start with 0x. - if (!std::equal(view.begin(), view.begin() + 2, "0x")) { + if (!view.starts_with("0x")) { return false; } // We must allow "0x". @@ -55,32 +56,32 @@ ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept { } // We have 0x followed by some characters, we need to check that they are // hexadecimals. - return std::all_of(view.begin() + 2, view.end(), - ada::unicode::is_lowercase_hex); + view.remove_prefix(2); + return std::ranges::all_of(view, ada::unicode::is_lowercase_hex); } // for use with path_signature, we include all characters that need percent // encoding. static constexpr std::array path_signature_table = - []() constexpr { - std::array result{}; - for (size_t i = 0; i < 256; i++) { - if (i <= 0x20 || i == 0x22 || i == 0x23 || i == 0x3c || i == 0x3e || - i == 0x3f || i == 0x60 || i == 0x7b || i == 0x7d || i > 0x7e) { - result[i] = 1; - } else if (i == 0x25) { - result[i] = 8; - } else if (i == 0x2e) { - result[i] = 4; - } else if (i == 0x5c) { - result[i] = 2; - } else { - result[i] = 0; - } - } - return result; -} -(); + []() consteval { + std::array result{}; + for (size_t i = 0; i < 256; i++) { + if (i <= 0x20 || i == 0x22 || i == 0x23 || i == 0x3c || i == 0x3e || + i == 0x3f || i == 0x5e || i == 0x60 || i == 0x7b || i == 0x7d || + i > 0x7e) { + result[i] = 1; + } else if (i == 0x25) { + result[i] = 8; + } else if (i == 0x2e) { + result[i] = 4; + } else if (i == 0x5c) { + result[i] = 2; + } else { + result[i] = 0; + } + } + return result; + }(); ada_really_inline constexpr uint8_t path_signature( std::string_view input) noexcept { @@ -134,7 +135,7 @@ ada_really_inline constexpr bool verify_dns_length( ADA_PUSH_DISABLE_ALL_WARNINGS /* begin file src/ada_idna.cpp */ -/* auto-generated on 2023-09-19 15:58:51 -0400. Do not edit! */ +/* auto-generated on 2025-06-26 23:04:30 -0300. Do not edit! */ /* begin file src/idna.cpp */ /* begin file src/unicode_transcoding.cpp */ @@ -147,7 +148,7 @@ namespace ada::idna { size_t utf8_to_utf32(const char* buf, size_t len, char32_t* utf32_output) { const uint8_t* data = reinterpret_cast(buf); size_t pos = 0; - char32_t* start{utf32_output}; + const char32_t* start{utf32_output}; while (pos < len) { // try to convert the next block of 16 ASCII bytes if (pos + 16 <= len) { // if it is safe to read 16 more @@ -266,7 +267,7 @@ size_t utf32_length_from_utf8(const char* buf, size_t len) { size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output) { const uint32_t* data = reinterpret_cast(buf); size_t pos = 0; - char* start{utf8_output}; + const char* start{utf8_output}; while (pos < len) { // try to convert the next block of 2 ASCII characters if (pos + 2 <= len) { // if it is safe to read 8 more @@ -326,7 +327,7 @@ size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output) { #include /* begin file src/mapping_tables.cpp */ -// IDNA 15.0.0 +// IDNA 16.0.0 // clang-format off #ifndef ADA_IDNA_TABLES_H @@ -335,7 +336,7 @@ size_t utf32_to_utf8(const char32_t* buf, size_t len, char* utf8_output) { namespace ada::idna { -const uint32_t mappings[5164] = +const uint32_t mappings[5236] = { 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 32, 32, 776, 32, 772, 50, 51, 32, 769, @@ -364,360 +365,366 @@ const uint32_t mappings[5164] = 1125, 1127, 1129, 1131, 1133, 1135, 1137, 1139, 1141, 1143, 1145, 1147, 1149, 1151, 1153, 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1183, 1185, 1187, 1189, 1191, 1193, 1195, 1197, 1199, 1201, 1203, 1205, 1207, 1209, 1211, 1213, 1215, - 1218, 1220, 1222, 1224, 1226, 1228, 1230, 1233, 1235, 1237, 1239, 1241, 1243, 1245, - 1247, 1249, 1251, 1253, 1255, 1257, 1259, 1261, 1263, 1265, 1267, 1269, 1271, 1273, - 1275, 1277, 1279, 1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1297, 1299, 1301, - 1303, 1305, 1307, 1309, 1311, 1313, 1315, 1317, 1319, 1321, 1323, 1325, 1327, 1377, - 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, - 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, - 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1381, 1410, 1575, 1652, 1608, - 1652, 1735, 1652, 1610, 1652, 2325, 2364, 2326, 2364, 2327, 2364, 2332, 2364, 2337, - 2364, 2338, 2364, 2347, 2364, 2351, 2364, 2465, 2492, 2466, 2492, 2479, 2492, 2610, - 2620, 2616, 2620, 2582, 2620, 2583, 2620, 2588, 2620, 2603, 2620, 2849, 2876, 2850, - 2876, 3661, 3634, 3789, 3762, 3755, 3737, 3755, 3745, 3851, 3906, 4023, 3916, 4023, - 3921, 4023, 3926, 4023, 3931, 4023, 3904, 4021, 3953, 3954, 3953, 3956, 4018, 3968, - 4018, 3953, 3968, 4019, 3968, 4019, 3953, 3968, 3986, 4023, 3996, 4023, 4001, 4023, - 4006, 4023, 4011, 4023, 3984, 4021, 11559, 11565, 4316, 5104, 5105, 5106, 5107, - 5108, 5109, 42571, 4304, 4305, 4306, 4307, 4308, 4309, 4310, 4311, 4312, 4313, 4314, - 4315, 4317, 4318, 4319, 4320, 4321, 4322, 4323, 4324, 4325, 4326, 4327, 4328, 4329, - 4330, 4331, 4332, 4333, 4334, 4335, 4336, 4337, 4338, 4339, 4340, 4341, 4342, 4343, - 4344, 4345, 4346, 4349, 4350, 4351, 592, 593, 7426, 604, 7446, 7447, 7453, 7461, - 594, 597, 607, 609, 613, 618, 7547, 669, 621, 7557, 671, 625, 624, 627, 628, 632, - 642, 427, 7452, 656, 657, 7681, 7683, 7685, 7687, 7689, 7691, 7693, 7695, 7697, - 7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, 7717, 7719, 7721, 7723, 7725, - 7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, 7747, 7749, 7751, 7753, - 7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, 7775, 7777, 7779, 7781, - 7783, 7785, 7787, 7789, 7791, 7793, 7795, 7797, 7799, 7801, 7803, 7805, 7807, 7809, - 7811, 7813, 7815, 7817, 7819, 7821, 7823, 7825, 7827, 7829, 97, 702, 115, 115, 7841, - 7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, 7861, 7863, 7865, 7867, 7869, - 7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, 7897, - 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, 7919, 7921, 7923, 7925, - 7927, 7929, 7931, 7933, 7935, 7936, 7937, 7938, 7939, 7940, 7941, 7942, 7943, 7952, - 7953, 7954, 7955, 7956, 7957, 7968, 7969, 7970, 7971, 7972, 7973, 7974, 7975, 7984, - 7985, 7986, 7987, 7988, 7989, 7990, 7991, 8000, 8001, 8002, 8003, 8004, 8005, 8017, - 8019, 8021, 8023, 8032, 8033, 8034, 8035, 8036, 8037, 8038, 8039, 7936, 953, 7937, - 953, 7938, 953, 7939, 953, 7940, 953, 7941, 953, 7942, 953, 7943, 953, 7968, 953, - 7969, 953, 7970, 953, 7971, 953, 7972, 953, 7973, 953, 7974, 953, 7975, 953, 8032, - 953, 8033, 953, 8034, 953, 8035, 953, 8036, 953, 8037, 953, 8038, 953, 8039, 953, - 8048, 953, 945, 953, 940, 953, 8118, 953, 8112, 8113, 32, 787, 32, 834, 32, 776, - 834, 8052, 953, 951, 953, 942, 953, 8134, 953, 8050, 32, 787, 768, 32, 787, 769, - 32, 787, 834, 912, 8144, 8145, 8054, 32, 788, 768, 32, 788, 769, 32, 788, 834, 944, - 8160, 8161, 8058, 8165, 32, 776, 768, 96, 8060, 953, 969, 953, 974, 953, 8182, 953, - 8056, 8208, 32, 819, 8242, 8242, 8242, 8242, 8242, 8245, 8245, 8245, 8245, 8245, - 33, 33, 32, 773, 63, 63, 63, 33, 33, 63, 48, 53, 54, 55, 56, 57, 43, 8722, 61, 40, - 41, 97, 47, 99, 97, 47, 115, 176, 99, 99, 47, 111, 99, 47, 117, 176, 102, 115, 109, - 116, 101, 108, 116, 109, 1488, 1489, 1490, 1491, 102, 97, 120, 8721, 49, 8260, 55, - 49, 8260, 57, 49, 8260, 49, 48, 49, 8260, 51, 50, 8260, 51, 49, 8260, 53, 50, 8260, - 53, 51, 8260, 53, 52, 8260, 53, 49, 8260, 54, 53, 8260, 54, 49, 8260, 56, 51, 8260, - 56, 53, 8260, 56, 55, 8260, 56, 105, 105, 105, 105, 105, 105, 118, 118, 105, 118, - 105, 105, 118, 105, 105, 105, 105, 120, 120, 105, 120, 105, 105, 48, 8260, 51, 8747, - 8747, 8747, 8747, 8747, 8750, 8750, 8750, 8750, 8750, 12296, 12297, 49, 50, 49, - 51, 49, 52, 49, 53, 49, 54, 49, 55, 49, 56, 49, 57, 50, 48, 40, 49, 41, 40, 50, - 41, 40, 51, 41, 40, 52, 41, 40, 53, 41, 40, 54, 41, 40, 55, 41, 40, 56, 41, 40, - 57, 41, 40, 49, 48, 41, 40, 49, 49, 41, 40, 49, 50, 41, 40, 49, 51, 41, 40, 49, - 52, 41, 40, 49, 53, 41, 40, 49, 54, 41, 40, 49, 55, 41, 40, 49, 56, 41, 40, 49, - 57, 41, 40, 50, 48, 41, 40, 97, 41, 40, 98, 41, 40, 99, 41, 40, 100, 41, 40, 101, - 41, 40, 102, 41, 40, 103, 41, 40, 104, 41, 40, 105, 41, 40, 106, 41, 40, 107, 41, - 40, 108, 41, 40, 109, 41, 40, 110, 41, 40, 111, 41, 40, 112, 41, 40, 113, 41, 40, - 114, 41, 40, 115, 41, 40, 116, 41, 40, 117, 41, 40, 118, 41, 40, 119, 41, 40, 120, - 41, 40, 121, 41, 40, 122, 41, 58, 58, 61, 61, 61, 10973, 824, 11312, 11313, 11314, - 11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, - 11327, 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, - 11339, 11340, 11341, 11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350, - 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358, 11359, 11361, 619, 7549, - 637, 11368, 11370, 11372, 11379, 11382, 575, 576, 11393, 11395, 11397, 11399, 11401, - 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, 11425, - 11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, - 11451, 11453, 11455, 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, - 11475, 11477, 11479, 11481, 11483, 11485, 11487, 11489, 11491, 11500, 11502, 11507, - 11617, 27597, 40863, 19968, 20008, 20022, 20031, 20057, 20101, 20108, 20128, 20154, - 20799, 20837, 20843, 20866, 20886, 20907, 20960, 20981, 20992, 21147, 21241, 21269, - 21274, 21304, 21313, 21340, 21353, 21378, 21430, 21448, 21475, 22231, 22303, 22763, - 22786, 22794, 22805, 22823, 22899, 23376, 23424, 23544, 23567, 23586, 23608, 23662, - 23665, 24027, 24037, 24049, 24062, 24178, 24186, 24191, 24308, 24318, 24331, 24339, - 24400, 24417, 24435, 24515, 25096, 25142, 25163, 25903, 25908, 25991, 26007, 26020, - 26041, 26080, 26085, 26352, 26376, 26408, 27424, 27490, 27513, 27571, 27595, 27604, - 27611, 27663, 27668, 27700, 28779, 29226, 29238, 29243, 29247, 29255, 29273, 29275, - 29356, 29572, 29577, 29916, 29926, 29976, 29983, 29992, 30000, 30091, 30098, 30326, - 30333, 30382, 30399, 30446, 30683, 30690, 30707, 31034, 31160, 31166, 31348, 31435, - 31481, 31859, 31992, 32566, 32593, 32650, 32701, 32769, 32780, 32786, 32819, 32895, - 32905, 33251, 33258, 33267, 33276, 33292, 33307, 33311, 33390, 33394, 33400, 34381, - 34411, 34880, 34892, 34915, 35198, 35211, 35282, 35328, 35895, 35910, 35925, 35960, - 35997, 36196, 36208, 36275, 36523, 36554, 36763, 36784, 36789, 37009, 37193, 37318, - 37324, 37329, 38263, 38272, 38428, 38582, 38585, 38632, 38737, 38750, 38754, 38761, - 38859, 38893, 38899, 38913, 39080, 39131, 39135, 39318, 39321, 39340, 39592, 39640, - 39647, 39717, 39727, 39730, 39740, 39770, 40165, 40565, 40575, 40613, 40635, 40643, - 40653, 40657, 40697, 40701, 40718, 40723, 40736, 40763, 40778, 40786, 40845, 40860, - 40864, 46, 12306, 21316, 21317, 32, 12441, 32, 12442, 12424, 12426, 12467, 12488, - 4352, 4353, 4522, 4354, 4524, 4525, 4355, 4356, 4357, 4528, 4529, 4530, 4531, 4532, - 4533, 4378, 4358, 4359, 4360, 4385, 4361, 4362, 4363, 4364, 4365, 4366, 4367, 4368, - 4369, 4370, 4449, 4450, 4451, 4452, 4453, 4454, 4455, 4456, 4457, 4458, 4459, 4460, - 4461, 4462, 4463, 4464, 4465, 4466, 4467, 4468, 4469, 4372, 4373, 4551, 4552, 4556, - 4558, 4563, 4567, 4569, 4380, 4573, 4575, 4381, 4382, 4384, 4386, 4387, 4391, 4393, - 4395, 4396, 4397, 4398, 4399, 4402, 4406, 4416, 4423, 4428, 4593, 4594, 4439, 4440, - 4441, 4484, 4485, 4488, 4497, 4498, 4500, 4510, 4513, 19977, 22235, 19978, 20013, - 19979, 30002, 19993, 19969, 22825, 22320, 40, 4352, 41, 40, 4354, 41, 40, 4355, - 41, 40, 4357, 41, 40, 4358, 41, 40, 4359, 41, 40, 4361, 41, 40, 4363, 41, 40, 4364, - 41, 40, 4366, 41, 40, 4367, 41, 40, 4368, 41, 40, 4369, 41, 40, 4370, 41, 40, 44032, - 41, 40, 45208, 41, 40, 45796, 41, 40, 46972, 41, 40, 47560, 41, 40, 48148, 41, 40, - 49324, 41, 40, 50500, 41, 40, 51088, 41, 40, 52264, 41, 40, 52852, 41, 40, 53440, - 41, 40, 54028, 41, 40, 54616, 41, 40, 51452, 41, 40, 50724, 51204, 41, 40, 50724, - 54980, 41, 40, 19968, 41, 40, 20108, 41, 40, 19977, 41, 40, 22235, 41, 40, 20116, - 41, 40, 20845, 41, 40, 19971, 41, 40, 20843, 41, 40, 20061, 41, 40, 21313, 41, 40, - 26376, 41, 40, 28779, 41, 40, 27700, 41, 40, 26408, 41, 40, 37329, 41, 40, 22303, - 41, 40, 26085, 41, 40, 26666, 41, 40, 26377, 41, 40, 31038, 41, 40, 21517, 41, 40, - 29305, 41, 40, 36001, 41, 40, 31069, 41, 40, 21172, 41, 40, 20195, 41, 40, 21628, - 41, 40, 23398, 41, 40, 30435, 41, 40, 20225, 41, 40, 36039, 41, 40, 21332, 41, 40, - 31085, 41, 40, 20241, 41, 40, 33258, 41, 40, 33267, 41, 21839, 24188, 31631, 112, - 116, 101, 50, 50, 50, 52, 50, 53, 50, 54, 50, 55, 50, 56, 50, 57, 51, 48, 51, 51, - 51, 52, 51, 53, 52280, 44256, 51452, 51032, 50864, 31192, 30007, 36969, 20778, 21360, - 27880, 38917, 20889, 27491, 24038, 21491, 21307, 23447, 22812, 51, 54, 51, 55, 51, - 56, 51, 57, 52, 48, 52, 52, 52, 53, 52, 54, 52, 55, 52, 56, 52, 57, 53, 48, 49, - 26376, 50, 26376, 51, 26376, 52, 26376, 53, 26376, 54, 26376, 55, 26376, 56, 26376, - 57, 26376, 49, 48, 26376, 49, 49, 26376, 49, 50, 26376, 104, 103, 101, 114, 103, - 101, 118, 108, 116, 100, 12450, 12452, 12454, 12456, 12458, 12459, 12461, 12463, - 12465, 12469, 12471, 12473, 12475, 12477, 12479, 12481, 12484, 12486, 12490, 12491, - 12492, 12493, 12494, 12495, 12498, 12501, 12504, 12507, 12510, 12511, 12512, 12513, - 12514, 12516, 12518, 12520, 12521, 12522, 12523, 12524, 12525, 12527, 12528, 12529, - 12530, 20196, 21644, 12450, 12497, 12540, 12488, 12450, 12523, 12501, 12449, 12450, - 12531, 12506, 12450, 12450, 12540, 12523, 12452, 12491, 12531, 12464, 12452, 12531, - 12481, 12454, 12457, 12531, 12456, 12473, 12463, 12540, 12489, 12456, 12540, 12459, - 12540, 12458, 12531, 12473, 12458, 12540, 12512, 12459, 12452, 12522, 12459, 12521, - 12483, 12488, 12459, 12525, 12522, 12540, 12460, 12525, 12531, 12460, 12531, 12510, - 12462, 12460, 12462, 12491, 12540, 12461, 12517, 12522, 12540, 12462, 12523, 12480, - 12540, 12461, 12525, 12461, 12525, 12464, 12521, 12512, 12461, 12525, 12513, 12540, - 12488, 12523, 12461, 12525, 12527, 12483, 12488, 12464, 12521, 12512, 12488, 12531, - 12463, 12523, 12476, 12452, 12525, 12463, 12525, 12540, 12493, 12465, 12540, 12473, - 12467, 12523, 12490, 12467, 12540, 12509, 12469, 12452, 12463, 12523, 12469, 12531, - 12481, 12540, 12512, 12471, 12522, 12531, 12464, 12475, 12531, 12481, 12475, 12531, - 12488, 12480, 12540, 12473, 12487, 12471, 12489, 12523, 12490, 12494, 12494, 12483, - 12488, 12495, 12452, 12484, 12497, 12540, 12475, 12531, 12488, 12497, 12540, 12484, - 12496, 12540, 12524, 12523, 12500, 12450, 12473, 12488, 12523, 12500, 12463, 12523, - 12500, 12467, 12499, 12523, 12501, 12449, 12521, 12483, 12489, 12501, 12451, 12540, - 12488, 12502, 12483, 12471, 12455, 12523, 12501, 12521, 12531, 12504, 12463, 12479, - 12540, 12523, 12506, 12477, 12506, 12491, 12498, 12504, 12523, 12484, 12506, 12531, - 12473, 12506, 12540, 12472, 12505, 12540, 12479, 12509, 12452, 12531, 12488, 12508, - 12523, 12488, 12507, 12531, 12509, 12531, 12489, 12507, 12540, 12523, 12507, 12540, - 12531, 12510, 12452, 12463, 12525, 12510, 12452, 12523, 12510, 12483, 12495, 12510, - 12523, 12463, 12510, 12531, 12471, 12519, 12531, 12511, 12463, 12525, 12531, 12511, - 12522, 12511, 12522, 12496, 12540, 12523, 12513, 12460, 12513, 12460, 12488, 12531, - 12516, 12540, 12489, 12516, 12540, 12523, 12518, 12450, 12531, 12522, 12483, 12488, - 12523, 12522, 12521, 12523, 12500, 12540, 12523, 12540, 12502, 12523, 12524, 12512, - 12524, 12531, 12488, 12466, 12531, 48, 28857, 49, 28857, 50, 28857, 51, 28857, 52, - 28857, 53, 28857, 54, 28857, 55, 28857, 56, 28857, 57, 28857, 49, 48, 28857, 49, - 49, 28857, 49, 50, 28857, 49, 51, 28857, 49, 52, 28857, 49, 53, 28857, 49, 54, 28857, - 49, 55, 28857, 49, 56, 28857, 49, 57, 28857, 50, 48, 28857, 50, 49, 28857, 50, 50, - 28857, 50, 51, 28857, 50, 52, 28857, 104, 112, 97, 100, 97, 97, 117, 98, 97, 114, - 111, 118, 112, 99, 100, 109, 100, 109, 50, 100, 109, 51, 105, 117, 24179, 25104, - 26157, 21644, 22823, 27491, 26126, 27835, 26666, 24335, 20250, 31038, 110, 97, 956, - 97, 109, 97, 107, 97, 107, 98, 109, 98, 103, 98, 99, 97, 108, 107, 99, 97, 108, - 112, 102, 110, 102, 956, 102, 956, 103, 109, 103, 107, 103, 104, 122, 107, 104, - 122, 109, 104, 122, 116, 104, 122, 956, 108, 109, 108, 100, 108, 102, 109, 110, - 109, 956, 109, 109, 109, 99, 109, 107, 109, 109, 109, 50, 99, 109, 50, 107, 109, - 50, 109, 109, 51, 99, 109, 51, 107, 109, 51, 109, 8725, 115, 109, 8725, 115, 50, - 107, 112, 97, 109, 112, 97, 103, 112, 97, 114, 97, 100, 114, 97, 100, 8725, 115, - 114, 97, 100, 8725, 115, 50, 112, 115, 110, 115, 956, 115, 109, 115, 112, 118, 110, - 118, 956, 118, 109, 118, 107, 118, 112, 119, 110, 119, 956, 119, 109, 119, 107, - 119, 107, 969, 109, 969, 98, 113, 99, 8725, 107, 103, 100, 98, 103, 121, 104, 97, - 105, 110, 107, 107, 107, 116, 108, 110, 108, 111, 103, 108, 120, 109, 105, 108, - 109, 111, 108, 112, 104, 112, 112, 109, 112, 114, 115, 118, 119, 98, 118, 8725, - 109, 97, 8725, 109, 49, 26085, 50, 26085, 51, 26085, 52, 26085, 53, 26085, 54, 26085, - 55, 26085, 56, 26085, 57, 26085, 49, 48, 26085, 49, 49, 26085, 49, 50, 26085, 49, - 51, 26085, 49, 52, 26085, 49, 53, 26085, 49, 54, 26085, 49, 55, 26085, 49, 56, 26085, - 49, 57, 26085, 50, 48, 26085, 50, 49, 26085, 50, 50, 26085, 50, 51, 26085, 50, 52, - 26085, 50, 53, 26085, 50, 54, 26085, 50, 55, 26085, 50, 56, 26085, 50, 57, 26085, - 51, 48, 26085, 51, 49, 26085, 103, 97, 108, 42561, 42563, 42565, 42567, 42569, 42573, - 42575, 42577, 42579, 42581, 42583, 42585, 42587, 42589, 42591, 42593, 42595, 42597, - 42599, 42601, 42603, 42605, 42625, 42627, 42629, 42631, 42633, 42635, 42637, 42639, - 42641, 42643, 42645, 42647, 42649, 42651, 42787, 42789, 42791, 42793, 42795, 42797, - 42799, 42803, 42805, 42807, 42809, 42811, 42813, 42815, 42817, 42819, 42821, 42823, - 42825, 42827, 42829, 42831, 42833, 42835, 42837, 42839, 42841, 42843, 42845, 42847, - 42849, 42851, 42853, 42855, 42857, 42859, 42861, 42863, 42874, 42876, 7545, 42879, - 42881, 42883, 42885, 42887, 42892, 42897, 42899, 42903, 42905, 42907, 42909, 42911, - 42913, 42915, 42917, 42919, 42921, 620, 670, 647, 43859, 42933, 42935, 42937, 42939, - 42941, 42943, 42945, 42947, 42900, 7566, 42952, 42954, 42961, 42967, 42969, 42998, - 43831, 43858, 653, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, - 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044, 5045, 5046, 5047, 5048, - 5049, 5050, 5051, 5052, 5053, 5054, 5055, 5056, 5057, 5058, 5059, 5060, 5061, 5062, - 5063, 5064, 5065, 5066, 5067, 5068, 5069, 5070, 5071, 5072, 5073, 5074, 5075, 5076, - 5077, 5078, 5079, 5080, 5081, 5082, 5083, 5084, 5085, 5086, 5087, 5088, 5089, 5090, - 5091, 5092, 5093, 5094, 5095, 5096, 5097, 5098, 5099, 5100, 5101, 5102, 5103, 35912, - 26356, 36040, 28369, 20018, 21477, 22865, 21895, 22856, 25078, 30313, 32645, 34367, - 34746, 35064, 37007, 27138, 27931, 28889, 29662, 33853, 37226, 39409, 20098, 21365, - 27396, 29211, 34349, 40478, 23888, 28651, 34253, 35172, 25289, 33240, 34847, 24266, - 26391, 28010, 29436, 37070, 20358, 20919, 21214, 25796, 27347, 29200, 30439, 34310, - 34396, 36335, 38706, 39791, 40442, 30860, 31103, 32160, 33737, 37636, 35542, 22751, - 24324, 31840, 32894, 29282, 30922, 36034, 38647, 22744, 23650, 27155, 28122, 28431, - 32047, 32311, 38475, 21202, 32907, 20956, 20940, 31260, 32190, 33777, 38517, 35712, - 25295, 35582, 20025, 23527, 24594, 29575, 30064, 21271, 30971, 20415, 24489, 19981, - 27852, 25976, 32034, 21443, 22622, 30465, 33865, 35498, 27578, 27784, 25342, 33509, - 25504, 30053, 20142, 20841, 20937, 26753, 31975, 33391, 35538, 37327, 21237, 21570, - 24300, 26053, 28670, 31018, 38317, 39530, 40599, 40654, 26310, 27511, 36706, 24180, - 24976, 25088, 25754, 28451, 29001, 29833, 31178, 32244, 32879, 36646, 34030, 36899, - 37706, 21015, 21155, 21693, 28872, 35010, 24265, 24565, 25467, 27566, 31806, 29557, - 22265, 23994, 24604, 29618, 29801, 32666, 32838, 37428, 38646, 38728, 38936, 20363, - 31150, 37300, 38584, 24801, 20102, 20698, 23534, 23615, 26009, 29134, 30274, 34044, - 36988, 26248, 38446, 21129, 26491, 26611, 27969, 28316, 29705, 30041, 30827, 32016, - 39006, 25134, 38520, 20523, 23833, 28138, 36650, 24459, 24900, 26647, 38534, 21033, - 21519, 23653, 26131, 26446, 26792, 27877, 29702, 30178, 32633, 35023, 35041, 38626, - 21311, 28346, 21533, 29136, 29848, 34298, 38563, 40023, 40607, 26519, 28107, 33256, - 31520, 31890, 29376, 28825, 35672, 20160, 33590, 21050, 20999, 24230, 25299, 31958, - 23429, 27934, 26292, 36667, 38477, 24275, 20800, 21952, 22618, 26228, 20958, 29482, - 30410, 31036, 31070, 31077, 31119, 38742, 31934, 34322, 35576, 36920, 37117, 39151, - 39164, 39208, 40372, 37086, 38583, 20398, 20711, 20813, 21193, 21220, 21329, 21917, - 22022, 22120, 22592, 22696, 23652, 24724, 24936, 24974, 25074, 25935, 26082, 26257, - 26757, 28023, 28186, 28450, 29038, 29227, 29730, 30865, 31049, 31048, 31056, 31062, - 31117, 31118, 31296, 31361, 31680, 32265, 32321, 32626, 32773, 33261, 33401, 33879, - 35088, 35222, 35585, 35641, 36051, 36104, 36790, 38627, 38911, 38971, 24693, 148206, - 33304, 20006, 20917, 20840, 20352, 20805, 20864, 21191, 21242, 21845, 21913, 21986, - 22707, 22852, 22868, 23138, 23336, 24274, 24281, 24425, 24493, 24792, 24910, 24840, - 24928, 25140, 25540, 25628, 25682, 25942, 26395, 26454, 28379, 28363, 28702, 30631, - 29237, 29359, 29809, 29958, 30011, 30237, 30239, 30427, 30452, 30538, 30528, 30924, - 31409, 31867, 32091, 32574, 33618, 33775, 34681, 35137, 35206, 35519, 35531, 35565, - 35722, 36664, 36978, 37273, 37494, 38524, 38875, 38923, 39698, 141386, 141380, 144341, - 15261, 16408, 16441, 152137, 154832, 163539, 40771, 40846, 102, 102, 102, 105, 102, - 108, 102, 102, 108, 1396, 1398, 1396, 1381, 1396, 1387, 1406, 1398, 1396, 1389, - 1497, 1460, 1522, 1463, 1506, 1492, 1499, 1500, 1501, 1512, 1514, 1513, 1473, 1513, - 1474, 1513, 1468, 1473, 1513, 1468, 1474, 1488, 1463, 1488, 1464, 1488, 1468, 1489, - 1468, 1490, 1468, 1491, 1468, 1492, 1468, 1493, 1468, 1494, 1468, 1496, 1468, 1497, - 1468, 1498, 1468, 1499, 1468, 1500, 1468, 1502, 1468, 1504, 1468, 1505, 1468, 1507, - 1468, 1508, 1468, 1510, 1468, 1511, 1468, 1512, 1468, 1514, 1468, 1493, 1465, 1489, - 1471, 1499, 1471, 1508, 1471, 1488, 1500, 1649, 1659, 1662, 1664, 1658, 1663, 1657, - 1700, 1702, 1668, 1667, 1670, 1671, 1677, 1676, 1678, 1672, 1688, 1681, 1705, 1711, - 1715, 1713, 1722, 1723, 1728, 1729, 1726, 1746, 1747, 1709, 1734, 1736, 1739, 1733, - 1737, 1744, 1609, 1574, 1575, 1574, 1749, 1574, 1608, 1574, 1735, 1574, 1734, 1574, - 1736, 1574, 1744, 1574, 1609, 1740, 1574, 1580, 1574, 1581, 1574, 1605, 1574, 1610, - 1576, 1580, 1576, 1581, 1576, 1582, 1576, 1605, 1576, 1609, 1576, 1610, 1578, 1580, - 1578, 1581, 1578, 1582, 1578, 1605, 1578, 1609, 1578, 1610, 1579, 1580, 1579, 1605, - 1579, 1609, 1579, 1610, 1580, 1581, 1580, 1605, 1581, 1605, 1582, 1580, 1582, 1581, - 1582, 1605, 1587, 1580, 1587, 1581, 1587, 1582, 1587, 1605, 1589, 1581, 1589, 1605, - 1590, 1580, 1590, 1581, 1590, 1582, 1590, 1605, 1591, 1581, 1591, 1605, 1592, 1605, - 1593, 1580, 1593, 1605, 1594, 1580, 1594, 1605, 1601, 1580, 1601, 1581, 1601, 1582, - 1601, 1605, 1601, 1609, 1601, 1610, 1602, 1581, 1602, 1605, 1602, 1609, 1602, 1610, - 1603, 1575, 1603, 1580, 1603, 1581, 1603, 1582, 1603, 1604, 1603, 1605, 1603, 1609, - 1603, 1610, 1604, 1580, 1604, 1581, 1604, 1582, 1604, 1605, 1604, 1609, 1604, 1610, - 1605, 1580, 1605, 1605, 1605, 1609, 1605, 1610, 1606, 1580, 1606, 1581, 1606, 1582, - 1606, 1605, 1606, 1609, 1606, 1610, 1607, 1580, 1607, 1605, 1607, 1609, 1607, 1610, - 1610, 1581, 1610, 1582, 1610, 1609, 1584, 1648, 1585, 1648, 1609, 1648, 32, 1612, - 1617, 32, 1613, 1617, 32, 1614, 1617, 32, 1615, 1617, 32, 1616, 1617, 32, 1617, - 1648, 1574, 1585, 1574, 1586, 1574, 1606, 1576, 1585, 1576, 1586, 1576, 1606, 1578, - 1585, 1578, 1586, 1578, 1606, 1579, 1585, 1579, 1586, 1579, 1606, 1605, 1575, 1606, - 1585, 1606, 1586, 1606, 1606, 1610, 1585, 1610, 1586, 1574, 1582, 1574, 1607, 1576, - 1607, 1578, 1607, 1589, 1582, 1604, 1607, 1606, 1607, 1607, 1648, 1579, 1607, 1587, - 1607, 1588, 1605, 1588, 1607, 1600, 1614, 1617, 1600, 1615, 1617, 1600, 1616, 1617, - 1591, 1609, 1591, 1610, 1593, 1609, 1593, 1610, 1594, 1609, 1594, 1610, 1587, 1609, - 1587, 1610, 1588, 1609, 1588, 1610, 1581, 1609, 1580, 1609, 1580, 1610, 1582, 1609, - 1589, 1609, 1589, 1610, 1590, 1609, 1590, 1610, 1588, 1580, 1588, 1581, 1588, 1582, - 1588, 1585, 1587, 1585, 1589, 1585, 1590, 1585, 1575, 1611, 1578, 1580, 1605, 1578, - 1581, 1580, 1578, 1581, 1605, 1578, 1582, 1605, 1578, 1605, 1580, 1578, 1605, 1581, - 1578, 1605, 1582, 1581, 1605, 1610, 1581, 1605, 1609, 1587, 1581, 1580, 1587, 1580, - 1581, 1587, 1580, 1609, 1587, 1605, 1581, 1587, 1605, 1580, 1587, 1605, 1605, 1589, - 1581, 1581, 1589, 1605, 1605, 1588, 1581, 1605, 1588, 1580, 1610, 1588, 1605, 1582, - 1588, 1605, 1605, 1590, 1581, 1609, 1590, 1582, 1605, 1591, 1605, 1581, 1591, 1605, - 1605, 1591, 1605, 1610, 1593, 1580, 1605, 1593, 1605, 1605, 1593, 1605, 1609, 1594, - 1605, 1605, 1594, 1605, 1610, 1594, 1605, 1609, 1601, 1582, 1605, 1602, 1605, 1581, - 1602, 1605, 1605, 1604, 1581, 1605, 1604, 1581, 1610, 1604, 1581, 1609, 1604, 1580, - 1580, 1604, 1582, 1605, 1604, 1605, 1581, 1605, 1581, 1580, 1605, 1581, 1610, 1605, - 1580, 1581, 1605, 1582, 1605, 1605, 1580, 1582, 1607, 1605, 1580, 1607, 1605, 1605, - 1606, 1581, 1605, 1606, 1581, 1609, 1606, 1580, 1605, 1606, 1580, 1609, 1606, 1605, - 1610, 1606, 1605, 1609, 1610, 1605, 1605, 1576, 1582, 1610, 1578, 1580, 1610, 1578, - 1580, 1609, 1578, 1582, 1610, 1578, 1582, 1609, 1578, 1605, 1610, 1578, 1605, 1609, - 1580, 1605, 1610, 1580, 1581, 1609, 1580, 1605, 1609, 1587, 1582, 1609, 1589, 1581, - 1610, 1588, 1581, 1610, 1590, 1581, 1610, 1604, 1580, 1610, 1604, 1605, 1610, 1610, - 1580, 1610, 1610, 1605, 1610, 1605, 1605, 1610, 1602, 1605, 1610, 1606, 1581, 1610, - 1593, 1605, 1610, 1603, 1605, 1610, 1606, 1580, 1581, 1605, 1582, 1610, 1604, 1580, - 1605, 1603, 1605, 1605, 1580, 1581, 1610, 1581, 1580, 1610, 1605, 1580, 1610, 1601, - 1605, 1610, 1576, 1581, 1610, 1587, 1582, 1610, 1606, 1580, 1610, 1589, 1604, 1746, - 1602, 1604, 1746, 1575, 1604, 1604, 1607, 1575, 1603, 1576, 1585, 1605, 1581, 1605, - 1583, 1589, 1604, 1593, 1605, 1585, 1587, 1608, 1604, 1593, 1604, 1610, 1607, 1608, - 1587, 1604, 1605, 1589, 1604, 1609, 1589, 1604, 1609, 32, 1575, 1604, 1604, 1607, - 32, 1593, 1604, 1610, 1607, 32, 1608, 1587, 1604, 1605, 1580, 1604, 32, 1580, 1604, - 1575, 1604, 1607, 1585, 1740, 1575, 1604, 44, 12289, 12310, 12311, 8212, 8211, 95, - 123, 125, 12308, 12309, 12304, 12305, 12298, 12299, 12300, 12301, 12302, 12303, - 91, 93, 35, 38, 42, 45, 60, 62, 92, 36, 37, 64, 32, 1611, 1600, 1611, 1600, 1617, - 32, 1618, 1600, 1618, 1569, 1570, 1571, 1572, 1573, 1577, 1604, 1570, 1604, 1571, - 1604, 1573, 34, 39, 94, 124, 126, 10629, 10630, 12539, 12453, 12515, 162, 163, 172, - 166, 165, 8361, 9474, 8592, 8593, 8594, 8595, 9632, 9675, 66600, 66601, 66602, 66603, - 66604, 66605, 66606, 66607, 66608, 66609, 66610, 66611, 66612, 66613, 66614, 66615, - 66616, 66617, 66618, 66619, 66620, 66621, 66622, 66623, 66624, 66625, 66626, 66627, - 66628, 66629, 66630, 66631, 66632, 66633, 66634, 66635, 66636, 66637, 66638, 66639, - 66776, 66777, 66778, 66779, 66780, 66781, 66782, 66783, 66784, 66785, 66786, 66787, - 66788, 66789, 66790, 66791, 66792, 66793, 66794, 66795, 66796, 66797, 66798, 66799, - 66800, 66801, 66802, 66803, 66804, 66805, 66806, 66807, 66808, 66809, 66810, 66811, - 66967, 66968, 66969, 66970, 66971, 66972, 66973, 66974, 66975, 66976, 66977, 66979, - 66980, 66981, 66982, 66983, 66984, 66985, 66986, 66987, 66988, 66989, 66990, 66991, - 66992, 66993, 66995, 66996, 66997, 66998, 66999, 67000, 67001, 67003, 67004, 720, - 721, 665, 675, 43878, 677, 676, 7569, 600, 606, 681, 612, 610, 667, 668, 615, 644, - 682, 683, 122628, 42894, 622, 122629, 654, 122630, 630, 631, 634, 122632, 638, 680, - 678, 43879, 679, 11377, 655, 673, 674, 664, 448, 449, 450, 122634, 122654, 68800, - 68801, 68802, 68803, 68804, 68805, 68806, 68807, 68808, 68809, 68810, 68811, 68812, - 68813, 68814, 68815, 68816, 68817, 68818, 68819, 68820, 68821, 68822, 68823, 68824, - 68825, 68826, 68827, 68828, 68829, 68830, 68831, 68832, 68833, 68834, 68835, 68836, - 68837, 68838, 68839, 68840, 68841, 68842, 68843, 68844, 68845, 68846, 68847, 68848, - 68849, 68850, 71872, 71873, 71874, 71875, 71876, 71877, 71878, 71879, 71880, 71881, - 71882, 71883, 71884, 71885, 71886, 71887, 71888, 71889, 71890, 71891, 71892, 71893, - 71894, 71895, 71896, 71897, 71898, 71899, 71900, 71901, 71902, 71903, 93792, 93793, - 93794, 93795, 93796, 93797, 93798, 93799, 93800, 93801, 93802, 93803, 93804, 93805, - 93806, 93807, 93808, 93809, 93810, 93811, 93812, 93813, 93814, 93815, 93816, 93817, - 93818, 93819, 93820, 93821, 93822, 93823, 119127, 119141, 119128, 119141, 119128, - 119141, 119150, 119128, 119141, 119151, 119128, 119141, 119152, 119128, 119141, - 119153, 119128, 119141, 119154, 119225, 119141, 119226, 119141, 119225, 119141, - 119150, 119226, 119141, 119150, 119225, 119141, 119151, 119226, 119141, 119151, - 305, 567, 8711, 8706, 1231, 125218, 125219, 125220, 125221, 125222, 125223, 125224, - 125225, 125226, 125227, 125228, 125229, 125230, 125231, 125232, 125233, 125234, - 125235, 125236, 125237, 125238, 125239, 125240, 125241, 125242, 125243, 125244, - 125245, 125246, 125247, 125248, 125249, 125250, 125251, 1646, 1697, 1647, 48, 44, - 49, 44, 50, 44, 51, 44, 52, 44, 53, 44, 54, 44, 55, 44, 56, 44, 57, 44, 12308, 115, - 12309, 119, 122, 104, 118, 115, 100, 112, 112, 118, 119, 99, 109, 114, 100, 106, - 12411, 12363, 12467, 12467, 23383, 21452, 22810, 35299, 20132, 26144, 28961, 21069, - 24460, 20877, 26032, 21021, 32066, 36009, 22768, 21561, 28436, 25237, 25429, 36938, - 25351, 25171, 31105, 31354, 21512, 28288, 30003, 21106, 21942, 37197, 12308, 26412, - 12309, 12308, 19977, 12309, 12308, 20108, 12309, 12308, 23433, 12309, 12308, 28857, - 12309, 12308, 25171, 12309, 12308, 30423, 12309, 12308, 21213, 12309, 12308, 25943, - 12309, 24471, 21487, 20029, 20024, 20033, 131362, 20320, 20411, 20482, 20602, 20633, - 20687, 13470, 132666, 20820, 20836, 20855, 132380, 13497, 20839, 132427, 20887, - 20900, 20172, 20908, 168415, 20995, 13535, 21051, 21062, 21111, 13589, 21253, 21254, - 21321, 21338, 21363, 21373, 21375, 133676, 28784, 21450, 21471, 133987, 21483, 21489, - 21510, 21662, 21560, 21576, 21608, 21666, 21750, 21776, 21843, 21859, 21892, 21931, - 21939, 21954, 22294, 22295, 22097, 22132, 22766, 22478, 22516, 22541, 22411, 22578, - 22577, 22700, 136420, 22770, 22775, 22790, 22818, 22882, 136872, 136938, 23020, - 23067, 23079, 23000, 23142, 14062, 23304, 23358, 137672, 23491, 23512, 23539, 138008, - 23551, 23558, 14209, 23648, 23744, 23693, 138724, 23875, 138726, 23918, 23915, 23932, - 24033, 24034, 14383, 24061, 24104, 24125, 24169, 14434, 139651, 14460, 24240, 24243, - 24246, 172946, 140081, 33281, 24354, 14535, 144056, 156122, 24418, 24427, 14563, - 24474, 24525, 24535, 24569, 24705, 14650, 14620, 141012, 24775, 24904, 24908, 24954, - 25010, 24996, 25007, 25054, 25115, 25181, 25265, 25300, 25424, 142092, 25405, 25340, - 25448, 25475, 25572, 142321, 25634, 25541, 25513, 14894, 25705, 25726, 25757, 25719, - 14956, 25964, 143370, 26083, 26360, 26185, 15129, 15112, 15076, 20882, 20885, 26368, - 26268, 32941, 17369, 26401, 26462, 26451, 144323, 15177, 26618, 26501, 26706, 144493, - 26766, 26655, 26900, 26946, 27043, 27114, 27304, 145059, 27355, 15384, 27425, 145575, - 27476, 15438, 27506, 27551, 27579, 146061, 138507, 146170, 27726, 146620, 27839, - 27853, 27751, 27926, 27966, 28009, 28024, 28037, 146718, 27956, 28207, 28270, 15667, - 28359, 147153, 28153, 28526, 147294, 147342, 28614, 28729, 28699, 15766, 28746, - 28797, 28791, 28845, 132389, 28997, 148067, 29084, 29224, 29264, 149000, 29312, - 29333, 149301, 149524, 29562, 29579, 16044, 29605, 16056, 29767, 29788, 29829, 29898, - 16155, 29988, 150582, 30014, 150674, 139679, 30224, 151457, 151480, 151620, 16380, - 16392, 151795, 151794, 151833, 151859, 30494, 30495, 30603, 16454, 16534, 152605, - 30798, 16611, 153126, 153242, 153285, 31211, 16687, 31306, 31311, 153980, 154279, - 16898, 154539, 31686, 31689, 16935, 154752, 31954, 17056, 31976, 31971, 32000, 155526, - 32099, 17153, 32199, 32258, 32325, 17204, 156200, 156231, 17241, 156377, 32634, - 156478, 32661, 32762, 156890, 156963, 32864, 157096, 32880, 144223, 17365, 32946, - 33027, 17419, 33086, 23221, 157607, 157621, 144275, 144284, 33284, 36766, 17515, - 33425, 33419, 33437, 21171, 33457, 33459, 33469, 33510, 158524, 33565, 33635, 33709, - 33571, 33725, 33767, 33619, 33738, 33740, 33756, 158774, 159083, 158933, 17707, - 34033, 34035, 34070, 160714, 34148, 159532, 17757, 17761, 159665, 159954, 17771, - 34384, 34407, 34409, 34473, 34440, 34574, 34530, 34600, 34667, 34694, 34785, 34817, - 17913, 34912, 161383, 35031, 35038, 17973, 35066, 13499, 161966, 162150, 18110, - 18119, 35488, 162984, 36011, 36033, 36123, 36215, 163631, 133124, 36299, 36284, - 36336, 133342, 36564, 165330, 165357, 37012, 37105, 37137, 165678, 37147, 37432, - 37591, 37592, 37500, 37881, 37909, 166906, 38283, 18837, 38327, 167287, 18918, 38595, - 23986, 38691, 168261, 168474, 19054, 19062, 38880, 168970, 19122, 169110, 38953, - 169398, 39138, 19251, 39209, 39335, 39362, 39422, 19406, 170800, 40000, 40189, 19662, - 19693, 40295, 172238, 19704, 172293, 172558, 172689, 19798, 40702, 40709, 40719, - 40726, 173568, + 1231, 1218, 1220, 1222, 1224, 1226, 1228, 1230, 1233, 1235, 1237, 1239, 1241, 1243, + 1245, 1247, 1249, 1251, 1253, 1255, 1257, 1259, 1261, 1263, 1265, 1267, 1269, 1271, + 1273, 1275, 1277, 1279, 1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1297, 1299, + 1301, 1303, 1305, 1307, 1309, 1311, 1313, 1315, 1317, 1319, 1321, 1323, 1325, 1327, + 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, + 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, + 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1381, 1410, 1575, 1652, + 1608, 1652, 1735, 1652, 1610, 1652, 2325, 2364, 2326, 2364, 2327, 2364, 2332, 2364, + 2337, 2364, 2338, 2364, 2347, 2364, 2351, 2364, 2465, 2492, 2466, 2492, 2479, 2492, + 2610, 2620, 2616, 2620, 2582, 2620, 2583, 2620, 2588, 2620, 2603, 2620, 2849, 2876, + 2850, 2876, 3661, 3634, 3789, 3762, 3755, 3737, 3755, 3745, 3851, 3906, 4023, 3916, + 4023, 3921, 4023, 3926, 4023, 3931, 4023, 3904, 4021, 3953, 3954, 3953, 3956, 4018, + 3968, 4018, 3953, 3968, 4019, 3968, 4019, 3953, 3968, 3986, 4023, 3996, 4023, 4001, + 4023, 4006, 4023, 4011, 4023, 3984, 4021, 11520, 11521, 11522, 11523, 11524, 11525, + 11526, 11527, 11528, 11529, 11530, 11531, 11532, 11533, 11534, 11535, 11536, 11537, + 11538, 11539, 11540, 11541, 11542, 11543, 11544, 11545, 11546, 11547, 11548, 11549, + 11550, 11551, 11552, 11553, 11554, 11555, 11556, 11557, 11559, 11565, 4316, 5104, + 5105, 5106, 5107, 5108, 5109, 42571, 7306, 4304, 4305, 4306, 4307, 4308, 4309, 4310, + 4311, 4312, 4313, 4314, 4315, 4317, 4318, 4319, 4320, 4321, 4322, 4323, 4324, 4325, + 4326, 4327, 4328, 4329, 4330, 4331, 4332, 4333, 4334, 4335, 4336, 4337, 4338, 4339, + 4340, 4341, 4342, 4343, 4344, 4345, 4346, 4349, 4350, 4351, 592, 593, 7426, 604, + 7446, 7447, 7453, 7461, 594, 597, 607, 609, 613, 618, 7547, 669, 621, 7557, 671, + 625, 624, 627, 628, 632, 642, 427, 7452, 656, 657, 7681, 7683, 7685, 7687, 7689, + 7691, 7693, 7695, 7697, 7699, 7701, 7703, 7705, 7707, 7709, 7711, 7713, 7715, 7717, + 7719, 7721, 7723, 7725, 7727, 7729, 7731, 7733, 7735, 7737, 7739, 7741, 7743, 7745, + 7747, 7749, 7751, 7753, 7755, 7757, 7759, 7761, 7763, 7765, 7767, 7769, 7771, 7773, + 7775, 7777, 7779, 7781, 7783, 7785, 7787, 7789, 7791, 7793, 7795, 7797, 7799, 7801, + 7803, 7805, 7807, 7809, 7811, 7813, 7815, 7817, 7819, 7821, 7823, 7825, 7827, 7829, + 97, 702, 223, 7841, 7843, 7845, 7847, 7849, 7851, 7853, 7855, 7857, 7859, 7861, + 7863, 7865, 7867, 7869, 7871, 7873, 7875, 7877, 7879, 7881, 7883, 7885, 7887, 7889, + 7891, 7893, 7895, 7897, 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7913, 7915, 7917, + 7919, 7921, 7923, 7925, 7927, 7929, 7931, 7933, 7935, 7936, 7937, 7938, 7939, 7940, + 7941, 7942, 7943, 7952, 7953, 7954, 7955, 7956, 7957, 7968, 7969, 7970, 7971, 7972, + 7973, 7974, 7975, 7984, 7985, 7986, 7987, 7988, 7989, 7990, 7991, 8000, 8001, 8002, + 8003, 8004, 8005, 8017, 8019, 8021, 8023, 8032, 8033, 8034, 8035, 8036, 8037, 8038, + 8039, 7936, 953, 7937, 953, 7938, 953, 7939, 953, 7940, 953, 7941, 953, 7942, 953, + 7943, 953, 7968, 953, 7969, 953, 7970, 953, 7971, 953, 7972, 953, 7973, 953, 7974, + 953, 7975, 953, 8032, 953, 8033, 953, 8034, 953, 8035, 953, 8036, 953, 8037, 953, + 8038, 953, 8039, 953, 8048, 953, 945, 953, 940, 953, 8118, 953, 8112, 8113, 32, + 787, 32, 834, 32, 776, 834, 8052, 953, 951, 953, 942, 953, 8134, 953, 8050, 32, + 787, 768, 32, 787, 769, 32, 787, 834, 912, 8144, 8145, 8054, 32, 788, 768, 32, 788, + 769, 32, 788, 834, 944, 8160, 8161, 8058, 8165, 32, 776, 768, 96, 8060, 953, 969, + 953, 974, 953, 8182, 953, 8056, 8208, 32, 819, 8242, 8242, 8242, 8242, 8242, 8245, + 8245, 8245, 8245, 8245, 33, 33, 32, 773, 63, 63, 63, 33, 33, 63, 48, 53, 54, 55, + 56, 57, 43, 8722, 61, 40, 41, 97, 47, 99, 97, 47, 115, 176, 99, 99, 47, 111, 99, + 47, 117, 176, 102, 115, 109, 116, 101, 108, 116, 109, 8526, 1488, 1489, 1490, 1491, + 102, 97, 120, 8721, 49, 8260, 55, 49, 8260, 57, 49, 8260, 49, 48, 49, 8260, 51, + 50, 8260, 51, 49, 8260, 53, 50, 8260, 53, 51, 8260, 53, 52, 8260, 53, 49, 8260, + 54, 53, 8260, 54, 49, 8260, 56, 51, 8260, 56, 53, 8260, 56, 55, 8260, 56, 105, 105, + 105, 105, 105, 105, 118, 118, 105, 118, 105, 105, 118, 105, 105, 105, 105, 120, + 120, 105, 120, 105, 105, 8580, 48, 8260, 51, 8747, 8747, 8747, 8747, 8747, 8750, + 8750, 8750, 8750, 8750, 12296, 12297, 49, 50, 49, 51, 49, 52, 49, 53, 49, 54, 49, + 55, 49, 56, 49, 57, 50, 48, 40, 49, 41, 40, 50, 41, 40, 51, 41, 40, 52, 41, 40, + 53, 41, 40, 54, 41, 40, 55, 41, 40, 56, 41, 40, 57, 41, 40, 49, 48, 41, 40, 49, + 49, 41, 40, 49, 50, 41, 40, 49, 51, 41, 40, 49, 52, 41, 40, 49, 53, 41, 40, 49, + 54, 41, 40, 49, 55, 41, 40, 49, 56, 41, 40, 49, 57, 41, 40, 50, 48, 41, 40, 97, + 41, 40, 98, 41, 40, 99, 41, 40, 100, 41, 40, 101, 41, 40, 102, 41, 40, 103, 41, + 40, 104, 41, 40, 105, 41, 40, 106, 41, 40, 107, 41, 40, 108, 41, 40, 109, 41, 40, + 110, 41, 40, 111, 41, 40, 112, 41, 40, 113, 41, 40, 114, 41, 40, 115, 41, 40, 116, + 41, 40, 117, 41, 40, 118, 41, 40, 119, 41, 40, 120, 41, 40, 121, 41, 40, 122, 41, + 58, 58, 61, 61, 61, 10973, 824, 11312, 11313, 11314, 11315, 11316, 11317, 11318, + 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, 11327, 11328, 11329, 11330, + 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, + 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, + 11355, 11356, 11357, 11358, 11359, 11361, 619, 7549, 637, 11368, 11370, 11372, 11379, + 11382, 575, 576, 11393, 11395, 11397, 11399, 11401, 11403, 11405, 11407, 11409, + 11411, 11413, 11415, 11417, 11419, 11421, 11423, 11425, 11427, 11429, 11431, 11433, + 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, 11457, + 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479, 11481, + 11483, 11485, 11487, 11489, 11491, 11500, 11502, 11507, 11617, 27597, 40863, 19968, + 20008, 20022, 20031, 20057, 20101, 20108, 20128, 20154, 20799, 20837, 20843, 20866, + 20886, 20907, 20960, 20981, 20992, 21147, 21241, 21269, 21274, 21304, 21313, 21340, + 21353, 21378, 21430, 21448, 21475, 22231, 22303, 22763, 22786, 22794, 22805, 22823, + 22899, 23376, 23424, 23544, 23567, 23586, 23608, 23662, 23665, 24027, 24037, 24049, + 24062, 24178, 24186, 24191, 24308, 24318, 24331, 24339, 24400, 24417, 24435, 24515, + 25096, 25142, 25163, 25903, 25908, 25991, 26007, 26020, 26041, 26080, 26085, 26352, + 26376, 26408, 27424, 27490, 27513, 27571, 27595, 27604, 27611, 27663, 27668, 27700, + 28779, 29226, 29238, 29243, 29247, 29255, 29273, 29275, 29356, 29572, 29577, 29916, + 29926, 29976, 29983, 29992, 30000, 30091, 30098, 30326, 30333, 30382, 30399, 30446, + 30683, 30690, 30707, 31034, 31160, 31166, 31348, 31435, 31481, 31859, 31992, 32566, + 32593, 32650, 32701, 32769, 32780, 32786, 32819, 32895, 32905, 33251, 33258, 33267, + 33276, 33292, 33307, 33311, 33390, 33394, 33400, 34381, 34411, 34880, 34892, 34915, + 35198, 35211, 35282, 35328, 35895, 35910, 35925, 35960, 35997, 36196, 36208, 36275, + 36523, 36554, 36763, 36784, 36789, 37009, 37193, 37318, 37324, 37329, 38263, 38272, + 38428, 38582, 38585, 38632, 38737, 38750, 38754, 38761, 38859, 38893, 38899, 38913, + 39080, 39131, 39135, 39318, 39321, 39340, 39592, 39640, 39647, 39717, 39727, 39730, + 39740, 39770, 40165, 40565, 40575, 40613, 40635, 40643, 40653, 40657, 40697, 40701, + 40718, 40723, 40736, 40763, 40778, 40786, 40845, 40860, 40864, 46, 12306, 21316, + 21317, 32, 12441, 32, 12442, 12424, 12426, 12467, 12488, 4352, 4353, 4522, 4354, + 4524, 4525, 4355, 4356, 4357, 4528, 4529, 4530, 4531, 4532, 4533, 4378, 4358, 4359, + 4360, 4385, 4361, 4362, 4363, 4364, 4365, 4366, 4367, 4368, 4369, 4370, 4449, 4450, + 4451, 4452, 4453, 4454, 4455, 4456, 4457, 4458, 4459, 4460, 4461, 4462, 4463, 4464, + 4465, 4466, 4467, 4468, 4469, 4372, 4373, 4551, 4552, 4556, 4558, 4563, 4567, 4569, + 4380, 4573, 4575, 4381, 4382, 4384, 4386, 4387, 4391, 4393, 4395, 4396, 4397, 4398, + 4399, 4402, 4406, 4416, 4423, 4428, 4593, 4594, 4439, 4440, 4441, 4484, 4485, 4488, + 4497, 4498, 4500, 4510, 4513, 19977, 22235, 19978, 20013, 19979, 30002, 19993, 19969, + 22825, 22320, 40, 4352, 41, 40, 4354, 41, 40, 4355, 41, 40, 4357, 41, 40, 4358, + 41, 40, 4359, 41, 40, 4361, 41, 40, 4363, 41, 40, 4364, 41, 40, 4366, 41, 40, 4367, + 41, 40, 4368, 41, 40, 4369, 41, 40, 4370, 41, 40, 44032, 41, 40, 45208, 41, 40, + 45796, 41, 40, 46972, 41, 40, 47560, 41, 40, 48148, 41, 40, 49324, 41, 40, 50500, + 41, 40, 51088, 41, 40, 52264, 41, 40, 52852, 41, 40, 53440, 41, 40, 54028, 41, 40, + 54616, 41, 40, 51452, 41, 40, 50724, 51204, 41, 40, 50724, 54980, 41, 40, 19968, + 41, 40, 20108, 41, 40, 19977, 41, 40, 22235, 41, 40, 20116, 41, 40, 20845, 41, 40, + 19971, 41, 40, 20843, 41, 40, 20061, 41, 40, 21313, 41, 40, 26376, 41, 40, 28779, + 41, 40, 27700, 41, 40, 26408, 41, 40, 37329, 41, 40, 22303, 41, 40, 26085, 41, 40, + 26666, 41, 40, 26377, 41, 40, 31038, 41, 40, 21517, 41, 40, 29305, 41, 40, 36001, + 41, 40, 31069, 41, 40, 21172, 41, 40, 20195, 41, 40, 21628, 41, 40, 23398, 41, 40, + 30435, 41, 40, 20225, 41, 40, 36039, 41, 40, 21332, 41, 40, 31085, 41, 40, 20241, + 41, 40, 33258, 41, 40, 33267, 41, 21839, 24188, 31631, 112, 116, 101, 50, 50, 50, + 52, 50, 53, 50, 54, 50, 55, 50, 56, 50, 57, 51, 48, 51, 51, 51, 52, 51, 53, 52280, + 44256, 51452, 51032, 50864, 31192, 30007, 36969, 20778, 21360, 27880, 38917, 20889, + 27491, 24038, 21491, 21307, 23447, 22812, 51, 54, 51, 55, 51, 56, 51, 57, 52, 48, + 52, 52, 52, 53, 52, 54, 52, 55, 52, 56, 52, 57, 53, 48, 49, 26376, 50, 26376, 51, + 26376, 52, 26376, 53, 26376, 54, 26376, 55, 26376, 56, 26376, 57, 26376, 49, 48, + 26376, 49, 49, 26376, 49, 50, 26376, 104, 103, 101, 114, 103, 101, 118, 108, 116, + 100, 12450, 12452, 12454, 12456, 12458, 12459, 12461, 12463, 12465, 12469, 12471, + 12473, 12475, 12477, 12479, 12481, 12484, 12486, 12490, 12491, 12492, 12493, 12494, + 12495, 12498, 12501, 12504, 12507, 12510, 12511, 12512, 12513, 12514, 12516, 12518, + 12520, 12521, 12522, 12523, 12524, 12525, 12527, 12528, 12529, 12530, 20196, 21644, + 12450, 12497, 12540, 12488, 12450, 12523, 12501, 12449, 12450, 12531, 12506, 12450, + 12450, 12540, 12523, 12452, 12491, 12531, 12464, 12452, 12531, 12481, 12454, 12457, + 12531, 12456, 12473, 12463, 12540, 12489, 12456, 12540, 12459, 12540, 12458, 12531, + 12473, 12458, 12540, 12512, 12459, 12452, 12522, 12459, 12521, 12483, 12488, 12459, + 12525, 12522, 12540, 12460, 12525, 12531, 12460, 12531, 12510, 12462, 12460, 12462, + 12491, 12540, 12461, 12517, 12522, 12540, 12462, 12523, 12480, 12540, 12461, 12525, + 12461, 12525, 12464, 12521, 12512, 12461, 12525, 12513, 12540, 12488, 12523, 12461, + 12525, 12527, 12483, 12488, 12464, 12521, 12512, 12488, 12531, 12463, 12523, 12476, + 12452, 12525, 12463, 12525, 12540, 12493, 12465, 12540, 12473, 12467, 12523, 12490, + 12467, 12540, 12509, 12469, 12452, 12463, 12523, 12469, 12531, 12481, 12540, 12512, + 12471, 12522, 12531, 12464, 12475, 12531, 12481, 12475, 12531, 12488, 12480, 12540, + 12473, 12487, 12471, 12489, 12523, 12490, 12494, 12494, 12483, 12488, 12495, 12452, + 12484, 12497, 12540, 12475, 12531, 12488, 12497, 12540, 12484, 12496, 12540, 12524, + 12523, 12500, 12450, 12473, 12488, 12523, 12500, 12463, 12523, 12500, 12467, 12499, + 12523, 12501, 12449, 12521, 12483, 12489, 12501, 12451, 12540, 12488, 12502, 12483, + 12471, 12455, 12523, 12501, 12521, 12531, 12504, 12463, 12479, 12540, 12523, 12506, + 12477, 12506, 12491, 12498, 12504, 12523, 12484, 12506, 12531, 12473, 12506, 12540, + 12472, 12505, 12540, 12479, 12509, 12452, 12531, 12488, 12508, 12523, 12488, 12507, + 12531, 12509, 12531, 12489, 12507, 12540, 12523, 12507, 12540, 12531, 12510, 12452, + 12463, 12525, 12510, 12452, 12523, 12510, 12483, 12495, 12510, 12523, 12463, 12510, + 12531, 12471, 12519, 12531, 12511, 12463, 12525, 12531, 12511, 12522, 12511, 12522, + 12496, 12540, 12523, 12513, 12460, 12513, 12460, 12488, 12531, 12516, 12540, 12489, + 12516, 12540, 12523, 12518, 12450, 12531, 12522, 12483, 12488, 12523, 12522, 12521, + 12523, 12500, 12540, 12523, 12540, 12502, 12523, 12524, 12512, 12524, 12531, 12488, + 12466, 12531, 48, 28857, 49, 28857, 50, 28857, 51, 28857, 52, 28857, 53, 28857, + 54, 28857, 55, 28857, 56, 28857, 57, 28857, 49, 48, 28857, 49, 49, 28857, 49, 50, + 28857, 49, 51, 28857, 49, 52, 28857, 49, 53, 28857, 49, 54, 28857, 49, 55, 28857, + 49, 56, 28857, 49, 57, 28857, 50, 48, 28857, 50, 49, 28857, 50, 50, 28857, 50, 51, + 28857, 50, 52, 28857, 104, 112, 97, 100, 97, 97, 117, 98, 97, 114, 111, 118, 112, + 99, 100, 109, 100, 109, 50, 100, 109, 51, 105, 117, 24179, 25104, 26157, 21644, + 22823, 27491, 26126, 27835, 26666, 24335, 20250, 31038, 110, 97, 956, 97, 109, 97, + 107, 97, 107, 98, 109, 98, 103, 98, 99, 97, 108, 107, 99, 97, 108, 112, 102, 110, + 102, 956, 102, 956, 103, 109, 103, 107, 103, 104, 122, 107, 104, 122, 109, 104, + 122, 116, 104, 122, 956, 108, 109, 108, 100, 108, 102, 109, 110, 109, 956, 109, + 109, 109, 99, 109, 107, 109, 109, 109, 50, 99, 109, 50, 107, 109, 50, 109, 109, + 51, 99, 109, 51, 107, 109, 51, 109, 8725, 115, 109, 8725, 115, 50, 107, 112, 97, + 109, 112, 97, 103, 112, 97, 114, 97, 100, 114, 97, 100, 8725, 115, 114, 97, 100, + 8725, 115, 50, 112, 115, 110, 115, 956, 115, 109, 115, 112, 118, 110, 118, 956, + 118, 109, 118, 107, 118, 112, 119, 110, 119, 956, 119, 109, 119, 107, 119, 107, + 969, 109, 969, 98, 113, 99, 8725, 107, 103, 100, 98, 103, 121, 104, 97, 105, 110, + 107, 107, 107, 116, 108, 110, 108, 111, 103, 108, 120, 109, 105, 108, 109, 111, + 108, 112, 104, 112, 112, 109, 112, 114, 115, 118, 119, 98, 118, 8725, 109, 97, 8725, + 109, 49, 26085, 50, 26085, 51, 26085, 52, 26085, 53, 26085, 54, 26085, 55, 26085, + 56, 26085, 57, 26085, 49, 48, 26085, 49, 49, 26085, 49, 50, 26085, 49, 51, 26085, + 49, 52, 26085, 49, 53, 26085, 49, 54, 26085, 49, 55, 26085, 49, 56, 26085, 49, 57, + 26085, 50, 48, 26085, 50, 49, 26085, 50, 50, 26085, 50, 51, 26085, 50, 52, 26085, + 50, 53, 26085, 50, 54, 26085, 50, 55, 26085, 50, 56, 26085, 50, 57, 26085, 51, 48, + 26085, 51, 49, 26085, 103, 97, 108, 42561, 42563, 42565, 42567, 42569, 42573, 42575, + 42577, 42579, 42581, 42583, 42585, 42587, 42589, 42591, 42593, 42595, 42597, 42599, + 42601, 42603, 42605, 42625, 42627, 42629, 42631, 42633, 42635, 42637, 42639, 42641, + 42643, 42645, 42647, 42649, 42651, 42787, 42789, 42791, 42793, 42795, 42797, 42799, + 42803, 42805, 42807, 42809, 42811, 42813, 42815, 42817, 42819, 42821, 42823, 42825, + 42827, 42829, 42831, 42833, 42835, 42837, 42839, 42841, 42843, 42845, 42847, 42849, + 42851, 42853, 42855, 42857, 42859, 42861, 42863, 42874, 42876, 7545, 42879, 42881, + 42883, 42885, 42887, 42892, 42897, 42899, 42903, 42905, 42907, 42909, 42911, 42913, + 42915, 42917, 42919, 42921, 620, 670, 647, 43859, 42933, 42935, 42937, 42939, 42941, + 42943, 42945, 42947, 42900, 7566, 42952, 42954, 612, 42957, 42961, 42967, 42969, + 42971, 411, 42998, 43831, 43858, 653, 5024, 5025, 5026, 5027, 5028, 5029, 5030, + 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044, + 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055, 5056, 5057, 5058, + 5059, 5060, 5061, 5062, 5063, 5064, 5065, 5066, 5067, 5068, 5069, 5070, 5071, 5072, + 5073, 5074, 5075, 5076, 5077, 5078, 5079, 5080, 5081, 5082, 5083, 5084, 5085, 5086, + 5087, 5088, 5089, 5090, 5091, 5092, 5093, 5094, 5095, 5096, 5097, 5098, 5099, 5100, + 5101, 5102, 5103, 35912, 26356, 36040, 28369, 20018, 21477, 22865, 21895, 22856, + 25078, 30313, 32645, 34367, 34746, 35064, 37007, 27138, 27931, 28889, 29662, 33853, + 37226, 39409, 20098, 21365, 27396, 29211, 34349, 40478, 23888, 28651, 34253, 35172, + 25289, 33240, 34847, 24266, 26391, 28010, 29436, 37070, 20358, 20919, 21214, 25796, + 27347, 29200, 30439, 34310, 34396, 36335, 38706, 39791, 40442, 30860, 31103, 32160, + 33737, 37636, 35542, 22751, 24324, 31840, 32894, 29282, 30922, 36034, 38647, 22744, + 23650, 27155, 28122, 28431, 32047, 32311, 38475, 21202, 32907, 20956, 20940, 31260, + 32190, 33777, 38517, 35712, 25295, 35582, 20025, 23527, 24594, 29575, 30064, 21271, + 30971, 20415, 24489, 19981, 27852, 25976, 32034, 21443, 22622, 30465, 33865, 35498, + 27578, 27784, 25342, 33509, 25504, 30053, 20142, 20841, 20937, 26753, 31975, 33391, + 35538, 37327, 21237, 21570, 24300, 26053, 28670, 31018, 38317, 39530, 40599, 40654, + 26310, 27511, 36706, 24180, 24976, 25088, 25754, 28451, 29001, 29833, 31178, 32244, + 32879, 36646, 34030, 36899, 37706, 21015, 21155, 21693, 28872, 35010, 24265, 24565, + 25467, 27566, 31806, 29557, 22265, 23994, 24604, 29618, 29801, 32666, 32838, 37428, + 38646, 38728, 38936, 20363, 31150, 37300, 38584, 24801, 20102, 20698, 23534, 23615, + 26009, 29134, 30274, 34044, 36988, 26248, 38446, 21129, 26491, 26611, 27969, 28316, + 29705, 30041, 30827, 32016, 39006, 25134, 38520, 20523, 23833, 28138, 36650, 24459, + 24900, 26647, 38534, 21033, 21519, 23653, 26131, 26446, 26792, 27877, 29702, 30178, + 32633, 35023, 35041, 38626, 21311, 28346, 21533, 29136, 29848, 34298, 38563, 40023, + 40607, 26519, 28107, 33256, 31520, 31890, 29376, 28825, 35672, 20160, 33590, 21050, + 20999, 24230, 25299, 31958, 23429, 27934, 26292, 36667, 38477, 24275, 20800, 21952, + 22618, 26228, 20958, 29482, 30410, 31036, 31070, 31077, 31119, 38742, 31934, 34322, + 35576, 36920, 37117, 39151, 39164, 39208, 40372, 37086, 38583, 20398, 20711, 20813, + 21193, 21220, 21329, 21917, 22022, 22120, 22592, 22696, 23652, 24724, 24936, 24974, + 25074, 25935, 26082, 26257, 26757, 28023, 28186, 28450, 29038, 29227, 29730, 30865, + 31049, 31048, 31056, 31062, 31117, 31118, 31296, 31361, 31680, 32265, 32321, 32626, + 32773, 33261, 33401, 33879, 35088, 35222, 35585, 35641, 36051, 36104, 36790, 38627, + 38911, 38971, 24693, 148206, 33304, 20006, 20917, 20840, 20352, 20805, 20864, 21191, + 21242, 21845, 21913, 21986, 22707, 22852, 22868, 23138, 23336, 24274, 24281, 24425, + 24493, 24792, 24910, 24840, 24928, 25140, 25540, 25628, 25682, 25942, 26395, 26454, + 28379, 28363, 28702, 30631, 29237, 29359, 29809, 29958, 30011, 30237, 30239, 30427, + 30452, 30538, 30528, 30924, 31409, 31867, 32091, 32574, 33618, 33775, 34681, 35137, + 35206, 35519, 35531, 35565, 35722, 36664, 36978, 37273, 37494, 38524, 38875, 38923, + 39698, 141386, 141380, 144341, 15261, 16408, 16441, 152137, 154832, 163539, 40771, + 40846, 102, 102, 102, 105, 102, 108, 102, 102, 108, 1396, 1398, 1396, 1381, 1396, + 1387, 1406, 1398, 1396, 1389, 1497, 1460, 1522, 1463, 1506, 1492, 1499, 1500, 1501, + 1512, 1514, 1513, 1473, 1513, 1474, 1513, 1468, 1473, 1513, 1468, 1474, 1488, 1463, + 1488, 1464, 1488, 1468, 1489, 1468, 1490, 1468, 1491, 1468, 1492, 1468, 1493, 1468, + 1494, 1468, 1496, 1468, 1497, 1468, 1498, 1468, 1499, 1468, 1500, 1468, 1502, 1468, + 1504, 1468, 1505, 1468, 1507, 1468, 1508, 1468, 1510, 1468, 1511, 1468, 1512, 1468, + 1514, 1468, 1493, 1465, 1489, 1471, 1499, 1471, 1508, 1471, 1488, 1500, 1649, 1659, + 1662, 1664, 1658, 1663, 1657, 1700, 1702, 1668, 1667, 1670, 1671, 1677, 1676, 1678, + 1672, 1688, 1681, 1705, 1711, 1715, 1713, 1722, 1723, 1728, 1729, 1726, 1746, 1747, + 1709, 1734, 1736, 1739, 1733, 1737, 1744, 1609, 1574, 1575, 1574, 1749, 1574, 1608, + 1574, 1735, 1574, 1734, 1574, 1736, 1574, 1744, 1574, 1609, 1740, 1574, 1580, 1574, + 1581, 1574, 1605, 1574, 1610, 1576, 1580, 1576, 1581, 1576, 1582, 1576, 1605, 1576, + 1609, 1576, 1610, 1578, 1580, 1578, 1581, 1578, 1582, 1578, 1605, 1578, 1609, 1578, + 1610, 1579, 1580, 1579, 1605, 1579, 1609, 1579, 1610, 1580, 1581, 1580, 1605, 1581, + 1605, 1582, 1580, 1582, 1581, 1582, 1605, 1587, 1580, 1587, 1581, 1587, 1582, 1587, + 1605, 1589, 1581, 1589, 1605, 1590, 1580, 1590, 1581, 1590, 1582, 1590, 1605, 1591, + 1581, 1591, 1605, 1592, 1605, 1593, 1580, 1593, 1605, 1594, 1580, 1594, 1605, 1601, + 1580, 1601, 1581, 1601, 1582, 1601, 1605, 1601, 1609, 1601, 1610, 1602, 1581, 1602, + 1605, 1602, 1609, 1602, 1610, 1603, 1575, 1603, 1580, 1603, 1581, 1603, 1582, 1603, + 1604, 1603, 1605, 1603, 1609, 1603, 1610, 1604, 1580, 1604, 1581, 1604, 1582, 1604, + 1605, 1604, 1609, 1604, 1610, 1605, 1580, 1605, 1605, 1605, 1609, 1605, 1610, 1606, + 1580, 1606, 1581, 1606, 1582, 1606, 1605, 1606, 1609, 1606, 1610, 1607, 1580, 1607, + 1605, 1607, 1609, 1607, 1610, 1610, 1581, 1610, 1582, 1610, 1609, 1584, 1648, 1585, + 1648, 1609, 1648, 32, 1612, 1617, 32, 1613, 1617, 32, 1614, 1617, 32, 1615, 1617, + 32, 1616, 1617, 32, 1617, 1648, 1574, 1585, 1574, 1586, 1574, 1606, 1576, 1585, + 1576, 1586, 1576, 1606, 1578, 1585, 1578, 1586, 1578, 1606, 1579, 1585, 1579, 1586, + 1579, 1606, 1605, 1575, 1606, 1585, 1606, 1586, 1606, 1606, 1610, 1585, 1610, 1586, + 1574, 1582, 1574, 1607, 1576, 1607, 1578, 1607, 1589, 1582, 1604, 1607, 1606, 1607, + 1607, 1648, 1579, 1607, 1587, 1607, 1588, 1605, 1588, 1607, 1600, 1614, 1617, 1600, + 1615, 1617, 1600, 1616, 1617, 1591, 1609, 1591, 1610, 1593, 1609, 1593, 1610, 1594, + 1609, 1594, 1610, 1587, 1609, 1587, 1610, 1588, 1609, 1588, 1610, 1581, 1609, 1580, + 1609, 1580, 1610, 1582, 1609, 1589, 1609, 1589, 1610, 1590, 1609, 1590, 1610, 1588, + 1580, 1588, 1581, 1588, 1582, 1588, 1585, 1587, 1585, 1589, 1585, 1590, 1585, 1575, + 1611, 1578, 1580, 1605, 1578, 1581, 1580, 1578, 1581, 1605, 1578, 1582, 1605, 1578, + 1605, 1580, 1578, 1605, 1581, 1578, 1605, 1582, 1581, 1605, 1610, 1581, 1605, 1609, + 1587, 1581, 1580, 1587, 1580, 1581, 1587, 1580, 1609, 1587, 1605, 1581, 1587, 1605, + 1580, 1587, 1605, 1605, 1589, 1581, 1581, 1589, 1605, 1605, 1588, 1581, 1605, 1588, + 1580, 1610, 1588, 1605, 1582, 1588, 1605, 1605, 1590, 1581, 1609, 1590, 1582, 1605, + 1591, 1605, 1581, 1591, 1605, 1605, 1591, 1605, 1610, 1593, 1580, 1605, 1593, 1605, + 1605, 1593, 1605, 1609, 1594, 1605, 1605, 1594, 1605, 1610, 1594, 1605, 1609, 1601, + 1582, 1605, 1602, 1605, 1581, 1602, 1605, 1605, 1604, 1581, 1605, 1604, 1581, 1610, + 1604, 1581, 1609, 1604, 1580, 1580, 1604, 1582, 1605, 1604, 1605, 1581, 1605, 1581, + 1580, 1605, 1581, 1610, 1605, 1580, 1581, 1605, 1582, 1605, 1605, 1580, 1582, 1607, + 1605, 1580, 1607, 1605, 1605, 1606, 1581, 1605, 1606, 1581, 1609, 1606, 1580, 1605, + 1606, 1580, 1609, 1606, 1605, 1610, 1606, 1605, 1609, 1610, 1605, 1605, 1576, 1582, + 1610, 1578, 1580, 1610, 1578, 1580, 1609, 1578, 1582, 1610, 1578, 1582, 1609, 1578, + 1605, 1610, 1578, 1605, 1609, 1580, 1605, 1610, 1580, 1581, 1609, 1580, 1605, 1609, + 1587, 1582, 1609, 1589, 1581, 1610, 1588, 1581, 1610, 1590, 1581, 1610, 1604, 1580, + 1610, 1604, 1605, 1610, 1610, 1580, 1610, 1610, 1605, 1610, 1605, 1605, 1610, 1602, + 1605, 1610, 1606, 1581, 1610, 1593, 1605, 1610, 1603, 1605, 1610, 1606, 1580, 1581, + 1605, 1582, 1610, 1604, 1580, 1605, 1603, 1605, 1605, 1580, 1581, 1610, 1581, 1580, + 1610, 1605, 1580, 1610, 1601, 1605, 1610, 1576, 1581, 1610, 1587, 1582, 1610, 1606, + 1580, 1610, 1589, 1604, 1746, 1602, 1604, 1746, 1575, 1604, 1604, 1607, 1575, 1603, + 1576, 1585, 1605, 1581, 1605, 1583, 1589, 1604, 1593, 1605, 1585, 1587, 1608, 1604, + 1593, 1604, 1610, 1607, 1608, 1587, 1604, 1605, 1589, 1604, 1609, 1589, 1604, 1609, + 32, 1575, 1604, 1604, 1607, 32, 1593, 1604, 1610, 1607, 32, 1608, 1587, 1604, 1605, + 1580, 1604, 32, 1580, 1604, 1575, 1604, 1607, 1585, 1740, 1575, 1604, 44, 12289, + 12310, 12311, 8212, 8211, 95, 123, 125, 12308, 12309, 12304, 12305, 12298, 12299, + 12300, 12301, 12302, 12303, 91, 93, 35, 38, 42, 45, 60, 62, 92, 36, 37, 64, 32, + 1611, 1600, 1611, 1600, 1617, 32, 1618, 1600, 1618, 1569, 1570, 1571, 1572, 1573, + 1577, 1604, 1570, 1604, 1571, 1604, 1573, 34, 39, 94, 124, 126, 10629, 10630, 12539, + 12453, 12515, 162, 163, 172, 166, 165, 8361, 9474, 8592, 8593, 8594, 8595, 9632, + 9675, 66600, 66601, 66602, 66603, 66604, 66605, 66606, 66607, 66608, 66609, 66610, + 66611, 66612, 66613, 66614, 66615, 66616, 66617, 66618, 66619, 66620, 66621, 66622, + 66623, 66624, 66625, 66626, 66627, 66628, 66629, 66630, 66631, 66632, 66633, 66634, + 66635, 66636, 66637, 66638, 66639, 66776, 66777, 66778, 66779, 66780, 66781, 66782, + 66783, 66784, 66785, 66786, 66787, 66788, 66789, 66790, 66791, 66792, 66793, 66794, + 66795, 66796, 66797, 66798, 66799, 66800, 66801, 66802, 66803, 66804, 66805, 66806, + 66807, 66808, 66809, 66810, 66811, 66967, 66968, 66969, 66970, 66971, 66972, 66973, + 66974, 66975, 66976, 66977, 66979, 66980, 66981, 66982, 66983, 66984, 66985, 66986, + 66987, 66988, 66989, 66990, 66991, 66992, 66993, 66995, 66996, 66997, 66998, 66999, + 67000, 67001, 67003, 67004, 720, 721, 665, 675, 43878, 677, 676, 7569, 600, 606, + 681, 610, 667, 668, 615, 644, 682, 683, 122628, 42894, 622, 122629, 654, 122630, + 630, 631, 634, 122632, 638, 680, 678, 43879, 679, 11377, 655, 673, 674, 664, 448, + 449, 450, 122634, 122654, 68800, 68801, 68802, 68803, 68804, 68805, 68806, 68807, + 68808, 68809, 68810, 68811, 68812, 68813, 68814, 68815, 68816, 68817, 68818, 68819, + 68820, 68821, 68822, 68823, 68824, 68825, 68826, 68827, 68828, 68829, 68830, 68831, + 68832, 68833, 68834, 68835, 68836, 68837, 68838, 68839, 68840, 68841, 68842, 68843, + 68844, 68845, 68846, 68847, 68848, 68849, 68850, 68976, 68977, 68978, 68979, 68980, + 68981, 68982, 68983, 68984, 68985, 68986, 68987, 68988, 68989, 68990, 68991, 68992, + 68993, 68994, 68995, 68996, 68997, 71872, 71873, 71874, 71875, 71876, 71877, 71878, + 71879, 71880, 71881, 71882, 71883, 71884, 71885, 71886, 71887, 71888, 71889, 71890, + 71891, 71892, 71893, 71894, 71895, 71896, 71897, 71898, 71899, 71900, 71901, 71902, + 71903, 93792, 93793, 93794, 93795, 93796, 93797, 93798, 93799, 93800, 93801, 93802, + 93803, 93804, 93805, 93806, 93807, 93808, 93809, 93810, 93811, 93812, 93813, 93814, + 93815, 93816, 93817, 93818, 93819, 93820, 93821, 93822, 93823, 119127, 119141, 119128, + 119141, 119128, 119141, 119150, 119128, 119141, 119151, 119128, 119141, 119152, + 119128, 119141, 119153, 119128, 119141, 119154, 119225, 119141, 119226, 119141, + 119225, 119141, 119150, 119226, 119141, 119150, 119225, 119141, 119151, 119226, + 119141, 119151, 305, 567, 8711, 8706, 125218, 125219, 125220, 125221, 125222, 125223, + 125224, 125225, 125226, 125227, 125228, 125229, 125230, 125231, 125232, 125233, + 125234, 125235, 125236, 125237, 125238, 125239, 125240, 125241, 125242, 125243, + 125244, 125245, 125246, 125247, 125248, 125249, 125250, 125251, 1646, 1697, 1647, + 48, 44, 49, 44, 50, 44, 51, 44, 52, 44, 53, 44, 54, 44, 55, 44, 56, 44, 57, 44, + 12308, 115, 12309, 119, 122, 104, 118, 115, 100, 115, 115, 112, 112, 118, 119, 99, + 109, 114, 100, 106, 12411, 12363, 12467, 12467, 23383, 21452, 22810, 35299, 20132, + 26144, 28961, 21069, 24460, 20877, 26032, 21021, 32066, 36009, 22768, 21561, 28436, + 25237, 25429, 36938, 25351, 25171, 31105, 31354, 21512, 28288, 30003, 21106, 21942, + 37197, 12308, 26412, 12309, 12308, 19977, 12309, 12308, 20108, 12309, 12308, 23433, + 12309, 12308, 28857, 12309, 12308, 25171, 12309, 12308, 30423, 12309, 12308, 21213, + 12309, 12308, 25943, 12309, 24471, 21487, 20029, 20024, 20033, 131362, 20320, 20411, + 20482, 20602, 20633, 20687, 13470, 132666, 20820, 20836, 20855, 132380, 13497, 20839, + 132427, 20887, 20900, 20172, 20908, 168415, 20995, 13535, 21051, 21062, 21111, 13589, + 21253, 21254, 21321, 21338, 21363, 21373, 21375, 133676, 28784, 21450, 21471, 133987, + 21483, 21489, 21510, 21662, 21560, 21576, 21608, 21666, 21750, 21776, 21843, 21859, + 21892, 21931, 21939, 21954, 22294, 22295, 22097, 22132, 22766, 22478, 22516, 22541, + 22411, 22578, 22577, 22700, 136420, 22770, 22775, 22790, 22818, 22882, 136872, 136938, + 23020, 23067, 23079, 23000, 23142, 14062, 14076, 23304, 23358, 137672, 23491, 23512, + 23539, 138008, 23551, 23558, 24403, 14209, 23648, 23744, 23693, 138724, 23875, 138726, + 23918, 23915, 23932, 24033, 24034, 14383, 24061, 24104, 24125, 24169, 14434, 139651, + 14460, 24240, 24243, 24246, 172946, 140081, 33281, 24354, 14535, 144056, 156122, + 24418, 24427, 14563, 24474, 24525, 24535, 24569, 24705, 14650, 14620, 141012, 24775, + 24904, 24908, 24954, 25010, 24996, 25007, 25054, 25115, 25181, 25265, 25300, 25424, + 142092, 25405, 25340, 25448, 25475, 25572, 142321, 25634, 25541, 25513, 14894, 25705, + 25726, 25757, 25719, 14956, 25964, 143370, 26083, 26360, 26185, 15129, 15112, 15076, + 20882, 20885, 26368, 26268, 32941, 17369, 26401, 26462, 26451, 144323, 15177, 26618, + 26501, 26706, 144493, 26766, 26655, 26900, 26946, 27043, 27114, 27304, 145059, 27355, + 15384, 27425, 145575, 27476, 15438, 27506, 27551, 27579, 146061, 138507, 146170, + 27726, 146620, 27839, 27853, 27751, 27926, 27966, 28009, 28024, 28037, 146718, 27956, + 28207, 28270, 15667, 28359, 147153, 28153, 28526, 147294, 147342, 28614, 28729, + 28699, 15766, 28746, 28797, 28791, 28845, 132389, 28997, 148067, 29084, 148395, + 29224, 29264, 149000, 29312, 29333, 149301, 149524, 29562, 29579, 16044, 29605, + 16056, 29767, 29788, 29829, 29898, 16155, 29988, 150582, 30014, 150674, 139679, + 30224, 151457, 151480, 151620, 16380, 16392, 151795, 151794, 151833, 151859, 30494, + 30495, 30603, 16454, 16534, 152605, 30798, 16611, 153126, 153242, 153285, 31211, + 16687, 31306, 31311, 153980, 154279, 31470, 16898, 154539, 31686, 31689, 16935, + 154752, 31954, 17056, 31976, 31971, 32000, 155526, 32099, 17153, 32199, 32258, 32325, + 17204, 156200, 156231, 17241, 156377, 32634, 156478, 32661, 32762, 156890, 156963, + 32864, 157096, 32880, 144223, 17365, 32946, 33027, 17419, 33086, 23221, 157607, + 157621, 144275, 144284, 33284, 36766, 17515, 33425, 33419, 33437, 21171, 33457, + 33459, 33469, 33510, 158524, 33565, 33635, 33709, 33571, 33725, 33767, 33619, 33738, + 33740, 33756, 158774, 159083, 158933, 17707, 34033, 34035, 34070, 160714, 34148, + 159532, 17757, 17761, 159665, 159954, 17771, 34384, 34407, 34409, 34473, 34440, + 34574, 34530, 34600, 34667, 34694, 17879, 34785, 34817, 17913, 34912, 161383, 35031, + 35038, 17973, 35066, 13499, 161966, 162150, 18110, 18119, 35488, 162984, 36011, + 36033, 36123, 36215, 163631, 133124, 36299, 36284, 36336, 133342, 36564, 165330, + 165357, 37012, 37105, 37137, 165678, 37147, 37432, 37591, 37592, 37500, 37881, 37909, + 166906, 38283, 18837, 38327, 167287, 18918, 38595, 23986, 38691, 168261, 168474, + 19054, 19062, 38880, 168970, 19122, 169110, 38953, 169398, 39138, 19251, 39209, + 39335, 39362, 39422, 19406, 170800, 40000, 40189, 19662, 19693, 40295, 172238, 19704, + 172293, 172558, 172689, 19798, 40702, 40709, 40719, 40726, 173568, }; -const uint32_t table[8000][2] = +const uint32_t table[8150][2] = { {0, 1}, {65, 16777219}, {66, 16777475}, {67, 16777731}, {68, 16777987}, {69, 16778243}, {70, 16778499}, {71, 16778755}, @@ -884,73 +891,73 @@ const uint32_t table[8000][2] = {1204, 16884483}, {1205, 1}, {1206, 16884739}, {1207, 1}, {1208, 16884995}, {1209, 1}, {1210, 16885251}, {1211, 1}, {1212, 16885507}, {1213, 1}, {1214, 16885763}, {1215, 1}, - {1216, 2}, {1217, 16886019}, {1218, 1}, {1219, 16886275}, - {1220, 1}, {1221, 16886531}, {1222, 1}, {1223, 16886787}, - {1224, 1}, {1225, 16887043}, {1226, 1}, {1227, 16887299}, - {1228, 1}, {1229, 16887555}, {1230, 1}, {1232, 16887811}, - {1233, 1}, {1234, 16888067}, {1235, 1}, {1236, 16888323}, - {1237, 1}, {1238, 16888579}, {1239, 1}, {1240, 16888835}, - {1241, 1}, {1242, 16889091}, {1243, 1}, {1244, 16889347}, - {1245, 1}, {1246, 16889603}, {1247, 1}, {1248, 16889859}, - {1249, 1}, {1250, 16890115}, {1251, 1}, {1252, 16890371}, - {1253, 1}, {1254, 16890627}, {1255, 1}, {1256, 16890883}, - {1257, 1}, {1258, 16891139}, {1259, 1}, {1260, 16891395}, - {1261, 1}, {1262, 16891651}, {1263, 1}, {1264, 16891907}, - {1265, 1}, {1266, 16892163}, {1267, 1}, {1268, 16892419}, - {1269, 1}, {1270, 16892675}, {1271, 1}, {1272, 16892931}, - {1273, 1}, {1274, 16893187}, {1275, 1}, {1276, 16893443}, - {1277, 1}, {1278, 16893699}, {1279, 1}, {1280, 16893955}, - {1281, 1}, {1282, 16894211}, {1283, 1}, {1284, 16894467}, - {1285, 1}, {1286, 16894723}, {1287, 1}, {1288, 16894979}, - {1289, 1}, {1290, 16895235}, {1291, 1}, {1292, 16895491}, - {1293, 1}, {1294, 16895747}, {1295, 1}, {1296, 16896003}, - {1297, 1}, {1298, 16896259}, {1299, 1}, {1300, 16896515}, - {1301, 1}, {1302, 16896771}, {1303, 1}, {1304, 16897027}, - {1305, 1}, {1306, 16897283}, {1307, 1}, {1308, 16897539}, - {1309, 1}, {1310, 16897795}, {1311, 1}, {1312, 16898051}, - {1313, 1}, {1314, 16898307}, {1315, 1}, {1316, 16898563}, - {1317, 1}, {1318, 16898819}, {1319, 1}, {1320, 16899075}, - {1321, 1}, {1322, 16899331}, {1323, 1}, {1324, 16899587}, - {1325, 1}, {1326, 16899843}, {1327, 1}, {1328, 2}, - {1329, 16900099}, {1330, 16900355}, {1331, 16900611}, {1332, 16900867}, - {1333, 16901123}, {1334, 16901379}, {1335, 16901635}, {1336, 16901891}, - {1337, 16902147}, {1338, 16902403}, {1339, 16902659}, {1340, 16902915}, - {1341, 16903171}, {1342, 16903427}, {1343, 16903683}, {1344, 16903939}, - {1345, 16904195}, {1346, 16904451}, {1347, 16904707}, {1348, 16904963}, - {1349, 16905219}, {1350, 16905475}, {1351, 16905731}, {1352, 16905987}, - {1353, 16906243}, {1354, 16906499}, {1355, 16906755}, {1356, 16907011}, - {1357, 16907267}, {1358, 16907523}, {1359, 16907779}, {1360, 16908035}, - {1361, 16908291}, {1362, 16908547}, {1363, 16908803}, {1364, 16909059}, - {1365, 16909315}, {1366, 16909571}, {1367, 2}, {1369, 1}, - {1415, 33687043}, {1416, 1}, {1419, 2}, {1421, 1}, + {1216, 16886019}, {1217, 16886275}, {1218, 1}, {1219, 16886531}, + {1220, 1}, {1221, 16886787}, {1222, 1}, {1223, 16887043}, + {1224, 1}, {1225, 16887299}, {1226, 1}, {1227, 16887555}, + {1228, 1}, {1229, 16887811}, {1230, 1}, {1232, 16888067}, + {1233, 1}, {1234, 16888323}, {1235, 1}, {1236, 16888579}, + {1237, 1}, {1238, 16888835}, {1239, 1}, {1240, 16889091}, + {1241, 1}, {1242, 16889347}, {1243, 1}, {1244, 16889603}, + {1245, 1}, {1246, 16889859}, {1247, 1}, {1248, 16890115}, + {1249, 1}, {1250, 16890371}, {1251, 1}, {1252, 16890627}, + {1253, 1}, {1254, 16890883}, {1255, 1}, {1256, 16891139}, + {1257, 1}, {1258, 16891395}, {1259, 1}, {1260, 16891651}, + {1261, 1}, {1262, 16891907}, {1263, 1}, {1264, 16892163}, + {1265, 1}, {1266, 16892419}, {1267, 1}, {1268, 16892675}, + {1269, 1}, {1270, 16892931}, {1271, 1}, {1272, 16893187}, + {1273, 1}, {1274, 16893443}, {1275, 1}, {1276, 16893699}, + {1277, 1}, {1278, 16893955}, {1279, 1}, {1280, 16894211}, + {1281, 1}, {1282, 16894467}, {1283, 1}, {1284, 16894723}, + {1285, 1}, {1286, 16894979}, {1287, 1}, {1288, 16895235}, + {1289, 1}, {1290, 16895491}, {1291, 1}, {1292, 16895747}, + {1293, 1}, {1294, 16896003}, {1295, 1}, {1296, 16896259}, + {1297, 1}, {1298, 16896515}, {1299, 1}, {1300, 16896771}, + {1301, 1}, {1302, 16897027}, {1303, 1}, {1304, 16897283}, + {1305, 1}, {1306, 16897539}, {1307, 1}, {1308, 16897795}, + {1309, 1}, {1310, 16898051}, {1311, 1}, {1312, 16898307}, + {1313, 1}, {1314, 16898563}, {1315, 1}, {1316, 16898819}, + {1317, 1}, {1318, 16899075}, {1319, 1}, {1320, 16899331}, + {1321, 1}, {1322, 16899587}, {1323, 1}, {1324, 16899843}, + {1325, 1}, {1326, 16900099}, {1327, 1}, {1328, 2}, + {1329, 16900355}, {1330, 16900611}, {1331, 16900867}, {1332, 16901123}, + {1333, 16901379}, {1334, 16901635}, {1335, 16901891}, {1336, 16902147}, + {1337, 16902403}, {1338, 16902659}, {1339, 16902915}, {1340, 16903171}, + {1341, 16903427}, {1342, 16903683}, {1343, 16903939}, {1344, 16904195}, + {1345, 16904451}, {1346, 16904707}, {1347, 16904963}, {1348, 16905219}, + {1349, 16905475}, {1350, 16905731}, {1351, 16905987}, {1352, 16906243}, + {1353, 16906499}, {1354, 16906755}, {1355, 16907011}, {1356, 16907267}, + {1357, 16907523}, {1358, 16907779}, {1359, 16908035}, {1360, 16908291}, + {1361, 16908547}, {1362, 16908803}, {1363, 16909059}, {1364, 16909315}, + {1365, 16909571}, {1366, 16909827}, {1367, 2}, {1369, 1}, + {1415, 33687299}, {1416, 1}, {1419, 2}, {1421, 1}, {1424, 2}, {1425, 1}, {1480, 2}, {1488, 1}, {1515, 2}, {1519, 1}, {1525, 2}, {1542, 1}, - {1564, 2}, {1565, 1}, {1653, 33687555}, {1654, 33688067}, - {1655, 33688579}, {1656, 33689091}, {1657, 1}, {1757, 2}, + {1564, 2}, {1565, 1}, {1653, 33687811}, {1654, 33688323}, + {1655, 33688835}, {1656, 33689347}, {1657, 1}, {1757, 2}, {1758, 1}, {1806, 2}, {1808, 1}, {1867, 2}, {1869, 1}, {1970, 2}, {1984, 1}, {2043, 2}, {2045, 1}, {2094, 2}, {2096, 1}, {2111, 2}, {2112, 1}, {2140, 2}, {2142, 1}, {2143, 2}, {2144, 1}, {2155, 2}, {2160, 1}, {2191, 2}, - {2200, 1}, {2274, 2}, {2275, 1}, {2392, 33689603}, - {2393, 33690115}, {2394, 33690627}, {2395, 33691139}, {2396, 33691651}, - {2397, 33692163}, {2398, 33692675}, {2399, 33693187}, {2400, 1}, + {2199, 1}, {2274, 2}, {2275, 1}, {2392, 33689859}, + {2393, 33690371}, {2394, 33690883}, {2395, 33691395}, {2396, 33691907}, + {2397, 33692419}, {2398, 33692931}, {2399, 33693443}, {2400, 1}, {2436, 2}, {2437, 1}, {2445, 2}, {2447, 1}, {2449, 2}, {2451, 1}, {2473, 2}, {2474, 1}, {2481, 2}, {2482, 1}, {2483, 2}, {2486, 1}, {2490, 2}, {2492, 1}, {2501, 2}, {2503, 1}, {2505, 2}, {2507, 1}, {2511, 2}, {2519, 1}, - {2520, 2}, {2524, 33693699}, {2525, 33694211}, {2526, 2}, - {2527, 33694723}, {2528, 1}, {2532, 2}, {2534, 1}, + {2520, 2}, {2524, 33693955}, {2525, 33694467}, {2526, 2}, + {2527, 33694979}, {2528, 1}, {2532, 2}, {2534, 1}, {2559, 2}, {2561, 1}, {2564, 2}, {2565, 1}, {2571, 2}, {2575, 1}, {2577, 2}, {2579, 1}, {2601, 2}, {2602, 1}, {2609, 2}, {2610, 1}, - {2611, 33695235}, {2612, 2}, {2613, 1}, {2614, 33695747}, + {2611, 33695491}, {2612, 2}, {2613, 1}, {2614, 33696003}, {2615, 2}, {2616, 1}, {2618, 2}, {2620, 1}, {2621, 2}, {2622, 1}, {2627, 2}, {2631, 1}, {2633, 2}, {2635, 1}, {2638, 2}, {2641, 1}, - {2642, 2}, {2649, 33696259}, {2650, 33696771}, {2651, 33697283}, - {2652, 1}, {2653, 2}, {2654, 33697795}, {2655, 2}, + {2642, 2}, {2649, 33696515}, {2650, 33697027}, {2651, 33697539}, + {2652, 1}, {2653, 2}, {2654, 33698051}, {2655, 2}, {2662, 1}, {2679, 2}, {2689, 1}, {2692, 2}, {2693, 1}, {2702, 2}, {2703, 1}, {2706, 2}, {2707, 1}, {2729, 2}, {2730, 1}, {2737, 2}, @@ -964,7 +971,7 @@ const uint32_t table[8000][2] = {2866, 1}, {2868, 2}, {2869, 1}, {2874, 2}, {2876, 1}, {2885, 2}, {2887, 1}, {2889, 2}, {2891, 1}, {2894, 2}, {2901, 1}, {2904, 2}, - {2908, 33698307}, {2909, 33698819}, {2910, 2}, {2911, 1}, + {2908, 33698563}, {2909, 33699075}, {2910, 2}, {2911, 1}, {2916, 2}, {2918, 1}, {2936, 2}, {2946, 1}, {2948, 2}, {2949, 1}, {2955, 2}, {2958, 1}, {2961, 2}, {2962, 1}, {2966, 2}, {2969, 1}, @@ -996,1735 +1003,1772 @@ const uint32_t table[8000][2] = {3531, 2}, {3535, 1}, {3541, 2}, {3542, 1}, {3543, 2}, {3544, 1}, {3552, 2}, {3558, 1}, {3568, 2}, {3570, 1}, {3573, 2}, {3585, 1}, - {3635, 33699331}, {3636, 1}, {3643, 2}, {3647, 1}, + {3635, 33699587}, {3636, 1}, {3643, 2}, {3647, 1}, {3676, 2}, {3713, 1}, {3715, 2}, {3716, 1}, {3717, 2}, {3718, 1}, {3723, 2}, {3724, 1}, {3748, 2}, {3749, 1}, {3750, 2}, {3751, 1}, - {3763, 33699843}, {3764, 1}, {3774, 2}, {3776, 1}, + {3763, 33700099}, {3764, 1}, {3774, 2}, {3776, 1}, {3781, 2}, {3782, 1}, {3783, 2}, {3784, 1}, - {3791, 2}, {3792, 1}, {3802, 2}, {3804, 33700355}, - {3805, 33700867}, {3806, 1}, {3808, 2}, {3840, 1}, - {3852, 16924163}, {3853, 1}, {3907, 33701635}, {3908, 1}, - {3912, 2}, {3913, 1}, {3917, 33702147}, {3918, 1}, - {3922, 33702659}, {3923, 1}, {3927, 33703171}, {3928, 1}, - {3932, 33703683}, {3933, 1}, {3945, 33704195}, {3946, 1}, - {3949, 2}, {3953, 1}, {3955, 33704707}, {3956, 1}, - {3957, 33705219}, {3958, 33705731}, {3959, 50483459}, {3960, 33707011}, - {3961, 50484739}, {3962, 1}, {3969, 33706499}, {3970, 1}, - {3987, 33708291}, {3988, 1}, {3992, 2}, {3993, 1}, - {3997, 33708803}, {3998, 1}, {4002, 33709315}, {4003, 1}, - {4007, 33709827}, {4008, 1}, {4012, 33710339}, {4013, 1}, - {4025, 33710851}, {4026, 1}, {4029, 2}, {4030, 1}, + {3791, 2}, {3792, 1}, {3802, 2}, {3804, 33700611}, + {3805, 33701123}, {3806, 1}, {3808, 2}, {3840, 1}, + {3852, 16924419}, {3853, 1}, {3907, 33701891}, {3908, 1}, + {3912, 2}, {3913, 1}, {3917, 33702403}, {3918, 1}, + {3922, 33702915}, {3923, 1}, {3927, 33703427}, {3928, 1}, + {3932, 33703939}, {3933, 1}, {3945, 33704451}, {3946, 1}, + {3949, 2}, {3953, 1}, {3955, 33704963}, {3956, 1}, + {3957, 33705475}, {3958, 33705987}, {3959, 50483715}, {3960, 33707267}, + {3961, 50484995}, {3962, 1}, {3969, 33706755}, {3970, 1}, + {3987, 33708547}, {3988, 1}, {3992, 2}, {3993, 1}, + {3997, 33709059}, {3998, 1}, {4002, 33709571}, {4003, 1}, + {4007, 33710083}, {4008, 1}, {4012, 33710595}, {4013, 1}, + {4025, 33711107}, {4026, 1}, {4029, 2}, {4030, 1}, {4045, 2}, {4046, 1}, {4059, 2}, {4096, 1}, - {4256, 2}, {4295, 16934147}, {4296, 2}, {4301, 16934403}, - {4302, 2}, {4304, 1}, {4348, 16934659}, {4349, 1}, - {4447, 2}, {4449, 1}, {4681, 2}, {4682, 1}, - {4686, 2}, {4688, 1}, {4695, 2}, {4696, 1}, - {4697, 2}, {4698, 1}, {4702, 2}, {4704, 1}, - {4745, 2}, {4746, 1}, {4750, 2}, {4752, 1}, - {4785, 2}, {4786, 1}, {4790, 2}, {4792, 1}, - {4799, 2}, {4800, 1}, {4801, 2}, {4802, 1}, - {4806, 2}, {4808, 1}, {4823, 2}, {4824, 1}, - {4881, 2}, {4882, 1}, {4886, 2}, {4888, 1}, - {4955, 2}, {4957, 1}, {4989, 2}, {4992, 1}, - {5018, 2}, {5024, 1}, {5110, 2}, {5112, 16934915}, - {5113, 16935171}, {5114, 16935427}, {5115, 16935683}, {5116, 16935939}, - {5117, 16936195}, {5118, 2}, {5120, 1}, {5760, 2}, - {5761, 1}, {5789, 2}, {5792, 1}, {5881, 2}, - {5888, 1}, {5910, 2}, {5919, 1}, {5943, 2}, - {5952, 1}, {5972, 2}, {5984, 1}, {5997, 2}, - {5998, 1}, {6001, 2}, {6002, 1}, {6004, 2}, - {6016, 1}, {6068, 2}, {6070, 1}, {6110, 2}, - {6112, 1}, {6122, 2}, {6128, 1}, {6138, 2}, - {6144, 1}, {6150, 2}, {6151, 1}, {6155, 0}, - {6158, 2}, {6159, 0}, {6160, 1}, {6170, 2}, - {6176, 1}, {6265, 2}, {6272, 1}, {6315, 2}, - {6320, 1}, {6390, 2}, {6400, 1}, {6431, 2}, - {6432, 1}, {6444, 2}, {6448, 1}, {6460, 2}, - {6464, 1}, {6465, 2}, {6468, 1}, {6510, 2}, - {6512, 1}, {6517, 2}, {6528, 1}, {6572, 2}, - {6576, 1}, {6602, 2}, {6608, 1}, {6619, 2}, - {6622, 1}, {6684, 2}, {6686, 1}, {6751, 2}, - {6752, 1}, {6781, 2}, {6783, 1}, {6794, 2}, - {6800, 1}, {6810, 2}, {6816, 1}, {6830, 2}, - {6832, 1}, {6863, 2}, {6912, 1}, {6989, 2}, - {6992, 1}, {7039, 2}, {7040, 1}, {7156, 2}, + {4256, 16934403}, {4257, 16934659}, {4258, 16934915}, {4259, 16935171}, + {4260, 16935427}, {4261, 16935683}, {4262, 16935939}, {4263, 16936195}, + {4264, 16936451}, {4265, 16936707}, {4266, 16936963}, {4267, 16937219}, + {4268, 16937475}, {4269, 16937731}, {4270, 16937987}, {4271, 16938243}, + {4272, 16938499}, {4273, 16938755}, {4274, 16939011}, {4275, 16939267}, + {4276, 16939523}, {4277, 16939779}, {4278, 16940035}, {4279, 16940291}, + {4280, 16940547}, {4281, 16940803}, {4282, 16941059}, {4283, 16941315}, + {4284, 16941571}, {4285, 16941827}, {4286, 16942083}, {4287, 16942339}, + {4288, 16942595}, {4289, 16942851}, {4290, 16943107}, {4291, 16943363}, + {4292, 16943619}, {4293, 16943875}, {4294, 2}, {4295, 16944131}, + {4296, 2}, {4301, 16944387}, {4302, 2}, {4304, 1}, + {4348, 16944643}, {4349, 1}, {4447, 0}, {4449, 1}, + {4681, 2}, {4682, 1}, {4686, 2}, {4688, 1}, + {4695, 2}, {4696, 1}, {4697, 2}, {4698, 1}, + {4702, 2}, {4704, 1}, {4745, 2}, {4746, 1}, + {4750, 2}, {4752, 1}, {4785, 2}, {4786, 1}, + {4790, 2}, {4792, 1}, {4799, 2}, {4800, 1}, + {4801, 2}, {4802, 1}, {4806, 2}, {4808, 1}, + {4823, 2}, {4824, 1}, {4881, 2}, {4882, 1}, + {4886, 2}, {4888, 1}, {4955, 2}, {4957, 1}, + {4989, 2}, {4992, 1}, {5018, 2}, {5024, 1}, + {5110, 2}, {5112, 16944899}, {5113, 16945155}, {5114, 16945411}, + {5115, 16945667}, {5116, 16945923}, {5117, 16946179}, {5118, 2}, + {5120, 1}, {5760, 2}, {5761, 1}, {5789, 2}, + {5792, 1}, {5881, 2}, {5888, 1}, {5910, 2}, + {5919, 1}, {5943, 2}, {5952, 1}, {5972, 2}, + {5984, 1}, {5997, 2}, {5998, 1}, {6001, 2}, + {6002, 1}, {6004, 2}, {6016, 1}, {6068, 0}, + {6070, 1}, {6110, 2}, {6112, 1}, {6122, 2}, + {6128, 1}, {6138, 2}, {6144, 1}, {6155, 0}, + {6160, 1}, {6170, 2}, {6176, 1}, {6265, 2}, + {6272, 1}, {6315, 2}, {6320, 1}, {6390, 2}, + {6400, 1}, {6431, 2}, {6432, 1}, {6444, 2}, + {6448, 1}, {6460, 2}, {6464, 1}, {6465, 2}, + {6468, 1}, {6510, 2}, {6512, 1}, {6517, 2}, + {6528, 1}, {6572, 2}, {6576, 1}, {6602, 2}, + {6608, 1}, {6619, 2}, {6622, 1}, {6684, 2}, + {6686, 1}, {6751, 2}, {6752, 1}, {6781, 2}, + {6783, 1}, {6794, 2}, {6800, 1}, {6810, 2}, + {6816, 1}, {6830, 2}, {6832, 1}, {6863, 2}, + {6912, 1}, {6989, 2}, {6990, 1}, {7156, 2}, {7164, 1}, {7224, 2}, {7227, 1}, {7242, 2}, {7245, 1}, {7296, 16867075}, {7297, 16867587}, {7298, 16870147}, {7299, 16870915}, {7300, 16871171}, {7302, 16873219}, {7303, 16875011}, - {7304, 16936451}, {7305, 2}, {7312, 16936707}, {7313, 16936963}, - {7314, 16937219}, {7315, 16937475}, {7316, 16937731}, {7317, 16937987}, - {7318, 16938243}, {7319, 16938499}, {7320, 16938755}, {7321, 16939011}, - {7322, 16939267}, {7323, 16939523}, {7324, 16934659}, {7325, 16939779}, - {7326, 16940035}, {7327, 16940291}, {7328, 16940547}, {7329, 16940803}, - {7330, 16941059}, {7331, 16941315}, {7332, 16941571}, {7333, 16941827}, - {7334, 16942083}, {7335, 16942339}, {7336, 16942595}, {7337, 16942851}, - {7338, 16943107}, {7339, 16943363}, {7340, 16943619}, {7341, 16943875}, - {7342, 16944131}, {7343, 16944387}, {7344, 16944643}, {7345, 16944899}, - {7346, 16945155}, {7347, 16945411}, {7348, 16945667}, {7349, 16945923}, - {7350, 16946179}, {7351, 16946435}, {7352, 16946691}, {7353, 16946947}, - {7354, 16947203}, {7355, 2}, {7357, 16947459}, {7358, 16947715}, - {7359, 16947971}, {7360, 1}, {7368, 2}, {7376, 1}, - {7419, 2}, {7424, 1}, {7468, 16777219}, {7469, 16791043}, - {7470, 16777475}, {7471, 1}, {7472, 16777987}, {7473, 16778243}, - {7474, 16816131}, {7475, 16778755}, {7476, 16779011}, {7477, 16779267}, - {7478, 16779523}, {7479, 16779779}, {7480, 16780035}, {7481, 16780291}, - {7482, 16780547}, {7483, 1}, {7484, 16780803}, {7485, 16835843}, - {7486, 16781059}, {7487, 16781571}, {7488, 16782083}, {7489, 16782339}, - {7490, 16782851}, {7491, 16777219}, {7492, 16948227}, {7493, 16948483}, - {7494, 16948739}, {7495, 16777475}, {7496, 16777987}, {7497, 16778243}, - {7498, 16816387}, {7499, 16816643}, {7500, 16948995}, {7501, 16778755}, - {7502, 1}, {7503, 16779779}, {7504, 16780291}, {7505, 16807171}, - {7506, 16780803}, {7507, 16814851}, {7508, 16949251}, {7509, 16949507}, - {7510, 16781059}, {7511, 16782083}, {7512, 16782339}, {7513, 16949763}, - {7514, 16818435}, {7515, 16782595}, {7516, 16950019}, {7517, 16851971}, - {7518, 16852227}, {7519, 16852483}, {7520, 16856323}, {7521, 16856579}, - {7522, 16779267}, {7523, 16781571}, {7524, 16782339}, {7525, 16782595}, - {7526, 16851971}, {7527, 16852227}, {7528, 16855299}, {7529, 16856323}, - {7530, 16856579}, {7531, 1}, {7544, 16869891}, {7545, 1}, - {7579, 16950275}, {7580, 16777731}, {7581, 16950531}, {7582, 16793603}, - {7583, 16948995}, {7584, 16778499}, {7585, 16950787}, {7586, 16951043}, - {7587, 16951299}, {7588, 16817923}, {7589, 16817667}, {7590, 16951555}, - {7591, 16951811}, {7592, 16952067}, {7593, 16952323}, {7594, 16952579}, - {7595, 16952835}, {7596, 16953091}, {7597, 16953347}, {7598, 16818691}, - {7599, 16953603}, {7600, 16953859}, {7601, 16818947}, {7602, 16954115}, - {7603, 16954371}, {7604, 16820483}, {7605, 16954627}, {7606, 16839683}, - {7607, 16821507}, {7608, 16954883}, {7609, 16821763}, {7610, 16839939}, - {7611, 16783619}, {7612, 16955139}, {7613, 16955395}, {7614, 16822531}, - {7615, 16853507}, {7616, 1}, {7680, 16955651}, {7681, 1}, - {7682, 16955907}, {7683, 1}, {7684, 16956163}, {7685, 1}, - {7686, 16956419}, {7687, 1}, {7688, 16956675}, {7689, 1}, - {7690, 16956931}, {7691, 1}, {7692, 16957187}, {7693, 1}, - {7694, 16957443}, {7695, 1}, {7696, 16957699}, {7697, 1}, - {7698, 16957955}, {7699, 1}, {7700, 16958211}, {7701, 1}, - {7702, 16958467}, {7703, 1}, {7704, 16958723}, {7705, 1}, - {7706, 16958979}, {7707, 1}, {7708, 16959235}, {7709, 1}, - {7710, 16959491}, {7711, 1}, {7712, 16959747}, {7713, 1}, - {7714, 16960003}, {7715, 1}, {7716, 16960259}, {7717, 1}, - {7718, 16960515}, {7719, 1}, {7720, 16960771}, {7721, 1}, - {7722, 16961027}, {7723, 1}, {7724, 16961283}, {7725, 1}, - {7726, 16961539}, {7727, 1}, {7728, 16961795}, {7729, 1}, - {7730, 16962051}, {7731, 1}, {7732, 16962307}, {7733, 1}, - {7734, 16962563}, {7735, 1}, {7736, 16962819}, {7737, 1}, - {7738, 16963075}, {7739, 1}, {7740, 16963331}, {7741, 1}, - {7742, 16963587}, {7743, 1}, {7744, 16963843}, {7745, 1}, - {7746, 16964099}, {7747, 1}, {7748, 16964355}, {7749, 1}, - {7750, 16964611}, {7751, 1}, {7752, 16964867}, {7753, 1}, - {7754, 16965123}, {7755, 1}, {7756, 16965379}, {7757, 1}, - {7758, 16965635}, {7759, 1}, {7760, 16965891}, {7761, 1}, - {7762, 16966147}, {7763, 1}, {7764, 16966403}, {7765, 1}, - {7766, 16966659}, {7767, 1}, {7768, 16966915}, {7769, 1}, - {7770, 16967171}, {7771, 1}, {7772, 16967427}, {7773, 1}, - {7774, 16967683}, {7775, 1}, {7776, 16967939}, {7777, 1}, - {7778, 16968195}, {7779, 1}, {7780, 16968451}, {7781, 1}, - {7782, 16968707}, {7783, 1}, {7784, 16968963}, {7785, 1}, - {7786, 16969219}, {7787, 1}, {7788, 16969475}, {7789, 1}, - {7790, 16969731}, {7791, 1}, {7792, 16969987}, {7793, 1}, - {7794, 16970243}, {7795, 1}, {7796, 16970499}, {7797, 1}, - {7798, 16970755}, {7799, 1}, {7800, 16971011}, {7801, 1}, - {7802, 16971267}, {7803, 1}, {7804, 16971523}, {7805, 1}, - {7806, 16971779}, {7807, 1}, {7808, 16972035}, {7809, 1}, - {7810, 16972291}, {7811, 1}, {7812, 16972547}, {7813, 1}, - {7814, 16972803}, {7815, 1}, {7816, 16973059}, {7817, 1}, - {7818, 16973315}, {7819, 1}, {7820, 16973571}, {7821, 1}, - {7822, 16973827}, {7823, 1}, {7824, 16974083}, {7825, 1}, - {7826, 16974339}, {7827, 1}, {7828, 16974595}, {7829, 1}, - {7834, 33752067}, {7835, 16967939}, {7836, 1}, {7838, 33752579}, - {7839, 1}, {7840, 16975875}, {7841, 1}, {7842, 16976131}, - {7843, 1}, {7844, 16976387}, {7845, 1}, {7846, 16976643}, - {7847, 1}, {7848, 16976899}, {7849, 1}, {7850, 16977155}, - {7851, 1}, {7852, 16977411}, {7853, 1}, {7854, 16977667}, - {7855, 1}, {7856, 16977923}, {7857, 1}, {7858, 16978179}, - {7859, 1}, {7860, 16978435}, {7861, 1}, {7862, 16978691}, - {7863, 1}, {7864, 16978947}, {7865, 1}, {7866, 16979203}, - {7867, 1}, {7868, 16979459}, {7869, 1}, {7870, 16979715}, - {7871, 1}, {7872, 16979971}, {7873, 1}, {7874, 16980227}, - {7875, 1}, {7876, 16980483}, {7877, 1}, {7878, 16980739}, - {7879, 1}, {7880, 16980995}, {7881, 1}, {7882, 16981251}, - {7883, 1}, {7884, 16981507}, {7885, 1}, {7886, 16981763}, - {7887, 1}, {7888, 16982019}, {7889, 1}, {7890, 16982275}, - {7891, 1}, {7892, 16982531}, {7893, 1}, {7894, 16982787}, - {7895, 1}, {7896, 16983043}, {7897, 1}, {7898, 16983299}, - {7899, 1}, {7900, 16983555}, {7901, 1}, {7902, 16983811}, - {7903, 1}, {7904, 16984067}, {7905, 1}, {7906, 16984323}, - {7907, 1}, {7908, 16984579}, {7909, 1}, {7910, 16984835}, - {7911, 1}, {7912, 16985091}, {7913, 1}, {7914, 16985347}, - {7915, 1}, {7916, 16985603}, {7917, 1}, {7918, 16985859}, - {7919, 1}, {7920, 16986115}, {7921, 1}, {7922, 16986371}, - {7923, 1}, {7924, 16986627}, {7925, 1}, {7926, 16986883}, - {7927, 1}, {7928, 16987139}, {7929, 1}, {7930, 16987395}, - {7931, 1}, {7932, 16987651}, {7933, 1}, {7934, 16987907}, - {7935, 1}, {7944, 16988163}, {7945, 16988419}, {7946, 16988675}, - {7947, 16988931}, {7948, 16989187}, {7949, 16989443}, {7950, 16989699}, - {7951, 16989955}, {7952, 1}, {7958, 2}, {7960, 16990211}, - {7961, 16990467}, {7962, 16990723}, {7963, 16990979}, {7964, 16991235}, - {7965, 16991491}, {7966, 2}, {7968, 1}, {7976, 16991747}, - {7977, 16992003}, {7978, 16992259}, {7979, 16992515}, {7980, 16992771}, - {7981, 16993027}, {7982, 16993283}, {7983, 16993539}, {7984, 1}, - {7992, 16993795}, {7993, 16994051}, {7994, 16994307}, {7995, 16994563}, - {7996, 16994819}, {7997, 16995075}, {7998, 16995331}, {7999, 16995587}, - {8000, 1}, {8006, 2}, {8008, 16995843}, {8009, 16996099}, - {8010, 16996355}, {8011, 16996611}, {8012, 16996867}, {8013, 16997123}, - {8014, 2}, {8016, 1}, {8024, 2}, {8025, 16997379}, - {8026, 2}, {8027, 16997635}, {8028, 2}, {8029, 16997891}, - {8030, 2}, {8031, 16998147}, {8032, 1}, {8040, 16998403}, - {8041, 16998659}, {8042, 16998915}, {8043, 16999171}, {8044, 16999427}, - {8045, 16999683}, {8046, 16999939}, {8047, 17000195}, {8048, 1}, - {8049, 16849923}, {8050, 1}, {8051, 16850179}, {8052, 1}, - {8053, 16850435}, {8054, 1}, {8055, 16850691}, {8056, 1}, - {8057, 16850947}, {8058, 1}, {8059, 16851203}, {8060, 1}, - {8061, 16851459}, {8062, 2}, {8064, 33777667}, {8065, 33778179}, - {8066, 33778691}, {8067, 33779203}, {8068, 33779715}, {8069, 33780227}, - {8070, 33780739}, {8071, 33781251}, {8072, 33777667}, {8073, 33778179}, - {8074, 33778691}, {8075, 33779203}, {8076, 33779715}, {8077, 33780227}, - {8078, 33780739}, {8079, 33781251}, {8080, 33781763}, {8081, 33782275}, - {8082, 33782787}, {8083, 33783299}, {8084, 33783811}, {8085, 33784323}, - {8086, 33784835}, {8087, 33785347}, {8088, 33781763}, {8089, 33782275}, - {8090, 33782787}, {8091, 33783299}, {8092, 33783811}, {8093, 33784323}, - {8094, 33784835}, {8095, 33785347}, {8096, 33785859}, {8097, 33786371}, - {8098, 33786883}, {8099, 33787395}, {8100, 33787907}, {8101, 33788419}, - {8102, 33788931}, {8103, 33789443}, {8104, 33785859}, {8105, 33786371}, - {8106, 33786883}, {8107, 33787395}, {8108, 33787907}, {8109, 33788419}, - {8110, 33788931}, {8111, 33789443}, {8112, 1}, {8114, 33789955}, - {8115, 33790467}, {8116, 33790979}, {8117, 2}, {8118, 1}, - {8119, 33791491}, {8120, 17014787}, {8121, 17015043}, {8122, 17012739}, - {8123, 16849923}, {8124, 33790467}, {8125, 33792515}, {8126, 16846851}, - {8127, 33792515}, {8128, 33793027}, {8129, 50570755}, {8130, 33794307}, - {8131, 33794819}, {8132, 33795331}, {8133, 2}, {8134, 1}, - {8135, 33795843}, {8136, 17019139}, {8137, 16850179}, {8138, 17017091}, - {8139, 16850435}, {8140, 33794819}, {8141, 50573827}, {8142, 50574595}, - {8143, 50575363}, {8144, 1}, {8147, 17021699}, {8148, 2}, - {8150, 1}, {8152, 17021955}, {8153, 17022211}, {8154, 17022467}, - {8155, 16850691}, {8156, 2}, {8157, 50577155}, {8158, 50577923}, - {8159, 50578691}, {8160, 1}, {8163, 17025027}, {8164, 1}, - {8168, 17025283}, {8169, 17025539}, {8170, 17025795}, {8171, 16851203}, - {8172, 17026051}, {8173, 50580739}, {8174, 50403587}, {8175, 17027075}, - {8176, 2}, {8178, 33804547}, {8179, 33805059}, {8180, 33805571}, - {8181, 2}, {8182, 1}, {8183, 33806083}, {8184, 17029379}, - {8185, 16850947}, {8186, 17027331}, {8187, 16851459}, {8188, 33805059}, - {8189, 33562883}, {8190, 33799939}, {8191, 2}, {8192, 16783875}, - {8203, 0}, {8204, 1}, {8206, 2}, {8208, 1}, - {8209, 17029635}, {8210, 1}, {8215, 33807107}, {8216, 1}, - {8228, 2}, {8231, 1}, {8232, 2}, {8239, 16783875}, - {8240, 1}, {8243, 33807619}, {8244, 50585347}, {8245, 1}, - {8246, 33808899}, {8247, 50586627}, {8248, 1}, {8252, 33810179}, - {8253, 1}, {8254, 33810691}, {8255, 1}, {8263, 33811203}, - {8264, 33811715}, {8265, 33812227}, {8266, 1}, {8279, 67362051}, - {8280, 1}, {8287, 16783875}, {8288, 0}, {8289, 2}, - {8292, 0}, {8293, 2}, {8304, 17035523}, {8305, 16779267}, - {8306, 2}, {8308, 16787715}, {8309, 17035779}, {8310, 17036035}, - {8311, 17036291}, {8312, 17036547}, {8313, 17036803}, {8314, 17037059}, - {8315, 17037315}, {8316, 17037571}, {8317, 17037827}, {8318, 17038083}, - {8319, 16780547}, {8320, 17035523}, {8321, 16786947}, {8322, 16785155}, - {8323, 16785411}, {8324, 16787715}, {8325, 17035779}, {8326, 17036035}, - {8327, 17036291}, {8328, 17036547}, {8329, 17036803}, {8330, 17037059}, - {8331, 17037315}, {8332, 17037571}, {8333, 17037827}, {8334, 17038083}, - {8335, 2}, {8336, 16777219}, {8337, 16778243}, {8338, 16780803}, - {8339, 16783107}, {8340, 16816387}, {8341, 16779011}, {8342, 16779779}, - {8343, 16780035}, {8344, 16780291}, {8345, 16780547}, {8346, 16781059}, - {8347, 16781827}, {8348, 16782083}, {8349, 2}, {8352, 1}, - {8360, 33558787}, {8361, 1}, {8385, 2}, {8400, 1}, - {8433, 2}, {8448, 50592771}, {8449, 50593539}, {8450, 16777731}, - {8451, 33817091}, {8452, 1}, {8453, 50594819}, {8454, 50595587}, - {8455, 16816643}, {8456, 1}, {8457, 33819139}, {8458, 16778755}, - {8459, 16779011}, {8463, 16802051}, {8464, 16779267}, {8466, 16780035}, - {8468, 1}, {8469, 16780547}, {8470, 33557763}, {8471, 1}, - {8473, 16781059}, {8474, 16781315}, {8475, 16781571}, {8478, 1}, - {8480, 33819651}, {8481, 50597379}, {8482, 33820931}, {8483, 1}, - {8484, 16783619}, {8485, 1}, {8486, 16857091}, {8487, 1}, - {8488, 16783619}, {8489, 1}, {8490, 16779779}, {8491, 16790787}, - {8492, 16777475}, {8493, 16777731}, {8494, 1}, {8495, 16778243}, - {8497, 16778499}, {8498, 2}, {8499, 16780291}, {8500, 16780803}, - {8501, 17044227}, {8502, 17044483}, {8503, 17044739}, {8504, 17044995}, - {8505, 16779267}, {8506, 1}, {8507, 50599683}, {8508, 16855043}, - {8509, 16852227}, {8511, 16855043}, {8512, 17046019}, {8513, 1}, - {8517, 16777987}, {8519, 16778243}, {8520, 16779267}, {8521, 16779523}, - {8522, 1}, {8528, 50600707}, {8529, 50601475}, {8530, 67379459}, - {8531, 50603267}, {8532, 50604035}, {8533, 50604803}, {8534, 50605571}, - {8535, 50606339}, {8536, 50607107}, {8537, 50607875}, {8538, 50608643}, - {8539, 50609411}, {8540, 50610179}, {8541, 50610947}, {8542, 50611715}, - {8543, 33564419}, {8544, 16779267}, {8545, 33835267}, {8546, 50612995}, - {8547, 33836547}, {8548, 16782595}, {8549, 33837059}, {8550, 50614787}, - {8551, 67392771}, {8552, 33839363}, {8553, 16783107}, {8554, 33839875}, - {8555, 50617603}, {8556, 16780035}, {8557, 16777731}, {8558, 16777987}, - {8559, 16780291}, {8560, 16779267}, {8561, 33835267}, {8562, 50612483}, - {8563, 33836547}, {8564, 16782595}, {8565, 33837059}, {8566, 50614787}, - {8567, 67392771}, {8568, 33839363}, {8569, 16783107}, {8570, 33839875}, - {8571, 50617603}, {8572, 16780035}, {8573, 16777731}, {8574, 16777987}, - {8575, 16780291}, {8576, 1}, {8579, 2}, {8580, 1}, - {8585, 50618371}, {8586, 1}, {8588, 2}, {8592, 1}, - {8748, 33841923}, {8749, 50619651}, {8750, 1}, {8751, 33843203}, - {8752, 50620931}, {8753, 1}, {9001, 17067267}, {9002, 17067523}, - {9003, 1}, {9255, 2}, {9280, 1}, {9291, 2}, - {9312, 16786947}, {9313, 16785155}, {9314, 16785411}, {9315, 16787715}, - {9316, 17035779}, {9317, 17036035}, {9318, 17036291}, {9319, 17036547}, - {9320, 17036803}, {9321, 33825539}, {9322, 33564163}, {9323, 33844995}, - {9324, 33845507}, {9325, 33846019}, {9326, 33846531}, {9327, 33847043}, - {9328, 33847555}, {9329, 33848067}, {9330, 33848579}, {9331, 33849091}, - {9332, 50626819}, {9333, 50627587}, {9334, 50628355}, {9335, 50629123}, - {9336, 50629891}, {9337, 50630659}, {9338, 50631427}, {9339, 50632195}, - {9340, 50632963}, {9341, 67410947}, {9342, 67411971}, {9343, 67412995}, - {9344, 67414019}, {9345, 67415043}, {9346, 67416067}, {9347, 67417091}, - {9348, 67418115}, {9349, 67419139}, {9350, 67420163}, {9351, 67421187}, - {9352, 2}, {9372, 50644995}, {9373, 50645763}, {9374, 50646531}, - {9375, 50647299}, {9376, 50648067}, {9377, 50648835}, {9378, 50649603}, - {9379, 50650371}, {9380, 50651139}, {9381, 50651907}, {9382, 50652675}, - {9383, 50653443}, {9384, 50654211}, {9385, 50654979}, {9386, 50655747}, - {9387, 50656515}, {9388, 50657283}, {9389, 50658051}, {9390, 50658819}, - {9391, 50659587}, {9392, 50660355}, {9393, 50661123}, {9394, 50661891}, - {9395, 50662659}, {9396, 50663427}, {9397, 50664195}, {9398, 16777219}, - {9399, 16777475}, {9400, 16777731}, {9401, 16777987}, {9402, 16778243}, - {9403, 16778499}, {9404, 16778755}, {9405, 16779011}, {9406, 16779267}, - {9407, 16779523}, {9408, 16779779}, {9409, 16780035}, {9410, 16780291}, - {9411, 16780547}, {9412, 16780803}, {9413, 16781059}, {9414, 16781315}, - {9415, 16781571}, {9416, 16781827}, {9417, 16782083}, {9418, 16782339}, - {9419, 16782595}, {9420, 16782851}, {9421, 16783107}, {9422, 16783363}, - {9423, 16783619}, {9424, 16777219}, {9425, 16777475}, {9426, 16777731}, - {9427, 16777987}, {9428, 16778243}, {9429, 16778499}, {9430, 16778755}, - {9431, 16779011}, {9432, 16779267}, {9433, 16779523}, {9434, 16779779}, - {9435, 16780035}, {9436, 16780291}, {9437, 16780547}, {9438, 16780803}, - {9439, 16781059}, {9440, 16781315}, {9441, 16781571}, {9442, 16781827}, - {9443, 16782083}, {9444, 16782339}, {9445, 16782595}, {9446, 16782851}, - {9447, 16783107}, {9448, 16783363}, {9449, 16783619}, {9450, 17035523}, - {9451, 1}, {10764, 67396355}, {10765, 1}, {10868, 50664963}, - {10869, 33888515}, {10870, 50665475}, {10871, 1}, {10972, 33889027}, - {10973, 1}, {11124, 2}, {11126, 1}, {11158, 2}, - {11159, 1}, {11264, 17112323}, {11265, 17112579}, {11266, 17112835}, - {11267, 17113091}, {11268, 17113347}, {11269, 17113603}, {11270, 17113859}, - {11271, 17114115}, {11272, 17114371}, {11273, 17114627}, {11274, 17114883}, - {11275, 17115139}, {11276, 17115395}, {11277, 17115651}, {11278, 17115907}, - {11279, 17116163}, {11280, 17116419}, {11281, 17116675}, {11282, 17116931}, - {11283, 17117187}, {11284, 17117443}, {11285, 17117699}, {11286, 17117955}, - {11287, 17118211}, {11288, 17118467}, {11289, 17118723}, {11290, 17118979}, - {11291, 17119235}, {11292, 17119491}, {11293, 17119747}, {11294, 17120003}, - {11295, 17120259}, {11296, 17120515}, {11297, 17120771}, {11298, 17121027}, - {11299, 17121283}, {11300, 17121539}, {11301, 17121795}, {11302, 17122051}, - {11303, 17122307}, {11304, 17122563}, {11305, 17122819}, {11306, 17123075}, - {11307, 17123331}, {11308, 17123587}, {11309, 17123843}, {11310, 17124099}, - {11311, 17124355}, {11312, 1}, {11360, 17124611}, {11361, 1}, - {11362, 17124867}, {11363, 17125123}, {11364, 17125379}, {11365, 1}, - {11367, 17125635}, {11368, 1}, {11369, 17125891}, {11370, 1}, - {11371, 17126147}, {11372, 1}, {11373, 16948483}, {11374, 16953091}, - {11375, 16948227}, {11376, 16950275}, {11377, 1}, {11378, 17126403}, - {11379, 1}, {11381, 17126659}, {11382, 1}, {11388, 16779523}, - {11389, 16782595}, {11390, 17126915}, {11391, 17127171}, {11392, 17127427}, - {11393, 1}, {11394, 17127683}, {11395, 1}, {11396, 17127939}, - {11397, 1}, {11398, 17128195}, {11399, 1}, {11400, 17128451}, - {11401, 1}, {11402, 17128707}, {11403, 1}, {11404, 17128963}, - {11405, 1}, {11406, 17129219}, {11407, 1}, {11408, 17129475}, - {11409, 1}, {11410, 17129731}, {11411, 1}, {11412, 17129987}, - {11413, 1}, {11414, 17130243}, {11415, 1}, {11416, 17130499}, - {11417, 1}, {11418, 17130755}, {11419, 1}, {11420, 17131011}, - {11421, 1}, {11422, 17131267}, {11423, 1}, {11424, 17131523}, - {11425, 1}, {11426, 17131779}, {11427, 1}, {11428, 17132035}, - {11429, 1}, {11430, 17132291}, {11431, 1}, {11432, 17132547}, - {11433, 1}, {11434, 17132803}, {11435, 1}, {11436, 17133059}, - {11437, 1}, {11438, 17133315}, {11439, 1}, {11440, 17133571}, - {11441, 1}, {11442, 17133827}, {11443, 1}, {11444, 17134083}, - {11445, 1}, {11446, 17134339}, {11447, 1}, {11448, 17134595}, - {11449, 1}, {11450, 17134851}, {11451, 1}, {11452, 17135107}, - {11453, 1}, {11454, 17135363}, {11455, 1}, {11456, 17135619}, - {11457, 1}, {11458, 17135875}, {11459, 1}, {11460, 17136131}, - {11461, 1}, {11462, 17136387}, {11463, 1}, {11464, 17136643}, - {11465, 1}, {11466, 17136899}, {11467, 1}, {11468, 17137155}, - {11469, 1}, {11470, 17137411}, {11471, 1}, {11472, 17137667}, - {11473, 1}, {11474, 17137923}, {11475, 1}, {11476, 17138179}, - {11477, 1}, {11478, 17138435}, {11479, 1}, {11480, 17138691}, - {11481, 1}, {11482, 17138947}, {11483, 1}, {11484, 17139203}, - {11485, 1}, {11486, 17139459}, {11487, 1}, {11488, 17139715}, - {11489, 1}, {11490, 17139971}, {11491, 1}, {11499, 17140227}, - {11500, 1}, {11501, 17140483}, {11502, 1}, {11506, 17140739}, - {11507, 1}, {11508, 2}, {11513, 1}, {11558, 2}, - {11559, 1}, {11560, 2}, {11565, 1}, {11566, 2}, - {11568, 1}, {11624, 2}, {11631, 17140995}, {11632, 1}, - {11633, 2}, {11647, 1}, {11671, 2}, {11680, 1}, - {11687, 2}, {11688, 1}, {11695, 2}, {11696, 1}, - {11703, 2}, {11704, 1}, {11711, 2}, {11712, 1}, - {11719, 2}, {11720, 1}, {11727, 2}, {11728, 1}, - {11735, 2}, {11736, 1}, {11743, 2}, {11744, 1}, - {11870, 2}, {11904, 1}, {11930, 2}, {11931, 1}, - {11935, 17141251}, {11936, 1}, {12019, 17141507}, {12020, 2}, - {12032, 17141763}, {12033, 17142019}, {12034, 17142275}, {12035, 17142531}, - {12036, 17142787}, {12037, 17143043}, {12038, 17143299}, {12039, 17143555}, - {12040, 17143811}, {12041, 17144067}, {12042, 17144323}, {12043, 17144579}, - {12044, 17144835}, {12045, 17145091}, {12046, 17145347}, {12047, 17145603}, - {12048, 17145859}, {12049, 17146115}, {12050, 17146371}, {12051, 17146627}, - {12052, 17146883}, {12053, 17147139}, {12054, 17147395}, {12055, 17147651}, - {12056, 17147907}, {12057, 17148163}, {12058, 17148419}, {12059, 17148675}, - {12060, 17148931}, {12061, 17149187}, {12062, 17149443}, {12063, 17149699}, - {12064, 17149955}, {12065, 17150211}, {12066, 17150467}, {12067, 17150723}, - {12068, 17150979}, {12069, 17151235}, {12070, 17151491}, {12071, 17151747}, - {12072, 17152003}, {12073, 17152259}, {12074, 17152515}, {12075, 17152771}, - {12076, 17153027}, {12077, 17153283}, {12078, 17153539}, {12079, 17153795}, - {12080, 17154051}, {12081, 17154307}, {12082, 17154563}, {12083, 17154819}, - {12084, 17155075}, {12085, 17155331}, {12086, 17155587}, {12087, 17155843}, - {12088, 17156099}, {12089, 17156355}, {12090, 17156611}, {12091, 17156867}, - {12092, 17157123}, {12093, 17157379}, {12094, 17157635}, {12095, 17157891}, - {12096, 17158147}, {12097, 17158403}, {12098, 17158659}, {12099, 17158915}, - {12100, 17159171}, {12101, 17159427}, {12102, 17159683}, {12103, 17159939}, - {12104, 17160195}, {12105, 17160451}, {12106, 17160707}, {12107, 17160963}, - {12108, 17161219}, {12109, 17161475}, {12110, 17161731}, {12111, 17161987}, - {12112, 17162243}, {12113, 17162499}, {12114, 17162755}, {12115, 17163011}, - {12116, 17163267}, {12117, 17163523}, {12118, 17163779}, {12119, 17164035}, - {12120, 17164291}, {12121, 17164547}, {12122, 17164803}, {12123, 17165059}, - {12124, 17165315}, {12125, 17165571}, {12126, 17165827}, {12127, 17166083}, - {12128, 17166339}, {12129, 17166595}, {12130, 17166851}, {12131, 17167107}, - {12132, 17167363}, {12133, 17167619}, {12134, 17167875}, {12135, 17168131}, - {12136, 17168387}, {12137, 17168643}, {12138, 17168899}, {12139, 17169155}, - {12140, 17169411}, {12141, 17169667}, {12142, 17169923}, {12143, 17170179}, - {12144, 17170435}, {12145, 17170691}, {12146, 17170947}, {12147, 17171203}, - {12148, 17171459}, {12149, 17171715}, {12150, 17171971}, {12151, 17172227}, - {12152, 17172483}, {12153, 17172739}, {12154, 17172995}, {12155, 17173251}, - {12156, 17173507}, {12157, 17173763}, {12158, 17174019}, {12159, 17174275}, - {12160, 17174531}, {12161, 17174787}, {12162, 17175043}, {12163, 17175299}, - {12164, 17175555}, {12165, 17175811}, {12166, 17176067}, {12167, 17176323}, - {12168, 17176579}, {12169, 17176835}, {12170, 17177091}, {12171, 17177347}, - {12172, 17177603}, {12173, 17177859}, {12174, 17178115}, {12175, 17178371}, - {12176, 17178627}, {12177, 17178883}, {12178, 17179139}, {12179, 17179395}, - {12180, 17179651}, {12181, 17179907}, {12182, 17180163}, {12183, 17180419}, - {12184, 17180675}, {12185, 17180931}, {12186, 17181187}, {12187, 17181443}, - {12188, 17181699}, {12189, 17181955}, {12190, 17182211}, {12191, 17182467}, - {12192, 17182723}, {12193, 17182979}, {12194, 17183235}, {12195, 17183491}, - {12196, 17183747}, {12197, 17184003}, {12198, 17184259}, {12199, 17184515}, - {12200, 17184771}, {12201, 17185027}, {12202, 17185283}, {12203, 17185539}, - {12204, 17185795}, {12205, 17186051}, {12206, 17186307}, {12207, 17186563}, - {12208, 17186819}, {12209, 17187075}, {12210, 17187331}, {12211, 17187587}, - {12212, 17187843}, {12213, 17188099}, {12214, 17188355}, {12215, 17188611}, - {12216, 17188867}, {12217, 17189123}, {12218, 17189379}, {12219, 17189635}, - {12220, 17189891}, {12221, 17190147}, {12222, 17190403}, {12223, 17190659}, - {12224, 17190915}, {12225, 17191171}, {12226, 17191427}, {12227, 17191683}, - {12228, 17191939}, {12229, 17192195}, {12230, 17192451}, {12231, 17192707}, - {12232, 17192963}, {12233, 17193219}, {12234, 17193475}, {12235, 17193731}, - {12236, 17193987}, {12237, 17194243}, {12238, 17194499}, {12239, 17194755}, - {12240, 17195011}, {12241, 17195267}, {12242, 17195523}, {12243, 17195779}, - {12244, 17196035}, {12245, 17196291}, {12246, 2}, {12288, 16783875}, - {12289, 1}, {12290, 17196547}, {12291, 1}, {12342, 17196803}, - {12343, 1}, {12344, 17147651}, {12345, 17197059}, {12346, 17197315}, - {12347, 1}, {12352, 2}, {12353, 1}, {12439, 2}, - {12441, 1}, {12443, 33974787}, {12444, 33975299}, {12445, 1}, - {12447, 33975811}, {12448, 1}, {12543, 33976323}, {12544, 2}, - {12549, 1}, {12592, 2}, {12593, 17199619}, {12594, 17199875}, - {12595, 17200131}, {12596, 17200387}, {12597, 17200643}, {12598, 17200899}, - {12599, 17201155}, {12600, 17201411}, {12601, 17201667}, {12602, 17201923}, - {12603, 17202179}, {12604, 17202435}, {12605, 17202691}, {12606, 17202947}, - {12607, 17203203}, {12608, 17203459}, {12609, 17203715}, {12610, 17203971}, - {12611, 17204227}, {12612, 17204483}, {12613, 17204739}, {12614, 17204995}, - {12615, 17205251}, {12616, 17205507}, {12617, 17205763}, {12618, 17206019}, - {12619, 17206275}, {12620, 17206531}, {12621, 17206787}, {12622, 17207043}, - {12623, 17207299}, {12624, 17207555}, {12625, 17207811}, {12626, 17208067}, - {12627, 17208323}, {12628, 17208579}, {12629, 17208835}, {12630, 17209091}, - {12631, 17209347}, {12632, 17209603}, {12633, 17209859}, {12634, 17210115}, - {12635, 17210371}, {12636, 17210627}, {12637, 17210883}, {12638, 17211139}, - {12639, 17211395}, {12640, 17211651}, {12641, 17211907}, {12642, 17212163}, - {12643, 17212419}, {12644, 2}, {12645, 17212675}, {12646, 17212931}, - {12647, 17213187}, {12648, 17213443}, {12649, 17213699}, {12650, 17213955}, - {12651, 17214211}, {12652, 17214467}, {12653, 17214723}, {12654, 17214979}, - {12655, 17215235}, {12656, 17215491}, {12657, 17215747}, {12658, 17216003}, - {12659, 17216259}, {12660, 17216515}, {12661, 17216771}, {12662, 17217027}, - {12663, 17217283}, {12664, 17217539}, {12665, 17217795}, {12666, 17218051}, - {12667, 17218307}, {12668, 17218563}, {12669, 17218819}, {12670, 17219075}, - {12671, 17219331}, {12672, 17219587}, {12673, 17219843}, {12674, 17220099}, - {12675, 17220355}, {12676, 17220611}, {12677, 17220867}, {12678, 17221123}, - {12679, 17221379}, {12680, 17221635}, {12681, 17221891}, {12682, 17222147}, - {12683, 17222403}, {12684, 17222659}, {12685, 17222915}, {12686, 17223171}, - {12687, 2}, {12688, 1}, {12690, 17141763}, {12691, 17143299}, - {12692, 17223427}, {12693, 17223683}, {12694, 17223939}, {12695, 17224195}, - {12696, 17224451}, {12697, 17224707}, {12698, 17142787}, {12699, 17224963}, - {12700, 17225219}, {12701, 17225475}, {12702, 17225731}, {12703, 17143811}, - {12704, 1}, {12772, 2}, {12784, 1}, {12800, 50780419}, - {12801, 50781187}, {12802, 50781955}, {12803, 50782723}, {12804, 50783491}, - {12805, 50784259}, {12806, 50785027}, {12807, 50785795}, {12808, 50786563}, - {12809, 50787331}, {12810, 50788099}, {12811, 50788867}, {12812, 50789635}, - {12813, 50790403}, {12814, 50791171}, {12815, 50791939}, {12816, 50792707}, - {12817, 50793475}, {12818, 50794243}, {12819, 50795011}, {12820, 50795779}, - {12821, 50796547}, {12822, 50797315}, {12823, 50798083}, {12824, 50798851}, - {12825, 50799619}, {12826, 50800387}, {12827, 50801155}, {12828, 50801923}, - {12829, 67579907}, {12830, 67580931}, {12831, 2}, {12832, 50804739}, - {12833, 50805507}, {12834, 50806275}, {12835, 50807043}, {12836, 50807811}, - {12837, 50808579}, {12838, 50809347}, {12839, 50810115}, {12840, 50810883}, - {12841, 50811651}, {12842, 50812419}, {12843, 50813187}, {12844, 50813955}, - {12845, 50814723}, {12846, 50815491}, {12847, 50816259}, {12848, 50817027}, - {12849, 50817795}, {12850, 50818563}, {12851, 50819331}, {12852, 50820099}, - {12853, 50820867}, {12854, 50821635}, {12855, 50822403}, {12856, 50823171}, - {12857, 50823939}, {12858, 50824707}, {12859, 50825475}, {12860, 50826243}, - {12861, 50827011}, {12862, 50827779}, {12863, 50828547}, {12864, 50829315}, - {12865, 50830083}, {12866, 50830851}, {12867, 50831619}, {12868, 17277955}, - {12869, 17278211}, {12870, 17158659}, {12871, 17278467}, {12872, 1}, - {12880, 50833155}, {12881, 33845251}, {12882, 34056707}, {12883, 33562371}, - {12884, 34057219}, {12885, 34057731}, {12886, 34058243}, {12887, 34058755}, - {12888, 34059267}, {12889, 34059779}, {12890, 34060291}, {12891, 33827331}, - {12892, 33826563}, {12893, 34060803}, {12894, 34061315}, {12895, 34061827}, - {12896, 17199619}, {12897, 17200387}, {12898, 17201155}, {12899, 17201667}, - {12900, 17203715}, {12901, 17203971}, {12902, 17204739}, {12903, 17205251}, - {12904, 17205507}, {12905, 17206019}, {12906, 17206275}, {12907, 17206531}, - {12908, 17206787}, {12909, 17207043}, {12910, 17236995}, {12911, 17237763}, - {12912, 17238531}, {12913, 17239299}, {12914, 17240067}, {12915, 17240835}, - {12916, 17241603}, {12917, 17242371}, {12918, 17243139}, {12919, 17243907}, - {12920, 17244675}, {12921, 17245443}, {12922, 17246211}, {12923, 17246979}, - {12924, 34062339}, {12925, 34062851}, {12926, 17286147}, {12927, 1}, - {12928, 17141763}, {12929, 17143299}, {12930, 17223427}, {12931, 17223683}, - {12932, 17253635}, {12933, 17254403}, {12934, 17255171}, {12935, 17144579}, - {12936, 17256707}, {12937, 17147651}, {12938, 17160451}, {12939, 17163523}, - {12940, 17163267}, {12941, 17160707}, {12942, 17184259}, {12943, 17149699}, - {12944, 17159939}, {12945, 17263619}, {12946, 17264387}, {12947, 17265155}, - {12948, 17265923}, {12949, 17266691}, {12950, 17267459}, {12951, 17268227}, - {12952, 17268995}, {12953, 17286403}, {12954, 17286659}, {12955, 17151235}, - {12956, 17286915}, {12957, 17287171}, {12958, 17287427}, {12959, 17287683}, - {12960, 17287939}, {12961, 17275907}, {12962, 17288195}, {12963, 17288451}, - {12964, 17223939}, {12965, 17224195}, {12966, 17224451}, {12967, 17288707}, - {12968, 17288963}, {12969, 17289219}, {12970, 17289475}, {12971, 17271299}, - {12972, 17272067}, {12973, 17272835}, {12974, 17273603}, {12975, 17274371}, - {12976, 17289731}, {12977, 34067203}, {12978, 34067715}, {12979, 34068227}, - {12980, 34068739}, {12981, 34069251}, {12982, 33564931}, {12983, 34057475}, - {12984, 34061571}, {12985, 34069763}, {12986, 34070275}, {12987, 34070787}, - {12988, 34071299}, {12989, 34071811}, {12990, 34072323}, {12991, 34072835}, - {12992, 34073347}, {12993, 34073859}, {12994, 34074371}, {12995, 34074883}, - {12996, 34075395}, {12997, 34075907}, {12998, 34076419}, {12999, 34076931}, - {13000, 34077443}, {13001, 50855171}, {13002, 50855939}, {13003, 50856707}, - {13004, 34080259}, {13005, 50857987}, {13006, 34081539}, {13007, 50859267}, - {13008, 17305603}, {13009, 17305859}, {13010, 17306115}, {13011, 17306371}, - {13012, 17306627}, {13013, 17306883}, {13014, 17307139}, {13015, 17307395}, - {13016, 17307651}, {13017, 17199107}, {13018, 17307907}, {13019, 17308163}, - {13020, 17308419}, {13021, 17308675}, {13022, 17308931}, {13023, 17309187}, - {13024, 17309443}, {13025, 17309699}, {13026, 17309955}, {13027, 17199363}, - {13028, 17310211}, {13029, 17310467}, {13030, 17310723}, {13031, 17310979}, - {13032, 17311235}, {13033, 17311491}, {13034, 17311747}, {13035, 17312003}, - {13036, 17312259}, {13037, 17312515}, {13038, 17312771}, {13039, 17313027}, - {13040, 17313283}, {13041, 17313539}, {13042, 17313795}, {13043, 17314051}, - {13044, 17314307}, {13045, 17314563}, {13046, 17314819}, {13047, 17315075}, - {13048, 17315331}, {13049, 17315587}, {13050, 17315843}, {13051, 17316099}, - {13052, 17316355}, {13053, 17316611}, {13054, 17316867}, {13055, 34094339}, - {13056, 67649283}, {13057, 67650307}, {13058, 67651331}, {13059, 50875139}, - {13060, 67653123}, {13061, 50876931}, {13062, 50877699}, {13063, 84432899}, - {13064, 67656963}, {13065, 50880771}, {13066, 50881539}, {13067, 50882307}, - {13068, 67660291}, {13069, 67661315}, {13070, 50885123}, {13071, 50885891}, - {13072, 34109443}, {13073, 50887171}, {13074, 67665155}, {13075, 67666179}, - {13076, 34112771}, {13077, 84444931}, {13078, 101223427}, {13079, 84447747}, - {13080, 50891011}, {13081, 84449027}, {13082, 84450307}, {13083, 67674371}, - {13084, 50898179}, {13085, 50898947}, {13086, 50899715}, {13087, 67677699}, - {13088, 84455939}, {13089, 67680003}, {13090, 50903811}, {13091, 50904579}, - {13092, 50905347}, {13093, 34128899}, {13094, 34129411}, {13095, 34118147}, - {13096, 34129923}, {13097, 50907651}, {13098, 50908419}, {13099, 84463619}, - {13100, 50910467}, {13101, 67688451}, {13102, 84466691}, {13103, 50913539}, - {13104, 34137091}, {13105, 34137603}, {13106, 84469763}, {13107, 67693827}, - {13108, 84472067}, {13109, 50918915}, {13110, 84474115}, {13111, 34143747}, - {13112, 50921475}, {13113, 50922243}, {13114, 50923011}, {13115, 50923779}, - {13116, 50924547}, {13117, 67702531}, {13118, 50926339}, {13119, 34149891}, - {13120, 50927619}, {13121, 50928387}, {13122, 50929155}, {13123, 67707139}, - {13124, 50930947}, {13125, 50931715}, {13126, 50932483}, {13127, 84487683}, - {13128, 67711747}, {13129, 34158339}, {13130, 84490499}, {13131, 34160131}, - {13132, 67715075}, {13133, 67669507}, {13134, 50938883}, {13135, 50939651}, - {13136, 50940419}, {13137, 67718403}, {13138, 34164995}, {13139, 50942723}, - {13140, 67720707}, {13141, 34167299}, {13142, 84499459}, {13143, 50893827}, - {13144, 34169091}, {13145, 34169603}, {13146, 34170115}, {13147, 34170627}, - {13148, 34171139}, {13149, 34171651}, {13150, 34172163}, {13151, 34172675}, - {13152, 34173187}, {13153, 34173699}, {13154, 50951427}, {13155, 50952195}, - {13156, 50952963}, {13157, 50953731}, {13158, 50954499}, {13159, 50955267}, - {13160, 50956035}, {13161, 50956803}, {13162, 50957571}, {13163, 50958339}, - {13164, 50959107}, {13165, 50959875}, {13166, 50960643}, {13167, 50961411}, - {13168, 50962179}, {13169, 50962947}, {13170, 34186499}, {13171, 34187011}, - {13172, 50964739}, {13173, 34188291}, {13174, 34188803}, {13175, 34189315}, - {13176, 50967043}, {13177, 50967811}, {13178, 34191363}, {13179, 34191875}, - {13180, 34192387}, {13181, 34192899}, {13182, 34193411}, {13183, 67748355}, - {13184, 34185987}, {13185, 34194947}, {13186, 34195459}, {13187, 34195971}, - {13188, 34196483}, {13189, 34196995}, {13190, 34197507}, {13191, 34198019}, - {13192, 50975747}, {13193, 67753731}, {13194, 34200323}, {13195, 34200835}, - {13196, 34201347}, {13197, 34201859}, {13198, 34202371}, {13199, 34202883}, - {13200, 34203395}, {13201, 50981123}, {13202, 50981891}, {13203, 50980355}, - {13204, 50982659}, {13205, 34206211}, {13206, 34206723}, {13207, 34207235}, - {13208, 33556995}, {13209, 34207747}, {13210, 34208259}, {13211, 34208771}, - {13212, 34209283}, {13213, 34209795}, {13214, 34210307}, {13215, 50988035}, - {13216, 50988803}, {13217, 34190083}, {13218, 50989571}, {13219, 50990339}, - {13220, 50991107}, {13221, 34190851}, {13222, 50991875}, {13223, 50992643}, - {13224, 67770627}, {13225, 34185987}, {13226, 50994435}, {13227, 50995203}, - {13228, 50995971}, {13229, 50996739}, {13230, 84551939}, {13231, 101330435}, - {13232, 34223107}, {13233, 34223619}, {13234, 34224131}, {13235, 34224643}, - {13236, 34225155}, {13237, 34225667}, {13238, 34226179}, {13239, 34226691}, - {13240, 34227203}, {13241, 34226691}, {13242, 34227715}, {13243, 34228227}, - {13244, 34228739}, {13245, 34229251}, {13246, 34229763}, {13247, 34229251}, - {13248, 34230275}, {13249, 34230787}, {13250, 2}, {13251, 34231299}, - {13252, 33817347}, {13253, 33554947}, {13254, 67786243}, {13255, 2}, - {13256, 34232835}, {13257, 34233347}, {13258, 34233859}, {13259, 34185731}, - {13260, 34234371}, {13261, 34234883}, {13262, 34210307}, {13263, 34235395}, - {13264, 33557251}, {13265, 34235907}, {13266, 51013635}, {13267, 34237187}, - {13268, 34197507}, {13269, 51014915}, {13270, 51015683}, {13271, 34239235}, - {13272, 2}, {13273, 51016963}, {13274, 34240515}, {13275, 34221315}, - {13276, 34241027}, {13277, 34241539}, {13278, 51019267}, {13279, 51020035}, - {13280, 34243587}, {13281, 34244099}, {13282, 34244611}, {13283, 34245123}, - {13284, 34245635}, {13285, 34246147}, {13286, 34246659}, {13287, 34247171}, - {13288, 34247683}, {13289, 51025411}, {13290, 51026179}, {13291, 51026947}, - {13292, 51027715}, {13293, 51028483}, {13294, 51029251}, {13295, 51030019}, - {13296, 51030787}, {13297, 51031555}, {13298, 51032323}, {13299, 51033091}, - {13300, 51033859}, {13301, 51034627}, {13302, 51035395}, {13303, 51036163}, - {13304, 51036931}, {13305, 51037699}, {13306, 51038467}, {13307, 51039235}, - {13308, 51040003}, {13309, 51040771}, {13310, 51041539}, {13311, 51042307}, - {13312, 1}, {42125, 2}, {42128, 1}, {42183, 2}, - {42192, 1}, {42540, 2}, {42560, 17488643}, {42561, 1}, - {42562, 17488899}, {42563, 1}, {42564, 17489155}, {42565, 1}, - {42566, 17489411}, {42567, 1}, {42568, 17489667}, {42569, 1}, - {42570, 16936451}, {42571, 1}, {42572, 17489923}, {42573, 1}, - {42574, 17490179}, {42575, 1}, {42576, 17490435}, {42577, 1}, - {42578, 17490691}, {42579, 1}, {42580, 17490947}, {42581, 1}, - {42582, 17491203}, {42583, 1}, {42584, 17491459}, {42585, 1}, - {42586, 17491715}, {42587, 1}, {42588, 17491971}, {42589, 1}, - {42590, 17492227}, {42591, 1}, {42592, 17492483}, {42593, 1}, - {42594, 17492739}, {42595, 1}, {42596, 17492995}, {42597, 1}, - {42598, 17493251}, {42599, 1}, {42600, 17493507}, {42601, 1}, - {42602, 17493763}, {42603, 1}, {42604, 17494019}, {42605, 1}, - {42624, 17494275}, {42625, 1}, {42626, 17494531}, {42627, 1}, - {42628, 17494787}, {42629, 1}, {42630, 17495043}, {42631, 1}, - {42632, 17495299}, {42633, 1}, {42634, 17495555}, {42635, 1}, - {42636, 17495811}, {42637, 1}, {42638, 17496067}, {42639, 1}, - {42640, 17496323}, {42641, 1}, {42642, 17496579}, {42643, 1}, - {42644, 17496835}, {42645, 1}, {42646, 17497091}, {42647, 1}, - {42648, 17497347}, {42649, 1}, {42650, 17497603}, {42651, 1}, - {42652, 16873219}, {42653, 16873731}, {42654, 1}, {42744, 2}, - {42752, 1}, {42786, 17497859}, {42787, 1}, {42788, 17498115}, - {42789, 1}, {42790, 17498371}, {42791, 1}, {42792, 17498627}, - {42793, 1}, {42794, 17498883}, {42795, 1}, {42796, 17499139}, - {42797, 1}, {42798, 17499395}, {42799, 1}, {42802, 17499651}, - {42803, 1}, {42804, 17499907}, {42805, 1}, {42806, 17500163}, - {42807, 1}, {42808, 17500419}, {42809, 1}, {42810, 17500675}, - {42811, 1}, {42812, 17500931}, {42813, 1}, {42814, 17501187}, - {42815, 1}, {42816, 17501443}, {42817, 1}, {42818, 17501699}, - {42819, 1}, {42820, 17501955}, {42821, 1}, {42822, 17502211}, - {42823, 1}, {42824, 17502467}, {42825, 1}, {42826, 17502723}, - {42827, 1}, {42828, 17502979}, {42829, 1}, {42830, 17503235}, - {42831, 1}, {42832, 17503491}, {42833, 1}, {42834, 17503747}, - {42835, 1}, {42836, 17504003}, {42837, 1}, {42838, 17504259}, - {42839, 1}, {42840, 17504515}, {42841, 1}, {42842, 17504771}, - {42843, 1}, {42844, 17505027}, {42845, 1}, {42846, 17505283}, - {42847, 1}, {42848, 17505539}, {42849, 1}, {42850, 17505795}, - {42851, 1}, {42852, 17506051}, {42853, 1}, {42854, 17506307}, - {42855, 1}, {42856, 17506563}, {42857, 1}, {42858, 17506819}, - {42859, 1}, {42860, 17507075}, {42861, 1}, {42862, 17507331}, - {42863, 1}, {42864, 17507331}, {42865, 1}, {42873, 17507587}, - {42874, 1}, {42875, 17507843}, {42876, 1}, {42877, 17508099}, - {42878, 17508355}, {42879, 1}, {42880, 17508611}, {42881, 1}, - {42882, 17508867}, {42883, 1}, {42884, 17509123}, {42885, 1}, - {42886, 17509379}, {42887, 1}, {42891, 17509635}, {42892, 1}, - {42893, 16951299}, {42894, 1}, {42896, 17509891}, {42897, 1}, - {42898, 17510147}, {42899, 1}, {42902, 17510403}, {42903, 1}, - {42904, 17510659}, {42905, 1}, {42906, 17510915}, {42907, 1}, - {42908, 17511171}, {42909, 1}, {42910, 17511427}, {42911, 1}, - {42912, 17511683}, {42913, 1}, {42914, 17511939}, {42915, 1}, - {42916, 17512195}, {42917, 1}, {42918, 17512451}, {42919, 1}, - {42920, 17512707}, {42921, 1}, {42922, 16841475}, {42923, 16948995}, - {42924, 16951043}, {42925, 17512963}, {42926, 16951555}, {42927, 1}, - {42928, 17513219}, {42929, 17513475}, {42930, 16952067}, {42931, 17513731}, - {42932, 17513987}, {42933, 1}, {42934, 17514243}, {42935, 1}, - {42936, 17514499}, {42937, 1}, {42938, 17514755}, {42939, 1}, - {42940, 17515011}, {42941, 1}, {42942, 17515267}, {42943, 1}, - {42944, 17515523}, {42945, 1}, {42946, 17515779}, {42947, 1}, - {42948, 17516035}, {42949, 16954371}, {42950, 17516291}, {42951, 17516547}, - {42952, 1}, {42953, 17516803}, {42954, 1}, {42955, 2}, - {42960, 17517059}, {42961, 1}, {42962, 2}, {42963, 1}, - {42964, 2}, {42965, 1}, {42966, 17517315}, {42967, 1}, - {42968, 17517571}, {42969, 1}, {42970, 2}, {42994, 16777731}, - {42995, 16778499}, {42996, 16781315}, {42997, 17517827}, {42998, 1}, - {43000, 16802051}, {43001, 16808195}, {43002, 1}, {43053, 2}, - {43056, 1}, {43066, 2}, {43072, 1}, {43128, 2}, - {43136, 1}, {43206, 2}, {43214, 1}, {43226, 2}, - {43232, 1}, {43348, 2}, {43359, 1}, {43389, 2}, - {43392, 1}, {43470, 2}, {43471, 1}, {43482, 2}, - {43486, 1}, {43519, 2}, {43520, 1}, {43575, 2}, - {43584, 1}, {43598, 2}, {43600, 1}, {43610, 2}, - {43612, 1}, {43715, 2}, {43739, 1}, {43767, 2}, - {43777, 1}, {43783, 2}, {43785, 1}, {43791, 2}, - {43793, 1}, {43799, 2}, {43808, 1}, {43815, 2}, - {43816, 1}, {43823, 2}, {43824, 1}, {43868, 17498371}, - {43869, 17518083}, {43870, 17124867}, {43871, 17518339}, {43872, 1}, - {43881, 17518595}, {43882, 1}, {43884, 2}, {43888, 17518851}, - {43889, 17519107}, {43890, 17519363}, {43891, 17519619}, {43892, 17519875}, - {43893, 17520131}, {43894, 17520387}, {43895, 17520643}, {43896, 17520899}, - {43897, 17521155}, {43898, 17521411}, {43899, 17521667}, {43900, 17521923}, - {43901, 17522179}, {43902, 17522435}, {43903, 17522691}, {43904, 17522947}, - {43905, 17523203}, {43906, 17523459}, {43907, 17523715}, {43908, 17523971}, - {43909, 17524227}, {43910, 17524483}, {43911, 17524739}, {43912, 17524995}, - {43913, 17525251}, {43914, 17525507}, {43915, 17525763}, {43916, 17526019}, - {43917, 17526275}, {43918, 17526531}, {43919, 17526787}, {43920, 17527043}, - {43921, 17527299}, {43922, 17527555}, {43923, 17527811}, {43924, 17528067}, - {43925, 17528323}, {43926, 17528579}, {43927, 17528835}, {43928, 17529091}, - {43929, 17529347}, {43930, 17529603}, {43931, 17529859}, {43932, 17530115}, - {43933, 17530371}, {43934, 17530627}, {43935, 17530883}, {43936, 17531139}, - {43937, 17531395}, {43938, 17531651}, {43939, 17531907}, {43940, 17532163}, - {43941, 17532419}, {43942, 17532675}, {43943, 17532931}, {43944, 17533187}, - {43945, 17533443}, {43946, 17533699}, {43947, 17533955}, {43948, 17534211}, - {43949, 17534467}, {43950, 17534723}, {43951, 17534979}, {43952, 17535235}, - {43953, 17535491}, {43954, 17535747}, {43955, 17536003}, {43956, 17536259}, - {43957, 17536515}, {43958, 17536771}, {43959, 17537027}, {43960, 17537283}, - {43961, 17537539}, {43962, 17537795}, {43963, 17538051}, {43964, 17538307}, - {43965, 17538563}, {43966, 17538819}, {43967, 17539075}, {43968, 1}, - {44014, 2}, {44016, 1}, {44026, 2}, {44032, 1}, - {55204, 2}, {55216, 1}, {55239, 2}, {55243, 1}, - {55292, 2}, {63744, 17539331}, {63745, 17539587}, {63746, 17182211}, - {63747, 17539843}, {63748, 17540099}, {63749, 17540355}, {63750, 17540611}, - {63751, 17196035}, {63753, 17540867}, {63754, 17184259}, {63755, 17541123}, - {63756, 17541379}, {63757, 17541635}, {63758, 17541891}, {63759, 17542147}, - {63760, 17542403}, {63761, 17542659}, {63762, 17542915}, {63763, 17543171}, - {63764, 17543427}, {63765, 17543683}, {63766, 17543939}, {63767, 17544195}, - {63768, 17544451}, {63769, 17544707}, {63770, 17544963}, {63771, 17545219}, - {63772, 17545475}, {63773, 17545731}, {63774, 17545987}, {63775, 17546243}, - {63776, 17546499}, {63777, 17546755}, {63778, 17547011}, {63779, 17547267}, - {63780, 17547523}, {63781, 17547779}, {63782, 17548035}, {63783, 17548291}, - {63784, 17548547}, {63785, 17548803}, {63786, 17549059}, {63787, 17549315}, - {63788, 17549571}, {63789, 17549827}, {63790, 17550083}, {63791, 17550339}, - {63792, 17550595}, {63793, 17550851}, {63794, 17551107}, {63795, 17551363}, - {63796, 17173507}, {63797, 17551619}, {63798, 17551875}, {63799, 17552131}, - {63800, 17552387}, {63801, 17552643}, {63802, 17552899}, {63803, 17553155}, - {63804, 17553411}, {63805, 17553667}, {63806, 17553923}, {63807, 17554179}, - {63808, 17192195}, {63809, 17554435}, {63810, 17554691}, {63811, 17554947}, - {63812, 17555203}, {63813, 17555459}, {63814, 17555715}, {63815, 17555971}, - {63816, 17556227}, {63817, 17556483}, {63818, 17556739}, {63819, 17556995}, - {63820, 17557251}, {63821, 17557507}, {63822, 17557763}, {63823, 17558019}, - {63824, 17558275}, {63825, 17558531}, {63826, 17558787}, {63827, 17559043}, - {63828, 17559299}, {63829, 17559555}, {63830, 17559811}, {63831, 17560067}, - {63832, 17560323}, {63833, 17560579}, {63834, 17560835}, {63835, 17561091}, - {63836, 17543427}, {63837, 17561347}, {63838, 17561603}, {63839, 17561859}, - {63840, 17562115}, {63841, 17562371}, {63842, 17562627}, {63843, 17562883}, - {63844, 17563139}, {63845, 17563395}, {63846, 17563651}, {63847, 17563907}, - {63848, 17564163}, {63849, 17564419}, {63850, 17564675}, {63851, 17564931}, - {63852, 17565187}, {63853, 17565443}, {63854, 17565699}, {63855, 17565955}, - {63856, 17566211}, {63857, 17182723}, {63858, 17566467}, {63859, 17566723}, - {63860, 17566979}, {63861, 17567235}, {63862, 17567491}, {63863, 17567747}, - {63864, 17568003}, {63865, 17568259}, {63866, 17568515}, {63867, 17568771}, - {63868, 17569027}, {63869, 17569283}, {63870, 17569539}, {63871, 17569795}, - {63872, 17570051}, {63873, 17151235}, {63874, 17570307}, {63875, 17570563}, - {63876, 17570819}, {63877, 17571075}, {63878, 17571331}, {63879, 17571587}, - {63880, 17571843}, {63881, 17572099}, {63882, 17146371}, {63883, 17572355}, - {63884, 17572611}, {63885, 17572867}, {63886, 17573123}, {63887, 17573379}, - {63888, 17573635}, {63889, 17573891}, {63890, 17574147}, {63891, 17574403}, - {63892, 17574659}, {63893, 17574915}, {63894, 17575171}, {63895, 17575427}, - {63896, 17575683}, {63897, 17575939}, {63898, 17576195}, {63899, 17576451}, - {63900, 17576707}, {63901, 17576963}, {63902, 17577219}, {63903, 17577475}, - {63904, 17577731}, {63905, 17565955}, {63906, 17577987}, {63907, 17578243}, - {63908, 17578499}, {63909, 17578755}, {63910, 17579011}, {63911, 17579267}, - {63912, 17317123}, {63913, 17579523}, {63914, 17561859}, {63915, 17579779}, - {63916, 17580035}, {63917, 17580291}, {63918, 17580547}, {63919, 17580803}, - {63920, 17581059}, {63921, 17581315}, {63922, 17581571}, {63923, 17581827}, - {63924, 17582083}, {63925, 17582339}, {63926, 17582595}, {63927, 17582851}, - {63928, 17583107}, {63929, 17583363}, {63930, 17583619}, {63931, 17583875}, - {63932, 17584131}, {63933, 17584387}, {63934, 17584643}, {63935, 17543427}, - {63936, 17584899}, {63937, 17585155}, {63938, 17585411}, {63939, 17585667}, - {63940, 17195779}, {63941, 17585923}, {63942, 17586179}, {63943, 17586435}, - {63944, 17586691}, {63945, 17586947}, {63946, 17587203}, {63947, 17587459}, - {63948, 17587715}, {63949, 17587971}, {63950, 17588227}, {63951, 17588483}, - {63952, 17588739}, {63953, 17254403}, {63954, 17588995}, {63955, 17589251}, - {63956, 17589507}, {63957, 17589763}, {63958, 17590019}, {63959, 17590275}, - {63960, 17590531}, {63961, 17590787}, {63962, 17591043}, {63963, 17562371}, - {63964, 17591299}, {63965, 17591555}, {63966, 17591811}, {63967, 17592067}, - {63968, 17592323}, {63969, 17592579}, {63970, 17592835}, {63971, 17593091}, - {63972, 17593347}, {63973, 17593603}, {63974, 17593859}, {63975, 17594115}, - {63976, 17594371}, {63977, 17184003}, {63978, 17594627}, {63979, 17594883}, - {63980, 17595139}, {63981, 17595395}, {63982, 17595651}, {63983, 17595907}, - {63984, 17596163}, {63985, 17596419}, {63986, 17596675}, {63987, 17596931}, - {63988, 17597187}, {63989, 17597443}, {63990, 17597699}, {63991, 17171459}, - {63992, 17597955}, {63993, 17598211}, {63994, 17598467}, {63995, 17598723}, - {63996, 17598979}, {63997, 17599235}, {63998, 17599491}, {63999, 17599747}, - {64000, 17600003}, {64001, 17600259}, {64002, 17600515}, {64003, 17600771}, - {64004, 17601027}, {64005, 17601283}, {64006, 17601539}, {64007, 17601795}, - {64008, 17178371}, {64009, 17602051}, {64010, 17179139}, {64011, 17602307}, - {64012, 17602563}, {64013, 17602819}, {64014, 1}, {64016, 17603075}, - {64017, 1}, {64018, 17603331}, {64019, 1}, {64021, 17603587}, - {64022, 17603843}, {64023, 17604099}, {64024, 17604355}, {64025, 17604611}, - {64026, 17604867}, {64027, 17605123}, {64028, 17605379}, {64029, 17605635}, - {64030, 17173251}, {64031, 1}, {64032, 17605891}, {64033, 1}, - {64034, 17606147}, {64035, 1}, {64037, 17606403}, {64038, 17606659}, - {64039, 1}, {64042, 17606915}, {64043, 17607171}, {64044, 17607427}, - {64045, 17607683}, {64046, 17607939}, {64047, 17608195}, {64048, 17608451}, - {64049, 17608707}, {64050, 17608963}, {64051, 17609219}, {64052, 17609475}, - {64053, 17609731}, {64054, 17609987}, {64055, 17610243}, {64056, 17610499}, - {64057, 17610755}, {64058, 17611011}, {64059, 17611267}, {64060, 17153027}, - {64061, 17611523}, {64062, 17611779}, {64063, 17612035}, {64064, 17612291}, - {64065, 17612547}, {64066, 17612803}, {64067, 17613059}, {64068, 17613315}, - {64069, 17613571}, {64070, 17613827}, {64071, 17614083}, {64072, 17614339}, - {64073, 17614595}, {64074, 17614851}, {64075, 17615107}, {64076, 17265155}, - {64077, 17615363}, {64078, 17615619}, {64079, 17615875}, {64080, 17616131}, - {64081, 17268227}, {64082, 17616387}, {64083, 17616643}, {64084, 17616899}, - {64085, 17617155}, {64086, 17617411}, {64087, 17575171}, {64088, 17617667}, - {64089, 17617923}, {64090, 17618179}, {64091, 17618435}, {64092, 17618691}, - {64093, 17618947}, {64095, 17619203}, {64096, 17619459}, {64097, 17619715}, - {64098, 17619971}, {64099, 17620227}, {64100, 17620483}, {64101, 17620739}, - {64102, 17620995}, {64103, 17606403}, {64104, 17621251}, {64105, 17621507}, - {64106, 17621763}, {64107, 17622019}, {64108, 17622275}, {64109, 17622531}, - {64110, 2}, {64112, 17622787}, {64113, 17623043}, {64114, 17623299}, - {64115, 17623555}, {64116, 17623811}, {64117, 17624067}, {64118, 17624323}, - {64119, 17624579}, {64120, 17609987}, {64121, 17624835}, {64122, 17625091}, - {64123, 17625347}, {64124, 17603075}, {64125, 17625603}, {64126, 17625859}, - {64127, 17626115}, {64128, 17626371}, {64129, 17626627}, {64130, 17626883}, - {64131, 17627139}, {64132, 17627395}, {64133, 17627651}, {64134, 17627907}, - {64135, 17628163}, {64136, 17628419}, {64137, 17612035}, {64138, 17628675}, - {64139, 17612291}, {64140, 17628931}, {64141, 17629187}, {64142, 17629443}, - {64143, 17629699}, {64144, 17629955}, {64145, 17603331}, {64146, 17548803}, - {64147, 17630211}, {64148, 17630467}, {64149, 17161475}, {64150, 17566211}, - {64151, 17587203}, {64152, 17630723}, {64153, 17630979}, {64154, 17614083}, - {64155, 17631235}, {64156, 17614339}, {64157, 17631491}, {64158, 17631747}, - {64159, 17632003}, {64160, 17603843}, {64161, 17632259}, {64162, 17632515}, - {64163, 17632771}, {64164, 17633027}, {64165, 17633283}, {64166, 17604099}, - {64167, 17633539}, {64168, 17633795}, {64169, 17634051}, {64170, 17634307}, - {64171, 17634563}, {64172, 17634819}, {64173, 17617411}, {64174, 17635075}, - {64175, 17635331}, {64176, 17575171}, {64177, 17635587}, {64178, 17618435}, - {64179, 17635843}, {64180, 17636099}, {64181, 17636355}, {64182, 17636611}, - {64183, 17636867}, {64184, 17619715}, {64185, 17637123}, {64186, 17606147}, - {64187, 17637379}, {64188, 17619971}, {64189, 17561347}, {64190, 17637635}, - {64191, 17620227}, {64192, 17637891}, {64193, 17620739}, {64194, 17638147}, - {64195, 17638403}, {64196, 17638659}, {64197, 17638915}, {64198, 17639171}, - {64199, 17621251}, {64200, 17605379}, {64201, 17639427}, {64202, 17621507}, - {64203, 17639683}, {64204, 17621763}, {64205, 17639939}, {64206, 17196035}, - {64207, 17640195}, {64208, 17640451}, {64209, 17640707}, {64210, 17640963}, - {64211, 17641219}, {64212, 17641475}, {64213, 17641731}, {64214, 17641987}, - {64215, 17642243}, {64216, 17642499}, {64217, 17642755}, {64218, 2}, - {64256, 34420227}, {64257, 34420739}, {64258, 34421251}, {64259, 51197699}, - {64260, 51198979}, {64261, 33559043}, {64263, 2}, {64275, 34422531}, - {64276, 34423043}, {64277, 34423555}, {64278, 34424067}, {64279, 34424579}, - {64280, 2}, {64285, 34425091}, {64286, 1}, {64287, 34425603}, - {64288, 17648899}, {64289, 17044227}, {64290, 17044995}, {64291, 17649155}, - {64292, 17649411}, {64293, 17649667}, {64294, 17649923}, {64295, 17650179}, - {64296, 17650435}, {64297, 17037059}, {64298, 34427907}, {64299, 34428419}, - {64300, 51206147}, {64301, 51206915}, {64302, 34430467}, {64303, 34430979}, - {64304, 34431491}, {64305, 34432003}, {64306, 34432515}, {64307, 34433027}, - {64308, 34433539}, {64309, 34434051}, {64310, 34434563}, {64311, 2}, - {64312, 34435075}, {64313, 34435587}, {64314, 34436099}, {64315, 34436611}, - {64316, 34437123}, {64317, 2}, {64318, 34437635}, {64319, 2}, - {64320, 34438147}, {64321, 34438659}, {64322, 2}, {64323, 34439171}, - {64324, 34439683}, {64325, 2}, {64326, 34440195}, {64327, 34440707}, - {64328, 34441219}, {64329, 34428931}, {64330, 34441731}, {64331, 34442243}, - {64332, 34442755}, {64333, 34443267}, {64334, 34443779}, {64335, 34444291}, - {64336, 17667587}, {64338, 17667843}, {64342, 17668099}, {64346, 17668355}, - {64350, 17668611}, {64354, 17668867}, {64358, 17669123}, {64362, 17669379}, - {64366, 17669635}, {64370, 17669891}, {64374, 17670147}, {64378, 17670403}, - {64382, 17670659}, {64386, 17670915}, {64388, 17671171}, {64390, 17671427}, - {64392, 17671683}, {64394, 17671939}, {64396, 17672195}, {64398, 17672451}, - {64402, 17672707}, {64406, 17672963}, {64410, 17673219}, {64414, 17673475}, - {64416, 17673731}, {64420, 17673987}, {64422, 17674243}, {64426, 17674499}, - {64430, 17674755}, {64432, 17675011}, {64434, 1}, {64451, 2}, - {64467, 17675267}, {64471, 16911363}, {64473, 17675523}, {64475, 17675779}, - {64477, 33688579}, {64478, 17676035}, {64480, 17676291}, {64482, 17676547}, - {64484, 17676803}, {64488, 17677059}, {64490, 34454531}, {64492, 34455043}, - {64494, 34455555}, {64496, 34456067}, {64498, 34456579}, {64500, 34457091}, - {64502, 34457603}, {64505, 34458115}, {64508, 17681411}, {64512, 34458883}, - {64513, 34459395}, {64514, 34459907}, {64515, 34458115}, {64516, 34460419}, - {64517, 34460931}, {64518, 34461443}, {64519, 34461955}, {64520, 34462467}, - {64521, 34462979}, {64522, 34463491}, {64523, 34464003}, {64524, 34464515}, - {64525, 34465027}, {64526, 34465539}, {64527, 34466051}, {64528, 34466563}, - {64529, 34467075}, {64530, 34467587}, {64531, 34468099}, {64532, 34468611}, - {64533, 34469123}, {64534, 34469635}, {64535, 34469379}, {64536, 34470147}, - {64537, 34470659}, {64538, 34471171}, {64539, 34471683}, {64540, 34472195}, - {64541, 34472707}, {64542, 34473219}, {64543, 34473731}, {64544, 34474243}, - {64545, 34474755}, {64546, 34475267}, {64547, 34475779}, {64548, 34476291}, - {64549, 34476803}, {64550, 34477315}, {64551, 34477827}, {64552, 34478339}, - {64553, 34478851}, {64554, 34479363}, {64555, 34479875}, {64556, 34480387}, - {64557, 34480899}, {64558, 34481411}, {64559, 34481923}, {64560, 34482435}, - {64561, 34482947}, {64562, 34483459}, {64563, 34483971}, {64564, 34484483}, - {64565, 34484995}, {64566, 34485507}, {64567, 34486019}, {64568, 34486531}, - {64569, 34487043}, {64570, 34487555}, {64571, 34488067}, {64572, 34488579}, - {64573, 34489091}, {64574, 34489603}, {64575, 34490115}, {64576, 34490627}, - {64577, 34491139}, {64578, 34491651}, {64579, 34492163}, {64580, 34492675}, - {64581, 34493187}, {64582, 34469891}, {64583, 34470403}, {64584, 34493699}, - {64585, 34494211}, {64586, 34494723}, {64587, 34495235}, {64588, 34495747}, - {64589, 34496259}, {64590, 34496771}, {64591, 34497283}, {64592, 34497795}, - {64593, 34498307}, {64594, 34498819}, {64595, 34499331}, {64596, 34499843}, - {64597, 34468867}, {64598, 34500355}, {64599, 34500867}, {64600, 34492931}, - {64601, 34501379}, {64602, 34500099}, {64603, 34501891}, {64604, 34502403}, - {64605, 34502915}, {64606, 51280643}, {64607, 51281411}, {64608, 51282179}, - {64609, 51282947}, {64610, 51283715}, {64611, 51284483}, {64612, 34508035}, - {64613, 34508547}, {64614, 34459907}, {64615, 34509059}, {64616, 34458115}, - {64617, 34460419}, {64618, 34509571}, {64619, 34510083}, {64620, 34462467}, - {64621, 34510595}, {64622, 34462979}, {64623, 34463491}, {64624, 34511107}, - {64625, 34511619}, {64626, 34465539}, {64627, 34512131}, {64628, 34466051}, - {64629, 34466563}, {64630, 34512643}, {64631, 34513155}, {64632, 34467587}, - {64633, 34513667}, {64634, 34468099}, {64635, 34468611}, {64636, 34482947}, - {64637, 34483459}, {64638, 34484995}, {64639, 34485507}, {64640, 34486019}, - {64641, 34488067}, {64642, 34488579}, {64643, 34489091}, {64644, 34489603}, - {64645, 34491651}, {64646, 34492163}, {64647, 34492675}, {64648, 34514179}, - {64649, 34493699}, {64650, 34514691}, {64651, 34515203}, {64652, 34496771}, - {64653, 34515715}, {64654, 34497283}, {64655, 34497795}, {64656, 34502915}, - {64657, 34516227}, {64658, 34516739}, {64659, 34492931}, {64660, 34494979}, - {64661, 34501379}, {64662, 34500099}, {64663, 34458883}, {64664, 34459395}, - {64665, 34517251}, {64666, 34459907}, {64667, 34517763}, {64668, 34460931}, - {64669, 34461443}, {64670, 34461955}, {64671, 34462467}, {64672, 34518275}, - {64673, 34464003}, {64674, 34464515}, {64675, 34465027}, {64676, 34465539}, - {64677, 34518787}, {64678, 34467587}, {64679, 34469123}, {64680, 34469635}, - {64681, 34469379}, {64682, 34470147}, {64683, 34470659}, {64684, 34471683}, - {64685, 34472195}, {64686, 34472707}, {64687, 34473219}, {64688, 34473731}, - {64689, 34474243}, {64690, 34519299}, {64691, 34474755}, {64692, 34475267}, - {64693, 34475779}, {64694, 34476291}, {64695, 34476803}, {64696, 34477315}, - {64697, 34478339}, {64698, 34478851}, {64699, 34479363}, {64700, 34479875}, - {64701, 34480387}, {64702, 34480899}, {64703, 34481411}, {64704, 34481923}, - {64705, 34482435}, {64706, 34483971}, {64707, 34484483}, {64708, 34486531}, - {64709, 34487043}, {64710, 34487555}, {64711, 34488067}, {64712, 34488579}, - {64713, 34490115}, {64714, 34490627}, {64715, 34491139}, {64716, 34491651}, - {64717, 34519811}, {64718, 34493187}, {64719, 34469891}, {64720, 34470403}, - {64721, 34493699}, {64722, 34495235}, {64723, 34495747}, {64724, 34496259}, - {64725, 34496771}, {64726, 34520323}, {64727, 34498307}, {64728, 34498819}, - {64729, 34520835}, {64730, 34468867}, {64731, 34500355}, {64732, 34500867}, - {64733, 34492931}, {64734, 34498051}, {64735, 34459907}, {64736, 34517763}, - {64737, 34462467}, {64738, 34518275}, {64739, 34465539}, {64740, 34518787}, - {64741, 34467587}, {64742, 34521347}, {64743, 34473731}, {64744, 34521859}, - {64745, 34522371}, {64746, 34522883}, {64747, 34488067}, {64748, 34488579}, - {64749, 34491651}, {64750, 34496771}, {64751, 34520323}, {64752, 34492931}, - {64753, 34498051}, {64754, 51300611}, {64755, 51301379}, {64756, 51302147}, - {64757, 34525699}, {64758, 34526211}, {64759, 34526723}, {64760, 34527235}, - {64761, 34527747}, {64762, 34528259}, {64763, 34528771}, {64764, 34529283}, - {64765, 34529795}, {64766, 34530307}, {64767, 34530819}, {64768, 34500611}, - {64769, 34531331}, {64770, 34531843}, {64771, 34532355}, {64772, 34501123}, - {64773, 34532867}, {64774, 34533379}, {64775, 34533891}, {64776, 34534403}, - {64777, 34534915}, {64778, 34535427}, {64779, 34535939}, {64780, 34522371}, - {64781, 34536451}, {64782, 34536963}, {64783, 34537475}, {64784, 34537987}, - {64785, 34525699}, {64786, 34526211}, {64787, 34526723}, {64788, 34527235}, - {64789, 34527747}, {64790, 34528259}, {64791, 34528771}, {64792, 34529283}, - {64793, 34529795}, {64794, 34530307}, {64795, 34530819}, {64796, 34500611}, - {64797, 34531331}, {64798, 34531843}, {64799, 34532355}, {64800, 34501123}, - {64801, 34532867}, {64802, 34533379}, {64803, 34533891}, {64804, 34534403}, - {64805, 34534915}, {64806, 34535427}, {64807, 34535939}, {64808, 34522371}, - {64809, 34536451}, {64810, 34536963}, {64811, 34537475}, {64812, 34537987}, - {64813, 34534915}, {64814, 34535427}, {64815, 34535939}, {64816, 34522371}, - {64817, 34521859}, {64818, 34522883}, {64819, 34477827}, {64820, 34472195}, - {64821, 34472707}, {64822, 34473219}, {64823, 34534915}, {64824, 34535427}, - {64825, 34535939}, {64826, 34477827}, {64827, 34478339}, {64828, 34538499}, - {64830, 1}, {64848, 51316227}, {64849, 51316995}, {64851, 51317763}, - {64852, 51318531}, {64853, 51319299}, {64854, 51320067}, {64855, 51320835}, - {64856, 51246851}, {64858, 51321603}, {64859, 51322371}, {64860, 51323139}, - {64861, 51323907}, {64862, 51324675}, {64863, 51325443}, {64865, 51326211}, - {64866, 51326979}, {64868, 51327747}, {64870, 51328515}, {64871, 51329283}, - {64873, 51330051}, {64874, 51330819}, {64876, 51331587}, {64878, 51332355}, - {64879, 51333123}, {64881, 51333891}, {64883, 51334659}, {64884, 51335427}, - {64885, 51336195}, {64886, 51336963}, {64888, 51337731}, {64889, 51338499}, - {64890, 51339267}, {64891, 51340035}, {64892, 51340803}, {64894, 51341571}, - {64895, 51342339}, {64896, 51343107}, {64897, 51343875}, {64898, 51344643}, - {64899, 51345411}, {64901, 51346179}, {64903, 51346947}, {64905, 51347715}, - {64906, 51247107}, {64907, 51348483}, {64908, 51349251}, {64909, 51270403}, - {64910, 51247619}, {64911, 51350019}, {64912, 2}, {64914, 51350787}, - {64915, 51351555}, {64916, 51352323}, {64917, 51353091}, {64918, 51353859}, - {64919, 51354627}, {64921, 51355395}, {64922, 51356163}, {64923, 51356931}, - {64924, 51357699}, {64926, 51358467}, {64927, 51359235}, {64928, 51360003}, - {64929, 51360771}, {64930, 51361539}, {64931, 51362307}, {64932, 51363075}, - {64933, 51363843}, {64934, 51364611}, {64935, 51365379}, {64936, 51366147}, - {64937, 51366915}, {64938, 51367683}, {64939, 51368451}, {64940, 51369219}, - {64941, 51369987}, {64942, 51277571}, {64943, 51370755}, {64944, 51371523}, - {64945, 51372291}, {64946, 51373059}, {64947, 51373827}, {64948, 51341571}, - {64949, 51343107}, {64950, 51374595}, {64951, 51375363}, {64952, 51376131}, - {64953, 51376899}, {64954, 51377667}, {64955, 51378435}, {64956, 51377667}, - {64957, 51376131}, {64958, 51379203}, {64959, 51379971}, {64960, 51380739}, - {64961, 51381507}, {64962, 51382275}, {64963, 51378435}, {64964, 51336195}, - {64965, 51328515}, {64966, 51383043}, {64967, 51383811}, {64968, 2}, - {64975, 1}, {64976, 2}, {65008, 51384579}, {65009, 51385347}, - {65010, 68163331}, {65011, 68164355}, {65012, 68165379}, {65013, 68166403}, - {65014, 68167427}, {65015, 68168451}, {65016, 68169475}, {65017, 51393283}, - {65018, 303052291}, {65019, 135284739}, {65020, 68177923}, {65021, 1}, - {65024, 0}, {65040, 17847299}, {65041, 17847555}, {65042, 2}, - {65043, 17110531}, {65044, 16848643}, {65045, 17032963}, {65046, 17033987}, - {65047, 17847811}, {65048, 17848067}, {65049, 2}, {65056, 1}, - {65072, 2}, {65073, 17848323}, {65074, 17848579}, {65075, 17848835}, - {65077, 17037827}, {65078, 17038083}, {65079, 17849091}, {65080, 17849347}, - {65081, 17849603}, {65082, 17849859}, {65083, 17850115}, {65084, 17850371}, - {65085, 17850627}, {65086, 17850883}, {65087, 17067267}, {65088, 17067523}, - {65089, 17851139}, {65090, 17851395}, {65091, 17851651}, {65092, 17851907}, - {65093, 1}, {65095, 17852163}, {65096, 17852419}, {65097, 33810691}, - {65101, 17848835}, {65104, 17847299}, {65105, 17847555}, {65106, 2}, - {65108, 16848643}, {65109, 17110531}, {65110, 17033987}, {65111, 17032963}, - {65112, 17848323}, {65113, 17037827}, {65114, 17038083}, {65115, 17849091}, - {65116, 17849347}, {65117, 17849603}, {65118, 17849859}, {65119, 17852675}, - {65120, 17852931}, {65121, 17853187}, {65122, 17037059}, {65123, 17853443}, - {65124, 17853699}, {65125, 17853955}, {65126, 17037571}, {65127, 2}, - {65128, 17854211}, {65129, 17854467}, {65130, 17854723}, {65131, 17854979}, - {65132, 2}, {65136, 34632451}, {65137, 34632963}, {65138, 34503427}, - {65139, 1}, {65140, 34504195}, {65141, 2}, {65142, 34504963}, - {65143, 34523395}, {65144, 34505731}, {65145, 34524163}, {65146, 34506499}, - {65147, 34524931}, {65148, 34507267}, {65149, 34633475}, {65150, 34633987}, - {65151, 34634499}, {65152, 17857795}, {65153, 17858051}, {65155, 17858307}, - {65157, 17858563}, {65159, 17858819}, {65161, 17677315}, {65165, 16910339}, - {65167, 17683715}, {65171, 17859075}, {65173, 17686787}, {65177, 17689859}, - {65181, 17681923}, {65185, 17682435}, {65189, 17684995}, {65193, 17834499}, - {65195, 17724675}, {65197, 17725187}, {65199, 17731587}, {65201, 17694979}, - {65205, 17745155}, {65209, 17697027}, {65213, 17698051}, {65217, 17700099}, - {65221, 17701123}, {65225, 17701635}, {65229, 17702659}, {65233, 17703683}, - {65237, 17706755}, {65241, 17708803}, {65245, 17711107}, {65249, 17682947}, - {65253, 17718019}, {65257, 17721091}, {65261, 16910851}, {65263, 17677059}, - {65265, 16911875}, {65269, 34636547}, {65271, 34637059}, {65273, 34637571}, - {65275, 34622467}, {65277, 2}, {65279, 0}, {65280, 2}, - {65281, 17032963}, {65282, 17860867}, {65283, 17852675}, {65284, 17854467}, - {65285, 17854723}, {65286, 17852931}, {65287, 17861123}, {65288, 17037827}, - {65289, 17038083}, {65290, 17853187}, {65291, 17037059}, {65292, 17847299}, - {65293, 17853443}, {65294, 17196547}, {65295, 17038595}, {65296, 17035523}, - {65297, 16786947}, {65298, 16785155}, {65299, 16785411}, {65300, 16787715}, - {65301, 17035779}, {65302, 17036035}, {65303, 17036291}, {65304, 17036547}, - {65305, 17036803}, {65306, 17110531}, {65307, 16848643}, {65308, 17853699}, - {65309, 17037571}, {65310, 17853955}, {65311, 17033987}, {65312, 17854979}, - {65313, 16777219}, {65314, 16777475}, {65315, 16777731}, {65316, 16777987}, - {65317, 16778243}, {65318, 16778499}, {65319, 16778755}, {65320, 16779011}, - {65321, 16779267}, {65322, 16779523}, {65323, 16779779}, {65324, 16780035}, - {65325, 16780291}, {65326, 16780547}, {65327, 16780803}, {65328, 16781059}, - {65329, 16781315}, {65330, 16781571}, {65331, 16781827}, {65332, 16782083}, - {65333, 16782339}, {65334, 16782595}, {65335, 16782851}, {65336, 16783107}, - {65337, 16783363}, {65338, 16783619}, {65339, 17852163}, {65340, 17854211}, - {65341, 17852419}, {65342, 17861379}, {65343, 17848835}, {65344, 17027075}, - {65345, 16777219}, {65346, 16777475}, {65347, 16777731}, {65348, 16777987}, - {65349, 16778243}, {65350, 16778499}, {65351, 16778755}, {65352, 16779011}, - {65353, 16779267}, {65354, 16779523}, {65355, 16779779}, {65356, 16780035}, - {65357, 16780291}, {65358, 16780547}, {65359, 16780803}, {65360, 16781059}, - {65361, 16781315}, {65362, 16781571}, {65363, 16781827}, {65364, 16782083}, - {65365, 16782339}, {65366, 16782595}, {65367, 16782851}, {65368, 16783107}, - {65369, 16783363}, {65370, 16783619}, {65371, 17849091}, {65372, 17861635}, - {65373, 17849347}, {65374, 17861891}, {65375, 17862147}, {65376, 17862403}, - {65377, 17196547}, {65378, 17851139}, {65379, 17851395}, {65380, 17847555}, - {65381, 17862659}, {65382, 17316867}, {65383, 17319427}, {65384, 17362435}, - {65385, 17862915}, {65386, 17363971}, {65387, 17323523}, {65388, 17863171}, - {65389, 17333763}, {65390, 17379587}, {65391, 17329155}, {65392, 17318147}, - {65393, 17305603}, {65394, 17305859}, {65395, 17306115}, {65396, 17306371}, - {65397, 17306627}, {65398, 17306883}, {65399, 17307139}, {65400, 17307395}, - {65401, 17307651}, {65402, 17199107}, {65403, 17307907}, {65404, 17308163}, - {65405, 17308419}, {65406, 17308675}, {65407, 17308931}, {65408, 17309187}, - {65409, 17309443}, {65410, 17309699}, {65411, 17309955}, {65412, 17199363}, - {65413, 17310211}, {65414, 17310467}, {65415, 17310723}, {65416, 17310979}, - {65417, 17311235}, {65418, 17311491}, {65419, 17311747}, {65420, 17312003}, - {65421, 17312259}, {65422, 17312515}, {65423, 17312771}, {65424, 17313027}, - {65425, 17313283}, {65426, 17313539}, {65427, 17313795}, {65428, 17314051}, - {65429, 17314307}, {65430, 17314563}, {65431, 17314819}, {65432, 17315075}, - {65433, 17315331}, {65434, 17315587}, {65435, 17315843}, {65436, 17316099}, - {65437, 17319939}, {65438, 17197827}, {65439, 17198339}, {65440, 2}, - {65441, 17199619}, {65442, 17199875}, {65443, 17200131}, {65444, 17200387}, - {65445, 17200643}, {65446, 17200899}, {65447, 17201155}, {65448, 17201411}, - {65449, 17201667}, {65450, 17201923}, {65451, 17202179}, {65452, 17202435}, - {65453, 17202691}, {65454, 17202947}, {65455, 17203203}, {65456, 17203459}, - {65457, 17203715}, {65458, 17203971}, {65459, 17204227}, {65460, 17204483}, - {65461, 17204739}, {65462, 17204995}, {65463, 17205251}, {65464, 17205507}, - {65465, 17205763}, {65466, 17206019}, {65467, 17206275}, {65468, 17206531}, - {65469, 17206787}, {65470, 17207043}, {65471, 2}, {65474, 17207299}, - {65475, 17207555}, {65476, 17207811}, {65477, 17208067}, {65478, 17208323}, - {65479, 17208579}, {65480, 2}, {65482, 17208835}, {65483, 17209091}, - {65484, 17209347}, {65485, 17209603}, {65486, 17209859}, {65487, 17210115}, - {65488, 2}, {65490, 17210371}, {65491, 17210627}, {65492, 17210883}, - {65493, 17211139}, {65494, 17211395}, {65495, 17211651}, {65496, 2}, - {65498, 17211907}, {65499, 17212163}, {65500, 17212419}, {65501, 2}, - {65504, 17863427}, {65505, 17863683}, {65506, 17863939}, {65507, 33561859}, - {65508, 17864195}, {65509, 17864451}, {65510, 17864707}, {65511, 2}, - {65512, 17864963}, {65513, 17865219}, {65514, 17865475}, {65515, 17865731}, - {65516, 17865987}, {65517, 17866243}, {65518, 17866499}, {65519, 2}, - {65536, 1}, {65548, 2}, {65549, 1}, {65575, 2}, - {65576, 1}, {65595, 2}, {65596, 1}, {65598, 2}, - {65599, 1}, {65614, 2}, {65616, 1}, {65630, 2}, - {65664, 1}, {65787, 2}, {65792, 1}, {65795, 2}, - {65799, 1}, {65844, 2}, {65847, 1}, {65935, 2}, - {65936, 1}, {65949, 2}, {65952, 1}, {65953, 2}, - {66000, 1}, {66046, 2}, {66176, 1}, {66205, 2}, - {66208, 1}, {66257, 2}, {66272, 1}, {66300, 2}, - {66304, 1}, {66340, 2}, {66349, 1}, {66379, 2}, - {66384, 1}, {66427, 2}, {66432, 1}, {66462, 2}, - {66463, 1}, {66500, 2}, {66504, 1}, {66518, 2}, - {66560, 17866755}, {66561, 17867011}, {66562, 17867267}, {66563, 17867523}, - {66564, 17867779}, {66565, 17868035}, {66566, 17868291}, {66567, 17868547}, - {66568, 17868803}, {66569, 17869059}, {66570, 17869315}, {66571, 17869571}, - {66572, 17869827}, {66573, 17870083}, {66574, 17870339}, {66575, 17870595}, - {66576, 17870851}, {66577, 17871107}, {66578, 17871363}, {66579, 17871619}, - {66580, 17871875}, {66581, 17872131}, {66582, 17872387}, {66583, 17872643}, - {66584, 17872899}, {66585, 17873155}, {66586, 17873411}, {66587, 17873667}, - {66588, 17873923}, {66589, 17874179}, {66590, 17874435}, {66591, 17874691}, - {66592, 17874947}, {66593, 17875203}, {66594, 17875459}, {66595, 17875715}, - {66596, 17875971}, {66597, 17876227}, {66598, 17876483}, {66599, 17876739}, - {66600, 1}, {66718, 2}, {66720, 1}, {66730, 2}, - {66736, 17876995}, {66737, 17877251}, {66738, 17877507}, {66739, 17877763}, - {66740, 17878019}, {66741, 17878275}, {66742, 17878531}, {66743, 17878787}, - {66744, 17879043}, {66745, 17879299}, {66746, 17879555}, {66747, 17879811}, - {66748, 17880067}, {66749, 17880323}, {66750, 17880579}, {66751, 17880835}, - {66752, 17881091}, {66753, 17881347}, {66754, 17881603}, {66755, 17881859}, - {66756, 17882115}, {66757, 17882371}, {66758, 17882627}, {66759, 17882883}, - {66760, 17883139}, {66761, 17883395}, {66762, 17883651}, {66763, 17883907}, - {66764, 17884163}, {66765, 17884419}, {66766, 17884675}, {66767, 17884931}, - {66768, 17885187}, {66769, 17885443}, {66770, 17885699}, {66771, 17885955}, - {66772, 2}, {66776, 1}, {66812, 2}, {66816, 1}, - {66856, 2}, {66864, 1}, {66916, 2}, {66927, 1}, - {66928, 17886211}, {66929, 17886467}, {66930, 17886723}, {66931, 17886979}, - {66932, 17887235}, {66933, 17887491}, {66934, 17887747}, {66935, 17888003}, - {66936, 17888259}, {66937, 17888515}, {66938, 17888771}, {66939, 2}, - {66940, 17889027}, {66941, 17889283}, {66942, 17889539}, {66943, 17889795}, - {66944, 17890051}, {66945, 17890307}, {66946, 17890563}, {66947, 17890819}, - {66948, 17891075}, {66949, 17891331}, {66950, 17891587}, {66951, 17891843}, - {66952, 17892099}, {66953, 17892355}, {66954, 17892611}, {66955, 2}, - {66956, 17892867}, {66957, 17893123}, {66958, 17893379}, {66959, 17893635}, - {66960, 17893891}, {66961, 17894147}, {66962, 17894403}, {66963, 2}, - {66964, 17894659}, {66965, 17894915}, {66966, 2}, {66967, 1}, - {66978, 2}, {66979, 1}, {66994, 2}, {66995, 1}, - {67002, 2}, {67003, 1}, {67005, 2}, {67072, 1}, - {67383, 2}, {67392, 1}, {67414, 2}, {67424, 1}, - {67432, 2}, {67456, 1}, {67457, 17895171}, {67458, 17895427}, - {67459, 16791043}, {67460, 17895683}, {67461, 16814083}, {67462, 2}, - {67463, 17895939}, {67464, 17896195}, {67465, 17896451}, {67466, 17896707}, - {67467, 16815363}, {67468, 16815619}, {67469, 17896963}, {67470, 17897219}, - {67471, 17897475}, {67472, 17897731}, {67473, 17897987}, {67474, 17898243}, - {67475, 16817155}, {67476, 17898499}, {67477, 16802051}, {67478, 17898755}, - {67479, 17899011}, {67480, 17899267}, {67481, 17899523}, {67482, 17899779}, - {67483, 17512963}, {67484, 17900035}, {67485, 17900291}, {67486, 17900547}, - {67487, 17900803}, {67488, 17901059}, {67489, 17901315}, {67490, 16795395}, - {67491, 17901571}, {67492, 17901827}, {67493, 16781315}, {67494, 17902083}, - {67495, 17902339}, {67496, 17125379}, {67497, 17902595}, {67498, 16819971}, - {67499, 17902851}, {67500, 17903107}, {67501, 17903363}, {67502, 17903619}, - {67503, 16820995}, {67504, 17903875}, {67505, 2}, {67506, 17904131}, - {67507, 17904387}, {67508, 17904643}, {67509, 17904899}, {67510, 17905155}, - {67511, 17905411}, {67512, 17905667}, {67513, 17905923}, {67514, 17906179}, - {67515, 2}, {67584, 1}, {67590, 2}, {67592, 1}, - {67593, 2}, {67594, 1}, {67638, 2}, {67639, 1}, - {67641, 2}, {67644, 1}, {67645, 2}, {67647, 1}, - {67670, 2}, {67671, 1}, {67743, 2}, {67751, 1}, - {67760, 2}, {67808, 1}, {67827, 2}, {67828, 1}, - {67830, 2}, {67835, 1}, {67868, 2}, {67871, 1}, - {67898, 2}, {67903, 1}, {67904, 2}, {67968, 1}, - {68024, 2}, {68028, 1}, {68048, 2}, {68050, 1}, - {68100, 2}, {68101, 1}, {68103, 2}, {68108, 1}, - {68116, 2}, {68117, 1}, {68120, 2}, {68121, 1}, - {68150, 2}, {68152, 1}, {68155, 2}, {68159, 1}, - {68169, 2}, {68176, 1}, {68185, 2}, {68192, 1}, - {68256, 2}, {68288, 1}, {68327, 2}, {68331, 1}, - {68343, 2}, {68352, 1}, {68406, 2}, {68409, 1}, - {68438, 2}, {68440, 1}, {68467, 2}, {68472, 1}, - {68498, 2}, {68505, 1}, {68509, 2}, {68521, 1}, - {68528, 2}, {68608, 1}, {68681, 2}, {68736, 17906435}, - {68737, 17906691}, {68738, 17906947}, {68739, 17907203}, {68740, 17907459}, - {68741, 17907715}, {68742, 17907971}, {68743, 17908227}, {68744, 17908483}, - {68745, 17908739}, {68746, 17908995}, {68747, 17909251}, {68748, 17909507}, - {68749, 17909763}, {68750, 17910019}, {68751, 17910275}, {68752, 17910531}, - {68753, 17910787}, {68754, 17911043}, {68755, 17911299}, {68756, 17911555}, - {68757, 17911811}, {68758, 17912067}, {68759, 17912323}, {68760, 17912579}, - {68761, 17912835}, {68762, 17913091}, {68763, 17913347}, {68764, 17913603}, - {68765, 17913859}, {68766, 17914115}, {68767, 17914371}, {68768, 17914627}, - {68769, 17914883}, {68770, 17915139}, {68771, 17915395}, {68772, 17915651}, - {68773, 17915907}, {68774, 17916163}, {68775, 17916419}, {68776, 17916675}, - {68777, 17916931}, {68778, 17917187}, {68779, 17917443}, {68780, 17917699}, - {68781, 17917955}, {68782, 17918211}, {68783, 17918467}, {68784, 17918723}, - {68785, 17918979}, {68786, 17919235}, {68787, 2}, {68800, 1}, - {68851, 2}, {68858, 1}, {68904, 2}, {68912, 1}, - {68922, 2}, {69216, 1}, {69247, 2}, {69248, 1}, - {69290, 2}, {69291, 1}, {69294, 2}, {69296, 1}, - {69298, 2}, {69373, 1}, {69416, 2}, {69424, 1}, - {69466, 2}, {69488, 1}, {69514, 2}, {69552, 1}, - {69580, 2}, {69600, 1}, {69623, 2}, {69632, 1}, - {69710, 2}, {69714, 1}, {69750, 2}, {69759, 1}, - {69821, 2}, {69822, 1}, {69827, 2}, {69840, 1}, - {69865, 2}, {69872, 1}, {69882, 2}, {69888, 1}, - {69941, 2}, {69942, 1}, {69960, 2}, {69968, 1}, - {70007, 2}, {70016, 1}, {70112, 2}, {70113, 1}, - {70133, 2}, {70144, 1}, {70162, 2}, {70163, 1}, - {70210, 2}, {70272, 1}, {70279, 2}, {70280, 1}, - {70281, 2}, {70282, 1}, {70286, 2}, {70287, 1}, - {70302, 2}, {70303, 1}, {70314, 2}, {70320, 1}, - {70379, 2}, {70384, 1}, {70394, 2}, {70400, 1}, - {70404, 2}, {70405, 1}, {70413, 2}, {70415, 1}, - {70417, 2}, {70419, 1}, {70441, 2}, {70442, 1}, - {70449, 2}, {70450, 1}, {70452, 2}, {70453, 1}, - {70458, 2}, {70459, 1}, {70469, 2}, {70471, 1}, - {70473, 2}, {70475, 1}, {70478, 2}, {70480, 1}, - {70481, 2}, {70487, 1}, {70488, 2}, {70493, 1}, - {70500, 2}, {70502, 1}, {70509, 2}, {70512, 1}, - {70517, 2}, {70656, 1}, {70748, 2}, {70749, 1}, - {70754, 2}, {70784, 1}, {70856, 2}, {70864, 1}, - {70874, 2}, {71040, 1}, {71094, 2}, {71096, 1}, - {71134, 2}, {71168, 1}, {71237, 2}, {71248, 1}, - {71258, 2}, {71264, 1}, {71277, 2}, {71296, 1}, - {71354, 2}, {71360, 1}, {71370, 2}, {71424, 1}, - {71451, 2}, {71453, 1}, {71468, 2}, {71472, 1}, - {71495, 2}, {71680, 1}, {71740, 2}, {71840, 17919491}, - {71841, 17919747}, {71842, 17920003}, {71843, 17920259}, {71844, 17920515}, - {71845, 17920771}, {71846, 17921027}, {71847, 17921283}, {71848, 17921539}, - {71849, 17921795}, {71850, 17922051}, {71851, 17922307}, {71852, 17922563}, - {71853, 17922819}, {71854, 17923075}, {71855, 17923331}, {71856, 17923587}, - {71857, 17923843}, {71858, 17924099}, {71859, 17924355}, {71860, 17924611}, - {71861, 17924867}, {71862, 17925123}, {71863, 17925379}, {71864, 17925635}, - {71865, 17925891}, {71866, 17926147}, {71867, 17926403}, {71868, 17926659}, - {71869, 17926915}, {71870, 17927171}, {71871, 17927427}, {71872, 1}, - {71923, 2}, {71935, 1}, {71943, 2}, {71945, 1}, - {71946, 2}, {71948, 1}, {71956, 2}, {71957, 1}, - {71959, 2}, {71960, 1}, {71990, 2}, {71991, 1}, - {71993, 2}, {71995, 1}, {72007, 2}, {72016, 1}, - {72026, 2}, {72096, 1}, {72104, 2}, {72106, 1}, - {72152, 2}, {72154, 1}, {72165, 2}, {72192, 1}, - {72264, 2}, {72272, 1}, {72355, 2}, {72368, 1}, - {72441, 2}, {72448, 1}, {72458, 2}, {72704, 1}, - {72713, 2}, {72714, 1}, {72759, 2}, {72760, 1}, - {72774, 2}, {72784, 1}, {72813, 2}, {72816, 1}, - {72848, 2}, {72850, 1}, {72872, 2}, {72873, 1}, - {72887, 2}, {72960, 1}, {72967, 2}, {72968, 1}, - {72970, 2}, {72971, 1}, {73015, 2}, {73018, 1}, - {73019, 2}, {73020, 1}, {73022, 2}, {73023, 1}, - {73032, 2}, {73040, 1}, {73050, 2}, {73056, 1}, - {73062, 2}, {73063, 1}, {73065, 2}, {73066, 1}, - {73103, 2}, {73104, 1}, {73106, 2}, {73107, 1}, - {73113, 2}, {73120, 1}, {73130, 2}, {73440, 1}, - {73465, 2}, {73472, 1}, {73489, 2}, {73490, 1}, - {73531, 2}, {73534, 1}, {73562, 2}, {73648, 1}, - {73649, 2}, {73664, 1}, {73714, 2}, {73727, 1}, - {74650, 2}, {74752, 1}, {74863, 2}, {74864, 1}, - {74869, 2}, {74880, 1}, {75076, 2}, {77712, 1}, - {77811, 2}, {77824, 1}, {78896, 2}, {78912, 1}, - {78934, 2}, {82944, 1}, {83527, 2}, {92160, 1}, - {92729, 2}, {92736, 1}, {92767, 2}, {92768, 1}, - {92778, 2}, {92782, 1}, {92863, 2}, {92864, 1}, - {92874, 2}, {92880, 1}, {92910, 2}, {92912, 1}, - {92918, 2}, {92928, 1}, {92998, 2}, {93008, 1}, - {93018, 2}, {93019, 1}, {93026, 2}, {93027, 1}, - {93048, 2}, {93053, 1}, {93072, 2}, {93760, 17927683}, - {93761, 17927939}, {93762, 17928195}, {93763, 17928451}, {93764, 17928707}, - {93765, 17928963}, {93766, 17929219}, {93767, 17929475}, {93768, 17929731}, - {93769, 17929987}, {93770, 17930243}, {93771, 17930499}, {93772, 17930755}, - {93773, 17931011}, {93774, 17931267}, {93775, 17931523}, {93776, 17931779}, - {93777, 17932035}, {93778, 17932291}, {93779, 17932547}, {93780, 17932803}, - {93781, 17933059}, {93782, 17933315}, {93783, 17933571}, {93784, 17933827}, - {93785, 17934083}, {93786, 17934339}, {93787, 17934595}, {93788, 17934851}, - {93789, 17935107}, {93790, 17935363}, {93791, 17935619}, {93792, 1}, - {93851, 2}, {93952, 1}, {94027, 2}, {94031, 1}, - {94088, 2}, {94095, 1}, {94112, 2}, {94176, 1}, - {94181, 2}, {94192, 1}, {94194, 2}, {94208, 1}, - {100344, 2}, {100352, 1}, {101590, 2}, {101632, 1}, - {101641, 2}, {110576, 1}, {110580, 2}, {110581, 1}, - {110588, 2}, {110589, 1}, {110591, 2}, {110592, 1}, - {110883, 2}, {110898, 1}, {110899, 2}, {110928, 1}, - {110931, 2}, {110933, 1}, {110934, 2}, {110948, 1}, - {110952, 2}, {110960, 1}, {111356, 2}, {113664, 1}, - {113771, 2}, {113776, 1}, {113789, 2}, {113792, 1}, - {113801, 2}, {113808, 1}, {113818, 2}, {113820, 1}, - {113824, 0}, {113828, 2}, {118528, 1}, {118574, 2}, - {118576, 1}, {118599, 2}, {118608, 1}, {118724, 2}, - {118784, 1}, {119030, 2}, {119040, 1}, {119079, 2}, - {119081, 1}, {119134, 34713091}, {119135, 34713603}, {119136, 51491331}, - {119137, 51492099}, {119138, 51492867}, {119139, 51493635}, {119140, 51494403}, - {119141, 1}, {119155, 2}, {119163, 1}, {119227, 34717955}, - {119228, 34718467}, {119229, 51496195}, {119230, 51496963}, {119231, 51497731}, - {119232, 51498499}, {119233, 1}, {119275, 2}, {119296, 1}, - {119366, 2}, {119488, 1}, {119508, 2}, {119520, 1}, - {119540, 2}, {119552, 1}, {119639, 2}, {119648, 1}, - {119673, 2}, {119808, 16777219}, {119809, 16777475}, {119810, 16777731}, - {119811, 16777987}, {119812, 16778243}, {119813, 16778499}, {119814, 16778755}, - {119815, 16779011}, {119816, 16779267}, {119817, 16779523}, {119818, 16779779}, - {119819, 16780035}, {119820, 16780291}, {119821, 16780547}, {119822, 16780803}, - {119823, 16781059}, {119824, 16781315}, {119825, 16781571}, {119826, 16781827}, - {119827, 16782083}, {119828, 16782339}, {119829, 16782595}, {119830, 16782851}, - {119831, 16783107}, {119832, 16783363}, {119833, 16783619}, {119834, 16777219}, - {119835, 16777475}, {119836, 16777731}, {119837, 16777987}, {119838, 16778243}, - {119839, 16778499}, {119840, 16778755}, {119841, 16779011}, {119842, 16779267}, - {119843, 16779523}, {119844, 16779779}, {119845, 16780035}, {119846, 16780291}, - {119847, 16780547}, {119848, 16780803}, {119849, 16781059}, {119850, 16781315}, - {119851, 16781571}, {119852, 16781827}, {119853, 16782083}, {119854, 16782339}, - {119855, 16782595}, {119856, 16782851}, {119857, 16783107}, {119858, 16783363}, - {119859, 16783619}, {119860, 16777219}, {119861, 16777475}, {119862, 16777731}, - {119863, 16777987}, {119864, 16778243}, {119865, 16778499}, {119866, 16778755}, - {119867, 16779011}, {119868, 16779267}, {119869, 16779523}, {119870, 16779779}, - {119871, 16780035}, {119872, 16780291}, {119873, 16780547}, {119874, 16780803}, - {119875, 16781059}, {119876, 16781315}, {119877, 16781571}, {119878, 16781827}, - {119879, 16782083}, {119880, 16782339}, {119881, 16782595}, {119882, 16782851}, - {119883, 16783107}, {119884, 16783363}, {119885, 16783619}, {119886, 16777219}, - {119887, 16777475}, {119888, 16777731}, {119889, 16777987}, {119890, 16778243}, - {119891, 16778499}, {119892, 16778755}, {119893, 2}, {119894, 16779267}, - {119895, 16779523}, {119896, 16779779}, {119897, 16780035}, {119898, 16780291}, - {119899, 16780547}, {119900, 16780803}, {119901, 16781059}, {119902, 16781315}, - {119903, 16781571}, {119904, 16781827}, {119905, 16782083}, {119906, 16782339}, - {119907, 16782595}, {119908, 16782851}, {119909, 16783107}, {119910, 16783363}, - {119911, 16783619}, {119912, 16777219}, {119913, 16777475}, {119914, 16777731}, - {119915, 16777987}, {119916, 16778243}, {119917, 16778499}, {119918, 16778755}, - {119919, 16779011}, {119920, 16779267}, {119921, 16779523}, {119922, 16779779}, - {119923, 16780035}, {119924, 16780291}, {119925, 16780547}, {119926, 16780803}, - {119927, 16781059}, {119928, 16781315}, {119929, 16781571}, {119930, 16781827}, - {119931, 16782083}, {119932, 16782339}, {119933, 16782595}, {119934, 16782851}, - {119935, 16783107}, {119936, 16783363}, {119937, 16783619}, {119938, 16777219}, - {119939, 16777475}, {119940, 16777731}, {119941, 16777987}, {119942, 16778243}, - {119943, 16778499}, {119944, 16778755}, {119945, 16779011}, {119946, 16779267}, - {119947, 16779523}, {119948, 16779779}, {119949, 16780035}, {119950, 16780291}, - {119951, 16780547}, {119952, 16780803}, {119953, 16781059}, {119954, 16781315}, - {119955, 16781571}, {119956, 16781827}, {119957, 16782083}, {119958, 16782339}, - {119959, 16782595}, {119960, 16782851}, {119961, 16783107}, {119962, 16783363}, - {119963, 16783619}, {119964, 16777219}, {119965, 2}, {119966, 16777731}, - {119967, 16777987}, {119968, 2}, {119970, 16778755}, {119971, 2}, - {119973, 16779523}, {119974, 16779779}, {119975, 2}, {119977, 16780547}, - {119978, 16780803}, {119979, 16781059}, {119980, 16781315}, {119981, 2}, - {119982, 16781827}, {119983, 16782083}, {119984, 16782339}, {119985, 16782595}, - {119986, 16782851}, {119987, 16783107}, {119988, 16783363}, {119989, 16783619}, - {119990, 16777219}, {119991, 16777475}, {119992, 16777731}, {119993, 16777987}, - {119994, 2}, {119995, 16778499}, {119996, 2}, {119997, 16779011}, - {119998, 16779267}, {119999, 16779523}, {120000, 16779779}, {120001, 16780035}, - {120002, 16780291}, {120003, 16780547}, {120004, 2}, {120005, 16781059}, - {120006, 16781315}, {120007, 16781571}, {120008, 16781827}, {120009, 16782083}, - {120010, 16782339}, {120011, 16782595}, {120012, 16782851}, {120013, 16783107}, - {120014, 16783363}, {120015, 16783619}, {120016, 16777219}, {120017, 16777475}, - {120018, 16777731}, {120019, 16777987}, {120020, 16778243}, {120021, 16778499}, - {120022, 16778755}, {120023, 16779011}, {120024, 16779267}, {120025, 16779523}, - {120026, 16779779}, {120027, 16780035}, {120028, 16780291}, {120029, 16780547}, - {120030, 16780803}, {120031, 16781059}, {120032, 16781315}, {120033, 16781571}, - {120034, 16781827}, {120035, 16782083}, {120036, 16782339}, {120037, 16782595}, - {120038, 16782851}, {120039, 16783107}, {120040, 16783363}, {120041, 16783619}, - {120042, 16777219}, {120043, 16777475}, {120044, 16777731}, {120045, 16777987}, - {120046, 16778243}, {120047, 16778499}, {120048, 16778755}, {120049, 16779011}, - {120050, 16779267}, {120051, 16779523}, {120052, 16779779}, {120053, 16780035}, - {120054, 16780291}, {120055, 16780547}, {120056, 16780803}, {120057, 16781059}, - {120058, 16781315}, {120059, 16781571}, {120060, 16781827}, {120061, 16782083}, - {120062, 16782339}, {120063, 16782595}, {120064, 16782851}, {120065, 16783107}, - {120066, 16783363}, {120067, 16783619}, {120068, 16777219}, {120069, 16777475}, - {120070, 2}, {120071, 16777987}, {120072, 16778243}, {120073, 16778499}, - {120074, 16778755}, {120075, 2}, {120077, 16779523}, {120078, 16779779}, - {120079, 16780035}, {120080, 16780291}, {120081, 16780547}, {120082, 16780803}, - {120083, 16781059}, {120084, 16781315}, {120085, 2}, {120086, 16781827}, - {120087, 16782083}, {120088, 16782339}, {120089, 16782595}, {120090, 16782851}, - {120091, 16783107}, {120092, 16783363}, {120093, 2}, {120094, 16777219}, - {120095, 16777475}, {120096, 16777731}, {120097, 16777987}, {120098, 16778243}, - {120099, 16778499}, {120100, 16778755}, {120101, 16779011}, {120102, 16779267}, - {120103, 16779523}, {120104, 16779779}, {120105, 16780035}, {120106, 16780291}, - {120107, 16780547}, {120108, 16780803}, {120109, 16781059}, {120110, 16781315}, - {120111, 16781571}, {120112, 16781827}, {120113, 16782083}, {120114, 16782339}, - {120115, 16782595}, {120116, 16782851}, {120117, 16783107}, {120118, 16783363}, - {120119, 16783619}, {120120, 16777219}, {120121, 16777475}, {120122, 2}, - {120123, 16777987}, {120124, 16778243}, {120125, 16778499}, {120126, 16778755}, - {120127, 2}, {120128, 16779267}, {120129, 16779523}, {120130, 16779779}, - {120131, 16780035}, {120132, 16780291}, {120133, 2}, {120134, 16780803}, - {120135, 2}, {120138, 16781827}, {120139, 16782083}, {120140, 16782339}, - {120141, 16782595}, {120142, 16782851}, {120143, 16783107}, {120144, 16783363}, - {120145, 2}, {120146, 16777219}, {120147, 16777475}, {120148, 16777731}, - {120149, 16777987}, {120150, 16778243}, {120151, 16778499}, {120152, 16778755}, - {120153, 16779011}, {120154, 16779267}, {120155, 16779523}, {120156, 16779779}, - {120157, 16780035}, {120158, 16780291}, {120159, 16780547}, {120160, 16780803}, - {120161, 16781059}, {120162, 16781315}, {120163, 16781571}, {120164, 16781827}, - {120165, 16782083}, {120166, 16782339}, {120167, 16782595}, {120168, 16782851}, - {120169, 16783107}, {120170, 16783363}, {120171, 16783619}, {120172, 16777219}, - {120173, 16777475}, {120174, 16777731}, {120175, 16777987}, {120176, 16778243}, - {120177, 16778499}, {120178, 16778755}, {120179, 16779011}, {120180, 16779267}, - {120181, 16779523}, {120182, 16779779}, {120183, 16780035}, {120184, 16780291}, - {120185, 16780547}, {120186, 16780803}, {120187, 16781059}, {120188, 16781315}, - {120189, 16781571}, {120190, 16781827}, {120191, 16782083}, {120192, 16782339}, - {120193, 16782595}, {120194, 16782851}, {120195, 16783107}, {120196, 16783363}, - {120197, 16783619}, {120198, 16777219}, {120199, 16777475}, {120200, 16777731}, - {120201, 16777987}, {120202, 16778243}, {120203, 16778499}, {120204, 16778755}, - {120205, 16779011}, {120206, 16779267}, {120207, 16779523}, {120208, 16779779}, - {120209, 16780035}, {120210, 16780291}, {120211, 16780547}, {120212, 16780803}, - {120213, 16781059}, {120214, 16781315}, {120215, 16781571}, {120216, 16781827}, - {120217, 16782083}, {120218, 16782339}, {120219, 16782595}, {120220, 16782851}, - {120221, 16783107}, {120222, 16783363}, {120223, 16783619}, {120224, 16777219}, - {120225, 16777475}, {120226, 16777731}, {120227, 16777987}, {120228, 16778243}, - {120229, 16778499}, {120230, 16778755}, {120231, 16779011}, {120232, 16779267}, - {120233, 16779523}, {120234, 16779779}, {120235, 16780035}, {120236, 16780291}, - {120237, 16780547}, {120238, 16780803}, {120239, 16781059}, {120240, 16781315}, - {120241, 16781571}, {120242, 16781827}, {120243, 16782083}, {120244, 16782339}, - {120245, 16782595}, {120246, 16782851}, {120247, 16783107}, {120248, 16783363}, - {120249, 16783619}, {120250, 16777219}, {120251, 16777475}, {120252, 16777731}, - {120253, 16777987}, {120254, 16778243}, {120255, 16778499}, {120256, 16778755}, - {120257, 16779011}, {120258, 16779267}, {120259, 16779523}, {120260, 16779779}, - {120261, 16780035}, {120262, 16780291}, {120263, 16780547}, {120264, 16780803}, - {120265, 16781059}, {120266, 16781315}, {120267, 16781571}, {120268, 16781827}, - {120269, 16782083}, {120270, 16782339}, {120271, 16782595}, {120272, 16782851}, - {120273, 16783107}, {120274, 16783363}, {120275, 16783619}, {120276, 16777219}, - {120277, 16777475}, {120278, 16777731}, {120279, 16777987}, {120280, 16778243}, - {120281, 16778499}, {120282, 16778755}, {120283, 16779011}, {120284, 16779267}, - {120285, 16779523}, {120286, 16779779}, {120287, 16780035}, {120288, 16780291}, - {120289, 16780547}, {120290, 16780803}, {120291, 16781059}, {120292, 16781315}, - {120293, 16781571}, {120294, 16781827}, {120295, 16782083}, {120296, 16782339}, - {120297, 16782595}, {120298, 16782851}, {120299, 16783107}, {120300, 16783363}, - {120301, 16783619}, {120302, 16777219}, {120303, 16777475}, {120304, 16777731}, - {120305, 16777987}, {120306, 16778243}, {120307, 16778499}, {120308, 16778755}, - {120309, 16779011}, {120310, 16779267}, {120311, 16779523}, {120312, 16779779}, - {120313, 16780035}, {120314, 16780291}, {120315, 16780547}, {120316, 16780803}, - {120317, 16781059}, {120318, 16781315}, {120319, 16781571}, {120320, 16781827}, - {120321, 16782083}, {120322, 16782339}, {120323, 16782595}, {120324, 16782851}, - {120325, 16783107}, {120326, 16783363}, {120327, 16783619}, {120328, 16777219}, - {120329, 16777475}, {120330, 16777731}, {120331, 16777987}, {120332, 16778243}, - {120333, 16778499}, {120334, 16778755}, {120335, 16779011}, {120336, 16779267}, - {120337, 16779523}, {120338, 16779779}, {120339, 16780035}, {120340, 16780291}, - {120341, 16780547}, {120342, 16780803}, {120343, 16781059}, {120344, 16781315}, - {120345, 16781571}, {120346, 16781827}, {120347, 16782083}, {120348, 16782339}, - {120349, 16782595}, {120350, 16782851}, {120351, 16783107}, {120352, 16783363}, - {120353, 16783619}, {120354, 16777219}, {120355, 16777475}, {120356, 16777731}, - {120357, 16777987}, {120358, 16778243}, {120359, 16778499}, {120360, 16778755}, - {120361, 16779011}, {120362, 16779267}, {120363, 16779523}, {120364, 16779779}, - {120365, 16780035}, {120366, 16780291}, {120367, 16780547}, {120368, 16780803}, - {120369, 16781059}, {120370, 16781315}, {120371, 16781571}, {120372, 16781827}, - {120373, 16782083}, {120374, 16782339}, {120375, 16782595}, {120376, 16782851}, - {120377, 16783107}, {120378, 16783363}, {120379, 16783619}, {120380, 16777219}, - {120381, 16777475}, {120382, 16777731}, {120383, 16777987}, {120384, 16778243}, - {120385, 16778499}, {120386, 16778755}, {120387, 16779011}, {120388, 16779267}, - {120389, 16779523}, {120390, 16779779}, {120391, 16780035}, {120392, 16780291}, - {120393, 16780547}, {120394, 16780803}, {120395, 16781059}, {120396, 16781315}, - {120397, 16781571}, {120398, 16781827}, {120399, 16782083}, {120400, 16782339}, - {120401, 16782595}, {120402, 16782851}, {120403, 16783107}, {120404, 16783363}, - {120405, 16783619}, {120406, 16777219}, {120407, 16777475}, {120408, 16777731}, - {120409, 16777987}, {120410, 16778243}, {120411, 16778499}, {120412, 16778755}, - {120413, 16779011}, {120414, 16779267}, {120415, 16779523}, {120416, 16779779}, - {120417, 16780035}, {120418, 16780291}, {120419, 16780547}, {120420, 16780803}, - {120421, 16781059}, {120422, 16781315}, {120423, 16781571}, {120424, 16781827}, - {120425, 16782083}, {120426, 16782339}, {120427, 16782595}, {120428, 16782851}, - {120429, 16783107}, {120430, 16783363}, {120431, 16783619}, {120432, 16777219}, - {120433, 16777475}, {120434, 16777731}, {120435, 16777987}, {120436, 16778243}, - {120437, 16778499}, {120438, 16778755}, {120439, 16779011}, {120440, 16779267}, - {120441, 16779523}, {120442, 16779779}, {120443, 16780035}, {120444, 16780291}, - {120445, 16780547}, {120446, 16780803}, {120447, 16781059}, {120448, 16781315}, - {120449, 16781571}, {120450, 16781827}, {120451, 16782083}, {120452, 16782339}, - {120453, 16782595}, {120454, 16782851}, {120455, 16783107}, {120456, 16783363}, - {120457, 16783619}, {120458, 16777219}, {120459, 16777475}, {120460, 16777731}, - {120461, 16777987}, {120462, 16778243}, {120463, 16778499}, {120464, 16778755}, - {120465, 16779011}, {120466, 16779267}, {120467, 16779523}, {120468, 16779779}, - {120469, 16780035}, {120470, 16780291}, {120471, 16780547}, {120472, 16780803}, - {120473, 16781059}, {120474, 16781315}, {120475, 16781571}, {120476, 16781827}, - {120477, 16782083}, {120478, 16782339}, {120479, 16782595}, {120480, 16782851}, - {120481, 16783107}, {120482, 16783363}, {120483, 16783619}, {120484, 17944835}, - {120485, 17945091}, {120486, 2}, {120488, 16851715}, {120489, 16851971}, - {120490, 16852227}, {120491, 16852483}, {120492, 16852739}, {120493, 16852995}, - {120494, 16853251}, {120495, 16853507}, {120496, 16846851}, {120497, 16853763}, - {120498, 16854019}, {120499, 16786179}, {120500, 16854275}, {120501, 16854531}, - {120502, 16854787}, {120503, 16855043}, {120504, 16855299}, {120505, 16853507}, - {120506, 16855555}, {120507, 16855811}, {120508, 16856067}, {120509, 16856323}, - {120510, 16856579}, {120511, 16856835}, {120512, 16857091}, {120513, 17945347}, - {120514, 16851715}, {120515, 16851971}, {120516, 16852227}, {120517, 16852483}, - {120518, 16852739}, {120519, 16852995}, {120520, 16853251}, {120521, 16853507}, - {120522, 16846851}, {120523, 16853763}, {120524, 16854019}, {120525, 16786179}, - {120526, 16854275}, {120527, 16854531}, {120528, 16854787}, {120529, 16855043}, - {120530, 16855299}, {120531, 16855555}, {120533, 16855811}, {120534, 16856067}, - {120535, 16856323}, {120536, 16856579}, {120537, 16856835}, {120538, 16857091}, - {120539, 17945603}, {120540, 16852739}, {120541, 16853507}, {120542, 16853763}, - {120543, 16856323}, {120544, 16855299}, {120545, 16855043}, {120546, 16851715}, - {120547, 16851971}, {120548, 16852227}, {120549, 16852483}, {120550, 16852739}, - {120551, 16852995}, {120552, 16853251}, {120553, 16853507}, {120554, 16846851}, - {120555, 16853763}, {120556, 16854019}, {120557, 16786179}, {120558, 16854275}, - {120559, 16854531}, {120560, 16854787}, {120561, 16855043}, {120562, 16855299}, - {120563, 16853507}, {120564, 16855555}, {120565, 16855811}, {120566, 16856067}, - {120567, 16856323}, {120568, 16856579}, {120569, 16856835}, {120570, 16857091}, - {120571, 17945347}, {120572, 16851715}, {120573, 16851971}, {120574, 16852227}, - {120575, 16852483}, {120576, 16852739}, {120577, 16852995}, {120578, 16853251}, - {120579, 16853507}, {120580, 16846851}, {120581, 16853763}, {120582, 16854019}, - {120583, 16786179}, {120584, 16854275}, {120585, 16854531}, {120586, 16854787}, - {120587, 16855043}, {120588, 16855299}, {120589, 16855555}, {120591, 16855811}, - {120592, 16856067}, {120593, 16856323}, {120594, 16856579}, {120595, 16856835}, - {120596, 16857091}, {120597, 17945603}, {120598, 16852739}, {120599, 16853507}, - {120600, 16853763}, {120601, 16856323}, {120602, 16855299}, {120603, 16855043}, - {120604, 16851715}, {120605, 16851971}, {120606, 16852227}, {120607, 16852483}, - {120608, 16852739}, {120609, 16852995}, {120610, 16853251}, {120611, 16853507}, - {120612, 16846851}, {120613, 16853763}, {120614, 16854019}, {120615, 16786179}, - {120616, 16854275}, {120617, 16854531}, {120618, 16854787}, {120619, 16855043}, - {120620, 16855299}, {120621, 16853507}, {120622, 16855555}, {120623, 16855811}, - {120624, 16856067}, {120625, 16856323}, {120626, 16856579}, {120627, 16856835}, - {120628, 16857091}, {120629, 17945347}, {120630, 16851715}, {120631, 16851971}, - {120632, 16852227}, {120633, 16852483}, {120634, 16852739}, {120635, 16852995}, - {120636, 16853251}, {120637, 16853507}, {120638, 16846851}, {120639, 16853763}, - {120640, 16854019}, {120641, 16786179}, {120642, 16854275}, {120643, 16854531}, - {120644, 16854787}, {120645, 16855043}, {120646, 16855299}, {120647, 16855555}, - {120649, 16855811}, {120650, 16856067}, {120651, 16856323}, {120652, 16856579}, - {120653, 16856835}, {120654, 16857091}, {120655, 17945603}, {120656, 16852739}, - {120657, 16853507}, {120658, 16853763}, {120659, 16856323}, {120660, 16855299}, - {120661, 16855043}, {120662, 16851715}, {120663, 16851971}, {120664, 16852227}, - {120665, 16852483}, {120666, 16852739}, {120667, 16852995}, {120668, 16853251}, - {120669, 16853507}, {120670, 16846851}, {120671, 16853763}, {120672, 16854019}, - {120673, 16786179}, {120674, 16854275}, {120675, 16854531}, {120676, 16854787}, - {120677, 16855043}, {120678, 16855299}, {120679, 16853507}, {120680, 16855555}, - {120681, 16855811}, {120682, 16856067}, {120683, 16856323}, {120684, 16856579}, - {120685, 16856835}, {120686, 16857091}, {120687, 17945347}, {120688, 16851715}, - {120689, 16851971}, {120690, 16852227}, {120691, 16852483}, {120692, 16852739}, - {120693, 16852995}, {120694, 16853251}, {120695, 16853507}, {120696, 16846851}, - {120697, 16853763}, {120698, 16854019}, {120699, 16786179}, {120700, 16854275}, - {120701, 16854531}, {120702, 16854787}, {120703, 16855043}, {120704, 16855299}, - {120705, 16855555}, {120707, 16855811}, {120708, 16856067}, {120709, 16856323}, - {120710, 16856579}, {120711, 16856835}, {120712, 16857091}, {120713, 17945603}, - {120714, 16852739}, {120715, 16853507}, {120716, 16853763}, {120717, 16856323}, - {120718, 16855299}, {120719, 16855043}, {120720, 16851715}, {120721, 16851971}, - {120722, 16852227}, {120723, 16852483}, {120724, 16852739}, {120725, 16852995}, - {120726, 16853251}, {120727, 16853507}, {120728, 16846851}, {120729, 16853763}, - {120730, 16854019}, {120731, 16786179}, {120732, 16854275}, {120733, 16854531}, - {120734, 16854787}, {120735, 16855043}, {120736, 16855299}, {120737, 16853507}, - {120738, 16855555}, {120739, 16855811}, {120740, 16856067}, {120741, 16856323}, - {120742, 16856579}, {120743, 16856835}, {120744, 16857091}, {120745, 17945347}, - {120746, 16851715}, {120747, 16851971}, {120748, 16852227}, {120749, 16852483}, - {120750, 16852739}, {120751, 16852995}, {120752, 16853251}, {120753, 16853507}, - {120754, 16846851}, {120755, 16853763}, {120756, 16854019}, {120757, 16786179}, - {120758, 16854275}, {120759, 16854531}, {120760, 16854787}, {120761, 16855043}, - {120762, 16855299}, {120763, 16855555}, {120765, 16855811}, {120766, 16856067}, - {120767, 16856323}, {120768, 16856579}, {120769, 16856835}, {120770, 16857091}, - {120771, 17945603}, {120772, 16852739}, {120773, 16853507}, {120774, 16853763}, - {120775, 16856323}, {120776, 16855299}, {120777, 16855043}, {120778, 16858627}, - {120780, 2}, {120782, 17035523}, {120783, 16786947}, {120784, 16785155}, - {120785, 16785411}, {120786, 16787715}, {120787, 17035779}, {120788, 17036035}, - {120789, 17036291}, {120790, 17036547}, {120791, 17036803}, {120792, 17035523}, - {120793, 16786947}, {120794, 16785155}, {120795, 16785411}, {120796, 16787715}, - {120797, 17035779}, {120798, 17036035}, {120799, 17036291}, {120800, 17036547}, - {120801, 17036803}, {120802, 17035523}, {120803, 16786947}, {120804, 16785155}, - {120805, 16785411}, {120806, 16787715}, {120807, 17035779}, {120808, 17036035}, - {120809, 17036291}, {120810, 17036547}, {120811, 17036803}, {120812, 17035523}, - {120813, 16786947}, {120814, 16785155}, {120815, 16785411}, {120816, 16787715}, - {120817, 17035779}, {120818, 17036035}, {120819, 17036291}, {120820, 17036547}, - {120821, 17036803}, {120822, 17035523}, {120823, 16786947}, {120824, 16785155}, - {120825, 16785411}, {120826, 16787715}, {120827, 17035779}, {120828, 17036035}, - {120829, 17036291}, {120830, 17036547}, {120831, 17036803}, {120832, 1}, - {121484, 2}, {121499, 1}, {121504, 2}, {121505, 1}, - {121520, 2}, {122624, 1}, {122655, 2}, {122661, 1}, - {122667, 2}, {122880, 1}, {122887, 2}, {122888, 1}, - {122905, 2}, {122907, 1}, {122914, 2}, {122915, 1}, - {122917, 2}, {122918, 1}, {122923, 2}, {122928, 16866563}, - {122929, 16866819}, {122930, 16867075}, {122931, 16867331}, {122932, 16867587}, - {122933, 16867843}, {122934, 16868099}, {122935, 16868355}, {122936, 16868611}, - {122937, 16869123}, {122938, 16869379}, {122939, 16869635}, {122940, 16870147}, - {122941, 16870403}, {122942, 16870659}, {122943, 16870915}, {122944, 16871171}, - {122945, 16871427}, {122946, 16871683}, {122947, 16871939}, {122948, 16872195}, - {122949, 16872451}, {122950, 16872707}, {122951, 16873475}, {122952, 16873987}, - {122953, 16874243}, {122954, 17495299}, {122955, 16888835}, {122956, 16864003}, - {122957, 16864515}, {122958, 16890883}, {122959, 16883715}, {122960, 17945859}, - {122961, 16866563}, {122962, 16866819}, {122963, 16867075}, {122964, 16867331}, - {122965, 16867587}, {122966, 16867843}, {122967, 16868099}, {122968, 16868355}, - {122969, 16868611}, {122970, 16869123}, {122971, 16869379}, {122972, 16870147}, - {122973, 16870403}, {122974, 16870915}, {122975, 16871427}, {122976, 16871683}, - {122977, 16871939}, {122978, 16872195}, {122979, 16872451}, {122980, 16872707}, - {122981, 16873219}, {122982, 16873475}, {122983, 16879875}, {122984, 16864003}, - {122985, 16863747}, {122986, 16866307}, {122987, 16883203}, {122988, 17490435}, - {122989, 16883971}, {122990, 2}, {123023, 1}, {123024, 2}, - {123136, 1}, {123181, 2}, {123184, 1}, {123198, 2}, - {123200, 1}, {123210, 2}, {123214, 1}, {123216, 2}, - {123536, 1}, {123567, 2}, {123584, 1}, {123642, 2}, - {123647, 1}, {123648, 2}, {124112, 1}, {124154, 2}, - {124896, 1}, {124903, 2}, {124904, 1}, {124908, 2}, - {124909, 1}, {124911, 2}, {124912, 1}, {124927, 2}, - {124928, 1}, {125125, 2}, {125127, 1}, {125143, 2}, - {125184, 17946115}, {125185, 17946371}, {125186, 17946627}, {125187, 17946883}, - {125188, 17947139}, {125189, 17947395}, {125190, 17947651}, {125191, 17947907}, - {125192, 17948163}, {125193, 17948419}, {125194, 17948675}, {125195, 17948931}, - {125196, 17949187}, {125197, 17949443}, {125198, 17949699}, {125199, 17949955}, - {125200, 17950211}, {125201, 17950467}, {125202, 17950723}, {125203, 17950979}, - {125204, 17951235}, {125205, 17951491}, {125206, 17951747}, {125207, 17952003}, - {125208, 17952259}, {125209, 17952515}, {125210, 17952771}, {125211, 17953027}, - {125212, 17953283}, {125213, 17953539}, {125214, 17953795}, {125215, 17954051}, - {125216, 17954307}, {125217, 17954563}, {125218, 1}, {125260, 2}, - {125264, 1}, {125274, 2}, {125278, 1}, {125280, 2}, - {126065, 1}, {126133, 2}, {126209, 1}, {126270, 2}, - {126464, 16910339}, {126465, 17683715}, {126466, 17681923}, {126467, 17834499}, - {126468, 2}, {126469, 16910851}, {126470, 17731587}, {126471, 17682435}, - {126472, 17700099}, {126473, 16911875}, {126474, 17708803}, {126475, 17711107}, - {126476, 17682947}, {126477, 17718019}, {126478, 17694979}, {126479, 17701635}, - {126480, 17703683}, {126481, 17697027}, {126482, 17706755}, {126483, 17725187}, - {126484, 17745155}, {126485, 17686787}, {126486, 17689859}, {126487, 17684995}, - {126488, 17724675}, {126489, 17698051}, {126490, 17701123}, {126491, 17702659}, - {126492, 17954819}, {126493, 17673475}, {126494, 17955075}, {126495, 17955331}, - {126496, 2}, {126497, 17683715}, {126498, 17681923}, {126499, 2}, - {126500, 17721091}, {126501, 2}, {126503, 17682435}, {126504, 2}, - {126505, 16911875}, {126506, 17708803}, {126507, 17711107}, {126508, 17682947}, - {126509, 17718019}, {126510, 17694979}, {126511, 17701635}, {126512, 17703683}, - {126513, 17697027}, {126514, 17706755}, {126515, 2}, {126516, 17745155}, - {126517, 17686787}, {126518, 17689859}, {126519, 17684995}, {126520, 2}, - {126521, 17698051}, {126522, 2}, {126523, 17702659}, {126524, 2}, - {126530, 17681923}, {126531, 2}, {126535, 17682435}, {126536, 2}, - {126537, 16911875}, {126538, 2}, {126539, 17711107}, {126540, 2}, - {126541, 17718019}, {126542, 17694979}, {126543, 17701635}, {126544, 2}, - {126545, 17697027}, {126546, 17706755}, {126547, 2}, {126548, 17745155}, - {126549, 2}, {126551, 17684995}, {126552, 2}, {126553, 17698051}, - {126554, 2}, {126555, 17702659}, {126556, 2}, {126557, 17673475}, - {126558, 2}, {126559, 17955331}, {126560, 2}, {126561, 17683715}, - {126562, 17681923}, {126563, 2}, {126564, 17721091}, {126565, 2}, - {126567, 17682435}, {126568, 17700099}, {126569, 16911875}, {126570, 17708803}, - {126571, 2}, {126572, 17682947}, {126573, 17718019}, {126574, 17694979}, - {126575, 17701635}, {126576, 17703683}, {126577, 17697027}, {126578, 17706755}, - {126579, 2}, {126580, 17745155}, {126581, 17686787}, {126582, 17689859}, - {126583, 17684995}, {126584, 2}, {126585, 17698051}, {126586, 17701123}, - {126587, 17702659}, {126588, 17954819}, {126589, 2}, {126590, 17955075}, - {126591, 2}, {126592, 16910339}, {126593, 17683715}, {126594, 17681923}, - {126595, 17834499}, {126596, 17721091}, {126597, 16910851}, {126598, 17731587}, - {126599, 17682435}, {126600, 17700099}, {126601, 16911875}, {126602, 2}, - {126603, 17711107}, {126604, 17682947}, {126605, 17718019}, {126606, 17694979}, - {126607, 17701635}, {126608, 17703683}, {126609, 17697027}, {126610, 17706755}, - {126611, 17725187}, {126612, 17745155}, {126613, 17686787}, {126614, 17689859}, - {126615, 17684995}, {126616, 17724675}, {126617, 17698051}, {126618, 17701123}, - {126619, 17702659}, {126620, 2}, {126625, 17683715}, {126626, 17681923}, - {126627, 17834499}, {126628, 2}, {126629, 16910851}, {126630, 17731587}, - {126631, 17682435}, {126632, 17700099}, {126633, 16911875}, {126634, 2}, - {126635, 17711107}, {126636, 17682947}, {126637, 17718019}, {126638, 17694979}, - {126639, 17701635}, {126640, 17703683}, {126641, 17697027}, {126642, 17706755}, - {126643, 17725187}, {126644, 17745155}, {126645, 17686787}, {126646, 17689859}, - {126647, 17684995}, {126648, 17724675}, {126649, 17698051}, {126650, 17701123}, - {126651, 17702659}, {126652, 2}, {126704, 1}, {126706, 2}, - {126976, 1}, {127020, 2}, {127024, 1}, {127124, 2}, - {127136, 1}, {127151, 2}, {127153, 1}, {127168, 2}, - {127169, 1}, {127184, 2}, {127185, 1}, {127222, 2}, - {127233, 34732803}, {127234, 34733315}, {127235, 34733827}, {127236, 34734339}, - {127237, 34734851}, {127238, 34735363}, {127239, 34735875}, {127240, 34736387}, - {127241, 34736899}, {127242, 34737411}, {127243, 1}, {127248, 50644995}, - {127249, 50645763}, {127250, 50646531}, {127251, 50647299}, {127252, 50648067}, - {127253, 50648835}, {127254, 50649603}, {127255, 50650371}, {127256, 50651139}, - {127257, 50651907}, {127258, 50652675}, {127259, 50653443}, {127260, 50654211}, - {127261, 50654979}, {127262, 50655747}, {127263, 50656515}, {127264, 50657283}, - {127265, 50658051}, {127266, 50658819}, {127267, 50659587}, {127268, 50660355}, - {127269, 50661123}, {127270, 50661891}, {127271, 50662659}, {127272, 50663427}, - {127273, 50664195}, {127274, 51515139}, {127275, 16777731}, {127276, 16781571}, - {127277, 33554947}, {127278, 34738691}, {127279, 1}, {127280, 16777219}, - {127281, 16777475}, {127282, 16777731}, {127283, 16777987}, {127284, 16778243}, - {127285, 16778499}, {127286, 16778755}, {127287, 16779011}, {127288, 16779267}, - {127289, 16779523}, {127290, 16779779}, {127291, 16780035}, {127292, 16780291}, - {127293, 16780547}, {127294, 16780803}, {127295, 16781059}, {127296, 16781315}, - {127297, 16781571}, {127298, 16781827}, {127299, 16782083}, {127300, 16782339}, - {127301, 16782595}, {127302, 16782851}, {127303, 16783107}, {127304, 16783363}, - {127305, 16783619}, {127306, 34739203}, {127307, 34226691}, {127308, 34739715}, - {127309, 33752579}, {127310, 51517443}, {127311, 34740995}, {127312, 1}, - {127338, 34209539}, {127339, 34189571}, {127340, 34741507}, {127341, 1}, - {127376, 34742019}, {127377, 1}, {127406, 2}, {127462, 1}, - {127488, 34742531}, {127489, 34743043}, {127490, 17307907}, {127491, 2}, - {127504, 17157891}, {127505, 17966339}, {127506, 17966595}, {127507, 17351683}, - {127508, 17143299}, {127509, 17966851}, {127510, 17967107}, {127511, 17225475}, - {127512, 17967363}, {127513, 17967619}, {127514, 17967875}, {127515, 17584643}, - {127516, 17968131}, {127517, 17968387}, {127518, 17968643}, {127519, 17968899}, - {127520, 17969155}, {127521, 17969411}, {127522, 17167107}, {127523, 17969667}, - {127524, 17969923}, {127525, 17970179}, {127526, 17970435}, {127527, 17970691}, - {127528, 17970947}, {127529, 17141763}, {127530, 17223427}, {127531, 17971203}, - {127532, 17288707}, {127533, 17224195}, {127534, 17288963}, {127535, 17971459}, - {127536, 17181443}, {127537, 17971715}, {127538, 17971971}, {127539, 17972227}, - {127540, 17972483}, {127541, 17972739}, {127542, 17264387}, {127543, 17160451}, - {127544, 17972995}, {127545, 17973251}, {127546, 17973507}, {127547, 17973763}, - {127548, 2}, {127552, 51528451}, {127553, 51529219}, {127554, 51529987}, - {127555, 51530755}, {127556, 51531523}, {127557, 51532291}, {127558, 51533059}, - {127559, 51533827}, {127560, 51534595}, {127561, 2}, {127568, 17980931}, - {127569, 17981187}, {127570, 2}, {127584, 1}, {127590, 2}, - {127744, 1}, {128728, 2}, {128732, 1}, {128749, 2}, - {128752, 1}, {128765, 2}, {128768, 1}, {128887, 2}, - {128891, 1}, {128986, 2}, {128992, 1}, {129004, 2}, - {129008, 1}, {129009, 2}, {129024, 1}, {129036, 2}, - {129040, 1}, {129096, 2}, {129104, 1}, {129114, 2}, - {129120, 1}, {129160, 2}, {129168, 1}, {129198, 2}, - {129200, 1}, {129202, 2}, {129280, 1}, {129620, 2}, - {129632, 1}, {129646, 2}, {129648, 1}, {129661, 2}, - {129664, 1}, {129673, 2}, {129680, 1}, {129726, 2}, - {129727, 1}, {129734, 2}, {129742, 1}, {129756, 2}, - {129760, 1}, {129769, 2}, {129776, 1}, {129785, 2}, - {129792, 1}, {129939, 2}, {129940, 1}, {129995, 2}, - {130032, 17035523}, {130033, 16786947}, {130034, 16785155}, {130035, 16785411}, - {130036, 16787715}, {130037, 17035779}, {130038, 17036035}, {130039, 17036291}, - {130040, 17036547}, {130041, 17036803}, {130042, 2}, {131072, 1}, + {7304, 16946435}, {7305, 16946691}, {7306, 1}, {7307, 2}, + {7312, 16946947}, {7313, 16947203}, {7314, 16947459}, {7315, 16947715}, + {7316, 16947971}, {7317, 16948227}, {7318, 16948483}, {7319, 16948739}, + {7320, 16948995}, {7321, 16949251}, {7322, 16949507}, {7323, 16949763}, + {7324, 16944643}, {7325, 16950019}, {7326, 16950275}, {7327, 16950531}, + {7328, 16950787}, {7329, 16951043}, {7330, 16951299}, {7331, 16951555}, + {7332, 16951811}, {7333, 16952067}, {7334, 16952323}, {7335, 16952579}, + {7336, 16952835}, {7337, 16953091}, {7338, 16953347}, {7339, 16953603}, + {7340, 16953859}, {7341, 16954115}, {7342, 16954371}, {7343, 16954627}, + {7344, 16954883}, {7345, 16955139}, {7346, 16955395}, {7347, 16955651}, + {7348, 16955907}, {7349, 16956163}, {7350, 16956419}, {7351, 16956675}, + {7352, 16956931}, {7353, 16957187}, {7354, 16957443}, {7355, 2}, + {7357, 16957699}, {7358, 16957955}, {7359, 16958211}, {7360, 1}, + {7368, 2}, {7376, 1}, {7419, 2}, {7424, 1}, + {7468, 16777219}, {7469, 16791043}, {7470, 16777475}, {7471, 1}, + {7472, 16777987}, {7473, 16778243}, {7474, 16816131}, {7475, 16778755}, + {7476, 16779011}, {7477, 16779267}, {7478, 16779523}, {7479, 16779779}, + {7480, 16780035}, {7481, 16780291}, {7482, 16780547}, {7483, 1}, + {7484, 16780803}, {7485, 16835843}, {7486, 16781059}, {7487, 16781571}, + {7488, 16782083}, {7489, 16782339}, {7490, 16782851}, {7491, 16777219}, + {7492, 16958467}, {7493, 16958723}, {7494, 16958979}, {7495, 16777475}, + {7496, 16777987}, {7497, 16778243}, {7498, 16816387}, {7499, 16816643}, + {7500, 16959235}, {7501, 16778755}, {7502, 1}, {7503, 16779779}, + {7504, 16780291}, {7505, 16807171}, {7506, 16780803}, {7507, 16814851}, + {7508, 16959491}, {7509, 16959747}, {7510, 16781059}, {7511, 16782083}, + {7512, 16782339}, {7513, 16960003}, {7514, 16818435}, {7515, 16782595}, + {7516, 16960259}, {7517, 16851971}, {7518, 16852227}, {7519, 16852483}, + {7520, 16856323}, {7521, 16856579}, {7522, 16779267}, {7523, 16781571}, + {7524, 16782339}, {7525, 16782595}, {7526, 16851971}, {7527, 16852227}, + {7528, 16855299}, {7529, 16856323}, {7530, 16856579}, {7531, 1}, + {7544, 16869891}, {7545, 1}, {7579, 16960515}, {7580, 16777731}, + {7581, 16960771}, {7582, 16793603}, {7583, 16959235}, {7584, 16778499}, + {7585, 16961027}, {7586, 16961283}, {7587, 16961539}, {7588, 16817923}, + {7589, 16817667}, {7590, 16961795}, {7591, 16962051}, {7592, 16962307}, + {7593, 16962563}, {7594, 16962819}, {7595, 16963075}, {7596, 16963331}, + {7597, 16963587}, {7598, 16818691}, {7599, 16963843}, {7600, 16964099}, + {7601, 16818947}, {7602, 16964355}, {7603, 16964611}, {7604, 16820483}, + {7605, 16964867}, {7606, 16839683}, {7607, 16821507}, {7608, 16965123}, + {7609, 16821763}, {7610, 16839939}, {7611, 16783619}, {7612, 16965379}, + {7613, 16965635}, {7614, 16822531}, {7615, 16853507}, {7616, 1}, + {7680, 16965891}, {7681, 1}, {7682, 16966147}, {7683, 1}, + {7684, 16966403}, {7685, 1}, {7686, 16966659}, {7687, 1}, + {7688, 16966915}, {7689, 1}, {7690, 16967171}, {7691, 1}, + {7692, 16967427}, {7693, 1}, {7694, 16967683}, {7695, 1}, + {7696, 16967939}, {7697, 1}, {7698, 16968195}, {7699, 1}, + {7700, 16968451}, {7701, 1}, {7702, 16968707}, {7703, 1}, + {7704, 16968963}, {7705, 1}, {7706, 16969219}, {7707, 1}, + {7708, 16969475}, {7709, 1}, {7710, 16969731}, {7711, 1}, + {7712, 16969987}, {7713, 1}, {7714, 16970243}, {7715, 1}, + {7716, 16970499}, {7717, 1}, {7718, 16970755}, {7719, 1}, + {7720, 16971011}, {7721, 1}, {7722, 16971267}, {7723, 1}, + {7724, 16971523}, {7725, 1}, {7726, 16971779}, {7727, 1}, + {7728, 16972035}, {7729, 1}, {7730, 16972291}, {7731, 1}, + {7732, 16972547}, {7733, 1}, {7734, 16972803}, {7735, 1}, + {7736, 16973059}, {7737, 1}, {7738, 16973315}, {7739, 1}, + {7740, 16973571}, {7741, 1}, {7742, 16973827}, {7743, 1}, + {7744, 16974083}, {7745, 1}, {7746, 16974339}, {7747, 1}, + {7748, 16974595}, {7749, 1}, {7750, 16974851}, {7751, 1}, + {7752, 16975107}, {7753, 1}, {7754, 16975363}, {7755, 1}, + {7756, 16975619}, {7757, 1}, {7758, 16975875}, {7759, 1}, + {7760, 16976131}, {7761, 1}, {7762, 16976387}, {7763, 1}, + {7764, 16976643}, {7765, 1}, {7766, 16976899}, {7767, 1}, + {7768, 16977155}, {7769, 1}, {7770, 16977411}, {7771, 1}, + {7772, 16977667}, {7773, 1}, {7774, 16977923}, {7775, 1}, + {7776, 16978179}, {7777, 1}, {7778, 16978435}, {7779, 1}, + {7780, 16978691}, {7781, 1}, {7782, 16978947}, {7783, 1}, + {7784, 16979203}, {7785, 1}, {7786, 16979459}, {7787, 1}, + {7788, 16979715}, {7789, 1}, {7790, 16979971}, {7791, 1}, + {7792, 16980227}, {7793, 1}, {7794, 16980483}, {7795, 1}, + {7796, 16980739}, {7797, 1}, {7798, 16980995}, {7799, 1}, + {7800, 16981251}, {7801, 1}, {7802, 16981507}, {7803, 1}, + {7804, 16981763}, {7805, 1}, {7806, 16982019}, {7807, 1}, + {7808, 16982275}, {7809, 1}, {7810, 16982531}, {7811, 1}, + {7812, 16982787}, {7813, 1}, {7814, 16983043}, {7815, 1}, + {7816, 16983299}, {7817, 1}, {7818, 16983555}, {7819, 1}, + {7820, 16983811}, {7821, 1}, {7822, 16984067}, {7823, 1}, + {7824, 16984323}, {7825, 1}, {7826, 16984579}, {7827, 1}, + {7828, 16984835}, {7829, 1}, {7834, 33762307}, {7835, 16978179}, + {7836, 1}, {7838, 16985603}, {7839, 1}, {7840, 16985859}, + {7841, 1}, {7842, 16986115}, {7843, 1}, {7844, 16986371}, + {7845, 1}, {7846, 16986627}, {7847, 1}, {7848, 16986883}, + {7849, 1}, {7850, 16987139}, {7851, 1}, {7852, 16987395}, + {7853, 1}, {7854, 16987651}, {7855, 1}, {7856, 16987907}, + {7857, 1}, {7858, 16988163}, {7859, 1}, {7860, 16988419}, + {7861, 1}, {7862, 16988675}, {7863, 1}, {7864, 16988931}, + {7865, 1}, {7866, 16989187}, {7867, 1}, {7868, 16989443}, + {7869, 1}, {7870, 16989699}, {7871, 1}, {7872, 16989955}, + {7873, 1}, {7874, 16990211}, {7875, 1}, {7876, 16990467}, + {7877, 1}, {7878, 16990723}, {7879, 1}, {7880, 16990979}, + {7881, 1}, {7882, 16991235}, {7883, 1}, {7884, 16991491}, + {7885, 1}, {7886, 16991747}, {7887, 1}, {7888, 16992003}, + {7889, 1}, {7890, 16992259}, {7891, 1}, {7892, 16992515}, + {7893, 1}, {7894, 16992771}, {7895, 1}, {7896, 16993027}, + {7897, 1}, {7898, 16993283}, {7899, 1}, {7900, 16993539}, + {7901, 1}, {7902, 16993795}, {7903, 1}, {7904, 16994051}, + {7905, 1}, {7906, 16994307}, {7907, 1}, {7908, 16994563}, + {7909, 1}, {7910, 16994819}, {7911, 1}, {7912, 16995075}, + {7913, 1}, {7914, 16995331}, {7915, 1}, {7916, 16995587}, + {7917, 1}, {7918, 16995843}, {7919, 1}, {7920, 16996099}, + {7921, 1}, {7922, 16996355}, {7923, 1}, {7924, 16996611}, + {7925, 1}, {7926, 16996867}, {7927, 1}, {7928, 16997123}, + {7929, 1}, {7930, 16997379}, {7931, 1}, {7932, 16997635}, + {7933, 1}, {7934, 16997891}, {7935, 1}, {7944, 16998147}, + {7945, 16998403}, {7946, 16998659}, {7947, 16998915}, {7948, 16999171}, + {7949, 16999427}, {7950, 16999683}, {7951, 16999939}, {7952, 1}, + {7958, 2}, {7960, 17000195}, {7961, 17000451}, {7962, 17000707}, + {7963, 17000963}, {7964, 17001219}, {7965, 17001475}, {7966, 2}, + {7968, 1}, {7976, 17001731}, {7977, 17001987}, {7978, 17002243}, + {7979, 17002499}, {7980, 17002755}, {7981, 17003011}, {7982, 17003267}, + {7983, 17003523}, {7984, 1}, {7992, 17003779}, {7993, 17004035}, + {7994, 17004291}, {7995, 17004547}, {7996, 17004803}, {7997, 17005059}, + {7998, 17005315}, {7999, 17005571}, {8000, 1}, {8006, 2}, + {8008, 17005827}, {8009, 17006083}, {8010, 17006339}, {8011, 17006595}, + {8012, 17006851}, {8013, 17007107}, {8014, 2}, {8016, 1}, + {8024, 2}, {8025, 17007363}, {8026, 2}, {8027, 17007619}, + {8028, 2}, {8029, 17007875}, {8030, 2}, {8031, 17008131}, + {8032, 1}, {8040, 17008387}, {8041, 17008643}, {8042, 17008899}, + {8043, 17009155}, {8044, 17009411}, {8045, 17009667}, {8046, 17009923}, + {8047, 17010179}, {8048, 1}, {8049, 16849923}, {8050, 1}, + {8051, 16850179}, {8052, 1}, {8053, 16850435}, {8054, 1}, + {8055, 16850691}, {8056, 1}, {8057, 16850947}, {8058, 1}, + {8059, 16851203}, {8060, 1}, {8061, 16851459}, {8062, 2}, + {8064, 33787651}, {8065, 33788163}, {8066, 33788675}, {8067, 33789187}, + {8068, 33789699}, {8069, 33790211}, {8070, 33790723}, {8071, 33791235}, + {8072, 33787651}, {8073, 33788163}, {8074, 33788675}, {8075, 33789187}, + {8076, 33789699}, {8077, 33790211}, {8078, 33790723}, {8079, 33791235}, + {8080, 33791747}, {8081, 33792259}, {8082, 33792771}, {8083, 33793283}, + {8084, 33793795}, {8085, 33794307}, {8086, 33794819}, {8087, 33795331}, + {8088, 33791747}, {8089, 33792259}, {8090, 33792771}, {8091, 33793283}, + {8092, 33793795}, {8093, 33794307}, {8094, 33794819}, {8095, 33795331}, + {8096, 33795843}, {8097, 33796355}, {8098, 33796867}, {8099, 33797379}, + {8100, 33797891}, {8101, 33798403}, {8102, 33798915}, {8103, 33799427}, + {8104, 33795843}, {8105, 33796355}, {8106, 33796867}, {8107, 33797379}, + {8108, 33797891}, {8109, 33798403}, {8110, 33798915}, {8111, 33799427}, + {8112, 1}, {8114, 33799939}, {8115, 33800451}, {8116, 33800963}, + {8117, 2}, {8118, 1}, {8119, 33801475}, {8120, 17024771}, + {8121, 17025027}, {8122, 17022723}, {8123, 16849923}, {8124, 33800451}, + {8125, 33802499}, {8126, 16846851}, {8127, 33802499}, {8128, 33803011}, + {8129, 50580739}, {8130, 33804291}, {8131, 33804803}, {8132, 33805315}, + {8133, 2}, {8134, 1}, {8135, 33805827}, {8136, 17029123}, + {8137, 16850179}, {8138, 17027075}, {8139, 16850435}, {8140, 33804803}, + {8141, 50583811}, {8142, 50584579}, {8143, 50585347}, {8144, 1}, + {8147, 17031683}, {8148, 2}, {8150, 1}, {8152, 17031939}, + {8153, 17032195}, {8154, 17032451}, {8155, 16850691}, {8156, 2}, + {8157, 50587139}, {8158, 50587907}, {8159, 50588675}, {8160, 1}, + {8163, 17035011}, {8164, 1}, {8168, 17035267}, {8169, 17035523}, + {8170, 17035779}, {8171, 16851203}, {8172, 17036035}, {8173, 50590723}, + {8174, 50403587}, {8175, 17037059}, {8176, 2}, {8178, 33814531}, + {8179, 33815043}, {8180, 33815555}, {8181, 2}, {8182, 1}, + {8183, 33816067}, {8184, 17039363}, {8185, 16850947}, {8186, 17037315}, + {8187, 16851459}, {8188, 33815043}, {8189, 33562883}, {8190, 33809923}, + {8191, 2}, {8192, 16783875}, {8203, 0}, {8204, 1}, + {8206, 2}, {8208, 1}, {8209, 17039619}, {8210, 1}, + {8215, 33817091}, {8216, 1}, {8228, 2}, {8231, 1}, + {8232, 2}, {8239, 16783875}, {8240, 1}, {8243, 33817603}, + {8244, 50595331}, {8245, 1}, {8246, 33818883}, {8247, 50596611}, + {8248, 1}, {8252, 33820163}, {8253, 1}, {8254, 33820675}, + {8255, 1}, {8263, 33821187}, {8264, 33821699}, {8265, 33822211}, + {8266, 1}, {8279, 67372035}, {8280, 1}, {8287, 16783875}, + {8288, 0}, {8293, 2}, {8298, 0}, {8304, 17045507}, + {8305, 16779267}, {8306, 2}, {8308, 16787715}, {8309, 17045763}, + {8310, 17046019}, {8311, 17046275}, {8312, 17046531}, {8313, 17046787}, + {8314, 17047043}, {8315, 17047299}, {8316, 17047555}, {8317, 17047811}, + {8318, 17048067}, {8319, 16780547}, {8320, 17045507}, {8321, 16786947}, + {8322, 16785155}, {8323, 16785411}, {8324, 16787715}, {8325, 17045763}, + {8326, 17046019}, {8327, 17046275}, {8328, 17046531}, {8329, 17046787}, + {8330, 17047043}, {8331, 17047299}, {8332, 17047555}, {8333, 17047811}, + {8334, 17048067}, {8335, 2}, {8336, 16777219}, {8337, 16778243}, + {8338, 16780803}, {8339, 16783107}, {8340, 16816387}, {8341, 16779011}, + {8342, 16779779}, {8343, 16780035}, {8344, 16780291}, {8345, 16780547}, + {8346, 16781059}, {8347, 16781827}, {8348, 16782083}, {8349, 2}, + {8352, 1}, {8360, 33558787}, {8361, 1}, {8385, 2}, + {8400, 1}, {8433, 2}, {8448, 50602755}, {8449, 50603523}, + {8450, 16777731}, {8451, 33827075}, {8452, 1}, {8453, 50604803}, + {8454, 50605571}, {8455, 16816643}, {8456, 1}, {8457, 33829123}, + {8458, 16778755}, {8459, 16779011}, {8463, 16802051}, {8464, 16779267}, + {8466, 16780035}, {8468, 1}, {8469, 16780547}, {8470, 33557763}, + {8471, 1}, {8473, 16781059}, {8474, 16781315}, {8475, 16781571}, + {8478, 1}, {8480, 33829635}, {8481, 50607363}, {8482, 33830915}, + {8483, 1}, {8484, 16783619}, {8485, 1}, {8486, 16857091}, + {8487, 1}, {8488, 16783619}, {8489, 1}, {8490, 16779779}, + {8491, 16790787}, {8492, 16777475}, {8493, 16777731}, {8494, 1}, + {8495, 16778243}, {8497, 16778499}, {8498, 17054211}, {8499, 16780291}, + {8500, 16780803}, {8501, 17054467}, {8502, 17054723}, {8503, 17054979}, + {8504, 17055235}, {8505, 16779267}, {8506, 1}, {8507, 50609923}, + {8508, 16855043}, {8509, 16852227}, {8511, 16855043}, {8512, 17056259}, + {8513, 1}, {8517, 16777987}, {8519, 16778243}, {8520, 16779267}, + {8521, 16779523}, {8522, 1}, {8528, 50610947}, {8529, 50611715}, + {8530, 67389699}, {8531, 50613507}, {8532, 50614275}, {8533, 50615043}, + {8534, 50615811}, {8535, 50616579}, {8536, 50617347}, {8537, 50618115}, + {8538, 50618883}, {8539, 50619651}, {8540, 50620419}, {8541, 50621187}, + {8542, 50621955}, {8543, 33564419}, {8544, 16779267}, {8545, 33845507}, + {8546, 50623235}, {8547, 33846787}, {8548, 16782595}, {8549, 33847299}, + {8550, 50625027}, {8551, 67403011}, {8552, 33849603}, {8553, 16783107}, + {8554, 33850115}, {8555, 50627843}, {8556, 16780035}, {8557, 16777731}, + {8558, 16777987}, {8559, 16780291}, {8560, 16779267}, {8561, 33845507}, + {8562, 50622723}, {8563, 33846787}, {8564, 16782595}, {8565, 33847299}, + {8566, 50625027}, {8567, 67403011}, {8568, 33849603}, {8569, 16783107}, + {8570, 33850115}, {8571, 50627843}, {8572, 16780035}, {8573, 16777731}, + {8574, 16777987}, {8575, 16780291}, {8576, 1}, {8579, 17074179}, + {8580, 1}, {8585, 50628867}, {8586, 1}, {8588, 2}, + {8592, 1}, {8748, 33852419}, {8749, 50630147}, {8750, 1}, + {8751, 33853699}, {8752, 50631427}, {8753, 1}, {9001, 17077763}, + {9002, 17078019}, {9003, 1}, {9258, 2}, {9280, 1}, + {9291, 2}, {9312, 16786947}, {9313, 16785155}, {9314, 16785411}, + {9315, 16787715}, {9316, 17045763}, {9317, 17046019}, {9318, 17046275}, + {9319, 17046531}, {9320, 17046787}, {9321, 33835779}, {9322, 33564163}, + {9323, 33855491}, {9324, 33856003}, {9325, 33856515}, {9326, 33857027}, + {9327, 33857539}, {9328, 33858051}, {9329, 33858563}, {9330, 33859075}, + {9331, 33859587}, {9332, 50637315}, {9333, 50638083}, {9334, 50638851}, + {9335, 50639619}, {9336, 50640387}, {9337, 50641155}, {9338, 50641923}, + {9339, 50642691}, {9340, 50643459}, {9341, 67421443}, {9342, 67422467}, + {9343, 67423491}, {9344, 67424515}, {9345, 67425539}, {9346, 67426563}, + {9347, 67427587}, {9348, 67428611}, {9349, 67429635}, {9350, 67430659}, + {9351, 67431683}, {9352, 2}, {9372, 50655491}, {9373, 50656259}, + {9374, 50657027}, {9375, 50657795}, {9376, 50658563}, {9377, 50659331}, + {9378, 50660099}, {9379, 50660867}, {9380, 50661635}, {9381, 50662403}, + {9382, 50663171}, {9383, 50663939}, {9384, 50664707}, {9385, 50665475}, + {9386, 50666243}, {9387, 50667011}, {9388, 50667779}, {9389, 50668547}, + {9390, 50669315}, {9391, 50670083}, {9392, 50670851}, {9393, 50671619}, + {9394, 50672387}, {9395, 50673155}, {9396, 50673923}, {9397, 50674691}, + {9398, 16777219}, {9399, 16777475}, {9400, 16777731}, {9401, 16777987}, + {9402, 16778243}, {9403, 16778499}, {9404, 16778755}, {9405, 16779011}, + {9406, 16779267}, {9407, 16779523}, {9408, 16779779}, {9409, 16780035}, + {9410, 16780291}, {9411, 16780547}, {9412, 16780803}, {9413, 16781059}, + {9414, 16781315}, {9415, 16781571}, {9416, 16781827}, {9417, 16782083}, + {9418, 16782339}, {9419, 16782595}, {9420, 16782851}, {9421, 16783107}, + {9422, 16783363}, {9423, 16783619}, {9424, 16777219}, {9425, 16777475}, + {9426, 16777731}, {9427, 16777987}, {9428, 16778243}, {9429, 16778499}, + {9430, 16778755}, {9431, 16779011}, {9432, 16779267}, {9433, 16779523}, + {9434, 16779779}, {9435, 16780035}, {9436, 16780291}, {9437, 16780547}, + {9438, 16780803}, {9439, 16781059}, {9440, 16781315}, {9441, 16781571}, + {9442, 16781827}, {9443, 16782083}, {9444, 16782339}, {9445, 16782595}, + {9446, 16782851}, {9447, 16783107}, {9448, 16783363}, {9449, 16783619}, + {9450, 17045507}, {9451, 1}, {10764, 67406851}, {10765, 1}, + {10868, 50675459}, {10869, 33899011}, {10870, 50675971}, {10871, 1}, + {10972, 33899523}, {10973, 1}, {11124, 2}, {11126, 1}, + {11158, 2}, {11159, 1}, {11264, 17122819}, {11265, 17123075}, + {11266, 17123331}, {11267, 17123587}, {11268, 17123843}, {11269, 17124099}, + {11270, 17124355}, {11271, 17124611}, {11272, 17124867}, {11273, 17125123}, + {11274, 17125379}, {11275, 17125635}, {11276, 17125891}, {11277, 17126147}, + {11278, 17126403}, {11279, 17126659}, {11280, 17126915}, {11281, 17127171}, + {11282, 17127427}, {11283, 17127683}, {11284, 17127939}, {11285, 17128195}, + {11286, 17128451}, {11287, 17128707}, {11288, 17128963}, {11289, 17129219}, + {11290, 17129475}, {11291, 17129731}, {11292, 17129987}, {11293, 17130243}, + {11294, 17130499}, {11295, 17130755}, {11296, 17131011}, {11297, 17131267}, + {11298, 17131523}, {11299, 17131779}, {11300, 17132035}, {11301, 17132291}, + {11302, 17132547}, {11303, 17132803}, {11304, 17133059}, {11305, 17133315}, + {11306, 17133571}, {11307, 17133827}, {11308, 17134083}, {11309, 17134339}, + {11310, 17134595}, {11311, 17134851}, {11312, 1}, {11360, 17135107}, + {11361, 1}, {11362, 17135363}, {11363, 17135619}, {11364, 17135875}, + {11365, 1}, {11367, 17136131}, {11368, 1}, {11369, 17136387}, + {11370, 1}, {11371, 17136643}, {11372, 1}, {11373, 16958723}, + {11374, 16963331}, {11375, 16958467}, {11376, 16960515}, {11377, 1}, + {11378, 17136899}, {11379, 1}, {11381, 17137155}, {11382, 1}, + {11388, 16779523}, {11389, 16782595}, {11390, 17137411}, {11391, 17137667}, + {11392, 17137923}, {11393, 1}, {11394, 17138179}, {11395, 1}, + {11396, 17138435}, {11397, 1}, {11398, 17138691}, {11399, 1}, + {11400, 17138947}, {11401, 1}, {11402, 17139203}, {11403, 1}, + {11404, 17139459}, {11405, 1}, {11406, 17139715}, {11407, 1}, + {11408, 17139971}, {11409, 1}, {11410, 17140227}, {11411, 1}, + {11412, 17140483}, {11413, 1}, {11414, 17140739}, {11415, 1}, + {11416, 17140995}, {11417, 1}, {11418, 17141251}, {11419, 1}, + {11420, 17141507}, {11421, 1}, {11422, 17141763}, {11423, 1}, + {11424, 17142019}, {11425, 1}, {11426, 17142275}, {11427, 1}, + {11428, 17142531}, {11429, 1}, {11430, 17142787}, {11431, 1}, + {11432, 17143043}, {11433, 1}, {11434, 17143299}, {11435, 1}, + {11436, 17143555}, {11437, 1}, {11438, 17143811}, {11439, 1}, + {11440, 17144067}, {11441, 1}, {11442, 17144323}, {11443, 1}, + {11444, 17144579}, {11445, 1}, {11446, 17144835}, {11447, 1}, + {11448, 17145091}, {11449, 1}, {11450, 17145347}, {11451, 1}, + {11452, 17145603}, {11453, 1}, {11454, 17145859}, {11455, 1}, + {11456, 17146115}, {11457, 1}, {11458, 17146371}, {11459, 1}, + {11460, 17146627}, {11461, 1}, {11462, 17146883}, {11463, 1}, + {11464, 17147139}, {11465, 1}, {11466, 17147395}, {11467, 1}, + {11468, 17147651}, {11469, 1}, {11470, 17147907}, {11471, 1}, + {11472, 17148163}, {11473, 1}, {11474, 17148419}, {11475, 1}, + {11476, 17148675}, {11477, 1}, {11478, 17148931}, {11479, 1}, + {11480, 17149187}, {11481, 1}, {11482, 17149443}, {11483, 1}, + {11484, 17149699}, {11485, 1}, {11486, 17149955}, {11487, 1}, + {11488, 17150211}, {11489, 1}, {11490, 17150467}, {11491, 1}, + {11499, 17150723}, {11500, 1}, {11501, 17150979}, {11502, 1}, + {11506, 17151235}, {11507, 1}, {11508, 2}, {11513, 1}, + {11558, 2}, {11559, 1}, {11560, 2}, {11565, 1}, + {11566, 2}, {11568, 1}, {11624, 2}, {11631, 17151491}, + {11632, 1}, {11633, 2}, {11647, 1}, {11671, 2}, + {11680, 1}, {11687, 2}, {11688, 1}, {11695, 2}, + {11696, 1}, {11703, 2}, {11704, 1}, {11711, 2}, + {11712, 1}, {11719, 2}, {11720, 1}, {11727, 2}, + {11728, 1}, {11735, 2}, {11736, 1}, {11743, 2}, + {11744, 1}, {11870, 2}, {11904, 1}, {11930, 2}, + {11931, 1}, {11935, 17151747}, {11936, 1}, {12019, 17152003}, + {12020, 2}, {12032, 17152259}, {12033, 17152515}, {12034, 17152771}, + {12035, 17153027}, {12036, 17153283}, {12037, 17153539}, {12038, 17153795}, + {12039, 17154051}, {12040, 17154307}, {12041, 17154563}, {12042, 17154819}, + {12043, 17155075}, {12044, 17155331}, {12045, 17155587}, {12046, 17155843}, + {12047, 17156099}, {12048, 17156355}, {12049, 17156611}, {12050, 17156867}, + {12051, 17157123}, {12052, 17157379}, {12053, 17157635}, {12054, 17157891}, + {12055, 17158147}, {12056, 17158403}, {12057, 17158659}, {12058, 17158915}, + {12059, 17159171}, {12060, 17159427}, {12061, 17159683}, {12062, 17159939}, + {12063, 17160195}, {12064, 17160451}, {12065, 17160707}, {12066, 17160963}, + {12067, 17161219}, {12068, 17161475}, {12069, 17161731}, {12070, 17161987}, + {12071, 17162243}, {12072, 17162499}, {12073, 17162755}, {12074, 17163011}, + {12075, 17163267}, {12076, 17163523}, {12077, 17163779}, {12078, 17164035}, + {12079, 17164291}, {12080, 17164547}, {12081, 17164803}, {12082, 17165059}, + {12083, 17165315}, {12084, 17165571}, {12085, 17165827}, {12086, 17166083}, + {12087, 17166339}, {12088, 17166595}, {12089, 17166851}, {12090, 17167107}, + {12091, 17167363}, {12092, 17167619}, {12093, 17167875}, {12094, 17168131}, + {12095, 17168387}, {12096, 17168643}, {12097, 17168899}, {12098, 17169155}, + {12099, 17169411}, {12100, 17169667}, {12101, 17169923}, {12102, 17170179}, + {12103, 17170435}, {12104, 17170691}, {12105, 17170947}, {12106, 17171203}, + {12107, 17171459}, {12108, 17171715}, {12109, 17171971}, {12110, 17172227}, + {12111, 17172483}, {12112, 17172739}, {12113, 17172995}, {12114, 17173251}, + {12115, 17173507}, {12116, 17173763}, {12117, 17174019}, {12118, 17174275}, + {12119, 17174531}, {12120, 17174787}, {12121, 17175043}, {12122, 17175299}, + {12123, 17175555}, {12124, 17175811}, {12125, 17176067}, {12126, 17176323}, + {12127, 17176579}, {12128, 17176835}, {12129, 17177091}, {12130, 17177347}, + {12131, 17177603}, {12132, 17177859}, {12133, 17178115}, {12134, 17178371}, + {12135, 17178627}, {12136, 17178883}, {12137, 17179139}, {12138, 17179395}, + {12139, 17179651}, {12140, 17179907}, {12141, 17180163}, {12142, 17180419}, + {12143, 17180675}, {12144, 17180931}, {12145, 17181187}, {12146, 17181443}, + {12147, 17181699}, {12148, 17181955}, {12149, 17182211}, {12150, 17182467}, + {12151, 17182723}, {12152, 17182979}, {12153, 17183235}, {12154, 17183491}, + {12155, 17183747}, {12156, 17184003}, {12157, 17184259}, {12158, 17184515}, + {12159, 17184771}, {12160, 17185027}, {12161, 17185283}, {12162, 17185539}, + {12163, 17185795}, {12164, 17186051}, {12165, 17186307}, {12166, 17186563}, + {12167, 17186819}, {12168, 17187075}, {12169, 17187331}, {12170, 17187587}, + {12171, 17187843}, {12172, 17188099}, {12173, 17188355}, {12174, 17188611}, + {12175, 17188867}, {12176, 17189123}, {12177, 17189379}, {12178, 17189635}, + {12179, 17189891}, {12180, 17190147}, {12181, 17190403}, {12182, 17190659}, + {12183, 17190915}, {12184, 17191171}, {12185, 17191427}, {12186, 17191683}, + {12187, 17191939}, {12188, 17192195}, {12189, 17192451}, {12190, 17192707}, + {12191, 17192963}, {12192, 17193219}, {12193, 17193475}, {12194, 17193731}, + {12195, 17193987}, {12196, 17194243}, {12197, 17194499}, {12198, 17194755}, + {12199, 17195011}, {12200, 17195267}, {12201, 17195523}, {12202, 17195779}, + {12203, 17196035}, {12204, 17196291}, {12205, 17196547}, {12206, 17196803}, + {12207, 17197059}, {12208, 17197315}, {12209, 17197571}, {12210, 17197827}, + {12211, 17198083}, {12212, 17198339}, {12213, 17198595}, {12214, 17198851}, + {12215, 17199107}, {12216, 17199363}, {12217, 17199619}, {12218, 17199875}, + {12219, 17200131}, {12220, 17200387}, {12221, 17200643}, {12222, 17200899}, + {12223, 17201155}, {12224, 17201411}, {12225, 17201667}, {12226, 17201923}, + {12227, 17202179}, {12228, 17202435}, {12229, 17202691}, {12230, 17202947}, + {12231, 17203203}, {12232, 17203459}, {12233, 17203715}, {12234, 17203971}, + {12235, 17204227}, {12236, 17204483}, {12237, 17204739}, {12238, 17204995}, + {12239, 17205251}, {12240, 17205507}, {12241, 17205763}, {12242, 17206019}, + {12243, 17206275}, {12244, 17206531}, {12245, 17206787}, {12246, 2}, + {12288, 16783875}, {12289, 1}, {12290, 17207043}, {12291, 1}, + {12342, 17207299}, {12343, 1}, {12344, 17158147}, {12345, 17207555}, + {12346, 17207811}, {12347, 1}, {12352, 2}, {12353, 1}, + {12439, 2}, {12441, 1}, {12443, 33985283}, {12444, 33985795}, + {12445, 1}, {12447, 33986307}, {12448, 1}, {12543, 33986819}, + {12544, 2}, {12549, 1}, {12592, 2}, {12593, 17210115}, + {12594, 17210371}, {12595, 17210627}, {12596, 17210883}, {12597, 17211139}, + {12598, 17211395}, {12599, 17211651}, {12600, 17211907}, {12601, 17212163}, + {12602, 17212419}, {12603, 17212675}, {12604, 17212931}, {12605, 17213187}, + {12606, 17213443}, {12607, 17213699}, {12608, 17213955}, {12609, 17214211}, + {12610, 17214467}, {12611, 17214723}, {12612, 17214979}, {12613, 17215235}, + {12614, 17215491}, {12615, 17215747}, {12616, 17216003}, {12617, 17216259}, + {12618, 17216515}, {12619, 17216771}, {12620, 17217027}, {12621, 17217283}, + {12622, 17217539}, {12623, 17217795}, {12624, 17218051}, {12625, 17218307}, + {12626, 17218563}, {12627, 17218819}, {12628, 17219075}, {12629, 17219331}, + {12630, 17219587}, {12631, 17219843}, {12632, 17220099}, {12633, 17220355}, + {12634, 17220611}, {12635, 17220867}, {12636, 17221123}, {12637, 17221379}, + {12638, 17221635}, {12639, 17221891}, {12640, 17222147}, {12641, 17222403}, + {12642, 17222659}, {12643, 17222915}, {12644, 0}, {12645, 17223171}, + {12646, 17223427}, {12647, 17223683}, {12648, 17223939}, {12649, 17224195}, + {12650, 17224451}, {12651, 17224707}, {12652, 17224963}, {12653, 17225219}, + {12654, 17225475}, {12655, 17225731}, {12656, 17225987}, {12657, 17226243}, + {12658, 17226499}, {12659, 17226755}, {12660, 17227011}, {12661, 17227267}, + {12662, 17227523}, {12663, 17227779}, {12664, 17228035}, {12665, 17228291}, + {12666, 17228547}, {12667, 17228803}, {12668, 17229059}, {12669, 17229315}, + {12670, 17229571}, {12671, 17229827}, {12672, 17230083}, {12673, 17230339}, + {12674, 17230595}, {12675, 17230851}, {12676, 17231107}, {12677, 17231363}, + {12678, 17231619}, {12679, 17231875}, {12680, 17232131}, {12681, 17232387}, + {12682, 17232643}, {12683, 17232899}, {12684, 17233155}, {12685, 17233411}, + {12686, 17233667}, {12687, 2}, {12688, 1}, {12690, 17152259}, + {12691, 17153795}, {12692, 17233923}, {12693, 17234179}, {12694, 17234435}, + {12695, 17234691}, {12696, 17234947}, {12697, 17235203}, {12698, 17153283}, + {12699, 17235459}, {12700, 17235715}, {12701, 17235971}, {12702, 17236227}, + {12703, 17154307}, {12704, 1}, {12774, 2}, {12784, 1}, + {12800, 50790915}, {12801, 50791683}, {12802, 50792451}, {12803, 50793219}, + {12804, 50793987}, {12805, 50794755}, {12806, 50795523}, {12807, 50796291}, + {12808, 50797059}, {12809, 50797827}, {12810, 50798595}, {12811, 50799363}, + {12812, 50800131}, {12813, 50800899}, {12814, 50801667}, {12815, 50802435}, + {12816, 50803203}, {12817, 50803971}, {12818, 50804739}, {12819, 50805507}, + {12820, 50806275}, {12821, 50807043}, {12822, 50807811}, {12823, 50808579}, + {12824, 50809347}, {12825, 50810115}, {12826, 50810883}, {12827, 50811651}, + {12828, 50812419}, {12829, 67590403}, {12830, 67591427}, {12831, 2}, + {12832, 50815235}, {12833, 50816003}, {12834, 50816771}, {12835, 50817539}, + {12836, 50818307}, {12837, 50819075}, {12838, 50819843}, {12839, 50820611}, + {12840, 50821379}, {12841, 50822147}, {12842, 50822915}, {12843, 50823683}, + {12844, 50824451}, {12845, 50825219}, {12846, 50825987}, {12847, 50826755}, + {12848, 50827523}, {12849, 50828291}, {12850, 50829059}, {12851, 50829827}, + {12852, 50830595}, {12853, 50831363}, {12854, 50832131}, {12855, 50832899}, + {12856, 50833667}, {12857, 50834435}, {12858, 50835203}, {12859, 50835971}, + {12860, 50836739}, {12861, 50837507}, {12862, 50838275}, {12863, 50839043}, + {12864, 50839811}, {12865, 50840579}, {12866, 50841347}, {12867, 50842115}, + {12868, 17288451}, {12869, 17288707}, {12870, 17169155}, {12871, 17288963}, + {12872, 1}, {12880, 50843651}, {12881, 33855747}, {12882, 34067203}, + {12883, 33562371}, {12884, 34067715}, {12885, 34068227}, {12886, 34068739}, + {12887, 34069251}, {12888, 34069763}, {12889, 34070275}, {12890, 34070787}, + {12891, 33837571}, {12892, 33836803}, {12893, 34071299}, {12894, 34071811}, + {12895, 34072323}, {12896, 17210115}, {12897, 17210883}, {12898, 17211651}, + {12899, 17212163}, {12900, 17214211}, {12901, 17214467}, {12902, 17215235}, + {12903, 17215747}, {12904, 17216003}, {12905, 17216515}, {12906, 17216771}, + {12907, 17217027}, {12908, 17217283}, {12909, 17217539}, {12910, 17247491}, + {12911, 17248259}, {12912, 17249027}, {12913, 17249795}, {12914, 17250563}, + {12915, 17251331}, {12916, 17252099}, {12917, 17252867}, {12918, 17253635}, + {12919, 17254403}, {12920, 17255171}, {12921, 17255939}, {12922, 17256707}, + {12923, 17257475}, {12924, 34072835}, {12925, 34073347}, {12926, 17296643}, + {12927, 1}, {12928, 17152259}, {12929, 17153795}, {12930, 17233923}, + {12931, 17234179}, {12932, 17264131}, {12933, 17264899}, {12934, 17265667}, + {12935, 17155075}, {12936, 17267203}, {12937, 17158147}, {12938, 17170947}, + {12939, 17174019}, {12940, 17173763}, {12941, 17171203}, {12942, 17194755}, + {12943, 17160195}, {12944, 17170435}, {12945, 17274115}, {12946, 17274883}, + {12947, 17275651}, {12948, 17276419}, {12949, 17277187}, {12950, 17277955}, + {12951, 17278723}, {12952, 17279491}, {12953, 17296899}, {12954, 17297155}, + {12955, 17161731}, {12956, 17297411}, {12957, 17297667}, {12958, 17297923}, + {12959, 17298179}, {12960, 17298435}, {12961, 17286403}, {12962, 17298691}, + {12963, 17298947}, {12964, 17234435}, {12965, 17234691}, {12966, 17234947}, + {12967, 17299203}, {12968, 17299459}, {12969, 17299715}, {12970, 17299971}, + {12971, 17281795}, {12972, 17282563}, {12973, 17283331}, {12974, 17284099}, + {12975, 17284867}, {12976, 17300227}, {12977, 34077699}, {12978, 34078211}, + {12979, 34078723}, {12980, 34079235}, {12981, 34079747}, {12982, 33564931}, + {12983, 34067971}, {12984, 34072067}, {12985, 34080259}, {12986, 34080771}, + {12987, 34081283}, {12988, 34081795}, {12989, 34082307}, {12990, 34082819}, + {12991, 34083331}, {12992, 34083843}, {12993, 34084355}, {12994, 34084867}, + {12995, 34085379}, {12996, 34085891}, {12997, 34086403}, {12998, 34086915}, + {12999, 34087427}, {13000, 34087939}, {13001, 50865667}, {13002, 50866435}, + {13003, 50867203}, {13004, 34090755}, {13005, 50868483}, {13006, 34092035}, + {13007, 50869763}, {13008, 17316099}, {13009, 17316355}, {13010, 17316611}, + {13011, 17316867}, {13012, 17317123}, {13013, 17317379}, {13014, 17317635}, + {13015, 17317891}, {13016, 17318147}, {13017, 17209603}, {13018, 17318403}, + {13019, 17318659}, {13020, 17318915}, {13021, 17319171}, {13022, 17319427}, + {13023, 17319683}, {13024, 17319939}, {13025, 17320195}, {13026, 17320451}, + {13027, 17209859}, {13028, 17320707}, {13029, 17320963}, {13030, 17321219}, + {13031, 17321475}, {13032, 17321731}, {13033, 17321987}, {13034, 17322243}, + {13035, 17322499}, {13036, 17322755}, {13037, 17323011}, {13038, 17323267}, + {13039, 17323523}, {13040, 17323779}, {13041, 17324035}, {13042, 17324291}, + {13043, 17324547}, {13044, 17324803}, {13045, 17325059}, {13046, 17325315}, + {13047, 17325571}, {13048, 17325827}, {13049, 17326083}, {13050, 17326339}, + {13051, 17326595}, {13052, 17326851}, {13053, 17327107}, {13054, 17327363}, + {13055, 34104835}, {13056, 67659779}, {13057, 67660803}, {13058, 67661827}, + {13059, 50885635}, {13060, 67663619}, {13061, 50887427}, {13062, 50888195}, + {13063, 84443395}, {13064, 67667459}, {13065, 50891267}, {13066, 50892035}, + {13067, 50892803}, {13068, 67670787}, {13069, 67671811}, {13070, 50895619}, + {13071, 50896387}, {13072, 34119939}, {13073, 50897667}, {13074, 67675651}, + {13075, 67676675}, {13076, 34123267}, {13077, 84455427}, {13078, 101233923}, + {13079, 84458243}, {13080, 50901507}, {13081, 84459523}, {13082, 84460803}, + {13083, 67684867}, {13084, 50908675}, {13085, 50909443}, {13086, 50910211}, + {13087, 67688195}, {13088, 84466435}, {13089, 67690499}, {13090, 50914307}, + {13091, 50915075}, {13092, 50915843}, {13093, 34139395}, {13094, 34139907}, + {13095, 34128643}, {13096, 34140419}, {13097, 50918147}, {13098, 50918915}, + {13099, 84474115}, {13100, 50920963}, {13101, 67698947}, {13102, 84477187}, + {13103, 50924035}, {13104, 34147587}, {13105, 34148099}, {13106, 84480259}, + {13107, 67704323}, {13108, 84482563}, {13109, 50929411}, {13110, 84484611}, + {13111, 34154243}, {13112, 50931971}, {13113, 50932739}, {13114, 50933507}, + {13115, 50934275}, {13116, 50935043}, {13117, 67713027}, {13118, 50936835}, + {13119, 34160387}, {13120, 50938115}, {13121, 50938883}, {13122, 50939651}, + {13123, 67717635}, {13124, 50941443}, {13125, 50942211}, {13126, 50942979}, + {13127, 84498179}, {13128, 67722243}, {13129, 34168835}, {13130, 84500995}, + {13131, 34170627}, {13132, 67725571}, {13133, 67680003}, {13134, 50949379}, + {13135, 50950147}, {13136, 50950915}, {13137, 67728899}, {13138, 34175491}, + {13139, 50953219}, {13140, 67731203}, {13141, 34177795}, {13142, 84509955}, + {13143, 50904323}, {13144, 34179587}, {13145, 34180099}, {13146, 34180611}, + {13147, 34181123}, {13148, 34181635}, {13149, 34182147}, {13150, 34182659}, + {13151, 34183171}, {13152, 34183683}, {13153, 34184195}, {13154, 50961923}, + {13155, 50962691}, {13156, 50963459}, {13157, 50964227}, {13158, 50964995}, + {13159, 50965763}, {13160, 50966531}, {13161, 50967299}, {13162, 50968067}, + {13163, 50968835}, {13164, 50969603}, {13165, 50970371}, {13166, 50971139}, + {13167, 50971907}, {13168, 50972675}, {13169, 50973443}, {13170, 34196995}, + {13171, 34197507}, {13172, 50975235}, {13173, 34198787}, {13174, 34199299}, + {13175, 34199811}, {13176, 50977539}, {13177, 50978307}, {13178, 34201859}, + {13179, 34202371}, {13180, 34202883}, {13181, 34203395}, {13182, 34203907}, + {13183, 67758851}, {13184, 34196483}, {13185, 34205443}, {13186, 34205955}, + {13187, 34206467}, {13188, 34206979}, {13189, 34207491}, {13190, 34208003}, + {13191, 34208515}, {13192, 50986243}, {13193, 67764227}, {13194, 34210819}, + {13195, 34211331}, {13196, 34211843}, {13197, 34212355}, {13198, 34212867}, + {13199, 34213379}, {13200, 34213891}, {13201, 50991619}, {13202, 50992387}, + {13203, 50990851}, {13204, 50993155}, {13205, 34216707}, {13206, 34217219}, + {13207, 34217731}, {13208, 33556995}, {13209, 34218243}, {13210, 34218755}, + {13211, 34219267}, {13212, 34219779}, {13213, 34220291}, {13214, 34220803}, + {13215, 50998531}, {13216, 50999299}, {13217, 34200579}, {13218, 51000067}, + {13219, 51000835}, {13220, 51001603}, {13221, 34201347}, {13222, 51002371}, + {13223, 51003139}, {13224, 67781123}, {13225, 34196483}, {13226, 51004931}, + {13227, 51005699}, {13228, 51006467}, {13229, 51007235}, {13230, 84562435}, + {13231, 101340931}, {13232, 34233603}, {13233, 34234115}, {13234, 34234627}, + {13235, 34235139}, {13236, 34235651}, {13237, 34236163}, {13238, 34236675}, + {13239, 34237187}, {13240, 34237699}, {13241, 34237187}, {13242, 34238211}, + {13243, 34238723}, {13244, 34239235}, {13245, 34239747}, {13246, 34240259}, + {13247, 34239747}, {13248, 34240771}, {13249, 34241283}, {13250, 2}, + {13251, 34241795}, {13252, 33827331}, {13253, 33554947}, {13254, 67796739}, + {13255, 2}, {13256, 34243331}, {13257, 34243843}, {13258, 34244355}, + {13259, 34196227}, {13260, 34244867}, {13261, 34245379}, {13262, 34220803}, + {13263, 34245891}, {13264, 33557251}, {13265, 34246403}, {13266, 51024131}, + {13267, 34247683}, {13268, 34208003}, {13269, 51025411}, {13270, 51026179}, + {13271, 34249731}, {13272, 2}, {13273, 51027459}, {13274, 34251011}, + {13275, 34231811}, {13276, 34251523}, {13277, 34252035}, {13278, 51029763}, + {13279, 51030531}, {13280, 34254083}, {13281, 34254595}, {13282, 34255107}, + {13283, 34255619}, {13284, 34256131}, {13285, 34256643}, {13286, 34257155}, + {13287, 34257667}, {13288, 34258179}, {13289, 51035907}, {13290, 51036675}, + {13291, 51037443}, {13292, 51038211}, {13293, 51038979}, {13294, 51039747}, + {13295, 51040515}, {13296, 51041283}, {13297, 51042051}, {13298, 51042819}, + {13299, 51043587}, {13300, 51044355}, {13301, 51045123}, {13302, 51045891}, + {13303, 51046659}, {13304, 51047427}, {13305, 51048195}, {13306, 51048963}, + {13307, 51049731}, {13308, 51050499}, {13309, 51051267}, {13310, 51052035}, + {13311, 51052803}, {13312, 1}, {42125, 2}, {42128, 1}, + {42183, 2}, {42192, 1}, {42540, 2}, {42560, 17499139}, + {42561, 1}, {42562, 17499395}, {42563, 1}, {42564, 17499651}, + {42565, 1}, {42566, 17499907}, {42567, 1}, {42568, 17500163}, + {42569, 1}, {42570, 16946435}, {42571, 1}, {42572, 17500419}, + {42573, 1}, {42574, 17500675}, {42575, 1}, {42576, 17500931}, + {42577, 1}, {42578, 17501187}, {42579, 1}, {42580, 17501443}, + {42581, 1}, {42582, 17501699}, {42583, 1}, {42584, 17501955}, + {42585, 1}, {42586, 17502211}, {42587, 1}, {42588, 17502467}, + {42589, 1}, {42590, 17502723}, {42591, 1}, {42592, 17502979}, + {42593, 1}, {42594, 17503235}, {42595, 1}, {42596, 17503491}, + {42597, 1}, {42598, 17503747}, {42599, 1}, {42600, 17504003}, + {42601, 1}, {42602, 17504259}, {42603, 1}, {42604, 17504515}, + {42605, 1}, {42624, 17504771}, {42625, 1}, {42626, 17505027}, + {42627, 1}, {42628, 17505283}, {42629, 1}, {42630, 17505539}, + {42631, 1}, {42632, 17505795}, {42633, 1}, {42634, 17506051}, + {42635, 1}, {42636, 17506307}, {42637, 1}, {42638, 17506563}, + {42639, 1}, {42640, 17506819}, {42641, 1}, {42642, 17507075}, + {42643, 1}, {42644, 17507331}, {42645, 1}, {42646, 17507587}, + {42647, 1}, {42648, 17507843}, {42649, 1}, {42650, 17508099}, + {42651, 1}, {42652, 16873219}, {42653, 16873731}, {42654, 1}, + {42744, 2}, {42752, 1}, {42786, 17508355}, {42787, 1}, + {42788, 17508611}, {42789, 1}, {42790, 17508867}, {42791, 1}, + {42792, 17509123}, {42793, 1}, {42794, 17509379}, {42795, 1}, + {42796, 17509635}, {42797, 1}, {42798, 17509891}, {42799, 1}, + {42802, 17510147}, {42803, 1}, {42804, 17510403}, {42805, 1}, + {42806, 17510659}, {42807, 1}, {42808, 17510915}, {42809, 1}, + {42810, 17511171}, {42811, 1}, {42812, 17511427}, {42813, 1}, + {42814, 17511683}, {42815, 1}, {42816, 17511939}, {42817, 1}, + {42818, 17512195}, {42819, 1}, {42820, 17512451}, {42821, 1}, + {42822, 17512707}, {42823, 1}, {42824, 17512963}, {42825, 1}, + {42826, 17513219}, {42827, 1}, {42828, 17513475}, {42829, 1}, + {42830, 17513731}, {42831, 1}, {42832, 17513987}, {42833, 1}, + {42834, 17514243}, {42835, 1}, {42836, 17514499}, {42837, 1}, + {42838, 17514755}, {42839, 1}, {42840, 17515011}, {42841, 1}, + {42842, 17515267}, {42843, 1}, {42844, 17515523}, {42845, 1}, + {42846, 17515779}, {42847, 1}, {42848, 17516035}, {42849, 1}, + {42850, 17516291}, {42851, 1}, {42852, 17516547}, {42853, 1}, + {42854, 17516803}, {42855, 1}, {42856, 17517059}, {42857, 1}, + {42858, 17517315}, {42859, 1}, {42860, 17517571}, {42861, 1}, + {42862, 17517827}, {42863, 1}, {42864, 17517827}, {42865, 1}, + {42873, 17518083}, {42874, 1}, {42875, 17518339}, {42876, 1}, + {42877, 17518595}, {42878, 17518851}, {42879, 1}, {42880, 17519107}, + {42881, 1}, {42882, 17519363}, {42883, 1}, {42884, 17519619}, + {42885, 1}, {42886, 17519875}, {42887, 1}, {42891, 17520131}, + {42892, 1}, {42893, 16961539}, {42894, 1}, {42896, 17520387}, + {42897, 1}, {42898, 17520643}, {42899, 1}, {42902, 17520899}, + {42903, 1}, {42904, 17521155}, {42905, 1}, {42906, 17521411}, + {42907, 1}, {42908, 17521667}, {42909, 1}, {42910, 17521923}, + {42911, 1}, {42912, 17522179}, {42913, 1}, {42914, 17522435}, + {42915, 1}, {42916, 17522691}, {42917, 1}, {42918, 17522947}, + {42919, 1}, {42920, 17523203}, {42921, 1}, {42922, 16841475}, + {42923, 16959235}, {42924, 16961283}, {42925, 17523459}, {42926, 16961795}, + {42927, 1}, {42928, 17523715}, {42929, 17523971}, {42930, 16962307}, + {42931, 17524227}, {42932, 17524483}, {42933, 1}, {42934, 17524739}, + {42935, 1}, {42936, 17524995}, {42937, 1}, {42938, 17525251}, + {42939, 1}, {42940, 17525507}, {42941, 1}, {42942, 17525763}, + {42943, 1}, {42944, 17526019}, {42945, 1}, {42946, 17526275}, + {42947, 1}, {42948, 17526531}, {42949, 16964611}, {42950, 17526787}, + {42951, 17527043}, {42952, 1}, {42953, 17527299}, {42954, 1}, + {42955, 17527555}, {42956, 17527811}, {42957, 1}, {42958, 2}, + {42960, 17528067}, {42961, 1}, {42962, 2}, {42963, 1}, + {42964, 2}, {42965, 1}, {42966, 17528323}, {42967, 1}, + {42968, 17528579}, {42969, 1}, {42970, 17528835}, {42971, 1}, + {42972, 17529091}, {42973, 2}, {42994, 16777731}, {42995, 16778499}, + {42996, 16781315}, {42997, 17529347}, {42998, 1}, {43000, 16802051}, + {43001, 16808195}, {43002, 1}, {43053, 2}, {43056, 1}, + {43066, 2}, {43072, 1}, {43128, 2}, {43136, 1}, + {43206, 2}, {43214, 1}, {43226, 2}, {43232, 1}, + {43348, 2}, {43359, 1}, {43389, 2}, {43392, 1}, + {43470, 2}, {43471, 1}, {43482, 2}, {43486, 1}, + {43519, 2}, {43520, 1}, {43575, 2}, {43584, 1}, + {43598, 2}, {43600, 1}, {43610, 2}, {43612, 1}, + {43715, 2}, {43739, 1}, {43767, 2}, {43777, 1}, + {43783, 2}, {43785, 1}, {43791, 2}, {43793, 1}, + {43799, 2}, {43808, 1}, {43815, 2}, {43816, 1}, + {43823, 2}, {43824, 1}, {43868, 17508867}, {43869, 17529603}, + {43870, 17135363}, {43871, 17529859}, {43872, 1}, {43881, 17530115}, + {43882, 1}, {43884, 2}, {43888, 17530371}, {43889, 17530627}, + {43890, 17530883}, {43891, 17531139}, {43892, 17531395}, {43893, 17531651}, + {43894, 17531907}, {43895, 17532163}, {43896, 17532419}, {43897, 17532675}, + {43898, 17532931}, {43899, 17533187}, {43900, 17533443}, {43901, 17533699}, + {43902, 17533955}, {43903, 17534211}, {43904, 17534467}, {43905, 17534723}, + {43906, 17534979}, {43907, 17535235}, {43908, 17535491}, {43909, 17535747}, + {43910, 17536003}, {43911, 17536259}, {43912, 17536515}, {43913, 17536771}, + {43914, 17537027}, {43915, 17537283}, {43916, 17537539}, {43917, 17537795}, + {43918, 17538051}, {43919, 17538307}, {43920, 17538563}, {43921, 17538819}, + {43922, 17539075}, {43923, 17539331}, {43924, 17539587}, {43925, 17539843}, + {43926, 17540099}, {43927, 17540355}, {43928, 17540611}, {43929, 17540867}, + {43930, 17541123}, {43931, 17541379}, {43932, 17541635}, {43933, 17541891}, + {43934, 17542147}, {43935, 17542403}, {43936, 17542659}, {43937, 17542915}, + {43938, 17543171}, {43939, 17543427}, {43940, 17543683}, {43941, 17543939}, + {43942, 17544195}, {43943, 17544451}, {43944, 17544707}, {43945, 17544963}, + {43946, 17545219}, {43947, 17545475}, {43948, 17545731}, {43949, 17545987}, + {43950, 17546243}, {43951, 17546499}, {43952, 17546755}, {43953, 17547011}, + {43954, 17547267}, {43955, 17547523}, {43956, 17547779}, {43957, 17548035}, + {43958, 17548291}, {43959, 17548547}, {43960, 17548803}, {43961, 17549059}, + {43962, 17549315}, {43963, 17549571}, {43964, 17549827}, {43965, 17550083}, + {43966, 17550339}, {43967, 17550595}, {43968, 1}, {44014, 2}, + {44016, 1}, {44026, 2}, {44032, 1}, {55204, 2}, + {55216, 1}, {55239, 2}, {55243, 1}, {55292, 2}, + {63744, 17550851}, {63745, 17551107}, {63746, 17192707}, {63747, 17551363}, + {63748, 17551619}, {63749, 17551875}, {63750, 17552131}, {63751, 17206531}, + {63753, 17552387}, {63754, 17194755}, {63755, 17552643}, {63756, 17552899}, + {63757, 17553155}, {63758, 17553411}, {63759, 17553667}, {63760, 17553923}, + {63761, 17554179}, {63762, 17554435}, {63763, 17554691}, {63764, 17554947}, + {63765, 17555203}, {63766, 17555459}, {63767, 17555715}, {63768, 17555971}, + {63769, 17556227}, {63770, 17556483}, {63771, 17556739}, {63772, 17556995}, + {63773, 17557251}, {63774, 17557507}, {63775, 17557763}, {63776, 17558019}, + {63777, 17558275}, {63778, 17558531}, {63779, 17558787}, {63780, 17559043}, + {63781, 17559299}, {63782, 17559555}, {63783, 17559811}, {63784, 17560067}, + {63785, 17560323}, {63786, 17560579}, {63787, 17560835}, {63788, 17561091}, + {63789, 17561347}, {63790, 17561603}, {63791, 17561859}, {63792, 17562115}, + {63793, 17562371}, {63794, 17562627}, {63795, 17562883}, {63796, 17184003}, + {63797, 17563139}, {63798, 17563395}, {63799, 17563651}, {63800, 17563907}, + {63801, 17564163}, {63802, 17564419}, {63803, 17564675}, {63804, 17564931}, + {63805, 17565187}, {63806, 17565443}, {63807, 17565699}, {63808, 17202691}, + {63809, 17565955}, {63810, 17566211}, {63811, 17566467}, {63812, 17566723}, + {63813, 17566979}, {63814, 17567235}, {63815, 17567491}, {63816, 17567747}, + {63817, 17568003}, {63818, 17568259}, {63819, 17568515}, {63820, 17568771}, + {63821, 17569027}, {63822, 17569283}, {63823, 17569539}, {63824, 17569795}, + {63825, 17570051}, {63826, 17570307}, {63827, 17570563}, {63828, 17570819}, + {63829, 17571075}, {63830, 17571331}, {63831, 17571587}, {63832, 17571843}, + {63833, 17572099}, {63834, 17572355}, {63835, 17572611}, {63836, 17554947}, + {63837, 17572867}, {63838, 17573123}, {63839, 17573379}, {63840, 17573635}, + {63841, 17573891}, {63842, 17574147}, {63843, 17574403}, {63844, 17574659}, + {63845, 17574915}, {63846, 17575171}, {63847, 17575427}, {63848, 17575683}, + {63849, 17575939}, {63850, 17576195}, {63851, 17576451}, {63852, 17576707}, + {63853, 17576963}, {63854, 17577219}, {63855, 17577475}, {63856, 17577731}, + {63857, 17193219}, {63858, 17577987}, {63859, 17578243}, {63860, 17578499}, + {63861, 17578755}, {63862, 17579011}, {63863, 17579267}, {63864, 17579523}, + {63865, 17579779}, {63866, 17580035}, {63867, 17580291}, {63868, 17580547}, + {63869, 17580803}, {63870, 17581059}, {63871, 17581315}, {63872, 17581571}, + {63873, 17161731}, {63874, 17581827}, {63875, 17582083}, {63876, 17582339}, + {63877, 17582595}, {63878, 17582851}, {63879, 17583107}, {63880, 17583363}, + {63881, 17583619}, {63882, 17156867}, {63883, 17583875}, {63884, 17584131}, + {63885, 17584387}, {63886, 17584643}, {63887, 17584899}, {63888, 17585155}, + {63889, 17585411}, {63890, 17585667}, {63891, 17585923}, {63892, 17586179}, + {63893, 17586435}, {63894, 17586691}, {63895, 17586947}, {63896, 17587203}, + {63897, 17587459}, {63898, 17587715}, {63899, 17587971}, {63900, 17588227}, + {63901, 17588483}, {63902, 17588739}, {63903, 17588995}, {63904, 17589251}, + {63905, 17577475}, {63906, 17589507}, {63907, 17589763}, {63908, 17590019}, + {63909, 17590275}, {63910, 17590531}, {63911, 17590787}, {63912, 17327619}, + {63913, 17591043}, {63914, 17573379}, {63915, 17591299}, {63916, 17591555}, + {63917, 17591811}, {63918, 17592067}, {63919, 17592323}, {63920, 17592579}, + {63921, 17592835}, {63922, 17593091}, {63923, 17593347}, {63924, 17593603}, + {63925, 17593859}, {63926, 17594115}, {63927, 17594371}, {63928, 17594627}, + {63929, 17594883}, {63930, 17595139}, {63931, 17595395}, {63932, 17595651}, + {63933, 17595907}, {63934, 17596163}, {63935, 17554947}, {63936, 17596419}, + {63937, 17596675}, {63938, 17596931}, {63939, 17597187}, {63940, 17206275}, + {63941, 17597443}, {63942, 17597699}, {63943, 17597955}, {63944, 17598211}, + {63945, 17598467}, {63946, 17598723}, {63947, 17598979}, {63948, 17599235}, + {63949, 17599491}, {63950, 17599747}, {63951, 17600003}, {63952, 17600259}, + {63953, 17264899}, {63954, 17600515}, {63955, 17600771}, {63956, 17601027}, + {63957, 17601283}, {63958, 17601539}, {63959, 17601795}, {63960, 17602051}, + {63961, 17602307}, {63962, 17602563}, {63963, 17573891}, {63964, 17602819}, + {63965, 17603075}, {63966, 17603331}, {63967, 17603587}, {63968, 17603843}, + {63969, 17604099}, {63970, 17604355}, {63971, 17604611}, {63972, 17604867}, + {63973, 17605123}, {63974, 17605379}, {63975, 17605635}, {63976, 17605891}, + {63977, 17194499}, {63978, 17606147}, {63979, 17606403}, {63980, 17606659}, + {63981, 17606915}, {63982, 17607171}, {63983, 17607427}, {63984, 17607683}, + {63985, 17607939}, {63986, 17608195}, {63987, 17608451}, {63988, 17608707}, + {63989, 17608963}, {63990, 17609219}, {63991, 17181955}, {63992, 17609475}, + {63993, 17609731}, {63994, 17609987}, {63995, 17610243}, {63996, 17610499}, + {63997, 17610755}, {63998, 17611011}, {63999, 17611267}, {64000, 17611523}, + {64001, 17611779}, {64002, 17612035}, {64003, 17612291}, {64004, 17612547}, + {64005, 17612803}, {64006, 17613059}, {64007, 17613315}, {64008, 17188867}, + {64009, 17613571}, {64010, 17189635}, {64011, 17613827}, {64012, 17614083}, + {64013, 17614339}, {64014, 1}, {64016, 17614595}, {64017, 1}, + {64018, 17614851}, {64019, 1}, {64021, 17615107}, {64022, 17615363}, + {64023, 17615619}, {64024, 17615875}, {64025, 17616131}, {64026, 17616387}, + {64027, 17616643}, {64028, 17616899}, {64029, 17617155}, {64030, 17183747}, + {64031, 1}, {64032, 17617411}, {64033, 1}, {64034, 17617667}, + {64035, 1}, {64037, 17617923}, {64038, 17618179}, {64039, 1}, + {64042, 17618435}, {64043, 17618691}, {64044, 17618947}, {64045, 17619203}, + {64046, 17619459}, {64047, 17619715}, {64048, 17619971}, {64049, 17620227}, + {64050, 17620483}, {64051, 17620739}, {64052, 17620995}, {64053, 17621251}, + {64054, 17621507}, {64055, 17621763}, {64056, 17622019}, {64057, 17622275}, + {64058, 17622531}, {64059, 17622787}, {64060, 17163523}, {64061, 17623043}, + {64062, 17623299}, {64063, 17623555}, {64064, 17623811}, {64065, 17624067}, + {64066, 17624323}, {64067, 17624579}, {64068, 17624835}, {64069, 17625091}, + {64070, 17625347}, {64071, 17625603}, {64072, 17625859}, {64073, 17626115}, + {64074, 17626371}, {64075, 17626627}, {64076, 17275651}, {64077, 17626883}, + {64078, 17627139}, {64079, 17627395}, {64080, 17627651}, {64081, 17278723}, + {64082, 17627907}, {64083, 17628163}, {64084, 17628419}, {64085, 17628675}, + {64086, 17628931}, {64087, 17586691}, {64088, 17629187}, {64089, 17629443}, + {64090, 17629699}, {64091, 17629955}, {64092, 17630211}, {64093, 17630467}, + {64095, 17630723}, {64096, 17630979}, {64097, 17631235}, {64098, 17631491}, + {64099, 17631747}, {64100, 17632003}, {64101, 17632259}, {64102, 17632515}, + {64103, 17617923}, {64104, 17632771}, {64105, 17633027}, {64106, 17633283}, + {64107, 17633539}, {64108, 17633795}, {64109, 17634051}, {64110, 2}, + {64112, 17634307}, {64113, 17634563}, {64114, 17634819}, {64115, 17635075}, + {64116, 17635331}, {64117, 17635587}, {64118, 17635843}, {64119, 17636099}, + {64120, 17621507}, {64121, 17636355}, {64122, 17636611}, {64123, 17636867}, + {64124, 17614595}, {64125, 17637123}, {64126, 17637379}, {64127, 17637635}, + {64128, 17637891}, {64129, 17638147}, {64130, 17638403}, {64131, 17638659}, + {64132, 17638915}, {64133, 17639171}, {64134, 17639427}, {64135, 17639683}, + {64136, 17639939}, {64137, 17623555}, {64138, 17640195}, {64139, 17623811}, + {64140, 17640451}, {64141, 17640707}, {64142, 17640963}, {64143, 17641219}, + {64144, 17641475}, {64145, 17614851}, {64146, 17560323}, {64147, 17641731}, + {64148, 17641987}, {64149, 17171971}, {64150, 17577731}, {64151, 17598723}, + {64152, 17642243}, {64153, 17642499}, {64154, 17625603}, {64155, 17642755}, + {64156, 17625859}, {64157, 17643011}, {64158, 17643267}, {64159, 17643523}, + {64160, 17615363}, {64161, 17643779}, {64162, 17644035}, {64163, 17644291}, + {64164, 17644547}, {64165, 17644803}, {64166, 17615619}, {64167, 17645059}, + {64168, 17645315}, {64169, 17645571}, {64170, 17645827}, {64171, 17646083}, + {64172, 17646339}, {64173, 17628931}, {64174, 17646595}, {64175, 17646851}, + {64176, 17586691}, {64177, 17647107}, {64178, 17629955}, {64179, 17647363}, + {64180, 17647619}, {64181, 17647875}, {64182, 17648131}, {64183, 17648387}, + {64184, 17631235}, {64185, 17648643}, {64186, 17617667}, {64187, 17648899}, + {64188, 17631491}, {64189, 17572867}, {64190, 17649155}, {64191, 17631747}, + {64192, 17649411}, {64193, 17632259}, {64194, 17649667}, {64195, 17649923}, + {64196, 17650179}, {64197, 17650435}, {64198, 17650691}, {64199, 17632771}, + {64200, 17616899}, {64201, 17650947}, {64202, 17633027}, {64203, 17651203}, + {64204, 17633283}, {64205, 17651459}, {64206, 17206531}, {64207, 17651715}, + {64208, 17651971}, {64209, 17652227}, {64210, 17652483}, {64211, 17652739}, + {64212, 17652995}, {64213, 17653251}, {64214, 17653507}, {64215, 17653763}, + {64216, 17654019}, {64217, 17654275}, {64218, 2}, {64256, 34431747}, + {64257, 34432259}, {64258, 34432771}, {64259, 51209219}, {64260, 51210499}, + {64261, 33559043}, {64263, 2}, {64275, 34434051}, {64276, 34434563}, + {64277, 34435075}, {64278, 34435587}, {64279, 34436099}, {64280, 2}, + {64285, 34436611}, {64286, 1}, {64287, 34437123}, {64288, 17660419}, + {64289, 17054467}, {64290, 17055235}, {64291, 17660675}, {64292, 17660931}, + {64293, 17661187}, {64294, 17661443}, {64295, 17661699}, {64296, 17661955}, + {64297, 17047043}, {64298, 34439427}, {64299, 34439939}, {64300, 51217667}, + {64301, 51218435}, {64302, 34441987}, {64303, 34442499}, {64304, 34443011}, + {64305, 34443523}, {64306, 34444035}, {64307, 34444547}, {64308, 34445059}, + {64309, 34445571}, {64310, 34446083}, {64311, 2}, {64312, 34446595}, + {64313, 34447107}, {64314, 34447619}, {64315, 34448131}, {64316, 34448643}, + {64317, 2}, {64318, 34449155}, {64319, 2}, {64320, 34449667}, + {64321, 34450179}, {64322, 2}, {64323, 34450691}, {64324, 34451203}, + {64325, 2}, {64326, 34451715}, {64327, 34452227}, {64328, 34452739}, + {64329, 34440451}, {64330, 34453251}, {64331, 34453763}, {64332, 34454275}, + {64333, 34454787}, {64334, 34455299}, {64335, 34455811}, {64336, 17679107}, + {64338, 17679363}, {64342, 17679619}, {64346, 17679875}, {64350, 17680131}, + {64354, 17680387}, {64358, 17680643}, {64362, 17680899}, {64366, 17681155}, + {64370, 17681411}, {64374, 17681667}, {64378, 17681923}, {64382, 17682179}, + {64386, 17682435}, {64388, 17682691}, {64390, 17682947}, {64392, 17683203}, + {64394, 17683459}, {64396, 17683715}, {64398, 17683971}, {64402, 17684227}, + {64406, 17684483}, {64410, 17684739}, {64414, 17684995}, {64416, 17685251}, + {64420, 17685507}, {64422, 17685763}, {64426, 17686019}, {64430, 17686275}, + {64432, 17686531}, {64434, 1}, {64451, 2}, {64467, 17686787}, + {64471, 16911619}, {64473, 17687043}, {64475, 17687299}, {64477, 33688835}, + {64478, 17687555}, {64480, 17687811}, {64482, 17688067}, {64484, 17688323}, + {64488, 17688579}, {64490, 34466051}, {64492, 34466563}, {64494, 34467075}, + {64496, 34467587}, {64498, 34468099}, {64500, 34468611}, {64502, 34469123}, + {64505, 34469635}, {64508, 17692931}, {64512, 34470403}, {64513, 34470915}, + {64514, 34471427}, {64515, 34469635}, {64516, 34471939}, {64517, 34472451}, + {64518, 34472963}, {64519, 34473475}, {64520, 34473987}, {64521, 34474499}, + {64522, 34475011}, {64523, 34475523}, {64524, 34476035}, {64525, 34476547}, + {64526, 34477059}, {64527, 34477571}, {64528, 34478083}, {64529, 34478595}, + {64530, 34479107}, {64531, 34479619}, {64532, 34480131}, {64533, 34480643}, + {64534, 34481155}, {64535, 34480899}, {64536, 34481667}, {64537, 34482179}, + {64538, 34482691}, {64539, 34483203}, {64540, 34483715}, {64541, 34484227}, + {64542, 34484739}, {64543, 34485251}, {64544, 34485763}, {64545, 34486275}, + {64546, 34486787}, {64547, 34487299}, {64548, 34487811}, {64549, 34488323}, + {64550, 34488835}, {64551, 34489347}, {64552, 34489859}, {64553, 34490371}, + {64554, 34490883}, {64555, 34491395}, {64556, 34491907}, {64557, 34492419}, + {64558, 34492931}, {64559, 34493443}, {64560, 34493955}, {64561, 34494467}, + {64562, 34494979}, {64563, 34495491}, {64564, 34496003}, {64565, 34496515}, + {64566, 34497027}, {64567, 34497539}, {64568, 34498051}, {64569, 34498563}, + {64570, 34499075}, {64571, 34499587}, {64572, 34500099}, {64573, 34500611}, + {64574, 34501123}, {64575, 34501635}, {64576, 34502147}, {64577, 34502659}, + {64578, 34503171}, {64579, 34503683}, {64580, 34504195}, {64581, 34504707}, + {64582, 34481411}, {64583, 34481923}, {64584, 34505219}, {64585, 34505731}, + {64586, 34506243}, {64587, 34506755}, {64588, 34507267}, {64589, 34507779}, + {64590, 34508291}, {64591, 34508803}, {64592, 34509315}, {64593, 34509827}, + {64594, 34510339}, {64595, 34510851}, {64596, 34511363}, {64597, 34480387}, + {64598, 34511875}, {64599, 34512387}, {64600, 34504451}, {64601, 34512899}, + {64602, 34511619}, {64603, 34513411}, {64604, 34513923}, {64605, 34514435}, + {64606, 51292163}, {64607, 51292931}, {64608, 51293699}, {64609, 51294467}, + {64610, 51295235}, {64611, 51296003}, {64612, 34519555}, {64613, 34520067}, + {64614, 34471427}, {64615, 34520579}, {64616, 34469635}, {64617, 34471939}, + {64618, 34521091}, {64619, 34521603}, {64620, 34473987}, {64621, 34522115}, + {64622, 34474499}, {64623, 34475011}, {64624, 34522627}, {64625, 34523139}, + {64626, 34477059}, {64627, 34523651}, {64628, 34477571}, {64629, 34478083}, + {64630, 34524163}, {64631, 34524675}, {64632, 34479107}, {64633, 34525187}, + {64634, 34479619}, {64635, 34480131}, {64636, 34494467}, {64637, 34494979}, + {64638, 34496515}, {64639, 34497027}, {64640, 34497539}, {64641, 34499587}, + {64642, 34500099}, {64643, 34500611}, {64644, 34501123}, {64645, 34503171}, + {64646, 34503683}, {64647, 34504195}, {64648, 34525699}, {64649, 34505219}, + {64650, 34526211}, {64651, 34526723}, {64652, 34508291}, {64653, 34527235}, + {64654, 34508803}, {64655, 34509315}, {64656, 34514435}, {64657, 34527747}, + {64658, 34528259}, {64659, 34504451}, {64660, 34506499}, {64661, 34512899}, + {64662, 34511619}, {64663, 34470403}, {64664, 34470915}, {64665, 34528771}, + {64666, 34471427}, {64667, 34529283}, {64668, 34472451}, {64669, 34472963}, + {64670, 34473475}, {64671, 34473987}, {64672, 34529795}, {64673, 34475523}, + {64674, 34476035}, {64675, 34476547}, {64676, 34477059}, {64677, 34530307}, + {64678, 34479107}, {64679, 34480643}, {64680, 34481155}, {64681, 34480899}, + {64682, 34481667}, {64683, 34482179}, {64684, 34483203}, {64685, 34483715}, + {64686, 34484227}, {64687, 34484739}, {64688, 34485251}, {64689, 34485763}, + {64690, 34530819}, {64691, 34486275}, {64692, 34486787}, {64693, 34487299}, + {64694, 34487811}, {64695, 34488323}, {64696, 34488835}, {64697, 34489859}, + {64698, 34490371}, {64699, 34490883}, {64700, 34491395}, {64701, 34491907}, + {64702, 34492419}, {64703, 34492931}, {64704, 34493443}, {64705, 34493955}, + {64706, 34495491}, {64707, 34496003}, {64708, 34498051}, {64709, 34498563}, + {64710, 34499075}, {64711, 34499587}, {64712, 34500099}, {64713, 34501635}, + {64714, 34502147}, {64715, 34502659}, {64716, 34503171}, {64717, 34531331}, + {64718, 34504707}, {64719, 34481411}, {64720, 34481923}, {64721, 34505219}, + {64722, 34506755}, {64723, 34507267}, {64724, 34507779}, {64725, 34508291}, + {64726, 34531843}, {64727, 34509827}, {64728, 34510339}, {64729, 34532355}, + {64730, 34480387}, {64731, 34511875}, {64732, 34512387}, {64733, 34504451}, + {64734, 34509571}, {64735, 34471427}, {64736, 34529283}, {64737, 34473987}, + {64738, 34529795}, {64739, 34477059}, {64740, 34530307}, {64741, 34479107}, + {64742, 34532867}, {64743, 34485251}, {64744, 34533379}, {64745, 34533891}, + {64746, 34534403}, {64747, 34499587}, {64748, 34500099}, {64749, 34503171}, + {64750, 34508291}, {64751, 34531843}, {64752, 34504451}, {64753, 34509571}, + {64754, 51312131}, {64755, 51312899}, {64756, 51313667}, {64757, 34537219}, + {64758, 34537731}, {64759, 34538243}, {64760, 34538755}, {64761, 34539267}, + {64762, 34539779}, {64763, 34540291}, {64764, 34540803}, {64765, 34541315}, + {64766, 34541827}, {64767, 34542339}, {64768, 34512131}, {64769, 34542851}, + {64770, 34543363}, {64771, 34543875}, {64772, 34512643}, {64773, 34544387}, + {64774, 34544899}, {64775, 34545411}, {64776, 34545923}, {64777, 34546435}, + {64778, 34546947}, {64779, 34547459}, {64780, 34533891}, {64781, 34547971}, + {64782, 34548483}, {64783, 34548995}, {64784, 34549507}, {64785, 34537219}, + {64786, 34537731}, {64787, 34538243}, {64788, 34538755}, {64789, 34539267}, + {64790, 34539779}, {64791, 34540291}, {64792, 34540803}, {64793, 34541315}, + {64794, 34541827}, {64795, 34542339}, {64796, 34512131}, {64797, 34542851}, + {64798, 34543363}, {64799, 34543875}, {64800, 34512643}, {64801, 34544387}, + {64802, 34544899}, {64803, 34545411}, {64804, 34545923}, {64805, 34546435}, + {64806, 34546947}, {64807, 34547459}, {64808, 34533891}, {64809, 34547971}, + {64810, 34548483}, {64811, 34548995}, {64812, 34549507}, {64813, 34546435}, + {64814, 34546947}, {64815, 34547459}, {64816, 34533891}, {64817, 34533379}, + {64818, 34534403}, {64819, 34489347}, {64820, 34483715}, {64821, 34484227}, + {64822, 34484739}, {64823, 34546435}, {64824, 34546947}, {64825, 34547459}, + {64826, 34489347}, {64827, 34489859}, {64828, 34550019}, {64830, 1}, + {64848, 51327747}, {64849, 51328515}, {64851, 51329283}, {64852, 51330051}, + {64853, 51330819}, {64854, 51331587}, {64855, 51332355}, {64856, 51258371}, + {64858, 51333123}, {64859, 51333891}, {64860, 51334659}, {64861, 51335427}, + {64862, 51336195}, {64863, 51336963}, {64865, 51337731}, {64866, 51338499}, + {64868, 51339267}, {64870, 51340035}, {64871, 51340803}, {64873, 51341571}, + {64874, 51342339}, {64876, 51343107}, {64878, 51343875}, {64879, 51344643}, + {64881, 51345411}, {64883, 51346179}, {64884, 51346947}, {64885, 51347715}, + {64886, 51348483}, {64888, 51349251}, {64889, 51350019}, {64890, 51350787}, + {64891, 51351555}, {64892, 51352323}, {64894, 51353091}, {64895, 51353859}, + {64896, 51354627}, {64897, 51355395}, {64898, 51356163}, {64899, 51356931}, + {64901, 51357699}, {64903, 51358467}, {64905, 51359235}, {64906, 51258627}, + {64907, 51360003}, {64908, 51360771}, {64909, 51281923}, {64910, 51259139}, + {64911, 51361539}, {64912, 2}, {64914, 51362307}, {64915, 51363075}, + {64916, 51363843}, {64917, 51364611}, {64918, 51365379}, {64919, 51366147}, + {64921, 51366915}, {64922, 51367683}, {64923, 51368451}, {64924, 51369219}, + {64926, 51369987}, {64927, 51370755}, {64928, 51371523}, {64929, 51372291}, + {64930, 51373059}, {64931, 51373827}, {64932, 51374595}, {64933, 51375363}, + {64934, 51376131}, {64935, 51376899}, {64936, 51377667}, {64937, 51378435}, + {64938, 51379203}, {64939, 51379971}, {64940, 51380739}, {64941, 51381507}, + {64942, 51289091}, {64943, 51382275}, {64944, 51383043}, {64945, 51383811}, + {64946, 51384579}, {64947, 51385347}, {64948, 51353091}, {64949, 51354627}, + {64950, 51386115}, {64951, 51386883}, {64952, 51387651}, {64953, 51388419}, + {64954, 51389187}, {64955, 51389955}, {64956, 51389187}, {64957, 51387651}, + {64958, 51390723}, {64959, 51391491}, {64960, 51392259}, {64961, 51393027}, + {64962, 51393795}, {64963, 51389955}, {64964, 51347715}, {64965, 51340035}, + {64966, 51394563}, {64967, 51395331}, {64968, 2}, {64975, 1}, + {64976, 2}, {65008, 51396099}, {65009, 51396867}, {65010, 68174851}, + {65011, 68175875}, {65012, 68176899}, {65013, 68177923}, {65014, 68178947}, + {65015, 68179971}, {65016, 68180995}, {65017, 51404803}, {65018, 303063811}, + {65019, 135296259}, {65020, 68189443}, {65021, 1}, {65024, 0}, + {65040, 17858819}, {65041, 17859075}, {65042, 2}, {65043, 17121027}, + {65044, 16848643}, {65045, 17042947}, {65046, 17043971}, {65047, 17859331}, + {65048, 17859587}, {65049, 2}, {65056, 1}, {65072, 2}, + {65073, 17859843}, {65074, 17860099}, {65075, 17860355}, {65077, 17047811}, + {65078, 17048067}, {65079, 17860611}, {65080, 17860867}, {65081, 17861123}, + {65082, 17861379}, {65083, 17861635}, {65084, 17861891}, {65085, 17862147}, + {65086, 17862403}, {65087, 17077763}, {65088, 17078019}, {65089, 17862659}, + {65090, 17862915}, {65091, 17863171}, {65092, 17863427}, {65093, 1}, + {65095, 17863683}, {65096, 17863939}, {65097, 33820675}, {65101, 17860355}, + {65104, 17858819}, {65105, 17859075}, {65106, 2}, {65108, 16848643}, + {65109, 17121027}, {65110, 17043971}, {65111, 17042947}, {65112, 17859843}, + {65113, 17047811}, {65114, 17048067}, {65115, 17860611}, {65116, 17860867}, + {65117, 17861123}, {65118, 17861379}, {65119, 17864195}, {65120, 17864451}, + {65121, 17864707}, {65122, 17047043}, {65123, 17864963}, {65124, 17865219}, + {65125, 17865475}, {65126, 17047555}, {65127, 2}, {65128, 17865731}, + {65129, 17865987}, {65130, 17866243}, {65131, 17866499}, {65132, 2}, + {65136, 34643971}, {65137, 34644483}, {65138, 34514947}, {65139, 1}, + {65140, 34515715}, {65141, 2}, {65142, 34516483}, {65143, 34534915}, + {65144, 34517251}, {65145, 34535683}, {65146, 34518019}, {65147, 34536451}, + {65148, 34518787}, {65149, 34644995}, {65150, 34645507}, {65151, 34646019}, + {65152, 17869315}, {65153, 17869571}, {65155, 17869827}, {65157, 17870083}, + {65159, 17870339}, {65161, 17688835}, {65165, 16910595}, {65167, 17695235}, + {65171, 17870595}, {65173, 17698307}, {65177, 17701379}, {65181, 17693443}, + {65185, 17693955}, {65189, 17696515}, {65193, 17846019}, {65195, 17736195}, + {65197, 17736707}, {65199, 17743107}, {65201, 17706499}, {65205, 17756675}, + {65209, 17708547}, {65213, 17709571}, {65217, 17711619}, {65221, 17712643}, + {65225, 17713155}, {65229, 17714179}, {65233, 17715203}, {65237, 17718275}, + {65241, 17720323}, {65245, 17722627}, {65249, 17694467}, {65253, 17729539}, + {65257, 17732611}, {65261, 16911107}, {65263, 17688579}, {65265, 16912131}, + {65269, 34648067}, {65271, 34648579}, {65273, 34649091}, {65275, 34633987}, + {65277, 2}, {65279, 0}, {65280, 2}, {65281, 17042947}, + {65282, 17872387}, {65283, 17864195}, {65284, 17865987}, {65285, 17866243}, + {65286, 17864451}, {65287, 17872643}, {65288, 17047811}, {65289, 17048067}, + {65290, 17864707}, {65291, 17047043}, {65292, 17858819}, {65293, 17864963}, + {65294, 17207043}, {65295, 17048579}, {65296, 17045507}, {65297, 16786947}, + {65298, 16785155}, {65299, 16785411}, {65300, 16787715}, {65301, 17045763}, + {65302, 17046019}, {65303, 17046275}, {65304, 17046531}, {65305, 17046787}, + {65306, 17121027}, {65307, 16848643}, {65308, 17865219}, {65309, 17047555}, + {65310, 17865475}, {65311, 17043971}, {65312, 17866499}, {65313, 16777219}, + {65314, 16777475}, {65315, 16777731}, {65316, 16777987}, {65317, 16778243}, + {65318, 16778499}, {65319, 16778755}, {65320, 16779011}, {65321, 16779267}, + {65322, 16779523}, {65323, 16779779}, {65324, 16780035}, {65325, 16780291}, + {65326, 16780547}, {65327, 16780803}, {65328, 16781059}, {65329, 16781315}, + {65330, 16781571}, {65331, 16781827}, {65332, 16782083}, {65333, 16782339}, + {65334, 16782595}, {65335, 16782851}, {65336, 16783107}, {65337, 16783363}, + {65338, 16783619}, {65339, 17863683}, {65340, 17865731}, {65341, 17863939}, + {65342, 17872899}, {65343, 17860355}, {65344, 17037059}, {65345, 16777219}, + {65346, 16777475}, {65347, 16777731}, {65348, 16777987}, {65349, 16778243}, + {65350, 16778499}, {65351, 16778755}, {65352, 16779011}, {65353, 16779267}, + {65354, 16779523}, {65355, 16779779}, {65356, 16780035}, {65357, 16780291}, + {65358, 16780547}, {65359, 16780803}, {65360, 16781059}, {65361, 16781315}, + {65362, 16781571}, {65363, 16781827}, {65364, 16782083}, {65365, 16782339}, + {65366, 16782595}, {65367, 16782851}, {65368, 16783107}, {65369, 16783363}, + {65370, 16783619}, {65371, 17860611}, {65372, 17873155}, {65373, 17860867}, + {65374, 17873411}, {65375, 17873667}, {65376, 17873923}, {65377, 17207043}, + {65378, 17862659}, {65379, 17862915}, {65380, 17859075}, {65381, 17874179}, + {65382, 17327363}, {65383, 17329923}, {65384, 17372931}, {65385, 17874435}, + {65386, 17374467}, {65387, 17334019}, {65388, 17874691}, {65389, 17344259}, + {65390, 17390083}, {65391, 17339651}, {65392, 17328643}, {65393, 17316099}, + {65394, 17316355}, {65395, 17316611}, {65396, 17316867}, {65397, 17317123}, + {65398, 17317379}, {65399, 17317635}, {65400, 17317891}, {65401, 17318147}, + {65402, 17209603}, {65403, 17318403}, {65404, 17318659}, {65405, 17318915}, + {65406, 17319171}, {65407, 17319427}, {65408, 17319683}, {65409, 17319939}, + {65410, 17320195}, {65411, 17320451}, {65412, 17209859}, {65413, 17320707}, + {65414, 17320963}, {65415, 17321219}, {65416, 17321475}, {65417, 17321731}, + {65418, 17321987}, {65419, 17322243}, {65420, 17322499}, {65421, 17322755}, + {65422, 17323011}, {65423, 17323267}, {65424, 17323523}, {65425, 17323779}, + {65426, 17324035}, {65427, 17324291}, {65428, 17324547}, {65429, 17324803}, + {65430, 17325059}, {65431, 17325315}, {65432, 17325571}, {65433, 17325827}, + {65434, 17326083}, {65435, 17326339}, {65436, 17326595}, {65437, 17330435}, + {65438, 17208323}, {65439, 17208835}, {65440, 0}, {65441, 17210115}, + {65442, 17210371}, {65443, 17210627}, {65444, 17210883}, {65445, 17211139}, + {65446, 17211395}, {65447, 17211651}, {65448, 17211907}, {65449, 17212163}, + {65450, 17212419}, {65451, 17212675}, {65452, 17212931}, {65453, 17213187}, + {65454, 17213443}, {65455, 17213699}, {65456, 17213955}, {65457, 17214211}, + {65458, 17214467}, {65459, 17214723}, {65460, 17214979}, {65461, 17215235}, + {65462, 17215491}, {65463, 17215747}, {65464, 17216003}, {65465, 17216259}, + {65466, 17216515}, {65467, 17216771}, {65468, 17217027}, {65469, 17217283}, + {65470, 17217539}, {65471, 2}, {65474, 17217795}, {65475, 17218051}, + {65476, 17218307}, {65477, 17218563}, {65478, 17218819}, {65479, 17219075}, + {65480, 2}, {65482, 17219331}, {65483, 17219587}, {65484, 17219843}, + {65485, 17220099}, {65486, 17220355}, {65487, 17220611}, {65488, 2}, + {65490, 17220867}, {65491, 17221123}, {65492, 17221379}, {65493, 17221635}, + {65494, 17221891}, {65495, 17222147}, {65496, 2}, {65498, 17222403}, + {65499, 17222659}, {65500, 17222915}, {65501, 2}, {65504, 17874947}, + {65505, 17875203}, {65506, 17875459}, {65507, 33561859}, {65508, 17875715}, + {65509, 17875971}, {65510, 17876227}, {65511, 2}, {65512, 17876483}, + {65513, 17876739}, {65514, 17876995}, {65515, 17877251}, {65516, 17877507}, + {65517, 17877763}, {65518, 17878019}, {65519, 2}, {65536, 1}, + {65548, 2}, {65549, 1}, {65575, 2}, {65576, 1}, + {65595, 2}, {65596, 1}, {65598, 2}, {65599, 1}, + {65614, 2}, {65616, 1}, {65630, 2}, {65664, 1}, + {65787, 2}, {65792, 1}, {65795, 2}, {65799, 1}, + {65844, 2}, {65847, 1}, {65935, 2}, {65936, 1}, + {65949, 2}, {65952, 1}, {65953, 2}, {66000, 1}, + {66046, 2}, {66176, 1}, {66205, 2}, {66208, 1}, + {66257, 2}, {66272, 1}, {66300, 2}, {66304, 1}, + {66340, 2}, {66349, 1}, {66379, 2}, {66384, 1}, + {66427, 2}, {66432, 1}, {66462, 2}, {66463, 1}, + {66500, 2}, {66504, 1}, {66518, 2}, {66560, 17878275}, + {66561, 17878531}, {66562, 17878787}, {66563, 17879043}, {66564, 17879299}, + {66565, 17879555}, {66566, 17879811}, {66567, 17880067}, {66568, 17880323}, + {66569, 17880579}, {66570, 17880835}, {66571, 17881091}, {66572, 17881347}, + {66573, 17881603}, {66574, 17881859}, {66575, 17882115}, {66576, 17882371}, + {66577, 17882627}, {66578, 17882883}, {66579, 17883139}, {66580, 17883395}, + {66581, 17883651}, {66582, 17883907}, {66583, 17884163}, {66584, 17884419}, + {66585, 17884675}, {66586, 17884931}, {66587, 17885187}, {66588, 17885443}, + {66589, 17885699}, {66590, 17885955}, {66591, 17886211}, {66592, 17886467}, + {66593, 17886723}, {66594, 17886979}, {66595, 17887235}, {66596, 17887491}, + {66597, 17887747}, {66598, 17888003}, {66599, 17888259}, {66600, 1}, + {66718, 2}, {66720, 1}, {66730, 2}, {66736, 17888515}, + {66737, 17888771}, {66738, 17889027}, {66739, 17889283}, {66740, 17889539}, + {66741, 17889795}, {66742, 17890051}, {66743, 17890307}, {66744, 17890563}, + {66745, 17890819}, {66746, 17891075}, {66747, 17891331}, {66748, 17891587}, + {66749, 17891843}, {66750, 17892099}, {66751, 17892355}, {66752, 17892611}, + {66753, 17892867}, {66754, 17893123}, {66755, 17893379}, {66756, 17893635}, + {66757, 17893891}, {66758, 17894147}, {66759, 17894403}, {66760, 17894659}, + {66761, 17894915}, {66762, 17895171}, {66763, 17895427}, {66764, 17895683}, + {66765, 17895939}, {66766, 17896195}, {66767, 17896451}, {66768, 17896707}, + {66769, 17896963}, {66770, 17897219}, {66771, 17897475}, {66772, 2}, + {66776, 1}, {66812, 2}, {66816, 1}, {66856, 2}, + {66864, 1}, {66916, 2}, {66927, 1}, {66928, 17897731}, + {66929, 17897987}, {66930, 17898243}, {66931, 17898499}, {66932, 17898755}, + {66933, 17899011}, {66934, 17899267}, {66935, 17899523}, {66936, 17899779}, + {66937, 17900035}, {66938, 17900291}, {66939, 2}, {66940, 17900547}, + {66941, 17900803}, {66942, 17901059}, {66943, 17901315}, {66944, 17901571}, + {66945, 17901827}, {66946, 17902083}, {66947, 17902339}, {66948, 17902595}, + {66949, 17902851}, {66950, 17903107}, {66951, 17903363}, {66952, 17903619}, + {66953, 17903875}, {66954, 17904131}, {66955, 2}, {66956, 17904387}, + {66957, 17904643}, {66958, 17904899}, {66959, 17905155}, {66960, 17905411}, + {66961, 17905667}, {66962, 17905923}, {66963, 2}, {66964, 17906179}, + {66965, 17906435}, {66966, 2}, {66967, 1}, {66978, 2}, + {66979, 1}, {66994, 2}, {66995, 1}, {67002, 2}, + {67003, 1}, {67005, 2}, {67008, 1}, {67060, 2}, + {67072, 1}, {67383, 2}, {67392, 1}, {67414, 2}, + {67424, 1}, {67432, 2}, {67456, 1}, {67457, 17906691}, + {67458, 17906947}, {67459, 16791043}, {67460, 17907203}, {67461, 16814083}, + {67462, 2}, {67463, 17907459}, {67464, 17907715}, {67465, 17907971}, + {67466, 17908227}, {67467, 16815363}, {67468, 16815619}, {67469, 17908483}, + {67470, 17908739}, {67471, 17908995}, {67472, 17909251}, {67473, 17527555}, + {67474, 17909507}, {67475, 16817155}, {67476, 17909763}, {67477, 16802051}, + {67478, 17910019}, {67479, 17910275}, {67480, 17910531}, {67481, 17910787}, + {67482, 17911043}, {67483, 17523459}, {67484, 17911299}, {67485, 17911555}, + {67486, 17911811}, {67487, 17912067}, {67488, 17912323}, {67489, 17912579}, + {67490, 16795395}, {67491, 17912835}, {67492, 17913091}, {67493, 16781315}, + {67494, 17913347}, {67495, 17913603}, {67496, 17135875}, {67497, 17913859}, + {67498, 16819971}, {67499, 17914115}, {67500, 17914371}, {67501, 17914627}, + {67502, 17914883}, {67503, 16820995}, {67504, 17915139}, {67505, 2}, + {67506, 17915395}, {67507, 17915651}, {67508, 17915907}, {67509, 17916163}, + {67510, 17916419}, {67511, 17916675}, {67512, 17916931}, {67513, 17917187}, + {67514, 17917443}, {67515, 2}, {67584, 1}, {67590, 2}, + {67592, 1}, {67593, 2}, {67594, 1}, {67638, 2}, + {67639, 1}, {67641, 2}, {67644, 1}, {67645, 2}, + {67647, 1}, {67670, 2}, {67671, 1}, {67743, 2}, + {67751, 1}, {67760, 2}, {67808, 1}, {67827, 2}, + {67828, 1}, {67830, 2}, {67835, 1}, {67868, 2}, + {67871, 1}, {67898, 2}, {67903, 1}, {67904, 2}, + {67968, 1}, {68024, 2}, {68028, 1}, {68048, 2}, + {68050, 1}, {68100, 2}, {68101, 1}, {68103, 2}, + {68108, 1}, {68116, 2}, {68117, 1}, {68120, 2}, + {68121, 1}, {68150, 2}, {68152, 1}, {68155, 2}, + {68159, 1}, {68169, 2}, {68176, 1}, {68185, 2}, + {68192, 1}, {68256, 2}, {68288, 1}, {68327, 2}, + {68331, 1}, {68343, 2}, {68352, 1}, {68406, 2}, + {68409, 1}, {68438, 2}, {68440, 1}, {68467, 2}, + {68472, 1}, {68498, 2}, {68505, 1}, {68509, 2}, + {68521, 1}, {68528, 2}, {68608, 1}, {68681, 2}, + {68736, 17917699}, {68737, 17917955}, {68738, 17918211}, {68739, 17918467}, + {68740, 17918723}, {68741, 17918979}, {68742, 17919235}, {68743, 17919491}, + {68744, 17919747}, {68745, 17920003}, {68746, 17920259}, {68747, 17920515}, + {68748, 17920771}, {68749, 17921027}, {68750, 17921283}, {68751, 17921539}, + {68752, 17921795}, {68753, 17922051}, {68754, 17922307}, {68755, 17922563}, + {68756, 17922819}, {68757, 17923075}, {68758, 17923331}, {68759, 17923587}, + {68760, 17923843}, {68761, 17924099}, {68762, 17924355}, {68763, 17924611}, + {68764, 17924867}, {68765, 17925123}, {68766, 17925379}, {68767, 17925635}, + {68768, 17925891}, {68769, 17926147}, {68770, 17926403}, {68771, 17926659}, + {68772, 17926915}, {68773, 17927171}, {68774, 17927427}, {68775, 17927683}, + {68776, 17927939}, {68777, 17928195}, {68778, 17928451}, {68779, 17928707}, + {68780, 17928963}, {68781, 17929219}, {68782, 17929475}, {68783, 17929731}, + {68784, 17929987}, {68785, 17930243}, {68786, 17930499}, {68787, 2}, + {68800, 1}, {68851, 2}, {68858, 1}, {68904, 2}, + {68912, 1}, {68922, 2}, {68928, 1}, {68944, 17930755}, + {68945, 17931011}, {68946, 17931267}, {68947, 17931523}, {68948, 17931779}, + {68949, 17932035}, {68950, 17932291}, {68951, 17932547}, {68952, 17932803}, + {68953, 17933059}, {68954, 17933315}, {68955, 17933571}, {68956, 17933827}, + {68957, 17934083}, {68958, 17934339}, {68959, 17934595}, {68960, 17934851}, + {68961, 17935107}, {68962, 17935363}, {68963, 17935619}, {68964, 17935875}, + {68965, 17936131}, {68966, 2}, {68969, 1}, {68998, 2}, + {69006, 1}, {69008, 2}, {69216, 1}, {69247, 2}, + {69248, 1}, {69290, 2}, {69291, 1}, {69294, 2}, + {69296, 1}, {69298, 2}, {69314, 1}, {69317, 2}, + {69372, 1}, {69416, 2}, {69424, 1}, {69466, 2}, + {69488, 1}, {69514, 2}, {69552, 1}, {69580, 2}, + {69600, 1}, {69623, 2}, {69632, 1}, {69710, 2}, + {69714, 1}, {69750, 2}, {69759, 1}, {69821, 2}, + {69822, 1}, {69827, 2}, {69840, 1}, {69865, 2}, + {69872, 1}, {69882, 2}, {69888, 1}, {69941, 2}, + {69942, 1}, {69960, 2}, {69968, 1}, {70007, 2}, + {70016, 1}, {70112, 2}, {70113, 1}, {70133, 2}, + {70144, 1}, {70162, 2}, {70163, 1}, {70210, 2}, + {70272, 1}, {70279, 2}, {70280, 1}, {70281, 2}, + {70282, 1}, {70286, 2}, {70287, 1}, {70302, 2}, + {70303, 1}, {70314, 2}, {70320, 1}, {70379, 2}, + {70384, 1}, {70394, 2}, {70400, 1}, {70404, 2}, + {70405, 1}, {70413, 2}, {70415, 1}, {70417, 2}, + {70419, 1}, {70441, 2}, {70442, 1}, {70449, 2}, + {70450, 1}, {70452, 2}, {70453, 1}, {70458, 2}, + {70459, 1}, {70469, 2}, {70471, 1}, {70473, 2}, + {70475, 1}, {70478, 2}, {70480, 1}, {70481, 2}, + {70487, 1}, {70488, 2}, {70493, 1}, {70500, 2}, + {70502, 1}, {70509, 2}, {70512, 1}, {70517, 2}, + {70528, 1}, {70538, 2}, {70539, 1}, {70540, 2}, + {70542, 1}, {70543, 2}, {70544, 1}, {70582, 2}, + {70583, 1}, {70593, 2}, {70594, 1}, {70595, 2}, + {70597, 1}, {70598, 2}, {70599, 1}, {70603, 2}, + {70604, 1}, {70614, 2}, {70615, 1}, {70617, 2}, + {70625, 1}, {70627, 2}, {70656, 1}, {70748, 2}, + {70749, 1}, {70754, 2}, {70784, 1}, {70856, 2}, + {70864, 1}, {70874, 2}, {71040, 1}, {71094, 2}, + {71096, 1}, {71134, 2}, {71168, 1}, {71237, 2}, + {71248, 1}, {71258, 2}, {71264, 1}, {71277, 2}, + {71296, 1}, {71354, 2}, {71360, 1}, {71370, 2}, + {71376, 1}, {71396, 2}, {71424, 1}, {71451, 2}, + {71453, 1}, {71468, 2}, {71472, 1}, {71495, 2}, + {71680, 1}, {71740, 2}, {71840, 17936387}, {71841, 17936643}, + {71842, 17936899}, {71843, 17937155}, {71844, 17937411}, {71845, 17937667}, + {71846, 17937923}, {71847, 17938179}, {71848, 17938435}, {71849, 17938691}, + {71850, 17938947}, {71851, 17939203}, {71852, 17939459}, {71853, 17939715}, + {71854, 17939971}, {71855, 17940227}, {71856, 17940483}, {71857, 17940739}, + {71858, 17940995}, {71859, 17941251}, {71860, 17941507}, {71861, 17941763}, + {71862, 17942019}, {71863, 17942275}, {71864, 17942531}, {71865, 17942787}, + {71866, 17943043}, {71867, 17943299}, {71868, 17943555}, {71869, 17943811}, + {71870, 17944067}, {71871, 17944323}, {71872, 1}, {71923, 2}, + {71935, 1}, {71943, 2}, {71945, 1}, {71946, 2}, + {71948, 1}, {71956, 2}, {71957, 1}, {71959, 2}, + {71960, 1}, {71990, 2}, {71991, 1}, {71993, 2}, + {71995, 1}, {72007, 2}, {72016, 1}, {72026, 2}, + {72096, 1}, {72104, 2}, {72106, 1}, {72152, 2}, + {72154, 1}, {72165, 2}, {72192, 1}, {72264, 2}, + {72272, 1}, {72355, 2}, {72368, 1}, {72441, 2}, + {72448, 1}, {72458, 2}, {72640, 1}, {72674, 2}, + {72688, 1}, {72698, 2}, {72704, 1}, {72713, 2}, + {72714, 1}, {72759, 2}, {72760, 1}, {72774, 2}, + {72784, 1}, {72813, 2}, {72816, 1}, {72848, 2}, + {72850, 1}, {72872, 2}, {72873, 1}, {72887, 2}, + {72960, 1}, {72967, 2}, {72968, 1}, {72970, 2}, + {72971, 1}, {73015, 2}, {73018, 1}, {73019, 2}, + {73020, 1}, {73022, 2}, {73023, 1}, {73032, 2}, + {73040, 1}, {73050, 2}, {73056, 1}, {73062, 2}, + {73063, 1}, {73065, 2}, {73066, 1}, {73103, 2}, + {73104, 1}, {73106, 2}, {73107, 1}, {73113, 2}, + {73120, 1}, {73130, 2}, {73440, 1}, {73465, 2}, + {73472, 1}, {73489, 2}, {73490, 1}, {73531, 2}, + {73534, 1}, {73563, 2}, {73648, 1}, {73649, 2}, + {73664, 1}, {73714, 2}, {73727, 1}, {74650, 2}, + {74752, 1}, {74863, 2}, {74864, 1}, {74869, 2}, + {74880, 1}, {75076, 2}, {77712, 1}, {77811, 2}, + {77824, 1}, {78896, 2}, {78912, 1}, {78934, 2}, + {78944, 1}, {82939, 2}, {82944, 1}, {83527, 2}, + {90368, 1}, {90426, 2}, {92160, 1}, {92729, 2}, + {92736, 1}, {92767, 2}, {92768, 1}, {92778, 2}, + {92782, 1}, {92863, 2}, {92864, 1}, {92874, 2}, + {92880, 1}, {92910, 2}, {92912, 1}, {92918, 2}, + {92928, 1}, {92998, 2}, {93008, 1}, {93018, 2}, + {93019, 1}, {93026, 2}, {93027, 1}, {93048, 2}, + {93053, 1}, {93072, 2}, {93504, 1}, {93562, 2}, + {93760, 17944579}, {93761, 17944835}, {93762, 17945091}, {93763, 17945347}, + {93764, 17945603}, {93765, 17945859}, {93766, 17946115}, {93767, 17946371}, + {93768, 17946627}, {93769, 17946883}, {93770, 17947139}, {93771, 17947395}, + {93772, 17947651}, {93773, 17947907}, {93774, 17948163}, {93775, 17948419}, + {93776, 17948675}, {93777, 17948931}, {93778, 17949187}, {93779, 17949443}, + {93780, 17949699}, {93781, 17949955}, {93782, 17950211}, {93783, 17950467}, + {93784, 17950723}, {93785, 17950979}, {93786, 17951235}, {93787, 17951491}, + {93788, 17951747}, {93789, 17952003}, {93790, 17952259}, {93791, 17952515}, + {93792, 1}, {93851, 2}, {93952, 1}, {94027, 2}, + {94031, 1}, {94088, 2}, {94095, 1}, {94112, 2}, + {94176, 1}, {94181, 2}, {94192, 1}, {94194, 2}, + {94208, 1}, {100344, 2}, {100352, 1}, {101590, 2}, + {101631, 1}, {101641, 2}, {110576, 1}, {110580, 2}, + {110581, 1}, {110588, 2}, {110589, 1}, {110591, 2}, + {110592, 1}, {110883, 2}, {110898, 1}, {110899, 2}, + {110928, 1}, {110931, 2}, {110933, 1}, {110934, 2}, + {110948, 1}, {110952, 2}, {110960, 1}, {111356, 2}, + {113664, 1}, {113771, 2}, {113776, 1}, {113789, 2}, + {113792, 1}, {113801, 2}, {113808, 1}, {113818, 2}, + {113820, 1}, {113824, 0}, {113828, 2}, {117760, 1}, + {117974, 16777219}, {117975, 16777475}, {117976, 16777731}, {117977, 16777987}, + {117978, 16778243}, {117979, 16778499}, {117980, 16778755}, {117981, 16779011}, + {117982, 16779267}, {117983, 16779523}, {117984, 16779779}, {117985, 16780035}, + {117986, 16780291}, {117987, 16780547}, {117988, 16780803}, {117989, 16781059}, + {117990, 16781315}, {117991, 16781571}, {117992, 16781827}, {117993, 16782083}, + {117994, 16782339}, {117995, 16782595}, {117996, 16782851}, {117997, 16783107}, + {117998, 16783363}, {117999, 16783619}, {118000, 17045507}, {118001, 16786947}, + {118002, 16785155}, {118003, 16785411}, {118004, 16787715}, {118005, 17045763}, + {118006, 17046019}, {118007, 17046275}, {118008, 17046531}, {118009, 17046787}, + {118010, 2}, {118016, 1}, {118452, 2}, {118528, 1}, + {118574, 2}, {118576, 1}, {118599, 2}, {118608, 1}, + {118724, 2}, {118784, 1}, {119030, 2}, {119040, 1}, + {119079, 2}, {119081, 1}, {119134, 34729987}, {119135, 34730499}, + {119136, 51508227}, {119137, 51508995}, {119138, 51509763}, {119139, 51510531}, + {119140, 51511299}, {119141, 1}, {119155, 0}, {119163, 1}, + {119227, 34734851}, {119228, 34735363}, {119229, 51513091}, {119230, 51513859}, + {119231, 51514627}, {119232, 51515395}, {119233, 1}, {119275, 2}, + {119296, 1}, {119366, 2}, {119488, 1}, {119508, 2}, + {119520, 1}, {119540, 2}, {119552, 1}, {119639, 2}, + {119648, 1}, {119673, 2}, {119808, 16777219}, {119809, 16777475}, + {119810, 16777731}, {119811, 16777987}, {119812, 16778243}, {119813, 16778499}, + {119814, 16778755}, {119815, 16779011}, {119816, 16779267}, {119817, 16779523}, + {119818, 16779779}, {119819, 16780035}, {119820, 16780291}, {119821, 16780547}, + {119822, 16780803}, {119823, 16781059}, {119824, 16781315}, {119825, 16781571}, + {119826, 16781827}, {119827, 16782083}, {119828, 16782339}, {119829, 16782595}, + {119830, 16782851}, {119831, 16783107}, {119832, 16783363}, {119833, 16783619}, + {119834, 16777219}, {119835, 16777475}, {119836, 16777731}, {119837, 16777987}, + {119838, 16778243}, {119839, 16778499}, {119840, 16778755}, {119841, 16779011}, + {119842, 16779267}, {119843, 16779523}, {119844, 16779779}, {119845, 16780035}, + {119846, 16780291}, {119847, 16780547}, {119848, 16780803}, {119849, 16781059}, + {119850, 16781315}, {119851, 16781571}, {119852, 16781827}, {119853, 16782083}, + {119854, 16782339}, {119855, 16782595}, {119856, 16782851}, {119857, 16783107}, + {119858, 16783363}, {119859, 16783619}, {119860, 16777219}, {119861, 16777475}, + {119862, 16777731}, {119863, 16777987}, {119864, 16778243}, {119865, 16778499}, + {119866, 16778755}, {119867, 16779011}, {119868, 16779267}, {119869, 16779523}, + {119870, 16779779}, {119871, 16780035}, {119872, 16780291}, {119873, 16780547}, + {119874, 16780803}, {119875, 16781059}, {119876, 16781315}, {119877, 16781571}, + {119878, 16781827}, {119879, 16782083}, {119880, 16782339}, {119881, 16782595}, + {119882, 16782851}, {119883, 16783107}, {119884, 16783363}, {119885, 16783619}, + {119886, 16777219}, {119887, 16777475}, {119888, 16777731}, {119889, 16777987}, + {119890, 16778243}, {119891, 16778499}, {119892, 16778755}, {119893, 2}, + {119894, 16779267}, {119895, 16779523}, {119896, 16779779}, {119897, 16780035}, + {119898, 16780291}, {119899, 16780547}, {119900, 16780803}, {119901, 16781059}, + {119902, 16781315}, {119903, 16781571}, {119904, 16781827}, {119905, 16782083}, + {119906, 16782339}, {119907, 16782595}, {119908, 16782851}, {119909, 16783107}, + {119910, 16783363}, {119911, 16783619}, {119912, 16777219}, {119913, 16777475}, + {119914, 16777731}, {119915, 16777987}, {119916, 16778243}, {119917, 16778499}, + {119918, 16778755}, {119919, 16779011}, {119920, 16779267}, {119921, 16779523}, + {119922, 16779779}, {119923, 16780035}, {119924, 16780291}, {119925, 16780547}, + {119926, 16780803}, {119927, 16781059}, {119928, 16781315}, {119929, 16781571}, + {119930, 16781827}, {119931, 16782083}, {119932, 16782339}, {119933, 16782595}, + {119934, 16782851}, {119935, 16783107}, {119936, 16783363}, {119937, 16783619}, + {119938, 16777219}, {119939, 16777475}, {119940, 16777731}, {119941, 16777987}, + {119942, 16778243}, {119943, 16778499}, {119944, 16778755}, {119945, 16779011}, + {119946, 16779267}, {119947, 16779523}, {119948, 16779779}, {119949, 16780035}, + {119950, 16780291}, {119951, 16780547}, {119952, 16780803}, {119953, 16781059}, + {119954, 16781315}, {119955, 16781571}, {119956, 16781827}, {119957, 16782083}, + {119958, 16782339}, {119959, 16782595}, {119960, 16782851}, {119961, 16783107}, + {119962, 16783363}, {119963, 16783619}, {119964, 16777219}, {119965, 2}, + {119966, 16777731}, {119967, 16777987}, {119968, 2}, {119970, 16778755}, + {119971, 2}, {119973, 16779523}, {119974, 16779779}, {119975, 2}, + {119977, 16780547}, {119978, 16780803}, {119979, 16781059}, {119980, 16781315}, + {119981, 2}, {119982, 16781827}, {119983, 16782083}, {119984, 16782339}, + {119985, 16782595}, {119986, 16782851}, {119987, 16783107}, {119988, 16783363}, + {119989, 16783619}, {119990, 16777219}, {119991, 16777475}, {119992, 16777731}, + {119993, 16777987}, {119994, 2}, {119995, 16778499}, {119996, 2}, + {119997, 16779011}, {119998, 16779267}, {119999, 16779523}, {120000, 16779779}, + {120001, 16780035}, {120002, 16780291}, {120003, 16780547}, {120004, 2}, + {120005, 16781059}, {120006, 16781315}, {120007, 16781571}, {120008, 16781827}, + {120009, 16782083}, {120010, 16782339}, {120011, 16782595}, {120012, 16782851}, + {120013, 16783107}, {120014, 16783363}, {120015, 16783619}, {120016, 16777219}, + {120017, 16777475}, {120018, 16777731}, {120019, 16777987}, {120020, 16778243}, + {120021, 16778499}, {120022, 16778755}, {120023, 16779011}, {120024, 16779267}, + {120025, 16779523}, {120026, 16779779}, {120027, 16780035}, {120028, 16780291}, + {120029, 16780547}, {120030, 16780803}, {120031, 16781059}, {120032, 16781315}, + {120033, 16781571}, {120034, 16781827}, {120035, 16782083}, {120036, 16782339}, + {120037, 16782595}, {120038, 16782851}, {120039, 16783107}, {120040, 16783363}, + {120041, 16783619}, {120042, 16777219}, {120043, 16777475}, {120044, 16777731}, + {120045, 16777987}, {120046, 16778243}, {120047, 16778499}, {120048, 16778755}, + {120049, 16779011}, {120050, 16779267}, {120051, 16779523}, {120052, 16779779}, + {120053, 16780035}, {120054, 16780291}, {120055, 16780547}, {120056, 16780803}, + {120057, 16781059}, {120058, 16781315}, {120059, 16781571}, {120060, 16781827}, + {120061, 16782083}, {120062, 16782339}, {120063, 16782595}, {120064, 16782851}, + {120065, 16783107}, {120066, 16783363}, {120067, 16783619}, {120068, 16777219}, + {120069, 16777475}, {120070, 2}, {120071, 16777987}, {120072, 16778243}, + {120073, 16778499}, {120074, 16778755}, {120075, 2}, {120077, 16779523}, + {120078, 16779779}, {120079, 16780035}, {120080, 16780291}, {120081, 16780547}, + {120082, 16780803}, {120083, 16781059}, {120084, 16781315}, {120085, 2}, + {120086, 16781827}, {120087, 16782083}, {120088, 16782339}, {120089, 16782595}, + {120090, 16782851}, {120091, 16783107}, {120092, 16783363}, {120093, 2}, + {120094, 16777219}, {120095, 16777475}, {120096, 16777731}, {120097, 16777987}, + {120098, 16778243}, {120099, 16778499}, {120100, 16778755}, {120101, 16779011}, + {120102, 16779267}, {120103, 16779523}, {120104, 16779779}, {120105, 16780035}, + {120106, 16780291}, {120107, 16780547}, {120108, 16780803}, {120109, 16781059}, + {120110, 16781315}, {120111, 16781571}, {120112, 16781827}, {120113, 16782083}, + {120114, 16782339}, {120115, 16782595}, {120116, 16782851}, {120117, 16783107}, + {120118, 16783363}, {120119, 16783619}, {120120, 16777219}, {120121, 16777475}, + {120122, 2}, {120123, 16777987}, {120124, 16778243}, {120125, 16778499}, + {120126, 16778755}, {120127, 2}, {120128, 16779267}, {120129, 16779523}, + {120130, 16779779}, {120131, 16780035}, {120132, 16780291}, {120133, 2}, + {120134, 16780803}, {120135, 2}, {120138, 16781827}, {120139, 16782083}, + {120140, 16782339}, {120141, 16782595}, {120142, 16782851}, {120143, 16783107}, + {120144, 16783363}, {120145, 2}, {120146, 16777219}, {120147, 16777475}, + {120148, 16777731}, {120149, 16777987}, {120150, 16778243}, {120151, 16778499}, + {120152, 16778755}, {120153, 16779011}, {120154, 16779267}, {120155, 16779523}, + {120156, 16779779}, {120157, 16780035}, {120158, 16780291}, {120159, 16780547}, + {120160, 16780803}, {120161, 16781059}, {120162, 16781315}, {120163, 16781571}, + {120164, 16781827}, {120165, 16782083}, {120166, 16782339}, {120167, 16782595}, + {120168, 16782851}, {120169, 16783107}, {120170, 16783363}, {120171, 16783619}, + {120172, 16777219}, {120173, 16777475}, {120174, 16777731}, {120175, 16777987}, + {120176, 16778243}, {120177, 16778499}, {120178, 16778755}, {120179, 16779011}, + {120180, 16779267}, {120181, 16779523}, {120182, 16779779}, {120183, 16780035}, + {120184, 16780291}, {120185, 16780547}, {120186, 16780803}, {120187, 16781059}, + {120188, 16781315}, {120189, 16781571}, {120190, 16781827}, {120191, 16782083}, + {120192, 16782339}, {120193, 16782595}, {120194, 16782851}, {120195, 16783107}, + {120196, 16783363}, {120197, 16783619}, {120198, 16777219}, {120199, 16777475}, + {120200, 16777731}, {120201, 16777987}, {120202, 16778243}, {120203, 16778499}, + {120204, 16778755}, {120205, 16779011}, {120206, 16779267}, {120207, 16779523}, + {120208, 16779779}, {120209, 16780035}, {120210, 16780291}, {120211, 16780547}, + {120212, 16780803}, {120213, 16781059}, {120214, 16781315}, {120215, 16781571}, + {120216, 16781827}, {120217, 16782083}, {120218, 16782339}, {120219, 16782595}, + {120220, 16782851}, {120221, 16783107}, {120222, 16783363}, {120223, 16783619}, + {120224, 16777219}, {120225, 16777475}, {120226, 16777731}, {120227, 16777987}, + {120228, 16778243}, {120229, 16778499}, {120230, 16778755}, {120231, 16779011}, + {120232, 16779267}, {120233, 16779523}, {120234, 16779779}, {120235, 16780035}, + {120236, 16780291}, {120237, 16780547}, {120238, 16780803}, {120239, 16781059}, + {120240, 16781315}, {120241, 16781571}, {120242, 16781827}, {120243, 16782083}, + {120244, 16782339}, {120245, 16782595}, {120246, 16782851}, {120247, 16783107}, + {120248, 16783363}, {120249, 16783619}, {120250, 16777219}, {120251, 16777475}, + {120252, 16777731}, {120253, 16777987}, {120254, 16778243}, {120255, 16778499}, + {120256, 16778755}, {120257, 16779011}, {120258, 16779267}, {120259, 16779523}, + {120260, 16779779}, {120261, 16780035}, {120262, 16780291}, {120263, 16780547}, + {120264, 16780803}, {120265, 16781059}, {120266, 16781315}, {120267, 16781571}, + {120268, 16781827}, {120269, 16782083}, {120270, 16782339}, {120271, 16782595}, + {120272, 16782851}, {120273, 16783107}, {120274, 16783363}, {120275, 16783619}, + {120276, 16777219}, {120277, 16777475}, {120278, 16777731}, {120279, 16777987}, + {120280, 16778243}, {120281, 16778499}, {120282, 16778755}, {120283, 16779011}, + {120284, 16779267}, {120285, 16779523}, {120286, 16779779}, {120287, 16780035}, + {120288, 16780291}, {120289, 16780547}, {120290, 16780803}, {120291, 16781059}, + {120292, 16781315}, {120293, 16781571}, {120294, 16781827}, {120295, 16782083}, + {120296, 16782339}, {120297, 16782595}, {120298, 16782851}, {120299, 16783107}, + {120300, 16783363}, {120301, 16783619}, {120302, 16777219}, {120303, 16777475}, + {120304, 16777731}, {120305, 16777987}, {120306, 16778243}, {120307, 16778499}, + {120308, 16778755}, {120309, 16779011}, {120310, 16779267}, {120311, 16779523}, + {120312, 16779779}, {120313, 16780035}, {120314, 16780291}, {120315, 16780547}, + {120316, 16780803}, {120317, 16781059}, {120318, 16781315}, {120319, 16781571}, + {120320, 16781827}, {120321, 16782083}, {120322, 16782339}, {120323, 16782595}, + {120324, 16782851}, {120325, 16783107}, {120326, 16783363}, {120327, 16783619}, + {120328, 16777219}, {120329, 16777475}, {120330, 16777731}, {120331, 16777987}, + {120332, 16778243}, {120333, 16778499}, {120334, 16778755}, {120335, 16779011}, + {120336, 16779267}, {120337, 16779523}, {120338, 16779779}, {120339, 16780035}, + {120340, 16780291}, {120341, 16780547}, {120342, 16780803}, {120343, 16781059}, + {120344, 16781315}, {120345, 16781571}, {120346, 16781827}, {120347, 16782083}, + {120348, 16782339}, {120349, 16782595}, {120350, 16782851}, {120351, 16783107}, + {120352, 16783363}, {120353, 16783619}, {120354, 16777219}, {120355, 16777475}, + {120356, 16777731}, {120357, 16777987}, {120358, 16778243}, {120359, 16778499}, + {120360, 16778755}, {120361, 16779011}, {120362, 16779267}, {120363, 16779523}, + {120364, 16779779}, {120365, 16780035}, {120366, 16780291}, {120367, 16780547}, + {120368, 16780803}, {120369, 16781059}, {120370, 16781315}, {120371, 16781571}, + {120372, 16781827}, {120373, 16782083}, {120374, 16782339}, {120375, 16782595}, + {120376, 16782851}, {120377, 16783107}, {120378, 16783363}, {120379, 16783619}, + {120380, 16777219}, {120381, 16777475}, {120382, 16777731}, {120383, 16777987}, + {120384, 16778243}, {120385, 16778499}, {120386, 16778755}, {120387, 16779011}, + {120388, 16779267}, {120389, 16779523}, {120390, 16779779}, {120391, 16780035}, + {120392, 16780291}, {120393, 16780547}, {120394, 16780803}, {120395, 16781059}, + {120396, 16781315}, {120397, 16781571}, {120398, 16781827}, {120399, 16782083}, + {120400, 16782339}, {120401, 16782595}, {120402, 16782851}, {120403, 16783107}, + {120404, 16783363}, {120405, 16783619}, {120406, 16777219}, {120407, 16777475}, + {120408, 16777731}, {120409, 16777987}, {120410, 16778243}, {120411, 16778499}, + {120412, 16778755}, {120413, 16779011}, {120414, 16779267}, {120415, 16779523}, + {120416, 16779779}, {120417, 16780035}, {120418, 16780291}, {120419, 16780547}, + {120420, 16780803}, {120421, 16781059}, {120422, 16781315}, {120423, 16781571}, + {120424, 16781827}, {120425, 16782083}, {120426, 16782339}, {120427, 16782595}, + {120428, 16782851}, {120429, 16783107}, {120430, 16783363}, {120431, 16783619}, + {120432, 16777219}, {120433, 16777475}, {120434, 16777731}, {120435, 16777987}, + {120436, 16778243}, {120437, 16778499}, {120438, 16778755}, {120439, 16779011}, + {120440, 16779267}, {120441, 16779523}, {120442, 16779779}, {120443, 16780035}, + {120444, 16780291}, {120445, 16780547}, {120446, 16780803}, {120447, 16781059}, + {120448, 16781315}, {120449, 16781571}, {120450, 16781827}, {120451, 16782083}, + {120452, 16782339}, {120453, 16782595}, {120454, 16782851}, {120455, 16783107}, + {120456, 16783363}, {120457, 16783619}, {120458, 16777219}, {120459, 16777475}, + {120460, 16777731}, {120461, 16777987}, {120462, 16778243}, {120463, 16778499}, + {120464, 16778755}, {120465, 16779011}, {120466, 16779267}, {120467, 16779523}, + {120468, 16779779}, {120469, 16780035}, {120470, 16780291}, {120471, 16780547}, + {120472, 16780803}, {120473, 16781059}, {120474, 16781315}, {120475, 16781571}, + {120476, 16781827}, {120477, 16782083}, {120478, 16782339}, {120479, 16782595}, + {120480, 16782851}, {120481, 16783107}, {120482, 16783363}, {120483, 16783619}, + {120484, 17961731}, {120485, 17961987}, {120486, 2}, {120488, 16851715}, + {120489, 16851971}, {120490, 16852227}, {120491, 16852483}, {120492, 16852739}, + {120493, 16852995}, {120494, 16853251}, {120495, 16853507}, {120496, 16846851}, + {120497, 16853763}, {120498, 16854019}, {120499, 16786179}, {120500, 16854275}, + {120501, 16854531}, {120502, 16854787}, {120503, 16855043}, {120504, 16855299}, + {120505, 16853507}, {120506, 16855555}, {120507, 16855811}, {120508, 16856067}, + {120509, 16856323}, {120510, 16856579}, {120511, 16856835}, {120512, 16857091}, + {120513, 17962243}, {120514, 16851715}, {120515, 16851971}, {120516, 16852227}, + {120517, 16852483}, {120518, 16852739}, {120519, 16852995}, {120520, 16853251}, + {120521, 16853507}, {120522, 16846851}, {120523, 16853763}, {120524, 16854019}, + {120525, 16786179}, {120526, 16854275}, {120527, 16854531}, {120528, 16854787}, + {120529, 16855043}, {120530, 16855299}, {120531, 16855555}, {120533, 16855811}, + {120534, 16856067}, {120535, 16856323}, {120536, 16856579}, {120537, 16856835}, + {120538, 16857091}, {120539, 17962499}, {120540, 16852739}, {120541, 16853507}, + {120542, 16853763}, {120543, 16856323}, {120544, 16855299}, {120545, 16855043}, + {120546, 16851715}, {120547, 16851971}, {120548, 16852227}, {120549, 16852483}, + {120550, 16852739}, {120551, 16852995}, {120552, 16853251}, {120553, 16853507}, + {120554, 16846851}, {120555, 16853763}, {120556, 16854019}, {120557, 16786179}, + {120558, 16854275}, {120559, 16854531}, {120560, 16854787}, {120561, 16855043}, + {120562, 16855299}, {120563, 16853507}, {120564, 16855555}, {120565, 16855811}, + {120566, 16856067}, {120567, 16856323}, {120568, 16856579}, {120569, 16856835}, + {120570, 16857091}, {120571, 17962243}, {120572, 16851715}, {120573, 16851971}, + {120574, 16852227}, {120575, 16852483}, {120576, 16852739}, {120577, 16852995}, + {120578, 16853251}, {120579, 16853507}, {120580, 16846851}, {120581, 16853763}, + {120582, 16854019}, {120583, 16786179}, {120584, 16854275}, {120585, 16854531}, + {120586, 16854787}, {120587, 16855043}, {120588, 16855299}, {120589, 16855555}, + {120591, 16855811}, {120592, 16856067}, {120593, 16856323}, {120594, 16856579}, + {120595, 16856835}, {120596, 16857091}, {120597, 17962499}, {120598, 16852739}, + {120599, 16853507}, {120600, 16853763}, {120601, 16856323}, {120602, 16855299}, + {120603, 16855043}, {120604, 16851715}, {120605, 16851971}, {120606, 16852227}, + {120607, 16852483}, {120608, 16852739}, {120609, 16852995}, {120610, 16853251}, + {120611, 16853507}, {120612, 16846851}, {120613, 16853763}, {120614, 16854019}, + {120615, 16786179}, {120616, 16854275}, {120617, 16854531}, {120618, 16854787}, + {120619, 16855043}, {120620, 16855299}, {120621, 16853507}, {120622, 16855555}, + {120623, 16855811}, {120624, 16856067}, {120625, 16856323}, {120626, 16856579}, + {120627, 16856835}, {120628, 16857091}, {120629, 17962243}, {120630, 16851715}, + {120631, 16851971}, {120632, 16852227}, {120633, 16852483}, {120634, 16852739}, + {120635, 16852995}, {120636, 16853251}, {120637, 16853507}, {120638, 16846851}, + {120639, 16853763}, {120640, 16854019}, {120641, 16786179}, {120642, 16854275}, + {120643, 16854531}, {120644, 16854787}, {120645, 16855043}, {120646, 16855299}, + {120647, 16855555}, {120649, 16855811}, {120650, 16856067}, {120651, 16856323}, + {120652, 16856579}, {120653, 16856835}, {120654, 16857091}, {120655, 17962499}, + {120656, 16852739}, {120657, 16853507}, {120658, 16853763}, {120659, 16856323}, + {120660, 16855299}, {120661, 16855043}, {120662, 16851715}, {120663, 16851971}, + {120664, 16852227}, {120665, 16852483}, {120666, 16852739}, {120667, 16852995}, + {120668, 16853251}, {120669, 16853507}, {120670, 16846851}, {120671, 16853763}, + {120672, 16854019}, {120673, 16786179}, {120674, 16854275}, {120675, 16854531}, + {120676, 16854787}, {120677, 16855043}, {120678, 16855299}, {120679, 16853507}, + {120680, 16855555}, {120681, 16855811}, {120682, 16856067}, {120683, 16856323}, + {120684, 16856579}, {120685, 16856835}, {120686, 16857091}, {120687, 17962243}, + {120688, 16851715}, {120689, 16851971}, {120690, 16852227}, {120691, 16852483}, + {120692, 16852739}, {120693, 16852995}, {120694, 16853251}, {120695, 16853507}, + {120696, 16846851}, {120697, 16853763}, {120698, 16854019}, {120699, 16786179}, + {120700, 16854275}, {120701, 16854531}, {120702, 16854787}, {120703, 16855043}, + {120704, 16855299}, {120705, 16855555}, {120707, 16855811}, {120708, 16856067}, + {120709, 16856323}, {120710, 16856579}, {120711, 16856835}, {120712, 16857091}, + {120713, 17962499}, {120714, 16852739}, {120715, 16853507}, {120716, 16853763}, + {120717, 16856323}, {120718, 16855299}, {120719, 16855043}, {120720, 16851715}, + {120721, 16851971}, {120722, 16852227}, {120723, 16852483}, {120724, 16852739}, + {120725, 16852995}, {120726, 16853251}, {120727, 16853507}, {120728, 16846851}, + {120729, 16853763}, {120730, 16854019}, {120731, 16786179}, {120732, 16854275}, + {120733, 16854531}, {120734, 16854787}, {120735, 16855043}, {120736, 16855299}, + {120737, 16853507}, {120738, 16855555}, {120739, 16855811}, {120740, 16856067}, + {120741, 16856323}, {120742, 16856579}, {120743, 16856835}, {120744, 16857091}, + {120745, 17962243}, {120746, 16851715}, {120747, 16851971}, {120748, 16852227}, + {120749, 16852483}, {120750, 16852739}, {120751, 16852995}, {120752, 16853251}, + {120753, 16853507}, {120754, 16846851}, {120755, 16853763}, {120756, 16854019}, + {120757, 16786179}, {120758, 16854275}, {120759, 16854531}, {120760, 16854787}, + {120761, 16855043}, {120762, 16855299}, {120763, 16855555}, {120765, 16855811}, + {120766, 16856067}, {120767, 16856323}, {120768, 16856579}, {120769, 16856835}, + {120770, 16857091}, {120771, 17962499}, {120772, 16852739}, {120773, 16853507}, + {120774, 16853763}, {120775, 16856323}, {120776, 16855299}, {120777, 16855043}, + {120778, 16858627}, {120780, 2}, {120782, 17045507}, {120783, 16786947}, + {120784, 16785155}, {120785, 16785411}, {120786, 16787715}, {120787, 17045763}, + {120788, 17046019}, {120789, 17046275}, {120790, 17046531}, {120791, 17046787}, + {120792, 17045507}, {120793, 16786947}, {120794, 16785155}, {120795, 16785411}, + {120796, 16787715}, {120797, 17045763}, {120798, 17046019}, {120799, 17046275}, + {120800, 17046531}, {120801, 17046787}, {120802, 17045507}, {120803, 16786947}, + {120804, 16785155}, {120805, 16785411}, {120806, 16787715}, {120807, 17045763}, + {120808, 17046019}, {120809, 17046275}, {120810, 17046531}, {120811, 17046787}, + {120812, 17045507}, {120813, 16786947}, {120814, 16785155}, {120815, 16785411}, + {120816, 16787715}, {120817, 17045763}, {120818, 17046019}, {120819, 17046275}, + {120820, 17046531}, {120821, 17046787}, {120822, 17045507}, {120823, 16786947}, + {120824, 16785155}, {120825, 16785411}, {120826, 16787715}, {120827, 17045763}, + {120828, 17046019}, {120829, 17046275}, {120830, 17046531}, {120831, 17046787}, + {120832, 1}, {121484, 2}, {121499, 1}, {121504, 2}, + {121505, 1}, {121520, 2}, {122624, 1}, {122655, 2}, + {122661, 1}, {122667, 2}, {122880, 1}, {122887, 2}, + {122888, 1}, {122905, 2}, {122907, 1}, {122914, 2}, + {122915, 1}, {122917, 2}, {122918, 1}, {122923, 2}, + {122928, 16866563}, {122929, 16866819}, {122930, 16867075}, {122931, 16867331}, + {122932, 16867587}, {122933, 16867843}, {122934, 16868099}, {122935, 16868355}, + {122936, 16868611}, {122937, 16869123}, {122938, 16869379}, {122939, 16869635}, + {122940, 16870147}, {122941, 16870403}, {122942, 16870659}, {122943, 16870915}, + {122944, 16871171}, {122945, 16871427}, {122946, 16871683}, {122947, 16871939}, + {122948, 16872195}, {122949, 16872451}, {122950, 16872707}, {122951, 16873475}, + {122952, 16873987}, {122953, 16874243}, {122954, 17505795}, {122955, 16889091}, + {122956, 16864003}, {122957, 16864515}, {122958, 16891139}, {122959, 16883715}, + {122960, 16886019}, {122961, 16866563}, {122962, 16866819}, {122963, 16867075}, + {122964, 16867331}, {122965, 16867587}, {122966, 16867843}, {122967, 16868099}, + {122968, 16868355}, {122969, 16868611}, {122970, 16869123}, {122971, 16869379}, + {122972, 16870147}, {122973, 16870403}, {122974, 16870915}, {122975, 16871427}, + {122976, 16871683}, {122977, 16871939}, {122978, 16872195}, {122979, 16872451}, + {122980, 16872707}, {122981, 16873219}, {122982, 16873475}, {122983, 16879875}, + {122984, 16864003}, {122985, 16863747}, {122986, 16866307}, {122987, 16883203}, + {122988, 17500931}, {122989, 16883971}, {122990, 2}, {123023, 1}, + {123024, 2}, {123136, 1}, {123181, 2}, {123184, 1}, + {123198, 2}, {123200, 1}, {123210, 2}, {123214, 1}, + {123216, 2}, {123536, 1}, {123567, 2}, {123584, 1}, + {123642, 2}, {123647, 1}, {123648, 2}, {124112, 1}, + {124154, 2}, {124368, 1}, {124411, 2}, {124415, 1}, + {124416, 2}, {124896, 1}, {124903, 2}, {124904, 1}, + {124908, 2}, {124909, 1}, {124911, 2}, {124912, 1}, + {124927, 2}, {124928, 1}, {125125, 2}, {125127, 1}, + {125143, 2}, {125184, 17962755}, {125185, 17963011}, {125186, 17963267}, + {125187, 17963523}, {125188, 17963779}, {125189, 17964035}, {125190, 17964291}, + {125191, 17964547}, {125192, 17964803}, {125193, 17965059}, {125194, 17965315}, + {125195, 17965571}, {125196, 17965827}, {125197, 17966083}, {125198, 17966339}, + {125199, 17966595}, {125200, 17966851}, {125201, 17967107}, {125202, 17967363}, + {125203, 17967619}, {125204, 17967875}, {125205, 17968131}, {125206, 17968387}, + {125207, 17968643}, {125208, 17968899}, {125209, 17969155}, {125210, 17969411}, + {125211, 17969667}, {125212, 17969923}, {125213, 17970179}, {125214, 17970435}, + {125215, 17970691}, {125216, 17970947}, {125217, 17971203}, {125218, 1}, + {125260, 2}, {125264, 1}, {125274, 2}, {125278, 1}, + {125280, 2}, {126065, 1}, {126133, 2}, {126209, 1}, + {126270, 2}, {126464, 16910595}, {126465, 17695235}, {126466, 17693443}, + {126467, 17846019}, {126468, 2}, {126469, 16911107}, {126470, 17743107}, + {126471, 17693955}, {126472, 17711619}, {126473, 16912131}, {126474, 17720323}, + {126475, 17722627}, {126476, 17694467}, {126477, 17729539}, {126478, 17706499}, + {126479, 17713155}, {126480, 17715203}, {126481, 17708547}, {126482, 17718275}, + {126483, 17736707}, {126484, 17756675}, {126485, 17698307}, {126486, 17701379}, + {126487, 17696515}, {126488, 17736195}, {126489, 17709571}, {126490, 17712643}, + {126491, 17714179}, {126492, 17971459}, {126493, 17684995}, {126494, 17971715}, + {126495, 17971971}, {126496, 2}, {126497, 17695235}, {126498, 17693443}, + {126499, 2}, {126500, 17732611}, {126501, 2}, {126503, 17693955}, + {126504, 2}, {126505, 16912131}, {126506, 17720323}, {126507, 17722627}, + {126508, 17694467}, {126509, 17729539}, {126510, 17706499}, {126511, 17713155}, + {126512, 17715203}, {126513, 17708547}, {126514, 17718275}, {126515, 2}, + {126516, 17756675}, {126517, 17698307}, {126518, 17701379}, {126519, 17696515}, + {126520, 2}, {126521, 17709571}, {126522, 2}, {126523, 17714179}, + {126524, 2}, {126530, 17693443}, {126531, 2}, {126535, 17693955}, + {126536, 2}, {126537, 16912131}, {126538, 2}, {126539, 17722627}, + {126540, 2}, {126541, 17729539}, {126542, 17706499}, {126543, 17713155}, + {126544, 2}, {126545, 17708547}, {126546, 17718275}, {126547, 2}, + {126548, 17756675}, {126549, 2}, {126551, 17696515}, {126552, 2}, + {126553, 17709571}, {126554, 2}, {126555, 17714179}, {126556, 2}, + {126557, 17684995}, {126558, 2}, {126559, 17971971}, {126560, 2}, + {126561, 17695235}, {126562, 17693443}, {126563, 2}, {126564, 17732611}, + {126565, 2}, {126567, 17693955}, {126568, 17711619}, {126569, 16912131}, + {126570, 17720323}, {126571, 2}, {126572, 17694467}, {126573, 17729539}, + {126574, 17706499}, {126575, 17713155}, {126576, 17715203}, {126577, 17708547}, + {126578, 17718275}, {126579, 2}, {126580, 17756675}, {126581, 17698307}, + {126582, 17701379}, {126583, 17696515}, {126584, 2}, {126585, 17709571}, + {126586, 17712643}, {126587, 17714179}, {126588, 17971459}, {126589, 2}, + {126590, 17971715}, {126591, 2}, {126592, 16910595}, {126593, 17695235}, + {126594, 17693443}, {126595, 17846019}, {126596, 17732611}, {126597, 16911107}, + {126598, 17743107}, {126599, 17693955}, {126600, 17711619}, {126601, 16912131}, + {126602, 2}, {126603, 17722627}, {126604, 17694467}, {126605, 17729539}, + {126606, 17706499}, {126607, 17713155}, {126608, 17715203}, {126609, 17708547}, + {126610, 17718275}, {126611, 17736707}, {126612, 17756675}, {126613, 17698307}, + {126614, 17701379}, {126615, 17696515}, {126616, 17736195}, {126617, 17709571}, + {126618, 17712643}, {126619, 17714179}, {126620, 2}, {126625, 17695235}, + {126626, 17693443}, {126627, 17846019}, {126628, 2}, {126629, 16911107}, + {126630, 17743107}, {126631, 17693955}, {126632, 17711619}, {126633, 16912131}, + {126634, 2}, {126635, 17722627}, {126636, 17694467}, {126637, 17729539}, + {126638, 17706499}, {126639, 17713155}, {126640, 17715203}, {126641, 17708547}, + {126642, 17718275}, {126643, 17736707}, {126644, 17756675}, {126645, 17698307}, + {126646, 17701379}, {126647, 17696515}, {126648, 17736195}, {126649, 17709571}, + {126650, 17712643}, {126651, 17714179}, {126652, 2}, {126704, 1}, + {126706, 2}, {126976, 1}, {127020, 2}, {127024, 1}, + {127124, 2}, {127136, 1}, {127151, 2}, {127153, 1}, + {127168, 2}, {127169, 1}, {127184, 2}, {127185, 1}, + {127222, 2}, {127233, 34749443}, {127234, 34749955}, {127235, 34750467}, + {127236, 34750979}, {127237, 34751491}, {127238, 34752003}, {127239, 34752515}, + {127240, 34753027}, {127241, 34753539}, {127242, 34754051}, {127243, 1}, + {127248, 50655491}, {127249, 50656259}, {127250, 50657027}, {127251, 50657795}, + {127252, 50658563}, {127253, 50659331}, {127254, 50660099}, {127255, 50660867}, + {127256, 50661635}, {127257, 50662403}, {127258, 50663171}, {127259, 50663939}, + {127260, 50664707}, {127261, 50665475}, {127262, 50666243}, {127263, 50667011}, + {127264, 50667779}, {127265, 50668547}, {127266, 50669315}, {127267, 50670083}, + {127268, 50670851}, {127269, 50671619}, {127270, 50672387}, {127271, 50673155}, + {127272, 50673923}, {127273, 50674691}, {127274, 51531779}, {127275, 16777731}, + {127276, 16781571}, {127277, 33554947}, {127278, 34755331}, {127279, 1}, + {127280, 16777219}, {127281, 16777475}, {127282, 16777731}, {127283, 16777987}, + {127284, 16778243}, {127285, 16778499}, {127286, 16778755}, {127287, 16779011}, + {127288, 16779267}, {127289, 16779523}, {127290, 16779779}, {127291, 16780035}, + {127292, 16780291}, {127293, 16780547}, {127294, 16780803}, {127295, 16781059}, + {127296, 16781315}, {127297, 16781571}, {127298, 16781827}, {127299, 16782083}, + {127300, 16782339}, {127301, 16782595}, {127302, 16782851}, {127303, 16783107}, + {127304, 16783363}, {127305, 16783619}, {127306, 34755843}, {127307, 34237187}, + {127308, 34756355}, {127309, 34756867}, {127310, 51534595}, {127311, 34758147}, + {127312, 1}, {127338, 34220035}, {127339, 34200067}, {127340, 34758659}, + {127341, 1}, {127376, 34759171}, {127377, 1}, {127406, 2}, + {127462, 1}, {127488, 34759683}, {127489, 34760195}, {127490, 17318403}, + {127491, 2}, {127504, 17168387}, {127505, 17983491}, {127506, 17983747}, + {127507, 17362179}, {127508, 17153795}, {127509, 17984003}, {127510, 17984259}, + {127511, 17235971}, {127512, 17984515}, {127513, 17984771}, {127514, 17985027}, + {127515, 17596163}, {127516, 17985283}, {127517, 17985539}, {127518, 17985795}, + {127519, 17986051}, {127520, 17986307}, {127521, 17986563}, {127522, 17177603}, + {127523, 17986819}, {127524, 17987075}, {127525, 17987331}, {127526, 17987587}, + {127527, 17987843}, {127528, 17988099}, {127529, 17152259}, {127530, 17233923}, + {127531, 17988355}, {127532, 17299203}, {127533, 17234691}, {127534, 17299459}, + {127535, 17988611}, {127536, 17191939}, {127537, 17988867}, {127538, 17989123}, + {127539, 17989379}, {127540, 17989635}, {127541, 17989891}, {127542, 17274883}, + {127543, 17170947}, {127544, 17990147}, {127545, 17990403}, {127546, 17990659}, + {127547, 17990915}, {127548, 2}, {127552, 51545603}, {127553, 51546371}, + {127554, 51547139}, {127555, 51547907}, {127556, 51548675}, {127557, 51549443}, + {127558, 51550211}, {127559, 51550979}, {127560, 51551747}, {127561, 2}, + {127568, 17998083}, {127569, 17998339}, {127570, 2}, {127584, 1}, + {127590, 2}, {127744, 1}, {128728, 2}, {128732, 1}, + {128749, 2}, {128752, 1}, {128765, 2}, {128768, 1}, + {128887, 2}, {128891, 1}, {128986, 2}, {128992, 1}, + {129004, 2}, {129008, 1}, {129009, 2}, {129024, 1}, + {129036, 2}, {129040, 1}, {129096, 2}, {129104, 1}, + {129114, 2}, {129120, 1}, {129160, 2}, {129168, 1}, + {129198, 2}, {129200, 1}, {129212, 2}, {129216, 1}, + {129218, 2}, {129280, 1}, {129620, 2}, {129632, 1}, + {129646, 2}, {129648, 1}, {129661, 2}, {129664, 1}, + {129674, 2}, {129679, 1}, {129735, 2}, {129742, 1}, + {129757, 2}, {129759, 1}, {129770, 2}, {129776, 1}, + {129785, 2}, {129792, 1}, {129939, 2}, {129940, 1}, + {130032, 17045507}, {130033, 16786947}, {130034, 16785155}, {130035, 16785411}, + {130036, 16787715}, {130037, 17045763}, {130038, 17046019}, {130039, 17046275}, + {130040, 17046531}, {130041, 17046787}, {130042, 2}, {131072, 1}, {173792, 2}, {173824, 1}, {177978, 2}, {177984, 1}, {178206, 2}, {178208, 1}, {183970, 2}, {183984, 1}, - {191457, 2}, {194560, 17981443}, {194561, 17981699}, {194562, 17981955}, - {194563, 17982211}, {194564, 17982467}, {194565, 17608451}, {194566, 17982723}, - {194567, 17982979}, {194568, 17983235}, {194569, 17983491}, {194570, 17608707}, - {194571, 17983747}, {194572, 17984003}, {194573, 17984259}, {194574, 17608963}, - {194575, 17984515}, {194576, 17984771}, {194577, 17985027}, {194578, 17985283}, - {194579, 17985539}, {194580, 17985795}, {194581, 17968643}, {194582, 17986051}, - {194583, 17986307}, {194584, 17986563}, {194585, 17986819}, {194586, 17987075}, - {194587, 17623043}, {194588, 17987331}, {194589, 17145859}, {194590, 17987587}, - {194591, 17987843}, {194592, 17988099}, {194593, 17988355}, {194594, 17973251}, - {194595, 17988611}, {194596, 17988867}, {194597, 17624323}, {194598, 17609219}, - {194599, 17609475}, {194600, 17624579}, {194601, 17989123}, {194602, 17989379}, - {194603, 17562883}, {194604, 17989635}, {194605, 17609731}, {194606, 17989891}, - {194607, 17990147}, {194608, 17990403}, {194609, 17990659}, {194612, 17990915}, - {194613, 17991171}, {194614, 17991427}, {194615, 17991683}, {194616, 17991939}, - {194617, 17992195}, {194618, 17992451}, {194619, 17992707}, {194620, 17992963}, - {194621, 17993219}, {194622, 17993475}, {194623, 17993731}, {194624, 17993987}, - {194625, 17994243}, {194626, 17994499}, {194627, 17994755}, {194628, 17995011}, - {194629, 17995267}, {194631, 17625091}, {194632, 17995523}, {194633, 17995779}, - {194634, 17996035}, {194635, 17996291}, {194636, 17610243}, {194637, 17996547}, - {194638, 17996803}, {194639, 17997059}, {194640, 17600003}, {194641, 17997315}, - {194642, 17997571}, {194643, 17997827}, {194644, 17998083}, {194645, 17998339}, - {194646, 17998595}, {194647, 17998851}, {194648, 17999107}, {194649, 17999363}, - {194650, 17999619}, {194651, 17999875}, {194652, 18000131}, {194653, 17966851}, - {194654, 18000387}, {194655, 18000643}, {194656, 18000899}, {194657, 18001155}, - {194658, 18001411}, {194659, 18001667}, {194660, 18001923}, {194661, 18002179}, - {194662, 18002435}, {194663, 18002691}, {194664, 2}, {194665, 18002947}, - {194666, 18003203}, {194668, 18003459}, {194669, 18003715}, {194670, 18003971}, - {194671, 17561859}, {194672, 18004227}, {194673, 18004483}, {194674, 18004739}, - {194675, 18004995}, {194676, 2}, {194677, 17152515}, {194678, 18005251}, - {194679, 18005507}, {194680, 17153027}, {194681, 18005763}, {194682, 18006019}, - {194683, 18006275}, {194684, 18006531}, {194685, 18006787}, {194686, 18007043}, - {194687, 18007299}, {194688, 18007555}, {194689, 18007811}, {194690, 18008067}, - {194691, 18008323}, {194692, 18008579}, {194693, 18008835}, {194694, 18009091}, - {194695, 18009347}, {194696, 18009603}, {194697, 18009859}, {194698, 18010115}, - {194699, 18010371}, {194700, 18010627}, {194701, 18010883}, {194702, 17548547}, - {194703, 18011139}, {194704, 17155587}, {194705, 18011395}, {194707, 18011651}, - {194708, 18011907}, {194710, 18012163}, {194711, 18012419}, {194712, 18012675}, - {194713, 18012931}, {194714, 18013187}, {194715, 18013443}, {194716, 18013699}, - {194717, 18013955}, {194718, 18014211}, {194719, 18014467}, {194720, 18014723}, - {194721, 18014979}, {194722, 18015235}, {194723, 17611523}, {194724, 18015491}, - {194725, 18015747}, {194726, 18016003}, {194727, 18016259}, {194728, 17628163}, - {194729, 18016259}, {194730, 18016515}, {194731, 17612035}, {194732, 18016771}, - {194733, 18017027}, {194734, 18017283}, {194735, 18017539}, {194736, 17612291}, - {194737, 17541635}, {194738, 17414915}, {194739, 18017795}, {194740, 18018051}, - {194741, 18018307}, {194742, 18018563}, {194743, 18018819}, {194744, 18019075}, - {194745, 18019331}, {194746, 18019587}, {194747, 18019843}, {194748, 18020099}, - {194749, 18020355}, {194750, 18020611}, {194751, 18020867}, {194752, 18021123}, - {194753, 18021379}, {194754, 18021635}, {194755, 18021891}, {194756, 18022147}, - {194757, 18022403}, {194758, 18022659}, {194759, 18022915}, {194760, 17612547}, - {194761, 18023171}, {194762, 18023427}, {194763, 18023683}, {194764, 18023939}, - {194765, 18024195}, {194766, 18024451}, {194767, 17613059}, {194768, 18024707}, - {194769, 18024963}, {194770, 18025219}, {194771, 18025475}, {194772, 18025731}, - {194773, 18025987}, {194774, 18026243}, {194775, 18026499}, {194776, 17548803}, - {194777, 17630211}, {194778, 18026755}, {194779, 18027011}, {194780, 18027267}, - {194781, 18027523}, {194782, 18027779}, {194783, 18028035}, {194784, 18028291}, - {194785, 18028547}, {194786, 17613315}, {194787, 18028803}, {194788, 18029059}, - {194789, 18029315}, {194790, 18029571}, {194791, 17640963}, {194792, 18029827}, - {194793, 18030083}, {194794, 18030339}, {194795, 18030595}, {194796, 18030851}, - {194797, 18031107}, {194798, 18031363}, {194799, 18031619}, {194800, 18031875}, - {194801, 18032131}, {194802, 18032387}, {194803, 18032643}, {194804, 18032899}, - {194805, 17566211}, {194806, 18033155}, {194807, 18033411}, {194808, 18033667}, - {194809, 18033923}, {194810, 18034179}, {194811, 18034435}, {194812, 18034691}, - {194813, 18034947}, {194814, 18035203}, {194815, 18035459}, {194816, 18035715}, - {194817, 17613571}, {194818, 17587203}, {194819, 18035971}, {194820, 18036227}, - {194821, 18036483}, {194822, 18036739}, {194823, 18036995}, {194824, 18037251}, - {194825, 18037507}, {194826, 18037763}, {194827, 17630979}, {194828, 18038019}, - {194829, 18038275}, {194830, 18038531}, {194831, 18038787}, {194832, 18039043}, - {194833, 18039299}, {194834, 18039555}, {194835, 18039811}, {194836, 17631235}, - {194837, 18040067}, {194838, 18040323}, {194839, 18040579}, {194840, 18040835}, - {194841, 18041091}, {194842, 18041347}, {194843, 18041603}, {194844, 18041859}, - {194845, 18042115}, {194846, 18042371}, {194847, 2}, {194848, 18042627}, - {194849, 17631747}, {194850, 18042883}, {194851, 18043139}, {194852, 18043395}, - {194853, 18043651}, {194854, 18043907}, {194855, 18044163}, {194856, 18044419}, - {194857, 18044675}, {194858, 18044931}, {194859, 18045187}, {194860, 18045443}, - {194862, 18045699}, {194863, 18045955}, {194864, 17632259}, {194865, 18046211}, - {194866, 18046467}, {194867, 18046723}, {194868, 18046979}, {194869, 18047235}, - {194870, 18047491}, {194871, 18047747}, {194872, 17562627}, {194873, 18048003}, - {194874, 18048259}, {194875, 18048515}, {194876, 18048771}, {194877, 18049027}, - {194878, 18049283}, {194879, 18049539}, {194880, 17633795}, {194881, 18049795}, - {194882, 18050051}, {194883, 18050307}, {194884, 18050563}, {194885, 18050819}, - {194886, 18051075}, {194888, 17634051}, {194889, 17641475}, {194890, 18051331}, - {194891, 18051587}, {194892, 18051843}, {194893, 18052099}, {194894, 18052355}, - {194895, 17553155}, {194896, 17634563}, {194897, 18052611}, {194898, 18052867}, - {194899, 17616131}, {194900, 18053123}, {194901, 18053379}, {194902, 17605123}, - {194903, 18053635}, {194904, 18053891}, {194905, 17616899}, {194906, 18054147}, - {194907, 18054403}, {194908, 18054659}, {194909, 18054915}, {194911, 2}, - {194912, 18055171}, {194913, 18055427}, {194914, 18055683}, {194915, 18055939}, - {194916, 18056195}, {194917, 18056451}, {194918, 18056707}, {194919, 18056963}, - {194920, 18057219}, {194921, 18057475}, {194922, 18057731}, {194923, 18057987}, - {194924, 18058243}, {194925, 18058499}, {194926, 18058755}, {194927, 18059011}, - {194928, 18059267}, {194929, 18059523}, {194930, 18059779}, {194931, 18060035}, - {194932, 18060291}, {194933, 18060547}, {194934, 18060803}, {194935, 18061059}, - {194936, 18061315}, {194937, 18061571}, {194938, 17618435}, {194939, 18061827}, - {194940, 18062083}, {194941, 18062339}, {194942, 18062595}, {194943, 18062851}, - {194944, 18063107}, {194945, 18063363}, {194946, 18063619}, {194947, 18063875}, - {194948, 18064131}, {194949, 18064387}, {194950, 18064643}, {194951, 18064899}, - {194952, 18065155}, {194953, 18065411}, {194954, 18065667}, {194955, 18011651}, - {194956, 18065923}, {194957, 18066179}, {194958, 18066435}, {194959, 18066691}, - {194960, 18066947}, {194961, 18067203}, {194962, 18067459}, {194963, 18067715}, - {194964, 18067971}, {194965, 18068227}, {194966, 18068483}, {194967, 18068739}, - {194968, 17566979}, {194969, 18068995}, {194970, 18069251}, {194971, 18069507}, - {194972, 18069763}, {194973, 18070019}, {194974, 18070275}, {194975, 17619203}, - {194976, 18070531}, {194977, 18070787}, {194978, 18071043}, {194979, 18071299}, - {194980, 18071555}, {194981, 18071811}, {194982, 18072067}, {194983, 18072323}, - {194984, 18072579}, {194985, 18072835}, {194986, 18073091}, {194987, 18073347}, - {194988, 18073603}, {194989, 18073859}, {194990, 18074115}, {194991, 18074371}, - {194992, 18074627}, {194993, 18074883}, {194994, 18075139}, {194995, 18075395}, - {194996, 17551875}, {194997, 18075651}, {194998, 18075907}, {194999, 18076163}, - {195000, 18076419}, {195001, 18076675}, {195002, 18076931}, {195003, 17636355}, - {195004, 18077187}, {195005, 18077443}, {195006, 18077699}, {195007, 2}, - {195008, 18077955}, {195009, 18078211}, {195010, 18078467}, {195011, 18078723}, - {195012, 17178627}, {195013, 18078979}, {195014, 18079235}, {195015, 18079491}, - {195016, 18079747}, {195017, 18080003}, {195018, 18080259}, {195019, 18080515}, - {195020, 18080771}, {195021, 18081027}, {195022, 18081283}, {195023, 18081539}, - {195024, 17637635}, {195025, 17637891}, {195026, 17180419}, {195027, 18081795}, - {195028, 18082051}, {195029, 18082307}, {195030, 18082563}, {195031, 18082819}, - {195032, 18083075}, {195033, 18083331}, {195034, 18083587}, {195035, 18083843}, - {195036, 18084099}, {195037, 18084355}, {195038, 18084611}, {195039, 17638147}, - {195040, 18084867}, {195041, 18085123}, {195042, 18085379}, {195043, 18085635}, - {195044, 18085891}, {195045, 18086147}, {195046, 18086403}, {195047, 18086659}, - {195048, 18086915}, {195049, 18087171}, {195050, 18087427}, {195051, 18087683}, - {195052, 18087939}, {195053, 18088195}, {195054, 18088451}, {195055, 18088707}, - {195056, 18088963}, {195057, 18089219}, {195058, 18089475}, {195059, 18089731}, - {195060, 18089987}, {195061, 18090243}, {195062, 18090499}, {195063, 18090755}, - {195064, 18091011}, {195065, 18091267}, {195066, 18091523}, {195067, 18091779}, - {195068, 18092035}, {195069, 18092291}, {195070, 17639683}, {195072, 18092547}, - {195073, 18092803}, {195074, 18093059}, {195075, 18093315}, {195076, 18093571}, - {195077, 18093827}, {195078, 18094083}, {195079, 18094339}, {195080, 18094595}, - {195081, 18094851}, {195082, 17639939}, {195083, 18095107}, {195084, 18095363}, - {195085, 18095619}, {195086, 18095875}, {195087, 18096131}, {195088, 18096387}, - {195089, 18096643}, {195090, 18096899}, {195091, 18097155}, {195092, 18097411}, - {195093, 17192707}, {195094, 18097667}, {195095, 17193731}, {195096, 18097923}, - {195097, 18098179}, {195098, 18098435}, {195099, 18098691}, {195100, 17195011}, - {195101, 18098947}, {195102, 2}, {196608, 1}, {201547, 2}, - {201552, 1}, {205744, 2}, {917760, 0}, {918000, 2} + {191457, 2}, {191472, 1}, {192094, 2}, {194560, 17998595}, + {194561, 17998851}, {194562, 17999107}, {194563, 17999363}, {194564, 17999619}, + {194565, 17619971}, {194566, 17999875}, {194567, 18000131}, {194568, 18000387}, + {194569, 18000643}, {194570, 17620227}, {194571, 18000899}, {194572, 18001155}, + {194573, 18001411}, {194574, 17620483}, {194575, 18001667}, {194576, 18001923}, + {194577, 18002179}, {194578, 18002435}, {194579, 18002691}, {194580, 18002947}, + {194581, 17985795}, {194582, 18003203}, {194583, 18003459}, {194584, 18003715}, + {194585, 18003971}, {194586, 18004227}, {194587, 17634563}, {194588, 18004483}, + {194589, 17156355}, {194590, 18004739}, {194591, 18004995}, {194592, 18005251}, + {194593, 18005507}, {194594, 17990403}, {194595, 18005763}, {194596, 18006019}, + {194597, 17635843}, {194598, 17620739}, {194599, 17620995}, {194600, 17636099}, + {194601, 18006275}, {194602, 18006531}, {194603, 17574403}, {194604, 18006787}, + {194605, 17621251}, {194606, 18007043}, {194607, 18007299}, {194608, 18007555}, + {194609, 18007811}, {194612, 18008067}, {194613, 18008323}, {194614, 18008579}, + {194615, 18008835}, {194616, 18009091}, {194617, 18009347}, {194618, 18009603}, + {194619, 18009859}, {194620, 18010115}, {194621, 18010371}, {194622, 18010627}, + {194623, 18010883}, {194624, 18011139}, {194625, 18011395}, {194626, 18011651}, + {194627, 18011907}, {194628, 18012163}, {194629, 18012419}, {194631, 17636611}, + {194632, 18012675}, {194633, 18012931}, {194634, 18013187}, {194635, 18013443}, + {194636, 17621763}, {194637, 18013699}, {194638, 18013955}, {194639, 18014211}, + {194640, 17611523}, {194641, 18014467}, {194642, 18014723}, {194643, 18014979}, + {194644, 18015235}, {194645, 18015491}, {194646, 18015747}, {194647, 18016003}, + {194648, 18016259}, {194649, 18016515}, {194650, 18016771}, {194651, 18017027}, + {194652, 18017283}, {194653, 17984003}, {194654, 18017539}, {194655, 18017795}, + {194656, 18018051}, {194657, 18018307}, {194658, 18018563}, {194659, 18018819}, + {194660, 18019075}, {194661, 18019331}, {194662, 18019587}, {194663, 18019843}, + {194664, 18020099}, {194665, 18020355}, {194666, 18020611}, {194668, 18020867}, + {194669, 18021123}, {194670, 18021379}, {194671, 17573379}, {194672, 18021635}, + {194673, 18021891}, {194674, 18022147}, {194675, 18022403}, {194676, 18022659}, + {194677, 17163011}, {194678, 18022915}, {194679, 18023171}, {194680, 17163523}, + {194681, 18023427}, {194682, 18023683}, {194683, 18023939}, {194684, 18024195}, + {194685, 18024451}, {194686, 18024707}, {194687, 18024963}, {194688, 18025219}, + {194689, 18025475}, {194690, 18025731}, {194691, 18025987}, {194692, 18026243}, + {194693, 18026499}, {194694, 18026755}, {194695, 18027011}, {194696, 18027267}, + {194697, 18027523}, {194698, 18027779}, {194699, 18028035}, {194700, 18028291}, + {194701, 18028547}, {194702, 17560067}, {194703, 18028803}, {194704, 17166083}, + {194705, 18029059}, {194707, 18029315}, {194708, 18029571}, {194710, 18029827}, + {194711, 18030083}, {194712, 18030339}, {194713, 18030595}, {194714, 18030851}, + {194715, 18031107}, {194716, 18031363}, {194717, 18031619}, {194718, 18031875}, + {194719, 18032131}, {194720, 18032387}, {194721, 18032643}, {194722, 18032899}, + {194723, 17623043}, {194724, 18033155}, {194725, 18033411}, {194726, 18033667}, + {194727, 18033923}, {194728, 17639683}, {194729, 18033923}, {194730, 18034179}, + {194731, 17623555}, {194732, 18034435}, {194733, 18034691}, {194734, 18034947}, + {194735, 18035203}, {194736, 17623811}, {194737, 17553155}, {194738, 17425411}, + {194739, 18035459}, {194740, 18035715}, {194741, 18035971}, {194742, 18036227}, + {194743, 18036483}, {194744, 18036739}, {194745, 18036995}, {194746, 18037251}, + {194747, 18037507}, {194748, 18037763}, {194749, 18038019}, {194750, 18038275}, + {194751, 18038531}, {194752, 18038787}, {194753, 18039043}, {194754, 18039299}, + {194755, 18039555}, {194756, 18039811}, {194757, 18040067}, {194758, 18040323}, + {194759, 18040579}, {194760, 17624067}, {194761, 18040835}, {194762, 18041091}, + {194763, 18041347}, {194764, 18041603}, {194765, 18041859}, {194766, 18042115}, + {194767, 17624579}, {194768, 18042371}, {194769, 18042627}, {194770, 18042883}, + {194771, 18043139}, {194772, 18043395}, {194773, 18043651}, {194774, 18043907}, + {194775, 18044163}, {194776, 17560323}, {194777, 17641731}, {194778, 18044419}, + {194779, 18044675}, {194780, 18044931}, {194781, 18045187}, {194782, 18045443}, + {194783, 18045699}, {194784, 18045955}, {194785, 18046211}, {194786, 17624835}, + {194787, 18046467}, {194788, 18046723}, {194789, 18046979}, {194790, 18047235}, + {194791, 17652483}, {194792, 18047491}, {194793, 18047747}, {194794, 18048003}, + {194795, 18048259}, {194796, 18048515}, {194797, 18048771}, {194798, 18049027}, + {194799, 18049283}, {194800, 18049539}, {194801, 18049795}, {194802, 18050051}, + {194803, 18050307}, {194804, 18050563}, {194805, 17577731}, {194806, 18050819}, + {194807, 18051075}, {194808, 18051331}, {194809, 18051587}, {194810, 18051843}, + {194811, 18052099}, {194812, 18052355}, {194813, 18052611}, {194814, 18052867}, + {194815, 18053123}, {194816, 18053379}, {194817, 17625091}, {194818, 17598723}, + {194819, 18053635}, {194820, 18053891}, {194821, 18054147}, {194822, 18054403}, + {194823, 18054659}, {194824, 18054915}, {194825, 18055171}, {194826, 18055427}, + {194827, 17642499}, {194828, 18055683}, {194829, 18055939}, {194830, 18056195}, + {194831, 18056451}, {194832, 18056707}, {194833, 18056963}, {194834, 18057219}, + {194835, 18057475}, {194836, 17642755}, {194837, 18057731}, {194838, 18057987}, + {194839, 18058243}, {194840, 18058499}, {194841, 18058755}, {194842, 18059011}, + {194843, 18059267}, {194844, 18059523}, {194845, 18059779}, {194846, 18060035}, + {194847, 18060291}, {194848, 18060547}, {194849, 17643267}, {194850, 18060803}, + {194851, 18061059}, {194852, 18061315}, {194853, 18061571}, {194854, 18061827}, + {194855, 18062083}, {194856, 18062339}, {194857, 18062595}, {194858, 18062851}, + {194859, 18063107}, {194860, 18063363}, {194862, 18063619}, {194863, 18063875}, + {194864, 17643779}, {194865, 18064131}, {194866, 18064387}, {194867, 18064643}, + {194868, 18064899}, {194869, 18065155}, {194870, 18065411}, {194871, 18065667}, + {194872, 17574147}, {194873, 18065923}, {194874, 18066179}, {194875, 18066435}, + {194876, 18066691}, {194877, 18066947}, {194878, 18067203}, {194879, 18067459}, + {194880, 17645315}, {194881, 18067715}, {194882, 18067971}, {194883, 18068227}, + {194884, 18068483}, {194885, 18068739}, {194886, 18068995}, {194888, 17645571}, + {194889, 17652995}, {194890, 18069251}, {194891, 18069507}, {194892, 18069763}, + {194893, 18070019}, {194894, 18070275}, {194895, 17564675}, {194896, 17646083}, + {194897, 18070531}, {194898, 18070787}, {194899, 17627651}, {194900, 18071043}, + {194901, 18071299}, {194902, 17616643}, {194903, 18071555}, {194904, 18071811}, + {194905, 17628419}, {194906, 18072067}, {194907, 18072323}, {194908, 18072579}, + {194909, 18072835}, {194911, 18073091}, {194912, 18073347}, {194913, 18073603}, + {194914, 18073859}, {194915, 18074115}, {194916, 18074371}, {194917, 18074627}, + {194918, 18074883}, {194919, 18075139}, {194920, 18075395}, {194921, 18075651}, + {194922, 18075907}, {194923, 18076163}, {194924, 18076419}, {194925, 18076675}, + {194926, 18076931}, {194927, 18077187}, {194928, 18077443}, {194929, 18077699}, + {194930, 18077955}, {194931, 18078211}, {194932, 18078467}, {194933, 18078723}, + {194934, 18078979}, {194935, 18079235}, {194936, 18079491}, {194937, 18079747}, + {194938, 17629955}, {194939, 18080003}, {194940, 18080259}, {194941, 18080515}, + {194942, 18080771}, {194943, 18081027}, {194944, 18081283}, {194945, 18081539}, + {194946, 18081795}, {194947, 18082051}, {194948, 18082307}, {194949, 18082563}, + {194950, 18082819}, {194951, 18083075}, {194952, 18083331}, {194953, 18083587}, + {194954, 18083843}, {194955, 18029315}, {194956, 18084099}, {194957, 18084355}, + {194958, 18084611}, {194959, 18084867}, {194960, 18085123}, {194961, 18085379}, + {194962, 18085635}, {194963, 18085891}, {194964, 18086147}, {194965, 18086403}, + {194966, 18086659}, {194967, 18086915}, {194968, 17578499}, {194969, 18087171}, + {194970, 18087427}, {194971, 18087683}, {194972, 18087939}, {194973, 18088195}, + {194974, 18088451}, {194975, 17630723}, {194976, 18088707}, {194977, 18088963}, + {194978, 18089219}, {194979, 18089475}, {194980, 18089731}, {194981, 18089987}, + {194982, 18090243}, {194983, 18090499}, {194984, 18090755}, {194985, 18091011}, + {194986, 18091267}, {194987, 18091523}, {194988, 18091779}, {194989, 18092035}, + {194990, 18092291}, {194991, 18092547}, {194992, 18092803}, {194993, 18093059}, + {194994, 18093315}, {194995, 18093571}, {194996, 17563395}, {194997, 18093827}, + {194998, 18094083}, {194999, 18094339}, {195000, 18094595}, {195001, 18094851}, + {195002, 18095107}, {195003, 17647875}, {195004, 18095363}, {195005, 18095619}, + {195006, 18095875}, {195007, 18096131}, {195008, 18096387}, {195009, 18096643}, + {195010, 18096899}, {195011, 18097155}, {195012, 17189123}, {195013, 18097411}, + {195014, 18097667}, {195015, 18097923}, {195016, 18098179}, {195017, 18098435}, + {195018, 18098691}, {195019, 18098947}, {195020, 18099203}, {195021, 18099459}, + {195022, 18099715}, {195023, 18099971}, {195024, 17649155}, {195025, 17649411}, + {195026, 17190915}, {195027, 18100227}, {195028, 18100483}, {195029, 18100739}, + {195030, 18100995}, {195031, 18101251}, {195032, 18101507}, {195033, 18101763}, + {195034, 18102019}, {195035, 18102275}, {195036, 18102531}, {195037, 18102787}, + {195038, 18103043}, {195039, 17649667}, {195040, 18103299}, {195041, 18103555}, + {195042, 18103811}, {195043, 18104067}, {195044, 18104323}, {195045, 18104579}, + {195046, 18104835}, {195047, 18105091}, {195048, 18105347}, {195049, 18105603}, + {195050, 18105859}, {195051, 18106115}, {195052, 18106371}, {195053, 18106627}, + {195054, 18106883}, {195055, 18107139}, {195056, 18107395}, {195057, 18107651}, + {195058, 18107907}, {195059, 18108163}, {195060, 18108419}, {195061, 18108675}, + {195062, 18108931}, {195063, 18109187}, {195064, 18109443}, {195065, 18109699}, + {195066, 18109955}, {195067, 18110211}, {195068, 18110467}, {195069, 18110723}, + {195070, 17651203}, {195072, 18110979}, {195073, 18111235}, {195074, 18111491}, + {195075, 18111747}, {195076, 18112003}, {195077, 18112259}, {195078, 18112515}, + {195079, 18112771}, {195080, 18113027}, {195081, 18113283}, {195082, 17651459}, + {195083, 18113539}, {195084, 18113795}, {195085, 18114051}, {195086, 18114307}, + {195087, 18114563}, {195088, 18114819}, {195089, 18115075}, {195090, 18115331}, + {195091, 18115587}, {195092, 18115843}, {195093, 17203203}, {195094, 18116099}, + {195095, 17204227}, {195096, 18116355}, {195097, 18116611}, {195098, 18116867}, + {195099, 18117123}, {195100, 17205507}, {195101, 18117379}, {195102, 2}, + {196608, 1}, {201547, 2}, {201552, 1}, {205744, 2}, + {917760, 0}, {918000, 2} }; } // namespace ada::idna #endif // ADA_IDNA_TABLES_H - /* end file src/mapping_tables.cpp */ namespace ada::idna { @@ -2753,30 +2797,6 @@ uint32_t find_range_index(uint32_t key) { return low == 0 ? 0 : low - 1; } -bool ascii_has_upper_case(char* input, size_t length) { - auto broadcast = [](uint8_t v) -> uint64_t { - return 0x101010101010101ull * v; - }; - uint64_t broadcast_80 = broadcast(0x80); - uint64_t broadcast_Ap = broadcast(128 - 'A'); - uint64_t broadcast_Zp = broadcast(128 - 'Z' - 1); - size_t i = 0; - - uint64_t runner{0}; - - for (; i + 7 < length; i += 8) { - uint64_t word{}; - memcpy(&word, input + i, sizeof(word)); - runner |= (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80); - } - if (i < length) { - uint64_t word{}; - memcpy(&word, input + i, length - i); - runner |= (((word + broadcast_Ap) ^ (word + broadcast_Zp)) & broadcast_80); - } - return runner != 0; -} - void ascii_map(char* input, size_t length) { auto broadcast = [](uint8_t v) -> uint64_t { return 0x101010101010101ull * v; @@ -7915,11 +7935,11 @@ void compose(std::u32string& input) { if (composition[1] != composition[0] && previous_ccc < ccc) { // Try finding a composition. - uint16_t left = composition[0]; - uint16_t right = composition[1]; + int left = composition[0]; + int right = composition[1]; while (left + 2 < right) { // mean without overflow - uint16_t middle = left + (((right - left) >> 1) & ~1); + int middle = left + (((right - left) >> 1) & ~1); if (composition_data[middle] <= input[input_count + 1]) { left = middle; } @@ -8002,6 +8022,10 @@ static constexpr int32_t adapt(int32_t d, int32_t n, bool firsttime) { } bool punycode_to_utf32(std::string_view input, std::u32string &out) { + // See https://github.com/whatwg/url/issues/803 + if (input.starts_with("xn--")) { + return false; + } int32_t written_out{0}; out.reserve(out.size() + input.size()); uint32_t n = initial_n; @@ -8058,11 +8082,13 @@ bool punycode_to_utf32(std::string_view input, std::u32string &out) { written_out++; ++i; } - return true; } bool verify_punycode(std::string_view input) { + if (input.starts_with("xn--")) { + return false; + } size_t written_out{0}; uint32_t n = initial_n; int32_t i = 0; @@ -8132,7 +8158,7 @@ bool utf32_to_punycode(std::u32string_view input, std::string &out) { ++h; out.push_back(char(c)); } - if (c > 0x10ffff || (c >= 0xd880 && c < 0xe000)) { + if (c > 0x10ffff || (c >= 0xd800 && c < 0xe000)) { return false; } } @@ -8969,7 +8995,7 @@ inline static direction find_direction(uint32_t code_point) noexcept { inline static size_t find_last_not_of_nsm( const std::u32string_view label) noexcept { - for (int i = label.size() - 1; i >= 0; i--) + for (int i = static_cast(label.size() - 1); i >= 0; i--) if (find_direction(label[i]) != direction::NSM) return i; return std::u32string_view::npos; @@ -9485,12 +9511,14 @@ bool is_label_valid(const std::u32string_view label) { for (size_t i = 0; i <= last_non_nsm_char; i++) { const direction d = find_direction(label[i]); + // NOLINTBEGIN(bugprone-assignment-in-if-condition) // In an RTL label, if an EN is present, no AN may be present, and vice // versa. if ((d == direction::EN && ((has_en = true) && has_an)) || (d == direction::AN && ((has_an = true) && has_en))) { return false; } + // NOLINTEND(bugprone-assignment-in-if-condition) if (!(d == direction::R || d == direction::AL || d == direction::AN || d == direction::EN || d == direction::ES || d == direction::CS || @@ -9519,25 +9547,14 @@ bool is_label_valid(const std::u32string_view label) { #include #include +#include -namespace ada::idna { - -bool begins_with(std::u32string_view view, std::u32string_view prefix) { - if (view.size() < prefix.size()) { - return false; - } - // constexpr as of C++20 - return std::equal(prefix.begin(), prefix.end(), view.begin()); -} +#ifdef ADA_USE_SIMDUTF +#include "simdutf.h" +#endif -bool begins_with(std::string_view view, std::string_view prefix) { - if (view.size() < prefix.size()) { - return false; - } - // constexpr as of C++20 - return std::equal(prefix.begin(), prefix.end(), view.begin()); -} +namespace ada::idna { bool constexpr is_ascii(std::u32string_view view) { for (uint32_t c : view) { @@ -9577,8 +9594,7 @@ inline bool is_forbidden_domain_code_point(const char c) noexcept { } bool contains_forbidden_domain_code_point(std::string_view view) { - return ( - std::any_of(view.begin(), view.end(), is_forbidden_domain_code_point)); + return std::ranges::any_of(view, is_forbidden_domain_code_point); } // We return "" on error. @@ -9601,7 +9617,7 @@ static std::string from_ascii_to_ascii(std::string_view ut8_string) { label_start += label_size_with_dot; if (label_size == 0) { // empty label? Nothing to do. - } else if (begins_with(label_view, "xn--")) { + } else if (label_view.starts_with("xn--")) { // The xn-- part is the expensive game. out.append(label_view); std::string_view puny_segment_ascii( @@ -9612,6 +9628,12 @@ static std::string from_ascii_to_ascii(std::string_view ut8_string) { if (!is_ok) { return error; } + // If the input is just ASCII, it should not have been encoded + // as punycode. + // https://github.com/whatwg/url/issues/760 + if (is_ascii(tmp_buffer)) { + return error; + } std::u32string post_map = ada::idna::map(tmp_buffer); if (tmp_buffer != post_map) { return error; @@ -9644,11 +9666,20 @@ std::string to_ascii(std::string_view ut8_string) { } static const std::string error = ""; // We convert to UTF-32 + +#ifdef ADA_USE_SIMDUTF + size_t utf32_length = + simdutf::utf32_length_from_utf8(ut8_string.data(), ut8_string.size()); + std::u32string utf32(utf32_length, '\0'); + size_t actual_utf32_length = simdutf::convert_utf8_to_utf32( + ut8_string.data(), ut8_string.size(), utf32.data()); +#else size_t utf32_length = ada::idna::utf32_length_from_utf8(ut8_string.data(), ut8_string.size()); std::u32string utf32(utf32_length, '\0'); size_t actual_utf32_length = ada::idna::utf8_to_utf32( ut8_string.data(), ut8_string.size(), utf32.data()); +#endif if (actual_utf32_length == 0) { return error; } @@ -9668,7 +9699,7 @@ std::string to_ascii(std::string_view ut8_string) { label_start += label_size_with_dot; if (label_size == 0) { // empty label? Nothing to do. - } else if (begins_with(label_view, U"xn--")) { + } else if (label_view.starts_with(U"xn--")) { // we do not need to check, e.g., Xn-- because mapping goes to lower case for (char32_t c : label_view) { if (c >= 0x80) { @@ -9684,6 +9715,12 @@ std::string to_ascii(std::string_view ut8_string) { if (!is_ok) { return error; } + // If the input is just ASCII, it should not have been encoded + // as punycode. + // https://github.com/whatwg/url/issues/760 + if (is_ascii(tmp_buffer)) { + return error; + } std::u32string post_map = ada::idna::map(tmp_buffer); if (tmp_buffer != post_map) { return error; @@ -9734,6 +9771,10 @@ std::string to_ascii(std::string_view ut8_string) { #include +#ifdef ADA_USE_SIMDUTF +#include "simdutf.h" +#endif + namespace ada::idna { std::string to_unicode(std::string_view input) { std::string output; @@ -9747,17 +9788,24 @@ std::string to_unicode(std::string_view input) { is_last_label ? input.size() - label_start : loc_dot - label_start; auto label_view = std::string_view(input.data() + label_start, label_size); - if (ada::idna::begins_with(label_view, "xn--") && - ada::idna::is_ascii(label_view)) { + if (label_view.starts_with("xn--") && ada::idna::is_ascii(label_view)) { label_view.remove_prefix(4); if (ada::idna::verify_punycode(label_view)) { std::u32string tmp_buffer; if (ada::idna::punycode_to_utf32(label_view, tmp_buffer)) { +#ifdef ADA_USE_SIMDUTF + auto utf8_size = simdutf::utf8_length_from_utf32(tmp_buffer.data(), + tmp_buffer.size()); + std::string final_utf8(utf8_size, '\0'); + simdutf::convert_utf32_to_utf8(tmp_buffer.data(), tmp_buffer.size(), + final_utf8.data()); +#else auto utf8_size = ada::idna::utf8_length_from_utf32(tmp_buffer.data(), tmp_buffer.size()); std::string final_utf8(utf8_size, '\0'); ada::idna::utf32_to_utf8(tmp_buffer.data(), tmp_buffer.size(), final_utf8.data()); +#endif output.append(final_utf8); } else { // ToUnicode never fails. If any step fails, then the original input @@ -9783,6 +9831,613 @@ std::string to_unicode(std::string_view input) { } } // namespace ada::idna /* end file src/to_unicode.cpp */ +/* begin file src/identifier.cpp */ + +#include +#include +#include + +/* begin file src/id_tables.cpp */ +// IDNA 16.0.0 + +// clang-format off +#ifndef ADA_IDNA_IDENTIFIER_TABLES_H +#define ADA_IDNA_IDENTIFIER_TABLES_H +#include + +namespace ada::idna { + +const uint32_t id_continue[1393][2] = +{ + {48, 57}, {65, 90}, {95, 95}, {97, 122}, + {170, 170}, {181, 181}, {183, 183}, {186, 186}, + {192, 214}, {216, 246}, {248, 442}, {443, 443}, + {444, 447}, {448, 451}, {452, 659}, {660, 660}, + {661, 687}, {688, 705}, {710, 721}, {736, 740}, + {748, 748}, {750, 750}, {768, 879}, {880, 883}, + {884, 884}, {886, 887}, {890, 890}, {891, 893}, + {895, 895}, {902, 902}, {903, 903}, {904, 906}, + {908, 908}, {910, 929}, {931, 1013}, {1015, 1153}, + {1155, 1159}, {1162, 1327}, {1329, 1366}, {1369, 1369}, + {1376, 1416}, {1425, 1469}, {1471, 1471}, {1473, 1474}, + {1476, 1477}, {1479, 1479}, {1488, 1514}, {1519, 1522}, + {1552, 1562}, {1568, 1599}, {1600, 1600}, {1601, 1610}, + {1611, 1631}, {1632, 1641}, {1646, 1647}, {1648, 1648}, + {1649, 1747}, {1749, 1749}, {1750, 1756}, {1759, 1764}, + {1765, 1766}, {1767, 1768}, {1770, 1773}, {1774, 1775}, + {1776, 1785}, {1786, 1788}, {1791, 1791}, {1808, 1808}, + {1809, 1809}, {1810, 1839}, {1840, 1866}, {1869, 1957}, + {1958, 1968}, {1969, 1969}, {1984, 1993}, {1994, 2026}, + {2027, 2035}, {2036, 2037}, {2042, 2042}, {2045, 2045}, + {2048, 2069}, {2070, 2073}, {2074, 2074}, {2075, 2083}, + {2084, 2084}, {2085, 2087}, {2088, 2088}, {2089, 2093}, + {2112, 2136}, {2137, 2139}, {2144, 2154}, {2160, 2183}, + {2185, 2190}, {2199, 2207}, {2208, 2248}, {2249, 2249}, + {2250, 2273}, {2275, 2306}, {2307, 2307}, {2308, 2361}, + {2362, 2362}, {2363, 2363}, {2364, 2364}, {2365, 2365}, + {2366, 2368}, {2369, 2376}, {2377, 2380}, {2381, 2381}, + {2382, 2383}, {2384, 2384}, {2385, 2391}, {2392, 2401}, + {2402, 2403}, {2406, 2415}, {2417, 2417}, {2418, 2432}, + {2433, 2433}, {2434, 2435}, {2437, 2444}, {2447, 2448}, + {2451, 2472}, {2474, 2480}, {2482, 2482}, {2486, 2489}, + {2492, 2492}, {2493, 2493}, {2494, 2496}, {2497, 2500}, + {2503, 2504}, {2507, 2508}, {2509, 2509}, {2510, 2510}, + {2519, 2519}, {2524, 2525}, {2527, 2529}, {2530, 2531}, + {2534, 2543}, {2544, 2545}, {2556, 2556}, {2558, 2558}, + {2561, 2562}, {2563, 2563}, {2565, 2570}, {2575, 2576}, + {2579, 2600}, {2602, 2608}, {2610, 2611}, {2613, 2614}, + {2616, 2617}, {2620, 2620}, {2622, 2624}, {2625, 2626}, + {2631, 2632}, {2635, 2637}, {2641, 2641}, {2649, 2652}, + {2654, 2654}, {2662, 2671}, {2672, 2673}, {2674, 2676}, + {2677, 2677}, {2689, 2690}, {2691, 2691}, {2693, 2701}, + {2703, 2705}, {2707, 2728}, {2730, 2736}, {2738, 2739}, + {2741, 2745}, {2748, 2748}, {2749, 2749}, {2750, 2752}, + {2753, 2757}, {2759, 2760}, {2761, 2761}, {2763, 2764}, + {2765, 2765}, {2768, 2768}, {2784, 2785}, {2786, 2787}, + {2790, 2799}, {2809, 2809}, {2810, 2815}, {2817, 2817}, + {2818, 2819}, {2821, 2828}, {2831, 2832}, {2835, 2856}, + {2858, 2864}, {2866, 2867}, {2869, 2873}, {2876, 2876}, + {2877, 2877}, {2878, 2878}, {2879, 2879}, {2880, 2880}, + {2881, 2884}, {2887, 2888}, {2891, 2892}, {2893, 2893}, + {2901, 2902}, {2903, 2903}, {2908, 2909}, {2911, 2913}, + {2914, 2915}, {2918, 2927}, {2929, 2929}, {2946, 2946}, + {2947, 2947}, {2949, 2954}, {2958, 2960}, {2962, 2965}, + {2969, 2970}, {2972, 2972}, {2974, 2975}, {2979, 2980}, + {2984, 2986}, {2990, 3001}, {3006, 3007}, {3008, 3008}, + {3009, 3010}, {3014, 3016}, {3018, 3020}, {3021, 3021}, + {3024, 3024}, {3031, 3031}, {3046, 3055}, {3072, 3072}, + {3073, 3075}, {3076, 3076}, {3077, 3084}, {3086, 3088}, + {3090, 3112}, {3114, 3129}, {3132, 3132}, {3133, 3133}, + {3134, 3136}, {3137, 3140}, {3142, 3144}, {3146, 3149}, + {3157, 3158}, {3160, 3162}, {3165, 3165}, {3168, 3169}, + {3170, 3171}, {3174, 3183}, {3200, 3200}, {3201, 3201}, + {3202, 3203}, {3205, 3212}, {3214, 3216}, {3218, 3240}, + {3242, 3251}, {3253, 3257}, {3260, 3260}, {3261, 3261}, + {3262, 3262}, {3263, 3263}, {3264, 3268}, {3270, 3270}, + {3271, 3272}, {3274, 3275}, {3276, 3277}, {3285, 3286}, + {3293, 3294}, {3296, 3297}, {3298, 3299}, {3302, 3311}, + {3313, 3314}, {3315, 3315}, {3328, 3329}, {3330, 3331}, + {3332, 3340}, {3342, 3344}, {3346, 3386}, {3387, 3388}, + {3389, 3389}, {3390, 3392}, {3393, 3396}, {3398, 3400}, + {3402, 3404}, {3405, 3405}, {3406, 3406}, {3412, 3414}, + {3415, 3415}, {3423, 3425}, {3426, 3427}, {3430, 3439}, + {3450, 3455}, {3457, 3457}, {3458, 3459}, {3461, 3478}, + {3482, 3505}, {3507, 3515}, {3517, 3517}, {3520, 3526}, + {3530, 3530}, {3535, 3537}, {3538, 3540}, {3542, 3542}, + {3544, 3551}, {3558, 3567}, {3570, 3571}, {3585, 3632}, + {3633, 3633}, {3634, 3635}, {3636, 3642}, {3648, 3653}, + {3654, 3654}, {3655, 3662}, {3664, 3673}, {3713, 3714}, + {3716, 3716}, {3718, 3722}, {3724, 3747}, {3749, 3749}, + {3751, 3760}, {3761, 3761}, {3762, 3763}, {3764, 3772}, + {3773, 3773}, {3776, 3780}, {3782, 3782}, {3784, 3790}, + {3792, 3801}, {3804, 3807}, {3840, 3840}, {3864, 3865}, + {3872, 3881}, {3893, 3893}, {3895, 3895}, {3897, 3897}, + {3902, 3903}, {3904, 3911}, {3913, 3948}, {3953, 3966}, + {3967, 3967}, {3968, 3972}, {3974, 3975}, {3976, 3980}, + {3981, 3991}, {3993, 4028}, {4038, 4038}, {4096, 4138}, + {4139, 4140}, {4141, 4144}, {4145, 4145}, {4146, 4151}, + {4152, 4152}, {4153, 4154}, {4155, 4156}, {4157, 4158}, + {4159, 4159}, {4160, 4169}, {4176, 4181}, {4182, 4183}, + {4184, 4185}, {4186, 4189}, {4190, 4192}, {4193, 4193}, + {4194, 4196}, {4197, 4198}, {4199, 4205}, {4206, 4208}, + {4209, 4212}, {4213, 4225}, {4226, 4226}, {4227, 4228}, + {4229, 4230}, {4231, 4236}, {4237, 4237}, {4238, 4238}, + {4239, 4239}, {4240, 4249}, {4250, 4252}, {4253, 4253}, + {4256, 4293}, {4295, 4295}, {4301, 4301}, {4304, 4346}, + {4348, 4348}, {4349, 4351}, {4352, 4680}, {4682, 4685}, + {4688, 4694}, {4696, 4696}, {4698, 4701}, {4704, 4744}, + {4746, 4749}, {4752, 4784}, {4786, 4789}, {4792, 4798}, + {4800, 4800}, {4802, 4805}, {4808, 4822}, {4824, 4880}, + {4882, 4885}, {4888, 4954}, {4957, 4959}, {4969, 4977}, + {4992, 5007}, {5024, 5109}, {5112, 5117}, {5121, 5740}, + {5743, 5759}, {5761, 5786}, {5792, 5866}, {5870, 5872}, + {5873, 5880}, {5888, 5905}, {5906, 5908}, {5909, 5909}, + {5919, 5937}, {5938, 5939}, {5940, 5940}, {5952, 5969}, + {5970, 5971}, {5984, 5996}, {5998, 6000}, {6002, 6003}, + {6016, 6067}, {6068, 6069}, {6070, 6070}, {6071, 6077}, + {6078, 6085}, {6086, 6086}, {6087, 6088}, {6089, 6099}, + {6103, 6103}, {6108, 6108}, {6109, 6109}, {6112, 6121}, + {6155, 6157}, {6159, 6159}, {6160, 6169}, {6176, 6210}, + {6211, 6211}, {6212, 6264}, {6272, 6276}, {6277, 6278}, + {6279, 6312}, {6313, 6313}, {6314, 6314}, {6320, 6389}, + {6400, 6430}, {6432, 6434}, {6435, 6438}, {6439, 6440}, + {6441, 6443}, {6448, 6449}, {6450, 6450}, {6451, 6456}, + {6457, 6459}, {6470, 6479}, {6480, 6509}, {6512, 6516}, + {6528, 6571}, {6576, 6601}, {6608, 6617}, {6618, 6618}, + {6656, 6678}, {6679, 6680}, {6681, 6682}, {6683, 6683}, + {6688, 6740}, {6741, 6741}, {6742, 6742}, {6743, 6743}, + {6744, 6750}, {6752, 6752}, {6753, 6753}, {6754, 6754}, + {6755, 6756}, {6757, 6764}, {6765, 6770}, {6771, 6780}, + {6783, 6783}, {6784, 6793}, {6800, 6809}, {6823, 6823}, + {6832, 6845}, {6847, 6862}, {6912, 6915}, {6916, 6916}, + {6917, 6963}, {6964, 6964}, {6965, 6965}, {6966, 6970}, + {6971, 6971}, {6972, 6972}, {6973, 6977}, {6978, 6978}, + {6979, 6980}, {6981, 6988}, {6992, 7001}, {7019, 7027}, + {7040, 7041}, {7042, 7042}, {7043, 7072}, {7073, 7073}, + {7074, 7077}, {7078, 7079}, {7080, 7081}, {7082, 7082}, + {7083, 7085}, {7086, 7087}, {7088, 7097}, {7098, 7141}, + {7142, 7142}, {7143, 7143}, {7144, 7145}, {7146, 7148}, + {7149, 7149}, {7150, 7150}, {7151, 7153}, {7154, 7155}, + {7168, 7203}, {7204, 7211}, {7212, 7219}, {7220, 7221}, + {7222, 7223}, {7232, 7241}, {7245, 7247}, {7248, 7257}, + {7258, 7287}, {7288, 7293}, {7296, 7306}, {7312, 7354}, + {7357, 7359}, {7376, 7378}, {7380, 7392}, {7393, 7393}, + {7394, 7400}, {7401, 7404}, {7405, 7405}, {7406, 7411}, + {7412, 7412}, {7413, 7414}, {7415, 7415}, {7416, 7417}, + {7418, 7418}, {7424, 7467}, {7468, 7530}, {7531, 7543}, + {7544, 7544}, {7545, 7578}, {7579, 7615}, {7616, 7679}, + {7680, 7957}, {7960, 7965}, {7968, 8005}, {8008, 8013}, + {8016, 8023}, {8025, 8025}, {8027, 8027}, {8029, 8029}, + {8031, 8061}, {8064, 8116}, {8118, 8124}, {8126, 8126}, + {8130, 8132}, {8134, 8140}, {8144, 8147}, {8150, 8155}, + {8160, 8172}, {8178, 8180}, {8182, 8188}, {8204, 8205}, + {8255, 8256}, {8276, 8276}, {8305, 8305}, {8319, 8319}, + {8336, 8348}, {8400, 8412}, {8417, 8417}, {8421, 8432}, + {8450, 8450}, {8455, 8455}, {8458, 8467}, {8469, 8469}, + {8472, 8472}, {8473, 8477}, {8484, 8484}, {8486, 8486}, + {8488, 8488}, {8490, 8493}, {8494, 8494}, {8495, 8500}, + {8501, 8504}, {8505, 8505}, {8508, 8511}, {8517, 8521}, + {8526, 8526}, {8544, 8578}, {8579, 8580}, {8581, 8584}, + {11264, 11387}, {11388, 11389}, {11390, 11492}, {11499, 11502}, + {11503, 11505}, {11506, 11507}, {11520, 11557}, {11559, 11559}, + {11565, 11565}, {11568, 11623}, {11631, 11631}, {11647, 11647}, + {11648, 11670}, {11680, 11686}, {11688, 11694}, {11696, 11702}, + {11704, 11710}, {11712, 11718}, {11720, 11726}, {11728, 11734}, + {11736, 11742}, {11744, 11775}, {12293, 12293}, {12294, 12294}, + {12295, 12295}, {12321, 12329}, {12330, 12333}, {12334, 12335}, + {12337, 12341}, {12344, 12346}, {12347, 12347}, {12348, 12348}, + {12353, 12438}, {12441, 12442}, {12443, 12444}, {12445, 12446}, + {12447, 12447}, {12449, 12538}, {12539, 12539}, {12540, 12542}, + {12543, 12543}, {12549, 12591}, {12593, 12686}, {12704, 12735}, + {12784, 12799}, {13312, 19903}, {19968, 40980}, {40981, 40981}, + {40982, 42124}, {42192, 42231}, {42232, 42237}, {42240, 42507}, + {42508, 42508}, {42512, 42527}, {42528, 42537}, {42538, 42539}, + {42560, 42605}, {42606, 42606}, {42607, 42607}, {42612, 42621}, + {42623, 42623}, {42624, 42651}, {42652, 42653}, {42654, 42655}, + {42656, 42725}, {42726, 42735}, {42736, 42737}, {42775, 42783}, + {42786, 42863}, {42864, 42864}, {42865, 42887}, {42888, 42888}, + {42891, 42894}, {42895, 42895}, {42896, 42957}, {42960, 42961}, + {42963, 42963}, {42965, 42972}, {42994, 42996}, {42997, 42998}, + {42999, 42999}, {43000, 43001}, {43002, 43002}, {43003, 43009}, + {43010, 43010}, {43011, 43013}, {43014, 43014}, {43015, 43018}, + {43019, 43019}, {43020, 43042}, {43043, 43044}, {43045, 43046}, + {43047, 43047}, {43052, 43052}, {43072, 43123}, {43136, 43137}, + {43138, 43187}, {43188, 43203}, {43204, 43205}, {43216, 43225}, + {43232, 43249}, {43250, 43255}, {43259, 43259}, {43261, 43262}, + {43263, 43263}, {43264, 43273}, {43274, 43301}, {43302, 43309}, + {43312, 43334}, {43335, 43345}, {43346, 43347}, {43360, 43388}, + {43392, 43394}, {43395, 43395}, {43396, 43442}, {43443, 43443}, + {43444, 43445}, {43446, 43449}, {43450, 43451}, {43452, 43453}, + {43454, 43456}, {43471, 43471}, {43472, 43481}, {43488, 43492}, + {43493, 43493}, {43494, 43494}, {43495, 43503}, {43504, 43513}, + {43514, 43518}, {43520, 43560}, {43561, 43566}, {43567, 43568}, + {43569, 43570}, {43571, 43572}, {43573, 43574}, {43584, 43586}, + {43587, 43587}, {43588, 43595}, {43596, 43596}, {43597, 43597}, + {43600, 43609}, {43616, 43631}, {43632, 43632}, {43633, 43638}, + {43642, 43642}, {43643, 43643}, {43644, 43644}, {43645, 43645}, + {43646, 43695}, {43696, 43696}, {43697, 43697}, {43698, 43700}, + {43701, 43702}, {43703, 43704}, {43705, 43709}, {43710, 43711}, + {43712, 43712}, {43713, 43713}, {43714, 43714}, {43739, 43740}, + {43741, 43741}, {43744, 43754}, {43755, 43755}, {43756, 43757}, + {43758, 43759}, {43762, 43762}, {43763, 43764}, {43765, 43765}, + {43766, 43766}, {43777, 43782}, {43785, 43790}, {43793, 43798}, + {43808, 43814}, {43816, 43822}, {43824, 43866}, {43868, 43871}, + {43872, 43880}, {43881, 43881}, {43888, 43967}, {43968, 44002}, + {44003, 44004}, {44005, 44005}, {44006, 44007}, {44008, 44008}, + {44009, 44010}, {44012, 44012}, {44013, 44013}, {44016, 44025}, + {44032, 55203}, {55216, 55238}, {55243, 55291}, {63744, 64109}, + {64112, 64217}, {64256, 64262}, {64275, 64279}, {64285, 64285}, + {64286, 64286}, {64287, 64296}, {64298, 64310}, {64312, 64316}, + {64318, 64318}, {64320, 64321}, {64323, 64324}, {64326, 64433}, + {64467, 64829}, {64848, 64911}, {64914, 64967}, {65008, 65019}, + {65024, 65039}, {65056, 65071}, {65075, 65076}, {65101, 65103}, + {65136, 65140}, {65142, 65276}, {65296, 65305}, {65313, 65338}, + {65343, 65343}, {65345, 65370}, {65381, 65381}, {65382, 65391}, + {65392, 65392}, {65393, 65437}, {65438, 65439}, {65440, 65470}, + {65474, 65479}, {65482, 65487}, {65490, 65495}, {65498, 65500}, + {65536, 65547}, {65549, 65574}, {65576, 65594}, {65596, 65597}, + {65599, 65613}, {65616, 65629}, {65664, 65786}, {65856, 65908}, + {66045, 66045}, {66176, 66204}, {66208, 66256}, {66272, 66272}, + {66304, 66335}, {66349, 66368}, {66369, 66369}, {66370, 66377}, + {66378, 66378}, {66384, 66421}, {66422, 66426}, {66432, 66461}, + {66464, 66499}, {66504, 66511}, {66513, 66517}, {66560, 66639}, + {66640, 66717}, {66720, 66729}, {66736, 66771}, {66776, 66811}, + {66816, 66855}, {66864, 66915}, {66928, 66938}, {66940, 66954}, + {66956, 66962}, {66964, 66965}, {66967, 66977}, {66979, 66993}, + {66995, 67001}, {67003, 67004}, {67008, 67059}, {67072, 67382}, + {67392, 67413}, {67424, 67431}, {67456, 67461}, {67463, 67504}, + {67506, 67514}, {67584, 67589}, {67592, 67592}, {67594, 67637}, + {67639, 67640}, {67644, 67644}, {67647, 67669}, {67680, 67702}, + {67712, 67742}, {67808, 67826}, {67828, 67829}, {67840, 67861}, + {67872, 67897}, {67968, 68023}, {68030, 68031}, {68096, 68096}, + {68097, 68099}, {68101, 68102}, {68108, 68111}, {68112, 68115}, + {68117, 68119}, {68121, 68149}, {68152, 68154}, {68159, 68159}, + {68192, 68220}, {68224, 68252}, {68288, 68295}, {68297, 68324}, + {68325, 68326}, {68352, 68405}, {68416, 68437}, {68448, 68466}, + {68480, 68497}, {68608, 68680}, {68736, 68786}, {68800, 68850}, + {68864, 68899}, {68900, 68903}, {68912, 68921}, {68928, 68937}, + {68938, 68941}, {68942, 68942}, {68943, 68943}, {68944, 68965}, + {68969, 68973}, {68975, 68975}, {68976, 68997}, {69248, 69289}, + {69291, 69292}, {69296, 69297}, {69314, 69316}, {69372, 69375}, + {69376, 69404}, {69415, 69415}, {69424, 69445}, {69446, 69456}, + {69488, 69505}, {69506, 69509}, {69552, 69572}, {69600, 69622}, + {69632, 69632}, {69633, 69633}, {69634, 69634}, {69635, 69687}, + {69688, 69702}, {69734, 69743}, {69744, 69744}, {69745, 69746}, + {69747, 69748}, {69749, 69749}, {69759, 69761}, {69762, 69762}, + {69763, 69807}, {69808, 69810}, {69811, 69814}, {69815, 69816}, + {69817, 69818}, {69826, 69826}, {69840, 69864}, {69872, 69881}, + {69888, 69890}, {69891, 69926}, {69927, 69931}, {69932, 69932}, + {69933, 69940}, {69942, 69951}, {69956, 69956}, {69957, 69958}, + {69959, 69959}, {69968, 70002}, {70003, 70003}, {70006, 70006}, + {70016, 70017}, {70018, 70018}, {70019, 70066}, {70067, 70069}, + {70070, 70078}, {70079, 70080}, {70081, 70084}, {70089, 70092}, + {70094, 70094}, {70095, 70095}, {70096, 70105}, {70106, 70106}, + {70108, 70108}, {70144, 70161}, {70163, 70187}, {70188, 70190}, + {70191, 70193}, {70194, 70195}, {70196, 70196}, {70197, 70197}, + {70198, 70199}, {70206, 70206}, {70207, 70208}, {70209, 70209}, + {70272, 70278}, {70280, 70280}, {70282, 70285}, {70287, 70301}, + {70303, 70312}, {70320, 70366}, {70367, 70367}, {70368, 70370}, + {70371, 70378}, {70384, 70393}, {70400, 70401}, {70402, 70403}, + {70405, 70412}, {70415, 70416}, {70419, 70440}, {70442, 70448}, + {70450, 70451}, {70453, 70457}, {70459, 70460}, {70461, 70461}, + {70462, 70463}, {70464, 70464}, {70465, 70468}, {70471, 70472}, + {70475, 70477}, {70480, 70480}, {70487, 70487}, {70493, 70497}, + {70498, 70499}, {70502, 70508}, {70512, 70516}, {70528, 70537}, + {70539, 70539}, {70542, 70542}, {70544, 70581}, {70583, 70583}, + {70584, 70586}, {70587, 70592}, {70594, 70594}, {70597, 70597}, + {70599, 70602}, {70604, 70605}, {70606, 70606}, {70607, 70607}, + {70608, 70608}, {70609, 70609}, {70610, 70610}, {70611, 70611}, + {70625, 70626}, {70656, 70708}, {70709, 70711}, {70712, 70719}, + {70720, 70721}, {70722, 70724}, {70725, 70725}, {70726, 70726}, + {70727, 70730}, {70736, 70745}, {70750, 70750}, {70751, 70753}, + {70784, 70831}, {70832, 70834}, {70835, 70840}, {70841, 70841}, + {70842, 70842}, {70843, 70846}, {70847, 70848}, {70849, 70849}, + {70850, 70851}, {70852, 70853}, {70855, 70855}, {70864, 70873}, + {71040, 71086}, {71087, 71089}, {71090, 71093}, {71096, 71099}, + {71100, 71101}, {71102, 71102}, {71103, 71104}, {71128, 71131}, + {71132, 71133}, {71168, 71215}, {71216, 71218}, {71219, 71226}, + {71227, 71228}, {71229, 71229}, {71230, 71230}, {71231, 71232}, + {71236, 71236}, {71248, 71257}, {71296, 71338}, {71339, 71339}, + {71340, 71340}, {71341, 71341}, {71342, 71343}, {71344, 71349}, + {71350, 71350}, {71351, 71351}, {71352, 71352}, {71360, 71369}, + {71376, 71395}, {71424, 71450}, {71453, 71453}, {71454, 71454}, + {71455, 71455}, {71456, 71457}, {71458, 71461}, {71462, 71462}, + {71463, 71467}, {71472, 71481}, {71488, 71494}, {71680, 71723}, + {71724, 71726}, {71727, 71735}, {71736, 71736}, {71737, 71738}, + {71840, 71903}, {71904, 71913}, {71935, 71942}, {71945, 71945}, + {71948, 71955}, {71957, 71958}, {71960, 71983}, {71984, 71989}, + {71991, 71992}, {71995, 71996}, {71997, 71997}, {71998, 71998}, + {71999, 71999}, {72000, 72000}, {72001, 72001}, {72002, 72002}, + {72003, 72003}, {72016, 72025}, {72096, 72103}, {72106, 72144}, + {72145, 72147}, {72148, 72151}, {72154, 72155}, {72156, 72159}, + {72160, 72160}, {72161, 72161}, {72163, 72163}, {72164, 72164}, + {72192, 72192}, {72193, 72202}, {72203, 72242}, {72243, 72248}, + {72249, 72249}, {72250, 72250}, {72251, 72254}, {72263, 72263}, + {72272, 72272}, {72273, 72278}, {72279, 72280}, {72281, 72283}, + {72284, 72329}, {72330, 72342}, {72343, 72343}, {72344, 72345}, + {72349, 72349}, {72368, 72440}, {72640, 72672}, {72688, 72697}, + {72704, 72712}, {72714, 72750}, {72751, 72751}, {72752, 72758}, + {72760, 72765}, {72766, 72766}, {72767, 72767}, {72768, 72768}, + {72784, 72793}, {72818, 72847}, {72850, 72871}, {72873, 72873}, + {72874, 72880}, {72881, 72881}, {72882, 72883}, {72884, 72884}, + {72885, 72886}, {72960, 72966}, {72968, 72969}, {72971, 73008}, + {73009, 73014}, {73018, 73018}, {73020, 73021}, {73023, 73029}, + {73030, 73030}, {73031, 73031}, {73040, 73049}, {73056, 73061}, + {73063, 73064}, {73066, 73097}, {73098, 73102}, {73104, 73105}, + {73107, 73108}, {73109, 73109}, {73110, 73110}, {73111, 73111}, + {73112, 73112}, {73120, 73129}, {73440, 73458}, {73459, 73460}, + {73461, 73462}, {73472, 73473}, {73474, 73474}, {73475, 73475}, + {73476, 73488}, {73490, 73523}, {73524, 73525}, {73526, 73530}, + {73534, 73535}, {73536, 73536}, {73537, 73537}, {73538, 73538}, + {73552, 73561}, {73562, 73562}, {73648, 73648}, {73728, 74649}, + {74752, 74862}, {74880, 75075}, {77712, 77808}, {77824, 78895}, + {78912, 78912}, {78913, 78918}, {78919, 78933}, {78944, 82938}, + {82944, 83526}, {90368, 90397}, {90398, 90409}, {90410, 90412}, + {90413, 90415}, {90416, 90425}, {92160, 92728}, {92736, 92766}, + {92768, 92777}, {92784, 92862}, {92864, 92873}, {92880, 92909}, + {92912, 92916}, {92928, 92975}, {92976, 92982}, {92992, 92995}, + {93008, 93017}, {93027, 93047}, {93053, 93071}, {93504, 93506}, + {93507, 93546}, {93547, 93548}, {93552, 93561}, {93760, 93823}, + {93952, 94026}, {94031, 94031}, {94032, 94032}, {94033, 94087}, + {94095, 94098}, {94099, 94111}, {94176, 94177}, {94179, 94179}, + {94180, 94180}, {94192, 94193}, {94208, 100343}, {100352, 101589}, + {101631, 101640}, {110576, 110579}, {110581, 110587}, {110589, 110590}, + {110592, 110882}, {110898, 110898}, {110928, 110930}, {110933, 110933}, + {110948, 110951}, {110960, 111355}, {113664, 113770}, {113776, 113788}, + {113792, 113800}, {113808, 113817}, {113821, 113822}, {118000, 118009}, + {118528, 118573}, {118576, 118598}, {119141, 119142}, {119143, 119145}, + {119149, 119154}, {119163, 119170}, {119173, 119179}, {119210, 119213}, + {119362, 119364}, {119808, 119892}, {119894, 119964}, {119966, 119967}, + {119970, 119970}, {119973, 119974}, {119977, 119980}, {119982, 119993}, + {119995, 119995}, {119997, 120003}, {120005, 120069}, {120071, 120074}, + {120077, 120084}, {120086, 120092}, {120094, 120121}, {120123, 120126}, + {120128, 120132}, {120134, 120134}, {120138, 120144}, {120146, 120485}, + {120488, 120512}, {120514, 120538}, {120540, 120570}, {120572, 120596}, + {120598, 120628}, {120630, 120654}, {120656, 120686}, {120688, 120712}, + {120714, 120744}, {120746, 120770}, {120772, 120779}, {120782, 120831}, + {121344, 121398}, {121403, 121452}, {121461, 121461}, {121476, 121476}, + {121499, 121503}, {121505, 121519}, {122624, 122633}, {122634, 122634}, + {122635, 122654}, {122661, 122666}, {122880, 122886}, {122888, 122904}, + {122907, 122913}, {122915, 122916}, {122918, 122922}, {122928, 122989}, + {123023, 123023}, {123136, 123180}, {123184, 123190}, {123191, 123197}, + {123200, 123209}, {123214, 123214}, {123536, 123565}, {123566, 123566}, + {123584, 123627}, {123628, 123631}, {123632, 123641}, {124112, 124138}, + {124139, 124139}, {124140, 124143}, {124144, 124153}, {124368, 124397}, + {124398, 124399}, {124400, 124400}, {124401, 124410}, {124896, 124902}, + {124904, 124907}, {124909, 124910}, {124912, 124926}, {124928, 125124}, + {125136, 125142}, {125184, 125251}, {125252, 125258}, {125259, 125259}, + {125264, 125273}, {126464, 126467}, {126469, 126495}, {126497, 126498}, + {126500, 126500}, {126503, 126503}, {126505, 126514}, {126516, 126519}, + {126521, 126521}, {126523, 126523}, {126530, 126530}, {126535, 126535}, + {126537, 126537}, {126539, 126539}, {126541, 126543}, {126545, 126546}, + {126548, 126548}, {126551, 126551}, {126553, 126553}, {126555, 126555}, + {126557, 126557}, {126559, 126559}, {126561, 126562}, {126564, 126564}, + {126567, 126570}, {126572, 126578}, {126580, 126583}, {126585, 126588}, + {126590, 126590}, {126592, 126601}, {126603, 126619}, {126625, 126627}, + {126629, 126633}, {126635, 126651}, {130032, 130041}, {131072, 173791}, + {173824, 177977}, {177984, 178205}, {178208, 183969}, {183984, 191456}, + {191472, 192093}, {194560, 195101}, {196608, 201546}, {201552, 205743}, + {917760, 917999} +}; +const uint32_t id_start[763][2] = +{ + {65, 90}, {97, 122}, {170, 170}, {181, 181}, + {186, 186}, {192, 214}, {216, 246}, {248, 442}, + {443, 443}, {444, 447}, {448, 451}, {452, 659}, + {660, 660}, {661, 687}, {688, 705}, {710, 721}, + {736, 740}, {748, 748}, {750, 750}, {880, 883}, + {884, 884}, {886, 887}, {890, 890}, {891, 893}, + {895, 895}, {902, 902}, {904, 906}, {908, 908}, + {910, 929}, {931, 1013}, {1015, 1153}, {1162, 1327}, + {1329, 1366}, {1369, 1369}, {1376, 1416}, {1488, 1514}, + {1519, 1522}, {1568, 1599}, {1600, 1600}, {1601, 1610}, + {1646, 1647}, {1649, 1747}, {1749, 1749}, {1765, 1766}, + {1774, 1775}, {1786, 1788}, {1791, 1791}, {1808, 1808}, + {1810, 1839}, {1869, 1957}, {1969, 1969}, {1994, 2026}, + {2036, 2037}, {2042, 2042}, {2048, 2069}, {2074, 2074}, + {2084, 2084}, {2088, 2088}, {2112, 2136}, {2144, 2154}, + {2160, 2183}, {2185, 2190}, {2208, 2248}, {2249, 2249}, + {2308, 2361}, {2365, 2365}, {2384, 2384}, {2392, 2401}, + {2417, 2417}, {2418, 2432}, {2437, 2444}, {2447, 2448}, + {2451, 2472}, {2474, 2480}, {2482, 2482}, {2486, 2489}, + {2493, 2493}, {2510, 2510}, {2524, 2525}, {2527, 2529}, + {2544, 2545}, {2556, 2556}, {2565, 2570}, {2575, 2576}, + {2579, 2600}, {2602, 2608}, {2610, 2611}, {2613, 2614}, + {2616, 2617}, {2649, 2652}, {2654, 2654}, {2674, 2676}, + {2693, 2701}, {2703, 2705}, {2707, 2728}, {2730, 2736}, + {2738, 2739}, {2741, 2745}, {2749, 2749}, {2768, 2768}, + {2784, 2785}, {2809, 2809}, {2821, 2828}, {2831, 2832}, + {2835, 2856}, {2858, 2864}, {2866, 2867}, {2869, 2873}, + {2877, 2877}, {2908, 2909}, {2911, 2913}, {2929, 2929}, + {2947, 2947}, {2949, 2954}, {2958, 2960}, {2962, 2965}, + {2969, 2970}, {2972, 2972}, {2974, 2975}, {2979, 2980}, + {2984, 2986}, {2990, 3001}, {3024, 3024}, {3077, 3084}, + {3086, 3088}, {3090, 3112}, {3114, 3129}, {3133, 3133}, + {3160, 3162}, {3165, 3165}, {3168, 3169}, {3200, 3200}, + {3205, 3212}, {3214, 3216}, {3218, 3240}, {3242, 3251}, + {3253, 3257}, {3261, 3261}, {3293, 3294}, {3296, 3297}, + {3313, 3314}, {3332, 3340}, {3342, 3344}, {3346, 3386}, + {3389, 3389}, {3406, 3406}, {3412, 3414}, {3423, 3425}, + {3450, 3455}, {3461, 3478}, {3482, 3505}, {3507, 3515}, + {3517, 3517}, {3520, 3526}, {3585, 3632}, {3634, 3635}, + {3648, 3653}, {3654, 3654}, {3713, 3714}, {3716, 3716}, + {3718, 3722}, {3724, 3747}, {3749, 3749}, {3751, 3760}, + {3762, 3763}, {3773, 3773}, {3776, 3780}, {3782, 3782}, + {3804, 3807}, {3840, 3840}, {3904, 3911}, {3913, 3948}, + {3976, 3980}, {4096, 4138}, {4159, 4159}, {4176, 4181}, + {4186, 4189}, {4193, 4193}, {4197, 4198}, {4206, 4208}, + {4213, 4225}, {4238, 4238}, {4256, 4293}, {4295, 4295}, + {4301, 4301}, {4304, 4346}, {4348, 4348}, {4349, 4351}, + {4352, 4680}, {4682, 4685}, {4688, 4694}, {4696, 4696}, + {4698, 4701}, {4704, 4744}, {4746, 4749}, {4752, 4784}, + {4786, 4789}, {4792, 4798}, {4800, 4800}, {4802, 4805}, + {4808, 4822}, {4824, 4880}, {4882, 4885}, {4888, 4954}, + {4992, 5007}, {5024, 5109}, {5112, 5117}, {5121, 5740}, + {5743, 5759}, {5761, 5786}, {5792, 5866}, {5870, 5872}, + {5873, 5880}, {5888, 5905}, {5919, 5937}, {5952, 5969}, + {5984, 5996}, {5998, 6000}, {6016, 6067}, {6103, 6103}, + {6108, 6108}, {6176, 6210}, {6211, 6211}, {6212, 6264}, + {6272, 6276}, {6277, 6278}, {6279, 6312}, {6314, 6314}, + {6320, 6389}, {6400, 6430}, {6480, 6509}, {6512, 6516}, + {6528, 6571}, {6576, 6601}, {6656, 6678}, {6688, 6740}, + {6823, 6823}, {6917, 6963}, {6981, 6988}, {7043, 7072}, + {7086, 7087}, {7098, 7141}, {7168, 7203}, {7245, 7247}, + {7258, 7287}, {7288, 7293}, {7296, 7306}, {7312, 7354}, + {7357, 7359}, {7401, 7404}, {7406, 7411}, {7413, 7414}, + {7418, 7418}, {7424, 7467}, {7468, 7530}, {7531, 7543}, + {7544, 7544}, {7545, 7578}, {7579, 7615}, {7680, 7957}, + {7960, 7965}, {7968, 8005}, {8008, 8013}, {8016, 8023}, + {8025, 8025}, {8027, 8027}, {8029, 8029}, {8031, 8061}, + {8064, 8116}, {8118, 8124}, {8126, 8126}, {8130, 8132}, + {8134, 8140}, {8144, 8147}, {8150, 8155}, {8160, 8172}, + {8178, 8180}, {8182, 8188}, {8305, 8305}, {8319, 8319}, + {8336, 8348}, {8450, 8450}, {8455, 8455}, {8458, 8467}, + {8469, 8469}, {8472, 8472}, {8473, 8477}, {8484, 8484}, + {8486, 8486}, {8488, 8488}, {8490, 8493}, {8494, 8494}, + {8495, 8500}, {8501, 8504}, {8505, 8505}, {8508, 8511}, + {8517, 8521}, {8526, 8526}, {8544, 8578}, {8579, 8580}, + {8581, 8584}, {11264, 11387}, {11388, 11389}, {11390, 11492}, + {11499, 11502}, {11506, 11507}, {11520, 11557}, {11559, 11559}, + {11565, 11565}, {11568, 11623}, {11631, 11631}, {11648, 11670}, + {11680, 11686}, {11688, 11694}, {11696, 11702}, {11704, 11710}, + {11712, 11718}, {11720, 11726}, {11728, 11734}, {11736, 11742}, + {12293, 12293}, {12294, 12294}, {12295, 12295}, {12321, 12329}, + {12337, 12341}, {12344, 12346}, {12347, 12347}, {12348, 12348}, + {12353, 12438}, {12443, 12444}, {12445, 12446}, {12447, 12447}, + {12449, 12538}, {12540, 12542}, {12543, 12543}, {12549, 12591}, + {12593, 12686}, {12704, 12735}, {12784, 12799}, {13312, 19903}, + {19968, 40980}, {40981, 40981}, {40982, 42124}, {42192, 42231}, + {42232, 42237}, {42240, 42507}, {42508, 42508}, {42512, 42527}, + {42538, 42539}, {42560, 42605}, {42606, 42606}, {42623, 42623}, + {42624, 42651}, {42652, 42653}, {42656, 42725}, {42726, 42735}, + {42775, 42783}, {42786, 42863}, {42864, 42864}, {42865, 42887}, + {42888, 42888}, {42891, 42894}, {42895, 42895}, {42896, 42957}, + {42960, 42961}, {42963, 42963}, {42965, 42972}, {42994, 42996}, + {42997, 42998}, {42999, 42999}, {43000, 43001}, {43002, 43002}, + {43003, 43009}, {43011, 43013}, {43015, 43018}, {43020, 43042}, + {43072, 43123}, {43138, 43187}, {43250, 43255}, {43259, 43259}, + {43261, 43262}, {43274, 43301}, {43312, 43334}, {43360, 43388}, + {43396, 43442}, {43471, 43471}, {43488, 43492}, {43494, 43494}, + {43495, 43503}, {43514, 43518}, {43520, 43560}, {43584, 43586}, + {43588, 43595}, {43616, 43631}, {43632, 43632}, {43633, 43638}, + {43642, 43642}, {43646, 43695}, {43697, 43697}, {43701, 43702}, + {43705, 43709}, {43712, 43712}, {43714, 43714}, {43739, 43740}, + {43741, 43741}, {43744, 43754}, {43762, 43762}, {43763, 43764}, + {43777, 43782}, {43785, 43790}, {43793, 43798}, {43808, 43814}, + {43816, 43822}, {43824, 43866}, {43868, 43871}, {43872, 43880}, + {43881, 43881}, {43888, 43967}, {43968, 44002}, {44032, 55203}, + {55216, 55238}, {55243, 55291}, {63744, 64109}, {64112, 64217}, + {64256, 64262}, {64275, 64279}, {64285, 64285}, {64287, 64296}, + {64298, 64310}, {64312, 64316}, {64318, 64318}, {64320, 64321}, + {64323, 64324}, {64326, 64433}, {64467, 64829}, {64848, 64911}, + {64914, 64967}, {65008, 65019}, {65136, 65140}, {65142, 65276}, + {65313, 65338}, {65345, 65370}, {65382, 65391}, {65392, 65392}, + {65393, 65437}, {65438, 65439}, {65440, 65470}, {65474, 65479}, + {65482, 65487}, {65490, 65495}, {65498, 65500}, {65536, 65547}, + {65549, 65574}, {65576, 65594}, {65596, 65597}, {65599, 65613}, + {65616, 65629}, {65664, 65786}, {65856, 65908}, {66176, 66204}, + {66208, 66256}, {66304, 66335}, {66349, 66368}, {66369, 66369}, + {66370, 66377}, {66378, 66378}, {66384, 66421}, {66432, 66461}, + {66464, 66499}, {66504, 66511}, {66513, 66517}, {66560, 66639}, + {66640, 66717}, {66736, 66771}, {66776, 66811}, {66816, 66855}, + {66864, 66915}, {66928, 66938}, {66940, 66954}, {66956, 66962}, + {66964, 66965}, {66967, 66977}, {66979, 66993}, {66995, 67001}, + {67003, 67004}, {67008, 67059}, {67072, 67382}, {67392, 67413}, + {67424, 67431}, {67456, 67461}, {67463, 67504}, {67506, 67514}, + {67584, 67589}, {67592, 67592}, {67594, 67637}, {67639, 67640}, + {67644, 67644}, {67647, 67669}, {67680, 67702}, {67712, 67742}, + {67808, 67826}, {67828, 67829}, {67840, 67861}, {67872, 67897}, + {67968, 68023}, {68030, 68031}, {68096, 68096}, {68112, 68115}, + {68117, 68119}, {68121, 68149}, {68192, 68220}, {68224, 68252}, + {68288, 68295}, {68297, 68324}, {68352, 68405}, {68416, 68437}, + {68448, 68466}, {68480, 68497}, {68608, 68680}, {68736, 68786}, + {68800, 68850}, {68864, 68899}, {68938, 68941}, {68942, 68942}, + {68943, 68943}, {68944, 68965}, {68975, 68975}, {68976, 68997}, + {69248, 69289}, {69296, 69297}, {69314, 69316}, {69376, 69404}, + {69415, 69415}, {69424, 69445}, {69488, 69505}, {69552, 69572}, + {69600, 69622}, {69635, 69687}, {69745, 69746}, {69749, 69749}, + {69763, 69807}, {69840, 69864}, {69891, 69926}, {69956, 69956}, + {69959, 69959}, {69968, 70002}, {70006, 70006}, {70019, 70066}, + {70081, 70084}, {70106, 70106}, {70108, 70108}, {70144, 70161}, + {70163, 70187}, {70207, 70208}, {70272, 70278}, {70280, 70280}, + {70282, 70285}, {70287, 70301}, {70303, 70312}, {70320, 70366}, + {70405, 70412}, {70415, 70416}, {70419, 70440}, {70442, 70448}, + {70450, 70451}, {70453, 70457}, {70461, 70461}, {70480, 70480}, + {70493, 70497}, {70528, 70537}, {70539, 70539}, {70542, 70542}, + {70544, 70581}, {70583, 70583}, {70609, 70609}, {70611, 70611}, + {70656, 70708}, {70727, 70730}, {70751, 70753}, {70784, 70831}, + {70852, 70853}, {70855, 70855}, {71040, 71086}, {71128, 71131}, + {71168, 71215}, {71236, 71236}, {71296, 71338}, {71352, 71352}, + {71424, 71450}, {71488, 71494}, {71680, 71723}, {71840, 71903}, + {71935, 71942}, {71945, 71945}, {71948, 71955}, {71957, 71958}, + {71960, 71983}, {71999, 71999}, {72001, 72001}, {72096, 72103}, + {72106, 72144}, {72161, 72161}, {72163, 72163}, {72192, 72192}, + {72203, 72242}, {72250, 72250}, {72272, 72272}, {72284, 72329}, + {72349, 72349}, {72368, 72440}, {72640, 72672}, {72704, 72712}, + {72714, 72750}, {72768, 72768}, {72818, 72847}, {72960, 72966}, + {72968, 72969}, {72971, 73008}, {73030, 73030}, {73056, 73061}, + {73063, 73064}, {73066, 73097}, {73112, 73112}, {73440, 73458}, + {73474, 73474}, {73476, 73488}, {73490, 73523}, {73648, 73648}, + {73728, 74649}, {74752, 74862}, {74880, 75075}, {77712, 77808}, + {77824, 78895}, {78913, 78918}, {78944, 82938}, {82944, 83526}, + {90368, 90397}, {92160, 92728}, {92736, 92766}, {92784, 92862}, + {92880, 92909}, {92928, 92975}, {92992, 92995}, {93027, 93047}, + {93053, 93071}, {93504, 93506}, {93507, 93546}, {93547, 93548}, + {93760, 93823}, {93952, 94026}, {94032, 94032}, {94099, 94111}, + {94176, 94177}, {94179, 94179}, {94208, 100343}, {100352, 101589}, + {101631, 101640}, {110576, 110579}, {110581, 110587}, {110589, 110590}, + {110592, 110882}, {110898, 110898}, {110928, 110930}, {110933, 110933}, + {110948, 110951}, {110960, 111355}, {113664, 113770}, {113776, 113788}, + {113792, 113800}, {113808, 113817}, {119808, 119892}, {119894, 119964}, + {119966, 119967}, {119970, 119970}, {119973, 119974}, {119977, 119980}, + {119982, 119993}, {119995, 119995}, {119997, 120003}, {120005, 120069}, + {120071, 120074}, {120077, 120084}, {120086, 120092}, {120094, 120121}, + {120123, 120126}, {120128, 120132}, {120134, 120134}, {120138, 120144}, + {120146, 120485}, {120488, 120512}, {120514, 120538}, {120540, 120570}, + {120572, 120596}, {120598, 120628}, {120630, 120654}, {120656, 120686}, + {120688, 120712}, {120714, 120744}, {120746, 120770}, {120772, 120779}, + {122624, 122633}, {122634, 122634}, {122635, 122654}, {122661, 122666}, + {122928, 122989}, {123136, 123180}, {123191, 123197}, {123214, 123214}, + {123536, 123565}, {123584, 123627}, {124112, 124138}, {124139, 124139}, + {124368, 124397}, {124400, 124400}, {124896, 124902}, {124904, 124907}, + {124909, 124910}, {124912, 124926}, {124928, 125124}, {125184, 125251}, + {125259, 125259}, {126464, 126467}, {126469, 126495}, {126497, 126498}, + {126500, 126500}, {126503, 126503}, {126505, 126514}, {126516, 126519}, + {126521, 126521}, {126523, 126523}, {126530, 126530}, {126535, 126535}, + {126537, 126537}, {126539, 126539}, {126541, 126543}, {126545, 126546}, + {126548, 126548}, {126551, 126551}, {126553, 126553}, {126555, 126555}, + {126557, 126557}, {126559, 126559}, {126561, 126562}, {126564, 126564}, + {126567, 126570}, {126572, 126578}, {126580, 126583}, {126585, 126588}, + {126590, 126590}, {126592, 126601}, {126603, 126619}, {126625, 126627}, + {126629, 126633}, {126635, 126651}, {131072, 173791}, {173824, 177977}, + {177984, 178205}, {178208, 183969}, {183984, 191456}, {191472, 192093}, + {194560, 195101}, {196608, 201546}, {201552, 205743} +}; + + +} // namespace ada::idna +#endif // ADA_IDNA_IDENTIFIER_TABLES_H +/* end file src/id_tables.cpp */ + +namespace ada::idna { +constexpr bool is_ascii_letter(char32_t c) noexcept { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +constexpr bool is_ascii_letter_or_digit(char32_t c) noexcept { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9'); +} + +bool valid_name_code_point(char32_t code_point, bool first) { + // https://tc39.es/ecma262/#prod-IdentifierStart + // Fast paths: + if (first && + (code_point == '$' || code_point == '_' || is_ascii_letter(code_point))) { + return true; + } + if (!first && (code_point == '$' || is_ascii_letter_or_digit(code_point))) { + return true; + } + // Slow path... + if (code_point == 0xffffffff) { + return false; // minimal error handling + } + if (first) { + auto iter = std::lower_bound( + std::begin(ada::idna::id_start), std::end(ada::idna::id_start), + code_point, + [](const uint32_t* range, uint32_t cp) { return range[1] < cp; }); + return iter != std::end(id_start) && code_point >= (*iter)[0]; + } else { + auto iter = std::lower_bound( + std::begin(id_continue), std::end(id_continue), code_point, + [](const uint32_t* range, uint32_t cp) { return range[1] < cp; }); + return iter != std::end(id_start) && code_point >= (*iter)[0]; + } +} +} // namespace ada::idna +/* end file src/identifier.cpp */ /* end file src/idna.cpp */ /* end file src/ada_idna.cpp */ ADA_POP_DISABLE_WARNINGS @@ -9792,8 +10447,12 @@ ADA_POP_DISABLE_WARNINGS #include #elif ADA_SSE2 #include +#elif ADA_LSX +#include #endif +#include + namespace ada::unicode { constexpr bool is_tabs_or_newline(char c) noexcept { @@ -9834,8 +10493,7 @@ ada_really_inline bool has_tabs_or_newline( std::string_view user_input) noexcept { // first check for short strings in which case we do it naively. if (user_input.size() < 16) { // slow path - return std::any_of(user_input.begin(), user_input.end(), - is_tabs_or_newline); + return std::ranges::any_of(user_input, is_tabs_or_newline); } // fast path for long strings (expected to be common) size_t i = 0; @@ -9873,8 +10531,7 @@ ada_really_inline bool has_tabs_or_newline( std::string_view user_input) noexcept { // first check for short strings in which case we do it naively. if (user_input.size() < 16) { // slow path - return std::any_of(user_input.begin(), user_input.end(), - is_tabs_or_newline); + return std::ranges::any_of(user_input, is_tabs_or_newline); } // fast path for long strings (expected to be common) size_t i = 0; @@ -9900,6 +10557,38 @@ ada_really_inline bool has_tabs_or_newline( } return _mm_movemask_epi8(running) != 0; } +#elif ADA_LSX +ada_really_inline bool has_tabs_or_newline( + std::string_view user_input) noexcept { + // first check for short strings in which case we do it naively. + if (user_input.size() < 16) { // slow path + return std::ranges::any_of(user_input, is_tabs_or_newline); + } + // fast path for long strings (expected to be common) + size_t i = 0; + const __m128i mask1 = __lsx_vrepli_b('\r'); + const __m128i mask2 = __lsx_vrepli_b('\n'); + const __m128i mask3 = __lsx_vrepli_b('\t'); + // If we supported SSSE3, we could use the algorithm that we use for NEON. + __m128i running{0}; + for (; i + 15 < user_input.size(); i += 16) { + __m128i word = __lsx_vld((const __m128i*)(user_input.data() + i), 0); + running = __lsx_vor_v( + __lsx_vor_v(running, __lsx_vor_v(__lsx_vseq_b(word, mask1), + __lsx_vseq_b(word, mask2))), + __lsx_vseq_b(word, mask3)); + } + if (i < user_input.size()) { + __m128i word = __lsx_vld( + (const __m128i*)(user_input.data() + user_input.length() - 16), 0); + running = __lsx_vor_v( + __lsx_vor_v(running, __lsx_vor_v(__lsx_vseq_b(word, mask1), + __lsx_vseq_b(word, mask2))), + __lsx_vseq_b(word, mask3)); + } + if (__lsx_bz_v(running)) return false; + return true; +} #else ada_really_inline bool has_tabs_or_newline( std::string_view user_input) noexcept { @@ -9936,7 +10625,7 @@ ada_really_inline bool has_tabs_or_newline( // U+003F (?), U+0040 (@), U+005B ([), U+005C (\), U+005D (]), U+005E (^), or // U+007C (|). constexpr static std::array is_forbidden_host_code_point_table = - []() constexpr { + []() consteval { std::array result{}; for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|'}) { @@ -9951,7 +10640,7 @@ ada_really_inline constexpr bool is_forbidden_host_code_point( } constexpr static std::array is_forbidden_domain_code_point_table = - []() constexpr { + []() consteval { std::array result{}; for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) { @@ -9990,7 +10679,7 @@ ada_really_inline constexpr bool contains_forbidden_domain_code_point( } constexpr static std::array - is_forbidden_domain_code_point_table_or_upper = []() constexpr { + is_forbidden_domain_code_point_table_or_upper = []() consteval { std::array result{}; for (uint8_t c : {'\0', '\x09', '\x0a', '\x0d', ' ', '#', '/', ':', '<', '>', '?', '@', '[', '\\', ']', '^', '|', '%'}) { @@ -10031,7 +10720,7 @@ contains_forbidden_domain_code_point_or_upper(const char* input, } // std::isalnum(c) || c == '+' || c == '-' || c == '.') is true for -constexpr static std::array is_alnum_plus_table = []() constexpr { +constexpr static std::array is_alnum_plus_table = []() consteval { std::array result{}; for (size_t c = 0; c < 256; c++) { result[c] = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || @@ -10052,6 +10741,17 @@ ada_really_inline constexpr bool is_ascii_hex_digit(const char c) noexcept { (c >= 'a' && c <= 'f'); } +ada_really_inline constexpr bool is_ascii_digit(const char c) noexcept { + // An ASCII digit is a code point in the range U+0030 (0) to U+0039 (9), + // inclusive. + return (c >= '0' && c <= '9'); +} + +ada_really_inline constexpr bool is_ascii(const char32_t c) noexcept { + // If code point is between U+0000 and U+007F inclusive, then return true. + return c <= 0x7F; +} + ada_really_inline constexpr bool is_c0_control_or_space(const char c) noexcept { return (unsigned char)c <= ' '; } @@ -10064,7 +10764,7 @@ ada_really_inline constexpr bool is_ascii_tab_or_newline( constexpr std::string_view table_is_double_dot_path_segment[] = { "..", "%2e.", ".%2e", "%2e%2e"}; -ada_really_inline ada_constexpr bool is_double_dot_path_segment( +ada_really_inline constexpr bool is_double_dot_path_segment( std::string_view input) noexcept { // This will catch most cases: // The length must be 2,4 or 6. @@ -10152,7 +10852,6 @@ std::string percent_decode(const std::string_view input, size_t first_percent) { !is_ascii_hex_digit(pointer[2])))) { dest += ch; pointer++; - continue; } else { unsigned a = convert_hex_to_binary(pointer[1]); unsigned b = convert_hex_to_binary(pointer[2]); @@ -10166,10 +10865,9 @@ std::string percent_decode(const std::string_view input, size_t first_percent) { std::string percent_encode(const std::string_view input, const uint8_t character_set[]) { - auto pointer = - std::find_if(input.begin(), input.end(), [character_set](const char c) { - return character_sets::bit_at(character_set, c); - }); + auto pointer = std::ranges::find_if(input, [character_set](const char c) { + return character_sets::bit_at(character_set, c); + }); // Optimization: Don't iterate if percent encode is not required if (pointer == input.end()) { return std::string(input); @@ -10196,10 +10894,9 @@ bool percent_encode(const std::string_view input, const uint8_t character_set[], std::string& out) { ada_log("percent_encode ", input, " to output string while ", append ? "appending" : "overwriting"); - auto pointer = - std::find_if(input.begin(), input.end(), [character_set](const char c) { - return character_sets::bit_at(character_set, c); - }); + auto pointer = std::ranges::find_if(input, [character_set](const char c) { + return character_sets::bit_at(character_set, c); + }); ada_log("percent_encode done checking, moved to ", std::distance(input.begin(), pointer)); @@ -10208,11 +10905,12 @@ bool percent_encode(const std::string_view input, const uint8_t character_set[], ada_log("percent_encode encoding not needed."); return false; } - if (!append) { + if constexpr (!append) { out.clear(); } ada_log("percent_encode appending ", std::distance(input.begin(), pointer), " bytes"); + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) out.append(input.data(), std::distance(input.begin(), pointer)); ada_log("percent_encode processing ", std::distance(pointer, input.end()), " bytes"); @@ -10247,6 +10945,7 @@ bool to_ascii(std::optional& out, const std::string_view plain, std::string percent_encode(const std::string_view input, const uint8_t character_set[], size_t index) { std::string out; + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) out.append(input.data(), index); auto pointer = input.begin() + index; for (; pointer != input.end(); pointer++) { @@ -10262,8 +10961,8 @@ std::string percent_encode(const std::string_view input, } // namespace ada::unicode /* end file src/unicode.cpp */ /* begin file src/serializers.cpp */ - #include +#include #include namespace ada::serializers { @@ -10343,18 +11042,19 @@ std::string ipv4(const uint64_t address) noexcept { } // namespace ada::serializers /* end file src/serializers.cpp */ /* begin file src/implementation.cpp */ + #include namespace ada { template -ada_warn_unused tl::expected parse( +ada_warn_unused tl::expected parse( std::string_view input, const result_type* base_url) { result_type u = ada::parser::parse_url_impl(input, base_url); if (!u.is_valid) { - return tl::unexpected(errors::generic_error); + return tl::unexpected(errors::type_error); } return u; } @@ -10406,7 +11106,7 @@ bool can_parse(std::string_view input, const std::string_view* base_input) { return result.is_valid; } -ada_warn_unused std::string to_string(ada::encoding_type type) { +ada_warn_unused std::string_view to_string(ada::encoding_type type) { switch (type) { case ada::encoding_type::UTF8: return "UTF-8"; @@ -10423,8 +11123,6 @@ ada_warn_unused std::string to_string(ada::encoding_type type) { /* end file src/implementation.cpp */ /* begin file src/helpers.cpp */ -#include -#include #include #include @@ -10518,13 +11216,11 @@ ada_really_inline std::optional prune_hash( ada_really_inline bool shorten_path(std::string& path, ada::scheme::type type) noexcept { - size_t first_delimiter = path.find_first_of('/', 1); - // Let path be url's path. // If url's scheme is "file", path's size is 1, and path[0] is a normalized // Windows drive letter, then return. if (type == ada::scheme::type::FILE && - first_delimiter == std::string_view::npos && !path.empty()) { + path.find('/', 1) == std::string_view::npos && !path.empty()) { if (checkers::is_normalized_windows_drive_letter( helpers::substring(path, 1))) { return false; @@ -10543,13 +11239,11 @@ ada_really_inline bool shorten_path(std::string& path, ada_really_inline bool shorten_path(std::string_view& path, ada::scheme::type type) noexcept { - size_t first_delimiter = path.find_first_of('/', 1); - // Let path be url's path. // If url's scheme is "file", path's size is 1, and path[0] is a normalized // Windows drive letter, then return. if (type == ada::scheme::type::FILE && - first_delimiter == std::string_view::npos && !path.empty()) { + path.find('/', 1) == std::string_view::npos && !path.empty()) { if (checkers::is_normalized_windows_drive_letter( helpers::substring(path, 1))) { return false; @@ -10572,15 +11266,11 @@ ada_really_inline void remove_ascii_tab_or_newline( std::string& input) noexcept { // if this ever becomes a performance issue, we could use an approach similar // to has_tabs_or_newline - input.erase(std::remove_if(input.begin(), input.end(), - [](char c) { - return ada::unicode::is_ascii_tab_or_newline(c); - }), - input.end()); + std::erase_if(input, ada::unicode::is_ascii_tab_or_newline); } -ada_really_inline std::string_view substring(std::string_view input, - size_t pos) noexcept { +ada_really_inline constexpr std::string_view substring(std::string_view input, + size_t pos) noexcept { ADA_ASSERT_TRUE(pos <= input.size()); // The following is safer but unneeded if we have the above line: // return pos > input.size() ? std::string_view() : input.substr(pos); @@ -10734,10 +11424,62 @@ ada_really_inline size_t find_next_host_delimiter_special( } return size_t(view.length()); } +#elif ADA_LSX +ada_really_inline size_t find_next_host_delimiter_special( + std::string_view view, size_t location) noexcept { + // first check for short strings in which case we do it naively. + if (view.size() - location < 16) { // slow path + for (size_t i = location; i < view.size(); i++) { + if (view[i] == ':' || view[i] == '/' || view[i] == '\\' || + view[i] == '?' || view[i] == '[') { + return i; + } + } + return size_t(view.size()); + } + // fast path for long strings (expected to be common) + size_t i = location; + const __m128i mask1 = __lsx_vrepli_b(':'); + const __m128i mask2 = __lsx_vrepli_b('/'); + const __m128i mask3 = __lsx_vrepli_b('\\'); + const __m128i mask4 = __lsx_vrepli_b('?'); + const __m128i mask5 = __lsx_vrepli_b('['); + + for (; i + 15 < view.size(); i += 16) { + __m128i word = __lsx_vld((const __m128i*)(view.data() + i), 0); + __m128i m1 = __lsx_vseq_b(word, mask1); + __m128i m2 = __lsx_vseq_b(word, mask2); + __m128i m3 = __lsx_vseq_b(word, mask3); + __m128i m4 = __lsx_vseq_b(word, mask4); + __m128i m5 = __lsx_vseq_b(word, mask5); + __m128i m = + __lsx_vor_v(__lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m3, m4)), m5); + int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); + if (mask != 0) { + return i + trailing_zeroes(mask); + } + } + if (i < view.size()) { + __m128i word = + __lsx_vld((const __m128i*)(view.data() + view.length() - 16), 0); + __m128i m1 = __lsx_vseq_b(word, mask1); + __m128i m2 = __lsx_vseq_b(word, mask2); + __m128i m3 = __lsx_vseq_b(word, mask3); + __m128i m4 = __lsx_vseq_b(word, mask4); + __m128i m5 = __lsx_vseq_b(word, mask5); + __m128i m = + __lsx_vor_v(__lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m3, m4)), m5); + int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); + if (mask != 0) { + return view.length() - 16 + trailing_zeroes(mask); + } + } + return size_t(view.length()); +} #else // : / [ \\ ? static constexpr std::array special_host_delimiters = - []() constexpr { + []() consteval { std::array result{}; for (int i : {':', '/', '[', '\\', '?'}) { result[i] = 1; @@ -10867,9 +11609,56 @@ ada_really_inline size_t find_next_host_delimiter(std::string_view view, } return size_t(view.length()); } +#elif ADA_LSX +ada_really_inline size_t find_next_host_delimiter(std::string_view view, + size_t location) noexcept { + // first check for short strings in which case we do it naively. + if (view.size() - location < 16) { // slow path + for (size_t i = location; i < view.size(); i++) { + if (view[i] == ':' || view[i] == '/' || view[i] == '?' || + view[i] == '[') { + return i; + } + } + return size_t(view.size()); + } + // fast path for long strings (expected to be common) + size_t i = location; + const __m128i mask1 = __lsx_vrepli_b(':'); + const __m128i mask2 = __lsx_vrepli_b('/'); + const __m128i mask4 = __lsx_vrepli_b('?'); + const __m128i mask5 = __lsx_vrepli_b('['); + + for (; i + 15 < view.size(); i += 16) { + __m128i word = __lsx_vld((const __m128i*)(view.data() + i), 0); + __m128i m1 = __lsx_vseq_b(word, mask1); + __m128i m2 = __lsx_vseq_b(word, mask2); + __m128i m4 = __lsx_vseq_b(word, mask4); + __m128i m5 = __lsx_vseq_b(word, mask5); + __m128i m = __lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m4, m5)); + int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); + if (mask != 0) { + return i + trailing_zeroes(mask); + } + } + if (i < view.size()) { + __m128i word = + __lsx_vld((const __m128i*)(view.data() + view.length() - 16), 0); + __m128i m1 = __lsx_vseq_b(word, mask1); + __m128i m2 = __lsx_vseq_b(word, mask2); + __m128i m4 = __lsx_vseq_b(word, mask4); + __m128i m5 = __lsx_vseq_b(word, mask5); + __m128i m = __lsx_vor_v(__lsx_vor_v(m1, m2), __lsx_vor_v(m4, m5)); + int mask = __lsx_vpickve2gr_hu(__lsx_vmsknz_b(m), 0); + if (mask != 0) { + return view.length() - 16 + trailing_zeroes(mask); + } + } + return size_t(view.length()); +} #else // : / [ ? -static constexpr std::array host_delimiters = []() constexpr { +static constexpr std::array host_delimiters = []() consteval { std::array result{}; for (int i : {':', '/', '?', '['}) { result[i] = 1; @@ -10969,7 +11758,7 @@ ada_really_inline std::pair get_host_delimiter_location( return {location, found_colon}; } -ada_really_inline void trim_c0_whitespace(std::string_view& input) noexcept { +void trim_c0_whitespace(std::string_view& input) noexcept { while (!input.empty() && ada::unicode::is_c0_control_or_space(input.front())) { input.remove_prefix(1); @@ -11009,15 +11798,20 @@ ada_really_inline void parse_prepared_path(std::string_view input, // Note: input cannot be empty, it must at least contain one character ('.') // Note: we know that '\' is not present. if (input[0] != '.') { - size_t slashdot = input.find("/."); - if (slashdot == std::string_view::npos) { // common case - trivial_path = true; - } else { // uncommon - // only three cases matter: /./, /.. or a final / - trivial_path = - !(slashdot + 2 == input.size() || input[slashdot + 2] == '.' || - input[slashdot + 2] == '/'); + size_t slashdot = 0; + bool dot_is_file = true; + for (;;) { + slashdot = input.find("/.", slashdot); + if (slashdot == std::string_view::npos) { // common case + break; + } else { // uncommon + // only three cases matter: /./, /.. or a final / + slashdot += 2; + dot_is_file &= !(slashdot == input.size() || input[slashdot] == '.' || + input[slashdot] == '/'); + } } + trivial_path = dot_is_file; } } if (trivial_path) { @@ -11106,8 +11900,8 @@ ada_really_inline void parse_prepared_path(std::string_view input, ? path_buffer_tmp : path_view; if (unicode::is_double_dot_path_segment(path_buffer)) { - if ((helpers::shorten_path(path, type) || special) && - location == std::string_view::npos) { + helpers::shorten_path(path, type); + if (location == std::string_view::npos) { path += '/'; } } else if (unicode::is_single_dot_path_segment(path_buffer) && @@ -11164,7 +11958,7 @@ ada_really_inline void strip_trailing_spaces_from_opaque_path( // @ / \\ ? static constexpr std::array authority_delimiter_special = - []() constexpr { + []() consteval { std::array result{}; for (uint8_t i : {'@', '/', '\\', '?'}) { result[i] = 1; @@ -11185,7 +11979,7 @@ find_authority_delimiter_special(std::string_view view) noexcept { } // @ / ? -static constexpr std::array authority_delimiter = []() constexpr { +static constexpr std::array authority_delimiter = []() consteval { std::array result{}; for (uint8_t i : {'@', '/', '?'}) { result[i] = 1; @@ -11218,14 +12012,16 @@ ada_warn_unused std::string to_string(ada::state state) { #include #include +#include +#include #include +#include namespace ada { bool url::parse_opaque_host(std::string_view input) { ada_log("parse_opaque_host ", input, " [", input.size(), " bytes]"); - if (std::any_of(input.begin(), input.end(), - ada::unicode::is_forbidden_host_code_point)) { + if (std::ranges::any_of(input, ada::unicode::is_forbidden_host_code_point)) { return is_valid = false; } @@ -11550,31 +12346,31 @@ ada_really_inline bool url::parse_scheme(const std::string_view input) { *http, https), in which case, we can go really fast. **/ if (is_input_special) { // fast path!!! - if (has_state_override) { + if constexpr (has_state_override) { // If url's scheme is not a special scheme and buffer is a special scheme, // then return. if (is_special() != is_input_special) { - return true; + return false; } // If url includes credentials or has a non-null port, and buffer is // "file", then return. if ((has_credentials() || port.has_value()) && parsed_type == ada::scheme::type::FILE) { - return true; + return false; } // If url's scheme is "file" and its host is an empty host, then return. // An empty host is the empty string. if (type == ada::scheme::type::FILE && host.has_value() && host.value().empty()) { - return true; + return false; } } type = parsed_type; - if (has_state_override) { + if constexpr (has_state_override) { // This is uncommon. uint16_t urls_scheme_port = get_special_port(); @@ -11594,7 +12390,7 @@ ada_really_inline bool url::parse_scheme(const std::string_view input) { // bool is_ascii = unicode::to_lower_ascii(_buffer.data(), _buffer.size()); - if (has_state_override) { + if constexpr (has_state_override) { // If url's scheme is a special scheme and buffer is not a special scheme, // then return. If url's scheme is not a special scheme and buffer is a // special scheme, then return. @@ -11618,7 +12414,7 @@ ada_really_inline bool url::parse_scheme(const std::string_view input) { set_scheme(std::move(_buffer)); - if (has_state_override) { + if constexpr (has_state_override) { // This is uncommon. uint16_t urls_scheme_port = get_special_port(); @@ -11727,18 +12523,14 @@ ada_really_inline void url::parse_path(std::string_view input) { path = "/"; } else if ((internal_input[0] == '/') || (internal_input[0] == '\\')) { helpers::parse_prepared_path(internal_input.substr(1), type, path); - return; } else { helpers::parse_prepared_path(internal_input, type, path); - return; } } else if (!internal_input.empty()) { if (internal_input[0] == '/') { helpers::parse_prepared_path(internal_input.substr(1), type, path); - return; } else { helpers::parse_prepared_path(internal_input, type, path); - return; } } else { if (!host.has_value()) { @@ -11783,6 +12575,7 @@ ada_really_inline void url::parse_path(std::string_view input) { if (has_search()) { answer.append(",\n"); answer.append("\t\"query\":\""); + // NOLINTNEXTLINE(bugprone-unchecked-optional-access) helpers::encode_json(query.value(), back); answer.append("\""); } @@ -11803,17 +12596,6 @@ ada_really_inline void url::parse_path(std::string_view input) { return checkers::verify_dns_length(host.value()); } -} // namespace ada -/* end file src/url.cpp */ -/* begin file src/url-getters.cpp */ -/** - * @file url-getters.cpp - * Includes all the getters of `ada::url` - */ - -#include - -namespace ada { [[nodiscard]] std::string url::get_origin() const noexcept { if (is_special()) { // Return a new opaque origin. @@ -11866,10 +12648,6 @@ namespace ada { return host.value_or(""); } -[[nodiscard]] std::string_view url::get_pathname() const noexcept { - return path; -} - [[nodiscard]] std::string url::get_search() const noexcept { // If this's URL's query is either null or the empty string, then return the // empty string. Return U+003F (?), followed by this's URL's query. @@ -11896,19 +12674,6 @@ namespace ada { : "#" + hash.value(); } -} // namespace ada -/* end file src/url-getters.cpp */ -/* begin file src/url-setters.cpp */ -/** - * @file url-setters.cpp - * Includes all the setters of `ada::url` - */ - -#include -#include - -namespace ada { - template bool url::set_host_or_hostname(const std::string_view input) { if (has_opaque_path) { @@ -11936,35 +12701,67 @@ bool url::set_host_or_hostname(const std::string_view input) { // Note: the 'found_colon' value is true if and only if a colon was // encountered while not inside brackets. if (found_colon) { - if (override_hostname) { + // If buffer is the empty string, host-missing validation error, return + // failure. + std::string_view buffer = host_view.substr(0, location); + if (buffer.empty()) { + return false; + } + + // If state override is given and state override is hostname state, then + // return failure. + if constexpr (override_hostname) { return false; } - std::string_view buffer = new_host.substr(location + 1); - if (!buffer.empty()) { - set_port(buffer); + + // Let host be the result of host parsing buffer with url is not special. + bool succeeded = parse_host(buffer); + if (!succeeded) { + host = std::move(previous_host); + update_base_port(previous_port); + return false; } - } - // If url is special and host_view is the empty string, validation error, - // return failure. Otherwise, if state override is given, host_view is the - // empty string, and either url includes credentials or url's port is - // non-null, return. - else if (host_view.empty() && - (is_special() || has_credentials() || port.has_value())) { - return false; - } - // Let host be the result of host parsing host_view with url is not special. - if (host_view.empty() && !is_special()) { - host = ""; + // Set url's host to host, buffer to the empty string, and state to port + // state. + std::string_view port_buffer = new_host.substr(location + 1); + if (!port_buffer.empty()) { + set_port(port_buffer); + } return true; } + // Otherwise, if one of the following is true: + // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) + // - url is special and c is U+005C (\) + else { + // If url is special and host_view is the empty string, host-missing + // validation error, return failure. + if (host_view.empty() && is_special()) { + return false; + } - bool succeeded = parse_host(host_view); - if (!succeeded) { - host = previous_host; - update_base_port(previous_port); + // Otherwise, if state override is given, host_view is the empty string, + // and either url includes credentials or url's port is non-null, then + // return failure. + if (host_view.empty() && (has_credentials() || port.has_value())) { + return false; + } + + // Let host be the result of host parsing host_view with url is not + // special. + if (host_view.empty() && !is_special()) { + host = ""; + return true; + } + + bool succeeded = parse_host(host_view); + if (!succeeded) { + host = std::move(previous_host); + update_base_port(previous_port); + return false; + } + return true; } - return succeeded; } size_t location = new_host.find_first_of("/\\?"); @@ -11978,13 +12775,13 @@ bool url::set_host_or_hostname(const std::string_view input) { } else { // Let host be the result of host parsing buffer with url is not special. if (!parse_host(new_host)) { - host = previous_host; + host = std::move(previous_host); update_base_port(previous_port); return false; } // If host is "localhost", then set host to the empty string. - if (host.has_value() && host.value() == "localhost") { + if (host == "localhost") { host = ""; } } @@ -12021,28 +12818,37 @@ bool url::set_port(const std::string_view input) { if (cannot_have_credentials_or_port()) { return false; } + + if (input.empty()) { + port = std::nullopt; + return true; + } + std::string trimmed(input); helpers::remove_ascii_tab_or_newline(trimmed); + if (trimmed.empty()) { - port = std::nullopt; return true; } - // Input should not start with control characters. - if (ada::unicode::is_c0_control_or_space(trimmed.front())) { - return false; - } - // Input should contain at least one ascii digit. - if (input.find_first_of("0123456789") == std::string_view::npos) { + + // Input should not start with a non-digit character. + if (!ada::unicode::is_ascii_digit(trimmed.front())) { return false; } + // Find the first non-digit character to determine the length of digits + auto first_non_digit = + std::ranges::find_if_not(trimmed, ada::unicode::is_ascii_digit); + std::string_view digits_to_parse = + std::string_view(trimmed.data(), first_non_digit - trimmed.begin()); + // Revert changes if parse_port fails. std::optional previous_port = port; - parse_port(trimmed); + parse_port(digits_to_parse); if (is_valid) { return true; } - port = previous_port; + port = std::move(previous_port); is_valid = true; return false; } @@ -12076,15 +12882,14 @@ void url::set_search(const std::string_view input) { is_special() ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE : ada::character_sets::QUERY_PERCENT_ENCODE; - query = ada::unicode::percent_encode(std::string_view(new_value), - query_percent_encode_set); + query = ada::unicode::percent_encode(new_value, query_percent_encode_set); } bool url::set_pathname(const std::string_view input) { if (has_opaque_path) { return false; } - path = ""; + path.clear(); parse_path(input); return true; } @@ -12104,7 +12909,7 @@ bool url::set_protocol(const std::string_view input) { view.append(":"); std::string::iterator pointer = - std::find_if_not(view.begin(), view.end(), unicode::is_alnum_plus); + std::ranges::find_if_not(view, unicode::is_alnum_plus); if (pointer != view.end() && *pointer == ':') { return parse_scheme( @@ -12117,26 +12922,18 @@ bool url::set_href(const std::string_view input) { ada::result out = ada::parse(input); if (out) { - username = out->username; - password = out->password; - host = out->host; - port = out->port; - path = out->path; - query = out->query; - hash = out->hash; - type = out->type; - non_special_scheme = out->non_special_scheme; - has_opaque_path = out->has_opaque_path; + *this = *out; } return out.has_value(); } } // namespace ada -/* end file src/url-setters.cpp */ +/* end file src/url.cpp */ /* begin file src/parser.cpp */ #include +#include namespace ada::parser { @@ -12149,10 +12946,9 @@ result_type parse_url_impl(std::string_view user_input, // means that doing if constexpr(result_type_is_ada_url) { something } else { // something else } is free (at runtime). This means that ada::url_aggregator // and ada::url **do not have to support the exact same API**. - constexpr bool result_type_is_ada_url = - std::is_same::value; + constexpr bool result_type_is_ada_url = std::is_same_v; constexpr bool result_type_is_ada_url_aggregator = - std::is_same::value; + std::is_same_v; static_assert(result_type_is_ada_url || result_type_is_ada_url_aggregator); // We don't support // anything else for now. @@ -12161,12 +12957,12 @@ result_type parse_url_impl(std::string_view user_input, " bytes],", (base_url != nullptr ? base_url->to_string() : "null"), ")"); - ada::state state = ada::state::SCHEME_START; + state state = state::SCHEME_START; result_type url{}; // We refuse to parse URL strings that exceed 4GB. Such strings are almost // surely the result of a bug or are otherwise a security concern. - if (user_input.size() > std::numeric_limits::max()) { + if (user_input.size() > std::numeric_limits::max()) [[unlikely]] { url.is_valid = false; } // Going forward, user_input.size() is in [0, @@ -12197,20 +12993,19 @@ result_type parse_url_impl(std::string_view user_input, url.reserve(reserve_capacity); } std::string tmp_buffer; - std::string_view internal_input; - if (unicode::has_tabs_or_newline(user_input)) { + std::string_view url_data; + if (unicode::has_tabs_or_newline(user_input)) [[unlikely]] { tmp_buffer = user_input; // Optimization opportunity: Instead of copying and then pruning, we could // just directly build the string from user_input. helpers::remove_ascii_tab_or_newline(tmp_buffer); - internal_input = tmp_buffer; - } else { - internal_input = user_input; + url_data = tmp_buffer; + } else [[likely]] { + url_data = user_input; } // Leading and trailing control characters are uncommon and easy to deal with // (no performance concern). - std::string_view url_data = internal_input; helpers::trim_c0_whitespace(url_data); // Optimization opportunity. Most websites do not have fragment. @@ -12233,27 +13028,27 @@ result_type parse_url_impl(std::string_view user_input, ada_log("In parsing at ", input_position, " out of ", input_size, " in state ", ada::to_string(state)); switch (state) { - case ada::state::SCHEME_START: { + case state::SCHEME_START: { ada_log("SCHEME_START ", helpers::substring(url_data, input_position)); // If c is an ASCII alpha, append c, lowercased, to buffer, and set // state to scheme state. if ((input_position != input_size) && checkers::is_alpha(url_data[input_position])) { - state = ada::state::SCHEME; + state = state::SCHEME; input_position++; } else { // Otherwise, if state override is not given, set state to no scheme // state and decrease pointer by 1. - state = ada::state::NO_SCHEME; + state = state::NO_SCHEME; } break; } - case ada::state::SCHEME: { + case state::SCHEME: { ada_log("SCHEME ", helpers::substring(url_data, input_position)); // If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E (.), // append c, lowercased, to buffer. while ((input_position != input_size) && - (ada::unicode::is_alnum_plus(url_data[input_position]))) { + (unicode::is_alnum_plus(url_data[input_position]))) { input_position++; } // Otherwise, if c is U+003A (:), then: @@ -12275,9 +13070,9 @@ result_type parse_url_impl(std::string_view user_input, ada_log("SCHEME the scheme is ", url.get_protocol()); // If url's scheme is "file", then: - if (url.type == ada::scheme::type::FILE) { + if (url.type == scheme::type::FILE) { // Set state to file state. - state = ada::state::FILE; + state = state::FILE; } // Otherwise, if url is special, base is non-null, and base's scheme // is url's scheme: Note: Doing base_url->scheme is unsafe if base_url @@ -12285,38 +13080,38 @@ result_type parse_url_impl(std::string_view user_input, else if (url.is_special() && base_url != nullptr && base_url->type == url.type) { // Set state to special relative or authority state. - state = ada::state::SPECIAL_RELATIVE_OR_AUTHORITY; + state = state::SPECIAL_RELATIVE_OR_AUTHORITY; } // Otherwise, if url is special, set state to special authority // slashes state. else if (url.is_special()) { - state = ada::state::SPECIAL_AUTHORITY_SLASHES; + state = state::SPECIAL_AUTHORITY_SLASHES; } // Otherwise, if remaining starts with an U+002F (/), set state to // path or authority state and increase pointer by 1. else if (input_position + 1 < input_size && url_data[input_position + 1] == '/') { - state = ada::state::PATH_OR_AUTHORITY; + state = state::PATH_OR_AUTHORITY; input_position++; } // Otherwise, set url's path to the empty string and set state to // opaque path state. else { - state = ada::state::OPAQUE_PATH; + state = state::OPAQUE_PATH; } } // Otherwise, if state override is not given, set buffer to the empty // string, state to no scheme state, and start over (from the first code // point in input). else { - state = ada::state::NO_SCHEME; + state = state::NO_SCHEME; input_position = 0; break; } input_position++; break; } - case ada::state::NO_SCHEME: { + case state::NO_SCHEME: { ada_log("NO_SCHEME ", helpers::substring(url_data, input_position)); // If base is null, or base has an opaque path and c is not U+0023 (#), // validation error, return failure. @@ -12347,18 +13142,18 @@ result_type parse_url_impl(std::string_view user_input, } // Otherwise, if base's scheme is not "file", set state to relative // state and decrease pointer by 1. - else if (base_url->type != ada::scheme::type::FILE) { + else if (base_url->type != scheme::type::FILE) { ada_log("NO_SCHEME non-file relative path"); - state = ada::state::RELATIVE_SCHEME; + state = state::RELATIVE_SCHEME; } // Otherwise, set state to file state and decrease pointer by 1. else { ada_log("NO_SCHEME file base type"); - state = ada::state::FILE; + state = state::FILE; } break; } - case ada::state::AUTHORITY: { + case state::AUTHORITY: { ada_log("AUTHORITY ", helpers::substring(url_data, input_position)); // most URLs have no @. Having no @ tells us that we don't have to worry // about AUTHORITY. Of course, we could have @ and still not have to @@ -12368,11 +13163,10 @@ result_type parse_url_impl(std::string_view user_input, // TODO: We could do various processing early on, using a single pass // over the string to collect information about it, e.g., telling us // whether there is a @ and if so, where (or how many). - const bool contains_ampersand = - (url_data.find('@', input_position) != std::string_view::npos); - if (!contains_ampersand) { - state = ada::state::HOST; + // Check if url data contains an @. + if (url_data.find('@', input_position) == std::string_view::npos) { + state = state::HOST; break; } bool at_sign_seen{false}; @@ -12383,12 +13177,12 @@ result_type parse_url_impl(std::string_view user_input, * --------^ */ do { - std::string_view view = helpers::substring(url_data, input_position); + std::string_view view = url_data.substr(input_position); // The delimiters are @, /, ? \\. size_t location = url.is_special() ? helpers::find_authority_delimiter_special(view) : helpers::find_authority_delimiter(view); - std::string_view authority_view(view.data(), location); + std::string_view authority_view = view.substr(0, location); size_t end_of_authority = input_position + authority_view.size(); // If c is U+0040 (@), then: if ((end_of_authority != input_size) && @@ -12469,7 +13263,7 @@ result_type parse_url_impl(std::string_view user_input, url.is_valid = false; return url; } - state = ada::state::HOST; + state = state::HOST; break; } if (end_of_authority == input_size) { @@ -12485,42 +13279,41 @@ result_type parse_url_impl(std::string_view user_input, break; } - case ada::state::SPECIAL_RELATIVE_OR_AUTHORITY: { + case state::SPECIAL_RELATIVE_OR_AUTHORITY: { ada_log("SPECIAL_RELATIVE_OR_AUTHORITY ", helpers::substring(url_data, input_position)); // If c is U+002F (/) and remaining starts with U+002F (/), // then set state to special authority ignore slashes state and increase // pointer by 1. - std::string_view view = helpers::substring(url_data, input_position); - if (ada::checkers::begins_with(view, "//")) { - state = ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES; + if (url_data.substr(input_position, 2) == "//") { + state = state::SPECIAL_AUTHORITY_IGNORE_SLASHES; input_position += 2; } else { // Otherwise, validation error, set state to relative state and // decrease pointer by 1. - state = ada::state::RELATIVE_SCHEME; + state = state::RELATIVE_SCHEME; } break; } - case ada::state::PATH_OR_AUTHORITY: { + case state::PATH_OR_AUTHORITY: { ada_log("PATH_OR_AUTHORITY ", helpers::substring(url_data, input_position)); // If c is U+002F (/), then set state to authority state. if ((input_position != input_size) && (url_data[input_position] == '/')) { - state = ada::state::AUTHORITY; + state = state::AUTHORITY; input_position++; } else { // Otherwise, set state to path state, and decrease pointer by 1. - state = ada::state::PATH; + state = state::PATH; } break; } - case ada::state::RELATIVE_SCHEME: { + case state::RELATIVE_SCHEME: { ada_log("RELATIVE_SCHEME ", helpers::substring(url_data, input_position)); @@ -12529,11 +13322,12 @@ result_type parse_url_impl(std::string_view user_input, // If c is U+002F (/), then set state to relative slash state. if ((input_position != input_size) && + // NOLINTNEXTLINE(bugprone-branch-clone) (url_data[input_position] == '/')) { ada_log( "RELATIVE_SCHEME if c is U+002F (/), then set state to relative " "slash state"); - state = ada::state::RELATIVE_SLASH; + state = state::RELATIVE_SLASH; } else if (url.is_special() && (input_position != input_size) && (url_data[input_position] == '\\')) { // Otherwise, if url is special and c is U+005C (\), validation error, @@ -12541,7 +13335,7 @@ result_type parse_url_impl(std::string_view user_input, ada_log( "RELATIVE_SCHEME if url is special and c is U+005C, validation " "error, set state to relative slash state"); - state = ada::state::RELATIVE_SLASH; + state = state::RELATIVE_SLASH; } else { ada_log("RELATIVE_SCHEME otherwise"); // Set url's username to base's username, url's password to base's @@ -12560,9 +13354,7 @@ result_type parse_url_impl(std::string_view user_input, } else { url.update_base_authority(base_url->get_href(), base_url->get_components()); - // TODO: Get rid of set_hostname and replace it with - // update_base_hostname - url.set_hostname(base_url->get_hostname()); + url.update_host_to_base_host(base_url->get_hostname()); url.update_base_port(base_url->retrieve_base_port()); // cloning the base path includes cloning the has_opaque_path flag url.has_opaque_path = base_url->has_opaque_path; @@ -12576,7 +13368,7 @@ result_type parse_url_impl(std::string_view user_input, // state to query state. if ((input_position != input_size) && (url_data[input_position] == '?')) { - state = ada::state::QUERY; + state = state::QUERY; } // Otherwise, if c is not the EOF code point: else if (input_position != input_size) { @@ -12588,18 +13380,18 @@ result_type parse_url_impl(std::string_view user_input, } else { std::string_view path = url.get_pathname(); if (helpers::shorten_path(path, url.type)) { - url.update_base_pathname(std::string(path)); + url.update_base_pathname(std::move(std::string(path))); } } // Set state to path state and decrease pointer by 1. - state = ada::state::PATH; + state = state::PATH; break; } } input_position++; break; } - case ada::state::RELATIVE_SLASH: { + case state::RELATIVE_SLASH: { ada_log("RELATIVE_SLASH ", helpers::substring(url_data, input_position)); @@ -12608,12 +13400,12 @@ result_type parse_url_impl(std::string_view user_input, (url_data[input_position] == '/' || url_data[input_position] == '\\')) { // Set state to special authority ignore slashes state. - state = ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES; + state = state::SPECIAL_AUTHORITY_IGNORE_SLASHES; } // Otherwise, if c is U+002F (/), then set state to authority state. else if ((input_position != input_size) && (url_data[input_position] == '/')) { - state = ada::state::AUTHORITY; + state = state::AUTHORITY; } // Otherwise, set // - url's username to base's username, @@ -12630,33 +13422,30 @@ result_type parse_url_impl(std::string_view user_input, } else { url.update_base_authority(base_url->get_href(), base_url->get_components()); - // TODO: Get rid of set_hostname and replace it with - // update_base_hostname - url.set_hostname(base_url->get_hostname()); + url.update_host_to_base_host(base_url->get_hostname()); url.update_base_port(base_url->retrieve_base_port()); } - state = ada::state::PATH; + state = state::PATH; break; } input_position++; break; } - case ada::state::SPECIAL_AUTHORITY_SLASHES: { + case state::SPECIAL_AUTHORITY_SLASHES: { ada_log("SPECIAL_AUTHORITY_SLASHES ", helpers::substring(url_data, input_position)); // If c is U+002F (/) and remaining starts with U+002F (/), // then set state to special authority ignore slashes state and increase // pointer by 1. - std::string_view view = helpers::substring(url_data, input_position); - if (ada::checkers::begins_with(view, "//")) { + if (url_data.substr(input_position, 2) == "//") { input_position += 2; } [[fallthrough]]; } - case ada::state::SPECIAL_AUTHORITY_IGNORE_SLASHES: { + case state::SPECIAL_AUTHORITY_IGNORE_SLASHES: { ada_log("SPECIAL_AUTHORITY_IGNORE_SLASHES ", helpers::substring(url_data, input_position)); @@ -12667,23 +13456,22 @@ result_type parse_url_impl(std::string_view user_input, (url_data[input_position] == '\\'))) { input_position++; } - state = ada::state::AUTHORITY; + state = state::AUTHORITY; break; } - case ada::state::QUERY: { + case state::QUERY: { ada_log("QUERY ", helpers::substring(url_data, input_position)); if constexpr (store_values) { // Let queryPercentEncodeSet be the special-query percent-encode set // if url is special; otherwise the query percent-encode set. const uint8_t* query_percent_encode_set = - url.is_special() - ? ada::character_sets::SPECIAL_QUERY_PERCENT_ENCODE - : ada::character_sets::QUERY_PERCENT_ENCODE; + url.is_special() ? character_sets::SPECIAL_QUERY_PERCENT_ENCODE + : character_sets::QUERY_PERCENT_ENCODE; // Percent-encode after encoding, with encoding, buffer, and // queryPercentEncodeSet, and append the result to url's query. - url.update_base_search(helpers::substring(url_data, input_position), + url.update_base_search(url_data.substr(input_position), query_percent_encode_set); ada_log("QUERY update_base_search completed "); if (fragment.has_value()) { @@ -12692,11 +13480,10 @@ result_type parse_url_impl(std::string_view user_input, } return url; } - case ada::state::HOST: { + case state::HOST: { ada_log("HOST ", helpers::substring(url_data, input_position)); - std::string_view host_view = - helpers::substring(url_data, input_position); + std::string_view host_view = url_data.substr(input_position); auto [location, found_colon] = helpers::get_host_delimiter_location(url.is_special(), host_view); input_position = (location != std::string_view::npos) @@ -12716,7 +13503,7 @@ result_type parse_url_impl(std::string_view user_input, ada_log("HOST parsing results in ", url.get_hostname()); // Set url's host to host, buffer to the empty string, and state to // port state. - state = ada::state::PORT; + state = state::PORT; input_position++; } // Otherwise, if one of the following is true: @@ -12727,7 +13514,7 @@ result_type parse_url_impl(std::string_view user_input, else { // If url is special and host_view is the empty string, validation // error, return failure. - if (url.is_special() && host_view.empty()) { + if (host_view.empty() && url.is_special()) { url.is_valid = false; return url; } @@ -12743,50 +13530,56 @@ result_type parse_url_impl(std::string_view user_input, " href=", url.get_href()); // Set url's host to host, and state to path start state. - state = ada::state::PATH_START; + state = state::PATH_START; } break; } - case ada::state::OPAQUE_PATH: { + case state::OPAQUE_PATH: { ada_log("OPAQUE_PATH ", helpers::substring(url_data, input_position)); - std::string_view view = helpers::substring(url_data, input_position); + std::string_view view = url_data.substr(input_position); // If c is U+003F (?), then set url's query to the empty string and // state to query state. size_t location = view.find('?'); if (location != std::string_view::npos) { view.remove_suffix(view.size() - location); - state = ada::state::QUERY; + state = state::QUERY; input_position += location + 1; } else { input_position = input_size + 1; } url.has_opaque_path = true; + // This is a really unlikely scenario in real world. We should not seek // to optimize it. - url.update_base_pathname(unicode::percent_encode( - view, character_sets::C0_CONTROL_PERCENT_ENCODE)); + if (view.ends_with(' ')) { + std::string modified_view = + std::string(view.substr(0, view.size() - 1)) + "%20"; + url.update_base_pathname(unicode::percent_encode( + modified_view, character_sets::C0_CONTROL_PERCENT_ENCODE)); + } else { + url.update_base_pathname(unicode::percent_encode( + view, character_sets::C0_CONTROL_PERCENT_ENCODE)); + } break; } - case ada::state::PORT: { + case state::PORT: { ada_log("PORT ", helpers::substring(url_data, input_position)); - std::string_view port_view = - helpers::substring(url_data, input_position); - size_t consumed_bytes = url.parse_port(port_view, true); - input_position += consumed_bytes; + std::string_view port_view = url_data.substr(input_position); + input_position += url.parse_port(port_view, true); if (!url.is_valid) { return url; } state = state::PATH_START; [[fallthrough]]; } - case ada::state::PATH_START: { + case state::PATH_START: { ada_log("PATH_START ", helpers::substring(url_data, input_position)); // If url is special, then: if (url.is_special()) { // Set state to path state. - state = ada::state::PATH; + state = state::PATH; // Optimization: Avoiding going into PATH state improves the // performance of urls ending with /. @@ -12811,12 +13604,12 @@ result_type parse_url_impl(std::string_view user_input, // set url's query to the empty string and state to query state. else if ((input_position != input_size) && (url_data[input_position] == '?')) { - state = ada::state::QUERY; + state = state::QUERY; } // Otherwise, if c is not the EOF code point: else if (input_position != input_size) { // Set state to path state. - state = ada::state::PATH; + state = state::PATH; // If c is not U+002F (/), then decrease pointer by 1. if (url_data[input_position] != '/') { @@ -12827,15 +13620,15 @@ result_type parse_url_impl(std::string_view user_input, input_position++; break; } - case ada::state::PATH: { - std::string_view view = helpers::substring(url_data, input_position); + case state::PATH: { ada_log("PATH ", helpers::substring(url_data, input_position)); + std::string_view view = url_data.substr(input_position); // Most time, we do not need percent encoding. // Furthermore, we can immediately locate the '?'. size_t locofquestionmark = view.find('?'); if (locofquestionmark != std::string_view::npos) { - state = ada::state::QUERY; + state = state::QUERY; view.remove_suffix(view.size() - locofquestionmark); input_position += locofquestionmark + 1; } else { @@ -12851,7 +13644,7 @@ result_type parse_url_impl(std::string_view user_input, } break; } - case ada::state::FILE_SLASH: { + case state::FILE_SLASH: { ada_log("FILE_SLASH ", helpers::substring(url_data, input_position)); // If c is U+002F (/) or U+005C (\), then: @@ -12860,21 +13653,19 @@ result_type parse_url_impl(std::string_view user_input, url_data[input_position] == '\\')) { ada_log("FILE_SLASH c is U+002F or U+005C"); // Set state to file host state. - state = ada::state::FILE_HOST; + state = state::FILE_HOST; input_position++; } else { ada_log("FILE_SLASH otherwise"); // If base is non-null and base's scheme is "file", then: // Note: it is unsafe to do base_url->scheme unless you know that // base_url_has_value() is true. - if (base_url != nullptr && - base_url->type == ada::scheme::type::FILE) { + if (base_url != nullptr && base_url->type == scheme::type::FILE) { // Set url's host to base's host. if constexpr (result_type_is_ada_url) { url.host = base_url->host; } else { - // TODO: Optimization opportunity. - url.set_host(base_url->get_host()); + url.update_host_to_base_host(base_url->get_host()); } // If the code point substring from pointer to the end of input does // not start with a Windows drive letter and base's path[0] is a @@ -12882,7 +13673,7 @@ result_type parse_url_impl(std::string_view user_input, // url's path. if (!base_url->get_pathname().empty()) { if (!checkers::is_windows_drive_letter( - helpers::substring(url_data, input_position))) { + url_data.substr(input_position))) { std::string_view first_base_url_path = base_url->get_pathname().substr(1); size_t loc = first_base_url_path.find('/'); @@ -12904,14 +13695,14 @@ result_type parse_url_impl(std::string_view user_input, } // Set state to path state, and decrease pointer by 1. - state = ada::state::PATH; + state = state::PATH; } break; } - case ada::state::FILE_HOST: { - std::string_view view = helpers::substring(url_data, input_position); + case state::FILE_HOST: { ada_log("FILE_HOST ", helpers::substring(url_data, input_position)); + std::string_view view = url_data.substr(input_position); size_t location = view.find_first_of("/\\?"); std::string_view file_host_buffer( @@ -12919,7 +13710,7 @@ result_type parse_url_impl(std::string_view user_input, (location != std::string_view::npos) ? location : view.size()); if (checkers::is_windows_drive_letter(file_host_buffer)) { - state = ada::state::PATH; + state = state::PATH; } else if (file_host_buffer.empty()) { // Set url's host to the empty string. if constexpr (result_type_is_ada_url) { @@ -12928,7 +13719,7 @@ result_type parse_url_impl(std::string_view user_input, url.update_base_hostname(""); } // Set state to path start state. - state = ada::state::PATH_START; + state = state::PATH_START; } else { size_t consumed_bytes = file_host_buffer.size(); input_position += consumed_bytes; @@ -12950,15 +13741,14 @@ result_type parse_url_impl(std::string_view user_input, } // Set buffer to the empty string and state to path start state. - state = ada::state::PATH_START; + state = state::PATH_START; } break; } - case ada::state::FILE: { + case state::FILE: { ada_log("FILE ", helpers::substring(url_data, input_position)); - std::string_view file_view = - helpers::substring(url_data, input_position); + std::string_view file_view = url_data.substr(input_position); url.set_protocol_as_file(); if constexpr (result_type_is_ada_url) { @@ -12973,11 +13763,10 @@ result_type parse_url_impl(std::string_view user_input, url_data[input_position] == '\\')) { ada_log("FILE c is U+002F or U+005C"); // Set state to file slash state. - state = ada::state::FILE_SLASH; + state = state::FILE_SLASH; } // Otherwise, if base is non-null and base's scheme is "file": - else if (base_url != nullptr && - base_url->type == ada::scheme::type::FILE) { + else if (base_url != nullptr && base_url->type == scheme::type::FILE) { // Set url's host to base's host, url's path to a clone of base's // path, and url's query to base's query. ada_log("FILE base non-null"); @@ -12986,9 +13775,7 @@ result_type parse_url_impl(std::string_view user_input, url.path = base_url->path; url.query = base_url->query; } else { - // TODO: Get rid of set_hostname and replace it with - // update_base_hostname - url.set_hostname(base_url->get_hostname()); + url.update_host_to_base_host(base_url->get_hostname()); url.update_base_pathname(base_url->get_pathname()); url.update_base_search(base_url->get_search()); } @@ -12997,7 +13784,7 @@ result_type parse_url_impl(std::string_view user_input, // If c is U+003F (?), then set url's query to the empty string and // state to query state. if (input_position != input_size && url_data[input_position] == '?') { - state = ada::state::QUERY; + state = state::QUERY; } // Otherwise, if c is not the EOF code point: else if (input_position != input_size) { @@ -13011,7 +13798,7 @@ result_type parse_url_impl(std::string_view user_input, } else { std::string_view path = url.get_pathname(); if (helpers::shorten_path(path, url.type)) { - url.update_base_pathname(std::string(path)); + url.update_base_pathname(std::move(std::string(path))); } } } @@ -13023,14 +13810,14 @@ result_type parse_url_impl(std::string_view user_input, } // Set state to path state and decrease pointer by 1. - state = ada::state::PATH; + state = state::PATH; break; } } // Otherwise, set state to path state, and decrease pointer by 1. else { ada_log("FILE go to path"); - state = ada::state::PATH; + state = state::PATH; break; } @@ -13038,7 +13825,7 @@ result_type parse_url_impl(std::string_view user_input, break; } default: - ada::unreachable(); + unreachable(); } } if constexpr (store_values) { @@ -13068,86 +13855,11 @@ template url_aggregator parse_url( /* end file src/parser.cpp */ /* begin file src/url_components.cpp */ -#include +#include #include namespace ada { -[[nodiscard]] bool url_components::check_offset_consistency() const noexcept { - /** - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - */ - // These conditions can be made more strict. - uint32_t index = 0; - - if (protocol_end == url_components::omitted) { - return false; - } - if (protocol_end < index) { - return false; - } - index = protocol_end; - - if (username_end == url_components::omitted) { - return false; - } - if (username_end < index) { - return false; - } - index = username_end; - - if (host_start == url_components::omitted) { - return false; - } - if (host_start < index) { - return false; - } - index = host_start; - - if (port != url_components::omitted) { - if (port > 0xffff) { - return false; - } - uint32_t port_length = helpers::fast_digit_count(port) + 1; - if (index + port_length < index) { - return false; - } - index += port_length; - } - - if (pathname_start == url_components::omitted) { - return false; - } - if (pathname_start < index) { - return false; - } - index = pathname_start; - - if (search_start != url_components::omitted) { - if (search_start < index) { - return false; - } - index = search_start; - } - - if (hash_start != url_components::omitted) { - if (hash_start < index) { - return false; - } - } - - return true; -} - [[nodiscard]] std::string url_components::to_string() const { std::string answer; auto back = std::back_insert_iterator(answer); @@ -13193,6 +13905,8 @@ namespace ada { /* end file src/url_components.cpp */ /* begin file src/url_aggregator.cpp */ +#include +#include #include #include @@ -13206,38 +13920,38 @@ template std::string_view input{input_with_colon}; input.remove_suffix(1); auto parsed_type = ada::scheme::get_scheme_type(input); - bool is_input_special = (parsed_type != ada::scheme::NOT_SPECIAL); + const bool is_input_special = (parsed_type != ada::scheme::NOT_SPECIAL); /** * In the common case, we will immediately recognize a special scheme (e.g., *http, https), in which case, we can go really fast. **/ if (is_input_special) { // fast path!!! - if (has_state_override) { + if constexpr (has_state_override) { // If url's scheme is not a special scheme and buffer is a special scheme, // then return. if (is_special() != is_input_special) { - return true; + return false; } // If url includes credentials or has a non-null port, and buffer is // "file", then return. if ((has_credentials() || components.port != url_components::omitted) && parsed_type == ada::scheme::type::FILE) { - return true; + return false; } // If url's scheme is "file" and its host is an empty host, then return. // An empty host is the empty string. if (type == ada::scheme::type::FILE && components.host_start == components.host_end) { - return true; + return false; } } type = parsed_type; set_scheme_from_view_with_colon(input_with_colon); - if (has_state_override) { + if constexpr (has_state_override) { // This is uncommon. uint16_t urls_scheme_port = get_special_port(); @@ -13254,7 +13968,7 @@ template // need to check the return value. unicode::to_lower_ascii(_buffer.data(), _buffer.size()); - if (has_state_override) { + if constexpr (has_state_override) { // If url's scheme is a special scheme and buffer is not a special scheme, // then return. If url's scheme is not a special scheme and buffer is a // special scheme, then return. @@ -13279,7 +13993,7 @@ template set_scheme(_buffer); - if (has_state_override) { + if constexpr (has_state_override) { // This is uncommon. uint16_t urls_scheme_port = get_special_port(); @@ -13408,11 +14122,11 @@ bool url_aggregator::set_protocol(const std::string_view input) { view.append(":"); std::string::iterator pointer = - std::find_if_not(view.begin(), view.end(), unicode::is_alnum_plus); + std::ranges::find_if_not(view, unicode::is_alnum_plus); if (pointer != view.end() && *pointer == ':') { return parse_scheme_with_colon( - std::string_view(view.data(), pointer - view.begin() + 1)); + view.substr(0, pointer - view.begin() + 1)); } return false; } @@ -13464,24 +14178,33 @@ bool url_aggregator::set_port(const std::string_view input) { if (cannot_have_credentials_or_port()) { return false; } + + if (input.empty()) { + clear_port(); + return true; + } + std::string trimmed(input); helpers::remove_ascii_tab_or_newline(trimmed); + if (trimmed.empty()) { - clear_port(); return true; } - // Input should not start with control characters. - if (ada::unicode::is_c0_control_or_space(trimmed.front())) { - return false; - } - // Input should contain at least one ascii digit. - if (input.find_first_of("0123456789") == std::string_view::npos) { + + // Input should not start with a non-digit character. + if (!ada::unicode::is_ascii_digit(trimmed.front())) { return false; } + // Find the first non-digit character to determine the length of digits + auto first_non_digit = + std::ranges::find_if_not(trimmed, ada::unicode::is_ascii_digit); + std::string_view digits_to_parse = + std::string_view(trimmed.data(), first_non_digit - trimmed.begin()); + // Revert changes if parse_port fails. uint32_t previous_port = components.port; - parse_port(trimmed); + parse_port(digits_to_parse); if (is_valid) { return true; } @@ -13500,8 +14223,7 @@ bool url_aggregator::set_pathname(const std::string_view input) { } clear_pathname(); parse_path(input); - if (checkers::begins_with(get_pathname(), "//") && !has_authority() && - !has_dash_dot()) { + if (get_pathname().starts_with("//") && !has_authority() && !has_dash_dot()) { buffer.insert(components.pathname_start, "/."); components.pathname_start += 2; } @@ -13678,8 +14400,8 @@ ada_really_inline bool url_aggregator::parse_host(std::string_view input) { ada_log("parse_host to_ascii succeeded ", *host, " [", host->size(), " bytes]"); - if (std::any_of(host.value().begin(), host.value().end(), - ada::unicode::is_forbidden_domain_code_point)) { + if (std::ranges::any_of(host.value(), + ada::unicode::is_forbidden_domain_code_point)) { return is_valid = false; } @@ -13725,43 +14447,75 @@ bool url_aggregator::set_host_or_hostname(const std::string_view input) { // Note: the 'found_colon' value is true if and only if a colon was // encountered while not inside brackets. if (found_colon) { - if (override_hostname) { + // If buffer is the empty string, host-missing validation error, return + // failure. + std::string_view host_buffer = host_view.substr(0, location); + if (host_buffer.empty()) { return false; } - std::string_view sub_buffer = new_host.substr(location + 1); - if (!sub_buffer.empty()) { - set_port(sub_buffer); + + // If state override is given and state override is hostname state, then + // return failure. + if constexpr (override_hostname) { + return false; } + + // Let host be the result of host parsing buffer with url is not special. + bool succeeded = parse_host(host_buffer); + if (!succeeded) { + update_base_hostname(previous_host); + update_base_port(previous_port); + return false; + } + + // Set url's host to host, buffer to the empty string, and state to port + // state. + std::string_view port_buffer = new_host.substr(location + 1); + if (!port_buffer.empty()) { + set_port(port_buffer); + } + return true; } - // If url is special and host_view is the empty string, validation error, - // return failure. Otherwise, if state override is given, host_view is the - // empty string, and either url includes credentials or url's port is - // non-null, return. - else if (host_view.empty() && - (is_special() || has_credentials() || has_port())) { - return false; - } + // Otherwise, if one of the following is true: + // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) + // - url is special and c is U+005C (\) + else { + // If url is special and host_view is the empty string, host-missing + // validation error, return failure. + if (host_view.empty() && is_special()) { + return false; + } + + // Otherwise, if state override is given, host_view is the empty string, + // and either url includes credentials or url's port is non-null, then + // return failure. + if (host_view.empty() && (has_credentials() || has_port())) { + return false; + } + + // Let host be the result of host parsing host_view with url is not + // special. + if (host_view.empty() && !is_special()) { + if (has_hostname()) { + clear_hostname(); // easy! + } else if (has_dash_dot()) { + add_authority_slashes_if_needed(); + delete_dash_dot(); + } + return true; + } - // Let host be the result of host parsing host_view with url is not special. - if (host_view.empty() && !is_special()) { - if (has_hostname()) { - clear_hostname(); // easy! + bool succeeded = parse_host(host_view); + if (!succeeded) { + update_base_hostname(previous_host); + update_base_port(previous_port); + return false; } else if (has_dash_dot()) { - add_authority_slashes_if_needed(); + // Should remove dash_dot from pathname delete_dash_dot(); } return true; } - - bool succeeded = parse_host(host_view); - if (!succeeded) { - update_base_hostname(previous_host); - update_base_port(previous_port); - } else if (has_dash_dot()) { - // Should remove dash_dot from pathname - delete_dash_dot(); - } - return succeeded; } size_t location = new_host.find_first_of("/\\?"); @@ -13831,7 +14585,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return "null"; } -[[nodiscard]] std::string_view url_aggregator::get_username() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_username() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_username"); if (has_non_empty_username()) { return helpers::substring(buffer, components.protocol_end + 2, @@ -13840,7 +14595,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return ""; } -[[nodiscard]] std::string_view url_aggregator::get_password() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_password() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_password"); if (has_non_empty_password()) { return helpers::substring(buffer, components.username_end + 1, @@ -13849,7 +14605,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return ""; } -[[nodiscard]] std::string_view url_aggregator::get_port() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_port() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_port"); if (components.port == url_components::omitted) { return ""; @@ -13858,7 +14615,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { components.pathname_start); } -[[nodiscard]] std::string_view url_aggregator::get_hash() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_hash() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_hash"); // If this's URL's fragment is either null or the empty string, then return // the empty string. Return U+0023 (#), followed by this's URL's fragment. @@ -13871,7 +14629,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return helpers::substring(buffer, components.hash_start); } -[[nodiscard]] std::string_view url_aggregator::get_host() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_host() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_host"); // Technically, we should check if there is a hostname, but // the code below works even if there isn't. @@ -13889,7 +14648,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return helpers::substring(buffer, start, components.pathname_start); } -[[nodiscard]] std::string_view url_aggregator::get_hostname() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_hostname() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_hostname"); // Technically, we should check if there is a hostname, but // the code below works even if there isn't. @@ -13903,21 +14663,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return helpers::substring(buffer, start, components.host_end); } -[[nodiscard]] std::string_view url_aggregator::get_pathname() const noexcept { - ada_log("url_aggregator::get_pathname pathname_start = ", - components.pathname_start, " buffer.size() = ", buffer.size(), - " components.search_start = ", components.search_start, - " components.hash_start = ", components.hash_start); - auto ending_index = uint32_t(buffer.size()); - if (components.search_start != url_components::omitted) { - ending_index = components.search_start; - } else if (components.hash_start != url_components::omitted) { - ending_index = components.hash_start; - } - return helpers::substring(buffer, components.pathname_start, ending_index); -} - -[[nodiscard]] std::string_view url_aggregator::get_search() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_search() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_search"); // If this's URL's query is either null or the empty string, then return the // empty string. Return U+003F (?), followed by this's URL's query. @@ -13934,7 +14681,8 @@ bool url_aggregator::set_hostname(const std::string_view input) { return helpers::substring(buffer, components.search_start, ending_index); } -[[nodiscard]] std::string_view url_aggregator::get_protocol() const noexcept { +[[nodiscard]] std::string_view url_aggregator::get_protocol() const noexcept + ada_lifetime_bound { ada_log("url_aggregator::get_protocol"); return helpers::substring(buffer, 0, components.protocol_end); } @@ -14377,8 +15125,7 @@ bool url_aggregator::parse_opaque_host(std::string_view input) { ada_log("parse_opaque_host ", input, " [", input.size(), " bytes]"); ADA_ASSERT_TRUE(validate()); ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); - if (std::any_of(input.begin(), input.end(), - ada::unicode::is_forbidden_host_code_point)) { + if (std::ranges::any_of(input, ada::unicode::is_forbidden_host_code_point)) { return is_valid = false; } @@ -14554,176 +15301,6 @@ bool url_aggregator::parse_opaque_host(std::string_view input) { return answer; } -[[nodiscard]] bool url_aggregator::validate() const noexcept { - if (!is_valid) { - return true; - } - if (!components.check_offset_consistency()) { - ada_log("url_aggregator::validate inconsistent components \n", - to_diagram()); - return false; - } - // We have a credible components struct, but let us investivate more - // carefully: - /** - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - */ - if (components.protocol_end == url_components::omitted) { - ada_log("url_aggregator::validate omitted protocol_end \n", to_diagram()); - return false; - } - if (components.username_end == url_components::omitted) { - ada_log("url_aggregator::validate omitted username_end \n", to_diagram()); - return false; - } - if (components.host_start == url_components::omitted) { - ada_log("url_aggregator::validate omitted host_start \n", to_diagram()); - return false; - } - if (components.host_end == url_components::omitted) { - ada_log("url_aggregator::validate omitted host_end \n", to_diagram()); - return false; - } - if (components.pathname_start == url_components::omitted) { - ada_log("url_aggregator::validate omitted pathname_start \n", to_diagram()); - return false; - } - - if (components.protocol_end > buffer.size()) { - ada_log("url_aggregator::validate protocol_end overflow \n", to_diagram()); - return false; - } - if (components.username_end > buffer.size()) { - ada_log("url_aggregator::validate username_end overflow \n", to_diagram()); - return false; - } - if (components.host_start > buffer.size()) { - ada_log("url_aggregator::validate host_start overflow \n", to_diagram()); - return false; - } - if (components.host_end > buffer.size()) { - ada_log("url_aggregator::validate host_end overflow \n", to_diagram()); - return false; - } - if (components.pathname_start > buffer.size()) { - ada_log("url_aggregator::validate pathname_start overflow \n", - to_diagram()); - return false; - } - - if (components.protocol_end > 0) { - if (buffer[components.protocol_end - 1] != ':') { - ada_log( - "url_aggregator::validate missing : at the end of the protocol \n", - to_diagram()); - return false; - } - } - - if (components.username_end != buffer.size() && - components.username_end > components.protocol_end + 2) { - if (buffer[components.username_end] != ':' && - buffer[components.username_end] != '@') { - ada_log( - "url_aggregator::validate missing : or @ at the end of the username " - "\n", - to_diagram()); - return false; - } - } - - if (components.host_start != buffer.size()) { - if (components.host_start > components.username_end) { - if (buffer[components.host_start] != '@') { - ada_log( - "url_aggregator::validate missing @ at the end of the password \n", - to_diagram()); - return false; - } - } else if (components.host_start == components.username_end && - components.host_end > components.host_start) { - if (components.host_start == components.protocol_end + 2) { - if (buffer[components.protocol_end] != '/' || - buffer[components.protocol_end + 1] != '/') { - ada_log( - "url_aggregator::validate missing // between protocol and host " - "\n", - to_diagram()); - return false; - } - } else { - if (components.host_start > components.protocol_end && - buffer[components.host_start] != '@') { - ada_log( - "url_aggregator::validate missing @ at the end of the username " - "\n", - to_diagram()); - return false; - } - } - } else { - if (components.host_end != components.host_start) { - ada_log("url_aggregator::validate expected omitted host \n", - to_diagram()); - return false; - } - } - } - if (components.host_end != buffer.size() && - components.pathname_start > components.host_end) { - if (components.pathname_start == components.host_end + 2 && - buffer[components.host_end] == '/' && - buffer[components.host_end + 1] == '.') { - if (components.pathname_start + 1 >= buffer.size() || - buffer[components.pathname_start] != '/' || - buffer[components.pathname_start + 1] != '/') { - ada_log( - "url_aggregator::validate expected the path to begin with // \n", - to_diagram()); - return false; - } - } else if (buffer[components.host_end] != ':') { - ada_log("url_aggregator::validate missing : at the port \n", - to_diagram()); - return false; - } - } - if (components.pathname_start != buffer.size() && - components.pathname_start < components.search_start && - components.pathname_start < components.hash_start && !has_opaque_path) { - if (buffer[components.pathname_start] != '/') { - ada_log("url_aggregator::validate missing / at the path \n", - to_diagram()); - return false; - } - } - if (components.search_start != url_components::omitted) { - if (buffer[components.search_start] != '?') { - ada_log("url_aggregator::validate missing ? at the search \n", - to_diagram()); - return false; - } - } - if (components.hash_start != url_components::omitted) { - if (buffer[components.hash_start] != '#') { - ada_log("url_aggregator::validate missing # at the hash \n", - to_diagram()); - return false; - } - } - - return true; -} - void url_aggregator::delete_dash_dot() { ada_log("url_aggregator::delete_dash_dot"); ADA_ASSERT_TRUE(validate()); @@ -14777,15 +15354,20 @@ inline void url_aggregator::consume_prepared_path(std::string_view input) { // Note: input cannot be empty, it must at least contain one character ('.') // Note: we know that '\' is not present. if (input[0] != '.') { - size_t slashdot = input.find("/."); - if (slashdot == std::string_view::npos) { // common case - trivial_path = true; - } else { // uncommon - // only three cases matter: /./, /.. or a final / - trivial_path = - !(slashdot + 2 == input.size() || input[slashdot + 2] == '.' || - input[slashdot + 2] == '/'); + size_t slashdot = 0; + bool dot_is_file = true; + for (;;) { + slashdot = input.find("/.", slashdot); + if (slashdot == std::string_view::npos) { // common case + break; + } else { // uncommon + // only three cases matter: /./, /.. or a final / + slashdot += 2; + dot_is_file &= !(slashdot == input.size() || input[slashdot] == '.' || + input[slashdot] == '/'); + } } + trivial_path = dot_is_file; } } if (trivial_path && is_at_path()) { @@ -14879,8 +15461,8 @@ inline void url_aggregator::consume_prepared_path(std::string_view input) { ? path_buffer_tmp : path_view; if (unicode::is_double_dot_path_segment(path_buffer)) { - if ((helpers::shorten_path(path, type) || special) && - location == std::string_view::npos) { + helpers::shorten_path(path, type); + if (location == std::string_view::npos) { path += '/'; } } else if (unicode::is_single_dot_path_segment(path_buffer) && @@ -14914,6 +15496,1470 @@ inline void url_aggregator::consume_prepared_path(std::string_view input) { } } // namespace ada /* end file src/url_aggregator.cpp */ + +#if ADA_INCLUDE_URL_PATTERN +/* begin file src/url_pattern.cpp */ +#if ADA_INCLUDE_URL_PATTERN + + +#include +#include +#include + +namespace ada { + +tl::expected url_pattern_init::process( + const url_pattern_init& init, url_pattern_init::process_type type, + std::optional protocol, + std::optional username, + std::optional password, + std::optional hostname, + std::optional port, + std::optional pathname, + std::optional search, + std::optional hash) { + // Let result be the result of creating a new URLPatternInit. + auto result = url_pattern_init{}; + + // If protocol is not null, set result["protocol"] to protocol. + if (protocol.has_value()) result.protocol = *protocol; + + // If username is not null, set result["username"] to username. + if (username.has_value()) result.username = *username; + + // If password is not null, set result["password"] to password. + if (password.has_value()) result.password = *password; + + // If hostname is not null, set result["hostname"] to hostname. + if (hostname.has_value()) result.hostname = *hostname; + + // If port is not null, set result["port"] to port. + if (port.has_value()) result.port = *port; + + // If pathname is not null, set result["pathname"] to pathname. + if (pathname.has_value()) result.pathname = *pathname; + + // If search is not null, set result["search"] to search. + if (search.has_value()) result.search = *search; + + // If hash is not null, set result["hash"] to hash. + if (hash.has_value()) result.hash = *hash; + + // Let baseURL be null. + std::optional base_url{}; + + // If init["baseURL"] exists: + if (init.base_url.has_value()) { + // Set baseURL to the result of parsing init["baseURL"]. + auto parsing_result = ada::parse(*init.base_url); + // If baseURL is failure, then throw a TypeError. + if (!parsing_result) { + return tl::unexpected(errors::type_error); + } + base_url = std::move(*parsing_result); + + // If init["protocol"] does not exist, then set result["protocol"] to the + // result of processing a base URL string given baseURL's scheme and type. + if (!init.protocol.has_value()) { + ADA_ASSERT_TRUE(base_url.has_value()); + std::string_view base_url_protocol = base_url->get_protocol(); + if (base_url_protocol.ends_with(":")) base_url_protocol.remove_suffix(1); + result.protocol = + url_pattern_helpers::process_base_url_string(base_url_protocol, type); + } + + // If type is not "pattern" and init contains none of "protocol", + // "hostname", "port" and "username", then set result["username"] to the + // result of processing a base URL string given baseURL's username and type. + if (type != process_type::pattern && !init.protocol && !init.hostname && + !init.port && !init.username) { + result.username = url_pattern_helpers::process_base_url_string( + base_url->get_username(), type); + } + + // TODO: Optimization opportunity: Merge this with the previous check. + // If type is not "pattern" and init contains none of "protocol", + // "hostname", "port", "username" and "password", then set + // result["password"] to the result of processing a base URL string given + // baseURL's password and type. + if (type != process_type::pattern && !init.protocol && !init.hostname && + !init.port && !init.username && !init.password) { + result.password = url_pattern_helpers::process_base_url_string( + base_url->get_password(), type); + } + + // If init contains neither "protocol" nor "hostname", then: + if (!init.protocol && !init.hostname) { + // Let baseHost be baseURL's host. + // If baseHost is null, then set baseHost to the empty string. + auto base_host = base_url->get_hostname(); + // Set result["hostname"] to the result of processing a base URL string + // given baseHost and type. + result.hostname = + url_pattern_helpers::process_base_url_string(base_host, type); + } + + // If init contains none of "protocol", "hostname", and "port", then: + if (!init.protocol && !init.hostname && !init.port) { + // If baseURL's port is null, then set result["port"] to the empty string. + // Otherwise, set result["port"] to baseURL's port, serialized. + result.port = base_url->get_port(); + } + + // If init contains none of "protocol", "hostname", "port", and "pathname", + // then set result["pathname"] to the result of processing a base URL string + // given the result of URL path serializing baseURL and type. + if (!init.protocol && !init.hostname && !init.port && !init.pathname) { + result.pathname = url_pattern_helpers::process_base_url_string( + base_url->get_pathname(), type); + } + + // If init contains none of "protocol", "hostname", "port", "pathname", and + // "search", then: + if (!init.protocol && !init.hostname && !init.port && !init.pathname && + !init.search) { + // Let baseQuery be baseURL's query. + // Set result["search"] to the result of processing a base URL string + // given baseQuery and type. + result.search = url_pattern_helpers::process_base_url_string( + base_url->get_search(), type); + } + + // If init contains none of "protocol", "hostname", "port", "pathname", + // "search", and "hash", then: + if (!init.protocol && !init.hostname && !init.port && !init.pathname && + !init.search && !init.hash) { + // Let baseFragment be baseURL's fragment. + // Set result["hash"] to the result of processing a base URL string given + // baseFragment and type. + result.hash = url_pattern_helpers::process_base_url_string( + base_url->get_hash(), type); + } + } + + // If init["protocol"] exists, then set result["protocol"] to the result of + // process protocol for init given init["protocol"] and type. + if (init.protocol) { + auto process_result = process_protocol(*init.protocol, type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.protocol = std::move(*process_result); + } + + // If init["username"] exists, then set result["username"] to the result of + // process username for init given init["username"] and type. + if (init.username.has_value()) { + auto process_result = process_username(*init.username, type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.username = std::move(*process_result); + } + + // If init["password"] exists, then set result["password"] to the result of + // process password for init given init["password"] and type. + if (init.password.has_value()) { + auto process_result = process_password(*init.password, type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.password = std::move(*process_result); + } + + // If init["hostname"] exists, then set result["hostname"] to the result of + // process hostname for init given init["hostname"] and type. + if (init.hostname.has_value()) { + auto process_result = process_hostname(*init.hostname, type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.hostname = std::move(*process_result); + } + + // If init["port"] exists, then set result["port"] to the result of process + // port for init given init["port"], result["protocol"], and type. + if (init.port) { + auto process_result = + process_port(*init.port, result.protocol.value_or("fake"), type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.port = std::move(*process_result); + } + + // If init["pathname"] exists: + if (init.pathname.has_value()) { + // Set result["pathname"] to init["pathname"]. + result.pathname = init.pathname; + + // If the following are all true: + // - baseURL is not null; + // - baseURL has an opaque path; and + // - the result of running is an absolute pathname given result["pathname"] + // and type is false, + if (base_url && !base_url->has_opaque_path && + !url_pattern_helpers::is_absolute_pathname(*result.pathname, type)) { + // Let baseURLPath be the result of running process a base URL string + // given the result of URL path serializing baseURL and type. + // TODO: Optimization opportunity: Avoid returning a string if no slash + // exist. + std::string base_url_path = url_pattern_helpers::process_base_url_string( + base_url->get_pathname(), type); + + // Let slash index be the index of the last U+002F (/) code point found in + // baseURLPath, interpreted as a sequence of code points, or null if there + // are no instances of the code point. + auto slash_index = base_url_path.find_last_of('/'); + + // If slash index is not null: + if (slash_index != std::string::npos) { + // Let new pathname be the code point substring from 0 to slash index + + // 1 within baseURLPath. + base_url_path.resize(slash_index + 1); + // Append result["pathname"] to the end of new pathname. + ADA_ASSERT_TRUE(result.pathname.has_value()); + base_url_path.append(std::move(*result.pathname)); + // Set result["pathname"] to new pathname. + result.pathname = std::move(base_url_path); + } + } + + // Set result["pathname"] to the result of process pathname for init given + // result["pathname"], result["protocol"], and type. + auto pathname_processing_result = + process_pathname(*result.pathname, result.protocol.value_or(""), type); + if (!pathname_processing_result) { + return tl::unexpected(pathname_processing_result.error()); + } + result.pathname = std::move(*pathname_processing_result); + } + + // If init["search"] exists then set result["search"] to the result of process + // search for init given init["search"] and type. + if (init.search) { + auto process_result = process_search(*init.search, type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.search = std::move(*process_result); + } + + // If init["hash"] exists then set result["hash"] to the result of process + // hash for init given init["hash"] and type. + if (init.hash) { + auto process_result = process_hash(*init.hash, type); + if (!process_result) { + return tl::unexpected(process_result.error()); + } + result.hash = std::move(*process_result); + } + // Return result. + return result; +} + +tl::expected url_pattern_init::process_protocol( + std::string_view value, process_type type) { + ada_log("process_protocol=", value, " [", type, "]"); + // Let strippedValue be the given value with a single trailing U+003A (:) + // removed, if any. + if (value.ends_with(":")) { + value.remove_suffix(1); + } + // If type is "pattern" then return strippedValue. + if (type == process_type::pattern) { + return std::string(value); + } + // Return the result of running canonicalize a protocol given strippedValue. + return url_pattern_helpers::canonicalize_protocol(value); +} + +tl::expected url_pattern_init::process_username( + std::string_view value, process_type type) { + // If type is "pattern" then return value. + if (type == process_type::pattern) { + return std::string(value); + } + // Return the result of running canonicalize a username given value. + return url_pattern_helpers::canonicalize_username(value); +} + +tl::expected url_pattern_init::process_password( + std::string_view value, process_type type) { + // If type is "pattern" then return value. + if (type == process_type::pattern) { + return std::string(value); + } + // Return the result of running canonicalize a password given value. + return url_pattern_helpers::canonicalize_password(value); +} + +tl::expected url_pattern_init::process_hostname( + std::string_view value, process_type type) { + ada_log("process_hostname value=", value, " type=", type); + // If type is "pattern" then return value. + if (type == process_type::pattern) { + return std::string(value); + } + // Return the result of running canonicalize a hostname given value. + return url_pattern_helpers::canonicalize_hostname(value); +} + +tl::expected url_pattern_init::process_port( + std::string_view port, std::string_view protocol, process_type type) { + // If type is "pattern" then return portValue. + if (type == process_type::pattern) { + return std::string(port); + } + // Return the result of running canonicalize a port given portValue and + // protocolValue. + return url_pattern_helpers::canonicalize_port_with_protocol(port, protocol); +} + +tl::expected url_pattern_init::process_pathname( + std::string_view value, std::string_view protocol, process_type type) { + // If type is "pattern" then return pathnameValue. + if (type == process_type::pattern) { + return std::string(value); + } + + // If protocolValue is a special scheme or the empty string, then return the + // result of running canonicalize a pathname given pathnameValue. + if (protocol.empty() || scheme::is_special(protocol)) { + return url_pattern_helpers::canonicalize_pathname(value); + } + + // Return the result of running canonicalize an opaque pathname given + // pathnameValue. + return url_pattern_helpers::canonicalize_opaque_pathname(value); +} + +tl::expected url_pattern_init::process_search( + std::string_view value, process_type type) { + // Let strippedValue be the given value with a single leading U+003F (?) + // removed, if any. + if (value.starts_with("?")) { + value.remove_prefix(1); + } + // We cannot assert that the value is no longer starting with a single + // question mark because technically it can start. The question is whether or + // not we should remove the first question mark. Ref: + // https://github.com/ada-url/ada/pull/992 The spec is not clear on this. + + // If type is "pattern" then return strippedValue. + if (type == process_type::pattern) { + return std::string(value); + } + // Return the result of running canonicalize a search given strippedValue. + return url_pattern_helpers::canonicalize_search(value); +} + +tl::expected url_pattern_init::process_hash( + std::string_view value, process_type type) { + // Let strippedValue be the given value with a single leading U+0023 (#) + // removed, if any. + if (value.starts_with("#")) { + value.remove_prefix(1); + } + ADA_ASSERT_TRUE(!value.starts_with("#")); + // If type is "pattern" then return strippedValue. + if (type == process_type::pattern) { + return std::string(value); + } + // Return the result of running canonicalize a hash given strippedValue. + return url_pattern_helpers::canonicalize_hash(value); +} + +} // namespace ada + +#endif // ADA_INCLUDE_URL_PATTERN +/* end file src/url_pattern.cpp */ +/* begin file src/url_pattern_helpers.cpp */ +#if ADA_INCLUDE_URL_PATTERN + +#include +#include +#include + +namespace ada::url_pattern_helpers { + +std::tuple> +generate_regular_expression_and_name_list( + const std::vector& part_list, + url_pattern_compile_component_options options) { + // Let result be "^" + std::string result = "^"; + + // Let name list be a new list + std::vector name_list{}; + + // For each part of part list: + for (const url_pattern_part& part : part_list) { + // If part's type is "fixed-text": + if (part.type == url_pattern_part_type::FIXED_TEXT) { + // If part's modifier is "none" + if (part.modifier == url_pattern_part_modifier::none) { + // Append the result of running escape a regexp string given part's + // value + result += escape_regexp_string(part.value); + } else { + // A "fixed-text" part with a modifier uses a non capturing group + // (?:) + // Append "(?:" to the end of result. + result.append("(?:"); + // Append the result of running escape a regexp string given part's + // value to the end of result. + result.append(escape_regexp_string(part.value)); + // Append ")" to the end of result. + result.append(")"); + // Append the result of running convert a modifier to a string given + // part's modifier to the end of result. + result.append(convert_modifier_to_string(part.modifier)); + } + continue; + } + + // Assert: part's name is not the empty string + ADA_ASSERT_TRUE(!part.name.empty()); + + // Append part's name to name list + name_list.push_back(part.name); + + // Let regexp value be part's value + std::string regexp_value = part.value; + + // If part's type is "segment-wildcard" + if (part.type == url_pattern_part_type::SEGMENT_WILDCARD) { + // then set regexp value to the result of running generate a segment + // wildcard regexp given options. + regexp_value = generate_segment_wildcard_regexp(options); + } + // Otherwise if part's type is "full-wildcard" + else if (part.type == url_pattern_part_type::FULL_WILDCARD) { + // then set regexp value to full wildcard regexp value. + regexp_value = ".*"; + } + + // If part's prefix is the empty string and part's suffix is the empty + // string + if (part.prefix.empty() && part.suffix.empty()) { + // If part's modifier is "none" or "optional" + if (part.modifier == url_pattern_part_modifier::none || + part.modifier == url_pattern_part_modifier::optional) { + // () + result += "(" + regexp_value + ")" + + convert_modifier_to_string(part.modifier); + } else { + // ((?:)) + result += "((?:" + regexp_value + ")" + + convert_modifier_to_string(part.modifier) + ")"; + } + continue; + } + + // If part's modifier is "none" or "optional" + if (part.modifier == url_pattern_part_modifier::none || + part.modifier == url_pattern_part_modifier::optional) { + // (?:()) + result += "(?:" + escape_regexp_string(part.prefix) + "(" + regexp_value + + ")" + escape_regexp_string(part.suffix) + ")" + + convert_modifier_to_string(part.modifier); + continue; + } + + // Assert: part's modifier is "zero-or-more" or "one-or-more" + ADA_ASSERT_TRUE(part.modifier == url_pattern_part_modifier::zero_or_more || + part.modifier == url_pattern_part_modifier::one_or_more); + + // Assert: part's prefix is not the empty string or part's suffix is not the + // empty string + ADA_ASSERT_TRUE(!part.prefix.empty() || !part.suffix.empty()); + + // (?:((?:)(?:(?:))*))? + // Append "(?:" to the end of result. + result.append("(?:"); + // Append the result of running escape a regexp string given part's prefix + // to the end of result. + result.append(escape_regexp_string(part.prefix)); + // Append "((?:" to the end of result. + result.append("((?:"); + // Append regexp value to the end of result. + result.append(regexp_value); + // Append ")(?:" to the end of result. + result.append(")(?:"); + // Append the result of running escape a regexp string given part's suffix + // to the end of result. + result.append(escape_regexp_string(part.suffix)); + // Append the result of running escape a regexp string given part's prefix + // to the end of result. + result.append(escape_regexp_string(part.prefix)); + // Append "(?:" to the end of result. + result.append("(?:"); + // Append regexp value to the end of result. + result.append(regexp_value); + // Append "))*)" to the end of result. + result.append("))*)"); + // Append the result of running escape a regexp string given part's suffix + // to the end of result. + result.append(escape_regexp_string(part.suffix)); + // Append ")" to the end of result. + result.append(")"); + + // If part's modifier is "zero-or-more" then append "?" to the end of result + if (part.modifier == url_pattern_part_modifier::zero_or_more) { + result += "?"; + } + } + + // Append "$" to the end of result + result += "$"; + + // Return (result, name list) + return {std::move(result), std::move(name_list)}; +} + +bool is_ipv6_address(std::string_view input) noexcept { + // If input's code point length is less than 2, then return false. + if (input.size() < 2) return false; + + // Let input code points be input interpreted as a list of code points. + // If input code points[0] is U+005B ([), then return true. + if (input.front() == '[') return true; + // If input code points[0] is U+007B ({) and input code points[1] is U+005B + // ([), then return true. + if (input.starts_with("{[")) return true; + // If input code points[0] is U+005C (\) and input code points[1] is U+005B + // ([), then return true. + return input.starts_with("\\["); +} + +std::string convert_modifier_to_string(url_pattern_part_modifier modifier) { + // TODO: Optimize this. + switch (modifier) { + // If modifier is "zero-or-more", then return "*". + case url_pattern_part_modifier::zero_or_more: + return "*"; + // If modifier is "optional", then return "?". + case url_pattern_part_modifier::optional: + return "?"; + // If modifier is "one-or-more", then return "+". + case url_pattern_part_modifier::one_or_more: + return "+"; + // Return the empty string. + default: + return ""; + } +} + +std::string generate_segment_wildcard_regexp( + url_pattern_compile_component_options options) { + // Let result be "[^". + std::string result = "[^"; + // Append the result of running escape a regexp string given options's + // delimiter code point to the end of result. + result.append(escape_regexp_string(options.get_delimiter())); + // Append "]+?" to the end of result. + result.append("]+?"); + // Return result. + ada_log("generate_segment_wildcard_regexp result: ", result); + return result; +} + +tl::expected canonicalize_protocol( + std::string_view input) { + ada_log("canonicalize_protocol called with input=", input); + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + + // IMPORTANT: Deviation from the spec. We remove the trailing ':' here. + if (input.ends_with(":")) { + input.remove_suffix(1); + } + + // Let dummyURL be a new URL record. + // Let parseResult be the result of running the basic URL parser given value + // followed by "://dummy.test", with dummyURL as url. + if (auto dummy_url = ada::parse( + std::string(input) + "://dummy.test", nullptr)) { + // IMPORTANT: Deviation from the spec. We remove the trailing ':' here. + // Since URL parser always return protocols ending with `:` + auto protocol = dummy_url->get_protocol(); + protocol.remove_suffix(1); + return std::string(protocol); + } + // If parseResult is failure, then throw a TypeError. + return tl::unexpected(errors::type_error); +} + +tl::expected canonicalize_username( + std::string_view input) { + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + auto url = ada::parse("fake://dummy.test", nullptr); + ADA_ASSERT_TRUE(url.has_value()); + // Set the username given dummyURL and value. + if (!url->set_username(input)) { + return tl::unexpected(errors::type_error); + } + // Return dummyURL's username. + return std::string(url->get_username()); +} + +tl::expected canonicalize_password( + std::string_view input) { + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + // Set the password given dummyURL and value. + auto url = ada::parse("fake://dummy.test", nullptr); + + ADA_ASSERT_TRUE(url.has_value()); + if (!url->set_password(input)) { + return tl::unexpected(errors::type_error); + } + // Return dummyURL's password. + return std::string(url->get_password()); +} + +tl::expected canonicalize_hostname( + std::string_view input) { + ada_log("canonicalize_hostname input=", input); + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + // Let parseResult be the result of running the basic URL parser given value + // with dummyURL as url and hostname state as state override. + + // IMPORTANT: The protocol needs to be a special protocol, otherwise the + // hostname will not be converted using IDNA. + auto url = ada::parse("https://dummy.test", nullptr); + ADA_ASSERT_TRUE(url); + // if (!isValidHostnameInput(hostname)) return kj::none; + if (!url->set_hostname(input)) { + // If parseResult is failure, then throw a TypeError. + return tl::unexpected(errors::type_error); + } + // Return dummyURL's host, serialized, or empty string if it is null. + return std::string(url->get_hostname()); +} + +tl::expected canonicalize_ipv6_hostname( + std::string_view input) { + ada_log("canonicalize_ipv6_hostname input=", input); + // TODO: Optimization opportunity: Use lookup table to speed up checking + if (std::ranges::any_of(input, [](char c) { + return c != '[' && c != ']' && c != ':' && + !unicode::is_ascii_hex_digit(c); + })) { + return tl::unexpected(errors::type_error); + } + // Append the result of running ASCII lowercase given code point to the end of + // result. + auto hostname = std::string(input); + unicode::to_lower_ascii(hostname.data(), hostname.size()); + return hostname; +} + +tl::expected canonicalize_port( + std::string_view port_value) { + // If portValue is the empty string, return portValue. + if (port_value.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + // If protocolValue was given, then set dummyURL's scheme to protocolValue. + // Let parseResult be the result of running basic URL parser given portValue + // with dummyURL as url and port state as state override. + auto url = ada::parse("fake://dummy.test", nullptr); + ADA_ASSERT_TRUE(url); + if (url->set_port(port_value)) { + // Return dummyURL's port, serialized, or empty string if it is null. + return std::string(url->get_port()); + } + // If parseResult is failure, then throw a TypeError. + return tl::unexpected(errors::type_error); +} + +tl::expected canonicalize_port_with_protocol( + std::string_view port_value, std::string_view protocol) { + // If portValue is the empty string, return portValue. + if (port_value.empty()) [[unlikely]] { + return ""; + } + + // TODO: Remove this + // We have an empty protocol because get_protocol() returns an empty string + // We should handle this in the caller rather than here. + if (protocol.empty()) { + protocol = "fake"; + } else if (protocol.ends_with(":")) { + protocol.remove_suffix(1); + } + // Let dummyURL be a new URL record. + // If protocolValue was given, then set dummyURL's scheme to protocolValue. + // Let parseResult be the result of running basic URL parser given portValue + // with dummyURL as url and port state as state override. + auto url = ada::parse(std::string(protocol) + "://dummy.test", + nullptr); + // TODO: Remove has_port() check. + // This is actually a bug with url parser where set_port() returns true for + // "invalid80" port value. + if (url && url->set_port(port_value) && url->has_port()) { + // Return dummyURL's port, serialized, or empty string if it is null. + return std::string(url->get_port()); + } + // TODO: Remove this once the previous has_port() check is removed. + if (url) { + if (scheme::is_special(protocol) && url->get_port().empty()) { + return ""; + } + } + // If parseResult is failure, then throw a TypeError. + return tl::unexpected(errors::type_error); +} + +tl::expected canonicalize_pathname( + std::string_view input) { + // If value is the empty string, then return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let leading slash be true if the first code point in value is U+002F (/) + // and otherwise false. + const bool leading_slash = input.starts_with("/"); + // Let modified value be "/-" if leading slash is false and otherwise the + // empty string. + const auto modified_value = leading_slash ? "" : "/-"; + const auto full_url = + std::string("fake://fake-url") + modified_value + std::string(input); + if (auto url = ada::parse(full_url, nullptr)) { + const auto pathname = url->get_pathname(); + // If leading slash is false, then set result to the code point substring + // from 2 to the end of the string within result. + return leading_slash ? std::string(pathname) + : std::string(pathname.substr(2)); + } + // If parseResult is failure, then throw a TypeError. + return tl::unexpected(errors::type_error); +} + +tl::expected canonicalize_opaque_pathname( + std::string_view input) { + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + // Set dummyURL's path to the empty string. + // Let parseResult be the result of running URL parsing given value with + // dummyURL as url and opaque path state as state override. + if (auto url = + ada::parse("fake:" + std::string(input), nullptr)) { + // Return the result of URL path serializing dummyURL. + return std::string(url->get_pathname()); + } + // If parseResult is failure, then throw a TypeError. + return tl::unexpected(errors::type_error); +} + +tl::expected canonicalize_search(std::string_view input) { + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + // Set dummyURL's query to the empty string. + // Let parseResult be the result of running basic URL parser given value with + // dummyURL as url and query state as state override. + auto url = ada::parse("fake://dummy.test", nullptr); + ADA_ASSERT_TRUE(url.has_value()); + url->set_search(input); + if (url->has_search()) { + const auto search = url->get_search(); + if (!search.empty()) { + return std::string(search.substr(1)); + } + return ""; + } + return tl::unexpected(errors::type_error); +} + +tl::expected canonicalize_hash(std::string_view input) { + // If value is the empty string, return value. + if (input.empty()) [[unlikely]] { + return ""; + } + // Let dummyURL be a new URL record. + // Set dummyURL's fragment to the empty string. + // Let parseResult be the result of running basic URL parser given value with + // dummyURL as url and fragment state as state override. + auto url = ada::parse("fake://dummy.test", nullptr); + ADA_ASSERT_TRUE(url.has_value()); + url->set_hash(input); + // Return dummyURL's fragment. + if (url->has_hash()) { + const auto hash = url->get_hash(); + if (!hash.empty()) { + return std::string(hash.substr(1)); + } + return ""; + } + return tl::unexpected(errors::type_error); +} + +tl::expected, errors> tokenize(std::string_view input, + token_policy policy) { + ada_log("tokenize input: ", input); + // Let tokenizer be a new tokenizer. + // Set tokenizer's input to input. + // Set tokenizer's policy to policy. + auto tokenizer = Tokenizer(input, policy); + // While tokenizer's index is less than tokenizer's input's code point length: + while (tokenizer.index < tokenizer.input.size()) { + // Run seek and get the next code point given tokenizer and tokenizer's + // index. + tokenizer.seek_and_get_next_code_point(tokenizer.index); + + // If tokenizer's code point is U+002A (*): + if (tokenizer.code_point == '*') { + // Run add a token with default position and length given tokenizer and + // "asterisk". + tokenizer.add_token_with_defaults(token_type::ASTERISK); + ada_log("add ASTERISK token"); + // Continue. + continue; + } + + // If tokenizer's code point is U+002B (+) or U+003F (?): + if (tokenizer.code_point == '+' || tokenizer.code_point == '?') { + // Run add a token with default position and length given tokenizer and + // "other-modifier". + tokenizer.add_token_with_defaults(token_type::OTHER_MODIFIER); + // Continue. + continue; + } + + // If tokenizer's code point is U+005C (\): + if (tokenizer.code_point == '\\') { + // If tokenizer's index is equal to tokenizer's input's code point length + // - 1: + if (tokenizer.index == tokenizer.input.size() - 1) { + // Run process a tokenizing error given tokenizer, tokenizer's next + // index, and tokenizer's index. + if (auto error = tokenizer.process_tokenizing_error( + tokenizer.next_index, tokenizer.index)) { + ada_log("process_tokenizing_error failed"); + return tl::unexpected(*error); + } + continue; + } + + // Let escaped index be tokenizer's next index. + auto escaped_index = tokenizer.next_index; + // Run get the next code point given tokenizer. + tokenizer.get_next_code_point(); + // Run add a token with default length given tokenizer, "escaped-char", + // tokenizer's next index, and escaped index. + tokenizer.add_token_with_default_length( + token_type::ESCAPED_CHAR, tokenizer.next_index, escaped_index); + ada_log("add ESCAPED_CHAR token on next_index ", tokenizer.next_index, + " with escaped index ", escaped_index); + // Continue. + continue; + } + + // If tokenizer's code point is U+007B ({): + if (tokenizer.code_point == '{') { + // Run add a token with default position and length given tokenizer and + // "open". + tokenizer.add_token_with_defaults(token_type::OPEN); + ada_log("add OPEN token"); + continue; + } + + // If tokenizer's code point is U+007D (}): + if (tokenizer.code_point == '}') { + // Run add a token with default position and length given tokenizer and + // "close". + tokenizer.add_token_with_defaults(token_type::CLOSE); + ada_log("add CLOSE token"); + continue; + } + + // If tokenizer's code point is U+003A (:): + if (tokenizer.code_point == ':') { + // Let name position be tokenizer's next index. + auto name_position = tokenizer.next_index; + // Let name start be name position. + auto name_start = name_position; + // While name position is less than tokenizer's input's code point length: + while (name_position < tokenizer.input.size()) { + // Run seek and get the next code point given tokenizer and name + // position. + tokenizer.seek_and_get_next_code_point(name_position); + // Let first code point be true if name position equals name start and + // false otherwise. + bool first_code_point = name_position == name_start; + // Let valid code point be the result of running is a valid name code + // point given tokenizer's code point and first code point. + auto valid_code_point = + idna::valid_name_code_point(tokenizer.code_point, first_code_point); + ada_log("tokenizer.code_point=", uint32_t(tokenizer.code_point), + " first_code_point=", first_code_point, + " valid_code_point=", valid_code_point); + // If valid code point is false break. + if (!valid_code_point) break; + // Set name position to tokenizer's next index. + name_position = tokenizer.next_index; + } + + // If name position is less than or equal to name start: + if (name_position <= name_start) { + // Run process a tokenizing error given tokenizer, name start, and + // tokenizer's index. + if (auto error = tokenizer.process_tokenizing_error(name_start, + tokenizer.index)) { + ada_log("process_tokenizing_error failed"); + return tl::unexpected(*error); + } + // Continue + continue; + } + + // Run add a token with default length given tokenizer, "name", name + // position, and name start. + tokenizer.add_token_with_default_length(token_type::NAME, name_position, + name_start); + continue; + } + + // If tokenizer's code point is U+0028 ((): + if (tokenizer.code_point == '(') { + // Let depth be 1. + size_t depth = 1; + // Let regexp position be tokenizer's next index. + auto regexp_position = tokenizer.next_index; + // Let regexp start be regexp position. + auto regexp_start = regexp_position; + // Let error be false. + bool error = false; + + // While regexp position is less than tokenizer's input's code point + // length: + while (regexp_position < tokenizer.input.size()) { + // Run seek and get the next code point given tokenizer and regexp + // position. + tokenizer.seek_and_get_next_code_point(regexp_position); + + // TODO: Optimization opportunity: The next 2 if statements can be + // merged. If the result of running is ASCII given tokenizer's code + // point is false: + if (!unicode::is_ascii(tokenizer.code_point)) { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + return tl::unexpected(*process_error); + } + // Set error to true. + error = true; + break; + } + + // If regexp position equals regexp start and tokenizer's code point is + // U+003F (?): + if (regexp_position == regexp_start && tokenizer.code_point == '?') { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + return tl::unexpected(*process_error); + } + // Set error to true; + error = true; + break; + } + + // If tokenizer's code point is U+005C (\): + if (tokenizer.code_point == '\\') { + // If regexp position equals tokenizer's input's code point length - 1 + if (regexp_position == tokenizer.input.size() - 1) { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + return tl::unexpected(*process_error); + } + // Set error to true. + error = true; + break; + } + // Run get the next code point given tokenizer. + tokenizer.get_next_code_point(); + // If the result of running is ASCII given tokenizer's code point is + // false: + if (!unicode::is_ascii(tokenizer.code_point)) { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index); + process_error.has_value()) { + return tl::unexpected(*process_error); + } + // Set error to true. + error = true; + break; + } + // Set regexp position to tokenizer's next index. + regexp_position = tokenizer.next_index; + continue; + } + + // If tokenizer's code point is U+0029 ()): + if (tokenizer.code_point == ')') { + // Decrement depth by 1. + depth--; + // If depth is 0: + if (depth == 0) { + // Set regexp position to tokenizer's next index. + regexp_position = tokenizer.next_index; + // Break. + break; + } + } else if (tokenizer.code_point == '(') { + // Otherwise if tokenizer's code point is U+0028 ((): + // Increment depth by 1. + depth++; + // If regexp position equals tokenizer's input's code point length - + // 1: + if (regexp_position == tokenizer.input.size() - 1) { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + return tl::unexpected(*process_error); + } + // Set error to true. + error = true; + break; + } + // Let temporary position be tokenizer's next index. + auto temporary_position = tokenizer.next_index; + // Run get the next code point given tokenizer. + tokenizer.get_next_code_point(); + // If tokenizer's code point is not U+003F (?): + if (tokenizer.code_point != '?') { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + return tl::unexpected(*process_error); + } + // Set error to true. + error = true; + break; + } + // Set tokenizer's next index to temporary position. + tokenizer.next_index = temporary_position; + } + // Set regexp position to tokenizer's next index. + regexp_position = tokenizer.next_index; + } + + // If error is true continue. + if (error) continue; + // If depth is not zero: + if (depth != 0) { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + return tl::unexpected(*process_error); + } + continue; + } + // Let regexp length be regexp position - regexp start - 1. + auto regexp_length = regexp_position - regexp_start - 1; + // If regexp length is zero: + if (regexp_length == 0) { + // Run process a tokenizing error given tokenizer, regexp start, and + // tokenizer's index. + if (auto process_error = tokenizer.process_tokenizing_error( + regexp_start, tokenizer.index)) { + ada_log("process_tokenizing_error failed"); + return tl::unexpected(*process_error); + } + continue; + } + // Run add a token given tokenizer, "regexp", regexp position, regexp + // start, and regexp length. + tokenizer.add_token(token_type::REGEXP, regexp_position, regexp_start, + regexp_length); + continue; + } + // Run add a token with default position and length given tokenizer and + // "char". + tokenizer.add_token_with_defaults(token_type::CHAR); + } + // Run add a token with default length given tokenizer, "end", tokenizer's + // index, and tokenizer's index. + tokenizer.add_token_with_default_length(token_type::END, tokenizer.index, + tokenizer.index); + + ada_log("tokenizer.token_list size is: ", tokenizer.token_list.size()); + // Return tokenizer's token list. + return tokenizer.token_list; +} + +std::string escape_pattern_string(std::string_view input) { + ada_log("escape_pattern_string called with input=", input); + if (input.empty()) [[unlikely]] { + return ""; + } + // Assert: input is an ASCII string. + ADA_ASSERT_TRUE(ada::idna::is_ascii(input)); + // Let result be the empty string. + std::string result{}; + result.reserve(input.size()); + + // TODO: Optimization opportunity: Use a lookup table + constexpr auto should_escape = [](const char c) { + return c == '+' || c == '*' || c == '?' || c == ':' || c == '{' || + c == '}' || c == '(' || c == ')' || c == '\\'; + }; + + // While index is less than input's length: + for (const auto& c : input) { + if (should_escape(c)) { + // then append U+005C (\) to the end of result. + result.append("\\"); + } + + // Append c to the end of result. + result += c; + } + // Return result. + return result; +} + +namespace { +constexpr std::array escape_regexp_table = []() consteval { + std::array out{}; + for (auto& c : {'.', '+', '*', '?', '^', '$', '{', '}', '(', ')', '[', ']', + '|', '/', '\\'}) { + out[c] = 1; + } + return out; +}(); + +constexpr bool should_escape_regexp_char(char c) { + return escape_regexp_table[(uint8_t)c]; +} +} // namespace + +std::string escape_regexp_string(std::string_view input) { + // Assert: input is an ASCII string. + ADA_ASSERT_TRUE(idna::is_ascii(input)); + // Let result be the empty string. + std::string result{}; + result.reserve(input.size()); + for (const auto& c : input) { + // TODO: Optimize this even further + if (should_escape_regexp_char(c)) { + result.append(std::string("\\") + c); + } else { + result.push_back(c); + } + } + return result; +} + +std::string process_base_url_string(std::string_view input, + url_pattern_init::process_type type) { + // If type is not "pattern" return input. + if (type != url_pattern_init::process_type::pattern) { + return std::string(input); + } + // Return the result of escaping a pattern string given input. + return escape_pattern_string(input); +} + +constexpr bool is_absolute_pathname( + std::string_view input, url_pattern_init::process_type type) noexcept { + // If input is the empty string, then return false. + if (input.empty()) [[unlikely]] { + return false; + } + // If input[0] is U+002F (/), then return true. + if (input.starts_with("/")) return true; + // If type is "url", then return false. + if (type == url_pattern_init::process_type::url) return false; + // If input's code point length is less than 2, then return false. + if (input.size() < 2) return false; + // If input[0] is U+005C (\) and input[1] is U+002F (/), then return true. + // If input[0] is U+007B ({) and input[1] is U+002F (/), then return true. + // Return false. + return input[1] == '/' && (input[0] == '\\' || input[0] == '{'); +} + +std::string generate_pattern_string( + std::vector& part_list, + url_pattern_compile_component_options& options) { + // Let result be the empty string. + std::string result{}; + // Let index list be the result of getting the indices for part list. + // For each index of index list: + for (size_t index = 0; index < part_list.size(); index++) { + // Let part be part list[index]. + auto part = part_list[index]; + // Let previous part be part list[index - 1] if index is greater than 0, + // otherwise let it be null. + // TODO: Optimization opportunity. Find a way to avoid making a copy here. + std::optional previous_part = + index == 0 ? std::nullopt : std::optional(part_list[index - 1]); + // Let next part be part list[index + 1] if index is less than index list's + // size - 1, otherwise let it be null. + std::optional next_part = + index < part_list.size() - 1 ? std::optional(part_list[index + 1]) + : std::nullopt; + // If part's type is "fixed-text" then: + if (part.type == url_pattern_part_type::FIXED_TEXT) { + // If part's modifier is "none" then: + if (part.modifier == url_pattern_part_modifier::none) { + // Append the result of running escape a pattern string given part's + // value to the end of result. + result.append(escape_pattern_string(part.value)); + continue; + } + // Append "{" to the end of result. + result += "{"; + // Append the result of running escape a pattern string given part's value + // to the end of result. + result.append(escape_pattern_string(part.value)); + // Append "}" to the end of result. + result += "}"; + // Append the result of running convert a modifier to a string given + // part's modifier to the end of result. + result.append(convert_modifier_to_string(part.modifier)); + continue; + } + // Let custom name be true if part's name[0] is not an ASCII digit; + // otherwise false. + bool custom_name = !unicode::is_ascii_digit(part.name[0]); + // Let needs grouping be true if at least one of the following are true, + // otherwise let it be false: + // - part's suffix is not the empty string. + // - part's prefix is not the empty string and is not options's prefix code + // point. + bool needs_grouping = + !part.suffix.empty() || + (!part.prefix.empty() && part.prefix[0] != options.get_prefix()[0]); + + // If all of the following are true: + // - needs grouping is false; and + // - custom name is true; and + // - part's type is "segment-wildcard"; and + // - part's modifier is "none"; and + // - next part is not null; and + // - next part's prefix is the empty string; and + // - next part's suffix is the empty string + if (!needs_grouping && custom_name && + part.type == url_pattern_part_type::SEGMENT_WILDCARD && + part.modifier == url_pattern_part_modifier::none && + next_part.has_value() && next_part->prefix.empty() && + next_part->suffix.empty()) { + // If next part's type is "fixed-text": + if (next_part->type == url_pattern_part_type::FIXED_TEXT) { + // Set needs grouping to true if the result of running is a valid name + // code point given next part's value's first code point and the boolean + // false is true. + if (idna::valid_name_code_point(next_part->value[0], false)) { + needs_grouping = true; + } + } else { + // Set needs grouping to true if next part's name[0] is an ASCII digit. + needs_grouping = !next_part->name.empty() && + unicode::is_ascii_digit(next_part->name[0]); + } + } + + // If all of the following are true: + // - needs grouping is false; and + // - part's prefix is the empty string; and + // - previous part is not null; and + // - previous part's type is "fixed-text"; and + // - previous part's value's last code point is options's prefix code point. + // then set needs grouping to true. + if (!needs_grouping && part.prefix.empty() && previous_part.has_value() && + previous_part->type == url_pattern_part_type::FIXED_TEXT && + !options.get_prefix().empty() && + previous_part->value.at(previous_part->value.size() - 1) == + options.get_prefix()[0]) { + needs_grouping = true; + } + + // Assert: part's name is not the empty string or null. + ADA_ASSERT_TRUE(!part.name.empty()); + + // If needs grouping is true, then append "{" to the end of result. + if (needs_grouping) { + result.append("{"); + } + + // Append the result of running escape a pattern string given part's prefix + // to the end of result. + result.append(escape_pattern_string(part.prefix)); + + // If custom name is true: + if (custom_name) { + // Append ":" to the end of result. + result.append(":"); + // Append part's name to the end of result. + result.append(part.name); + } + + // If part's type is "regexp" then: + if (part.type == url_pattern_part_type::REGEXP) { + // Append "(" to the end of result. + result.append("("); + // Append part's value to the end of result. + result.append(part.value); + // Append ")" to the end of result. + result.append(")"); + } else if (part.type == url_pattern_part_type::SEGMENT_WILDCARD && + !custom_name) { + // Otherwise if part's type is "segment-wildcard" and custom name is + // false: Append "(" to the end of result. + result.append("("); + // Append the result of running generate a segment wildcard regexp given + // options to the end of result. + result.append(generate_segment_wildcard_regexp(options)); + // Append ")" to the end of result. + result.append(")"); + } else if (part.type == url_pattern_part_type::FULL_WILDCARD) { + // Otherwise if part's type is "full-wildcard": + // If custom name is false and one of the following is true: + // - previous part is null; or + // - previous part's type is "fixed-text"; or + // - previous part's modifier is not "none"; or + // - needs grouping is true; or + // - part's prefix is not the empty string + // - then append "*" to the end of result. + if (!custom_name && + (!previous_part.has_value() || + previous_part->type == url_pattern_part_type::FIXED_TEXT || + previous_part->modifier != url_pattern_part_modifier::none || + needs_grouping || !part.prefix.empty())) { + result.append("*"); + } else { + // Append "(" to the end of result. + // Append full wildcard regexp value to the end of result. + // Append ")" to the end of result. + result.append("(.*)"); + } + } + + // If all of the following are true: + // - part's type is "segment-wildcard"; and + // - custom name is true; and + // - part's suffix is not the empty string; and + // - The result of running is a valid name code point given part's suffix's + // first code point and the boolean false is true then append U+005C (\) to + // the end of result. + if (part.type == url_pattern_part_type::SEGMENT_WILDCARD && custom_name && + !part.suffix.empty() && + idna::valid_name_code_point(part.suffix[0], false)) { + result.append("\\"); + } + + // Append the result of running escape a pattern string given part's suffix + // to the end of result. + result.append(escape_pattern_string(part.suffix)); + // If needs grouping is true, then append "}" to the end of result. + if (needs_grouping) result.append("}"); + // Append the result of running convert a modifier to a string given part's + // modifier to the end of result. + result.append(convert_modifier_to_string(part.modifier)); + } + // Return result. + return result; +} +} // namespace ada::url_pattern_helpers + +#endif // ADA_INCLUDE_URL_PATTERN +/* end file src/url_pattern_helpers.cpp */ +/* begin file src/url_pattern_regex.cpp */ +#if ADA_INCLUDE_URL_PATTERN + + +namespace ada::url_pattern_regex { + +#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER +std::optional std_regex_provider::create_instance( + std::string_view pattern, bool ignore_case) { + // Let flags be an empty string. + // If options's ignore case is true then set flags to "vi". + // Otherwise set flags to "v" + auto flags = ignore_case + ? std::regex::icase | std::regex_constants::ECMAScript + : std::regex_constants::ECMAScript; + try { + return std::regex(pattern.data(), pattern.size(), flags); + } catch (const std::regex_error& e) { + (void)e; + ada_log("std_regex_provider::create_instance failed:", e.what()); + return std::nullopt; + } +} + +std::optional>> +std_regex_provider::regex_search(std::string_view input, + const std::regex& pattern) { + std::string input_str( + input.begin(), + input.end()); // Convert string_view to string for regex_search + std::smatch match_result; + if (!std::regex_search(input_str, match_result, pattern, + std::regex_constants::match_any)) { + return std::nullopt; + } + std::vector> matches; + // If input is empty, let's assume the result will be empty as well. + if (input.empty() || match_result.empty()) { + return matches; + } + matches.reserve(match_result.size()); + for (size_t i = 1; i < match_result.size(); ++i) { + if (auto entry = match_result[i]; entry.matched) { + matches.emplace_back(entry.str()); + } + } + return matches; +} + +bool std_regex_provider::regex_match(std::string_view input, + const std::regex& pattern) { + return std::regex_match(input.begin(), input.end(), pattern); +} + +#endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER + +} // namespace ada::url_pattern_regex + +#endif // ADA_INCLUDE_URL_PATTERN +/* end file src/url_pattern_regex.cpp */ +#endif // ADA_INCLUDE_URL_PATTERN + /* begin file src/ada_c.cpp */ ada::result& get_instance(void* result) noexcept { diff --git a/NativeScript/runtime/modules/url/ada/ada.h b/NativeScript/runtime/modules/url/ada/ada.h index a42ed6efe..db2803c8e 100644 --- a/NativeScript/runtime/modules/url/ada/ada.h +++ b/NativeScript/runtime/modules/url/ada/ada.h @@ -1,4 +1,4 @@ -/* auto-generated on 2024-07-06 17:38:56 -0400. Do not edit! */ +/* auto-generated on 2025-09-23 12:57:35 -0400. Do not edit! */ /* begin file include/ada.h */ /** * @file ada.h @@ -8,7 +8,7 @@ #define ADA_H /* begin file include/ada/ada_idna.h */ -/* auto-generated on 2023-09-19 15:58:51 -0400. Do not edit! */ +/* auto-generated on 2025-03-08 13:17:11 -0500. Do not edit! */ /* begin file include/idna.h */ #ifndef ADA_IDNA_H #define ADA_IDNA_H @@ -45,8 +45,6 @@ namespace ada::idna { // If the input is ascii, then the mapping is just -> lower case. void ascii_map(char* input, size_t length); -// check whether an ascii string needs mapping -bool ascii_has_upper_case(char* input, size_t length); // Map the characters according to IDNA, returning the empty string on error. std::u32string map(std::u32string_view input); @@ -129,9 +127,6 @@ std::string to_ascii(std::string_view ut8_string); // https://url.spec.whatwg.org/#forbidden-domain-code-point bool contains_forbidden_domain_code_point(std::string_view ascii_string); -bool begins_with(std::u32string_view view, std::u32string_view prefix); -bool begins_with(std::string_view view, std::string_view prefix); - bool constexpr is_ascii(std::u32string_view view); bool constexpr is_ascii(std::string_view view); @@ -154,20 +149,32 @@ std::string to_unicode(std::string_view input); #endif // ADA_IDNA_TO_UNICODE_H /* end file include/ada/idna/to_unicode.h */ +/* begin file include/ada/idna/identifier.h */ +#ifndef ADA_IDNA_IDENTIFIER_H +#define ADA_IDNA_IDENTIFIER_H + +#include +#include + +namespace ada::idna { + +// Verify if it is valid name code point given a Unicode code point and a +// boolean first: If first is true return the result of checking if code point +// is contained in the IdentifierStart set of code points. Otherwise return the +// result of checking if code point is contained in the IdentifierPart set of +// code points. Returns false if the input is empty or the code point is not +// valid. There is minimal Unicode error handling: the input should be valid +// UTF-8. https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point +bool valid_name_code_point(char32_t code_point, bool first); + +} // namespace ada::idna + +#endif +/* end file include/ada/idna/identifier.h */ #endif /* end file include/idna.h */ /* end file include/ada/ada_idna.h */ -/* begin file include/ada/character_sets-inl.h */ -/** - * @file character_sets-inl.h - * @brief Definitions of the character sets used by unicode functions. - * @author Node.js - * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc - */ -#ifndef ADA_CHARACTER_SETS_INL_H -#define ADA_CHARACTER_SETS_INL_H - /* begin file include/ada/character_sets.h */ /** * @file character_sets.h @@ -186,6 +193,10 @@ std::string to_unicode(std::string_view input); #ifndef ADA_COMMON_DEFS_H #define ADA_COMMON_DEFS_H +// https://en.cppreference.com/w/cpp/feature_test#Library_features +// detect C++20 features +#include + #ifdef _MSC_VER #define ADA_VISUAL_STUDIO 1 /** @@ -230,13 +241,6 @@ std::string to_unicode(std::string_view input); #define ada_unused #define ada_warn_unused -#ifndef ada_likely -#define ada_likely(x) x -#endif -#ifndef ada_unlikely -#define ada_unlikely(x) x -#endif - #define ADA_PUSH_DISABLE_WARNINGS __pragma(warning(push)) #define ADA_PUSH_DISABLE_ALL_WARNINGS __pragma(warning(push, 0)) #define ADA_DISABLE_VS_WARNING(WARNING_NUMBER) \ @@ -268,13 +272,6 @@ std::string to_unicode(std::string_view input); #define ada_unused __attribute__((unused)) #define ada_warn_unused __attribute__((warn_unused_result)) -#ifndef ada_likely -#define ada_likely(x) __builtin_expect(!!(x), 1) -#endif -#ifndef ada_unlikely -#define ada_unlikely(x) __builtin_expect(!!(x), 0) -#endif - #define ADA_PUSH_DISABLE_WARNINGS _Pragma("GCC diagnostic push") // gcc doesn't seem to disable all warnings with all and extra, add warnings // here as necessary @@ -290,7 +287,8 @@ std::string to_unicode(std::string_view input); ADA_DISABLE_GCC_WARNING("-Wreturn-type") \ ADA_DISABLE_GCC_WARNING("-Wshadow") \ ADA_DISABLE_GCC_WARNING("-Wunused-parameter") \ - ADA_DISABLE_GCC_WARNING("-Wunused-variable") + ADA_DISABLE_GCC_WARNING("-Wunused-variable") \ + ADA_DISABLE_GCC_WARNING("-Wsign-compare") #define ADA_PRAGMA(P) _Pragma(#P) #define ADA_DISABLE_GCC_WARNING(WARNING) \ ADA_PRAGMA(GCC diagnostic ignored WARNING) @@ -354,52 +352,6 @@ namespace ada { } } // namespace ada -#if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ <= 8 -#define ADA_OLD_GCC 1 -#endif // __GNUC__ <= 8 -#endif // defined(__GNUC__) && !defined(__clang__) - -#if ADA_OLD_GCC -#define ada_constexpr -#else -#define ada_constexpr constexpr -#endif - -#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) -#define ADA_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) -#elif defined(_WIN32) -#define ADA_IS_BIG_ENDIAN 0 -#else -#if defined(__APPLE__) || \ - defined(__FreeBSD__) // defined __BYTE_ORDER__ && defined - // __ORDER_BIG_ENDIAN__ -#include -#elif defined(sun) || \ - defined(__sun) // defined(__APPLE__) || defined(__FreeBSD__) -#include -#else // defined(__APPLE__) || defined(__FreeBSD__) - -#ifdef __has_include -#if __has_include() -#include -#endif //__has_include() -#endif //__has_include - -#endif // defined(__APPLE__) || defined(__FreeBSD__) - -#ifndef !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) -#define ADA_IS_BIG_ENDIAN 0 -#endif - -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define ADA_IS_BIG_ENDIAN 0 -#else // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -#define ADA_IS_BIG_ENDIAN 1 -#endif // __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - -#endif // defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__ - // Unless the programmer has already set ADA_DEVELOPMENT_CHECKS, // we want to set it under debug builds. We detect a debug build // under Visual Studio when the _DEBUG macro is set. Under the other @@ -479,6 +431,33 @@ namespace ada { #define ADA_NEON 1 #endif +#if defined(__loongarch_sx) +#define ADA_LSX 1 +#endif + +#ifndef __has_cpp_attribute +#define ada_lifetime_bound +#elif __has_cpp_attribute(msvc::lifetimebound) +#define ada_lifetime_bound [[msvc::lifetimebound]] +#elif __has_cpp_attribute(clang::lifetimebound) +#define ada_lifetime_bound [[clang::lifetimebound]] +#elif __has_cpp_attribute(lifetimebound) +#define ada_lifetime_bound [[lifetimebound]] +#else +#define ada_lifetime_bound +#endif + +#ifdef __cpp_lib_format +#if __cpp_lib_format >= 202110L +#include +#define ADA_HAS_FORMAT 1 +#endif +#endif + +#ifndef ADA_INCLUDE_URL_PATTERN +#define ADA_INCLUDE_URL_PATTERN 1 +#endif // ADA_INCLUDE_URL_PATTERN + #endif // ADA_COMMON_DEFS_H /* end file include/ada/common_defs.h */ #include @@ -491,11 +470,21 @@ namespace ada { * @brief Includes the definitions for unicode character sets. */ namespace ada::character_sets { -ada_really_inline bool bit_at(const uint8_t a[], uint8_t i); +ada_really_inline constexpr bool bit_at(const uint8_t a[], uint8_t i); } // namespace ada::character_sets #endif // ADA_CHARACTER_SETS_H /* end file include/ada/character_sets.h */ +/* begin file include/ada/character_sets-inl.h */ +/** + * @file character_sets-inl.h + * @brief Definitions of the character sets used by unicode functions. + * @author Node.js + * @see https://github.com/nodejs/node/blob/main/src/node_url_tables.cc + */ +#ifndef ADA_CHARACTER_SETS_INL_H +#define ADA_CHARACTER_SETS_INL_H + /** * These functions are not part of our public API and may @@ -892,7 +881,7 @@ constexpr uint8_t PATH_PERCENT_ENCODE[32] = { // 50 51 52 53 54 55 56 57 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, + 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x40 | 0x00, // 60 61 62 63 64 65 66 67 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, // 68 69 6A 6B 6C 6D 6E 6F @@ -958,7 +947,7 @@ constexpr uint8_t WWW_FORM_URLENCODED_PERCENT_ENCODE[32] = { // 50 51 52 53 54 55 56 57 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, // 58 59 5A 5B 5C 5D 5E 5F - 0x00 | 0x00 | 0x00 | 0x08 | 0x00 | 0x20 | 0x40 | 0x00, + 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00, // 60 61 62 63 64 65 66 67 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00, // 68 69 6A 6B 6C 6D 6E 6F @@ -1000,7 +989,7 @@ constexpr uint8_t WWW_FORM_URLENCODED_PERCENT_ENCODE[32] = { // F8 F9 FA FB FC FD FE FF 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80}; -ada_really_inline bool bit_at(const uint8_t a[], const uint8_t i) { +ada_really_inline constexpr bool bit_at(const uint8_t a[], const uint8_t i) { return !!(a[i >> 3] & (1 << (i & 7))); } @@ -1016,22 +1005,19 @@ ada_really_inline bool bit_at(const uint8_t a[], const uint8_t i) { #ifndef ADA_CHECKERS_INL_H #define ADA_CHECKERS_INL_H -#include -#include +#include #include namespace ada::checkers { -inline bool has_hex_prefix_unsafe(std::string_view input) { +constexpr bool has_hex_prefix_unsafe(std::string_view input) { // This is actually efficient code, see has_hex_prefix for the assembly. - uint32_t value_one = 1; - bool is_little_endian = (reinterpret_cast(&value_one)[0] == 1); - uint16_t word0x{}; - std::memcpy(&word0x, "0x", 2); // we would use bit_cast in C++20 and the - // function could be constexpr. - uint16_t two_first_bytes{}; - std::memcpy(&two_first_bytes, input.data(), 2); - if (is_little_endian) { + constexpr bool is_little_endian = std::endian::native == std::endian::little; + constexpr uint16_t word0x = 0x7830; + uint16_t two_first_bytes = + static_cast(input[0]) | + static_cast((static_cast(input[1]) << 8)); + if constexpr (is_little_endian) { two_first_bytes |= 0x2000; } else { two_first_bytes |= 0x020; @@ -1039,7 +1025,7 @@ inline bool has_hex_prefix_unsafe(std::string_view input) { return two_first_bytes == word0x; } -inline bool has_hex_prefix(std::string_view input) { +constexpr bool has_hex_prefix(std::string_view input) { return input.size() >= 2 && has_hex_prefix_unsafe(input); } @@ -1051,26 +1037,18 @@ constexpr bool is_alpha(char x) noexcept { return (to_lower(x) >= 'a') && (to_lower(x) <= 'z'); } -inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept { +constexpr bool is_windows_drive_letter(std::string_view input) noexcept { return input.size() >= 2 && (is_alpha(input[0]) && ((input[1] == ':') || (input[1] == '|'))) && ((input.size() == 2) || (input[2] == '/' || input[2] == '\\' || input[2] == '?' || input[2] == '#')); } -inline constexpr bool is_normalized_windows_drive_letter( +constexpr bool is_normalized_windows_drive_letter( std::string_view input) noexcept { return input.size() >= 2 && (is_alpha(input[0]) && (input[1] == ':')); } -ada_really_inline bool begins_with(std::string_view view, - std::string_view prefix) { - // in C++20, you have view.begins_with(prefix) - // std::equal is constexpr in C++20 - return view.size() >= prefix.size() && - std::equal(prefix.begin(), prefix.end(), view.begin()); -} - } // namespace ada::checkers #endif // ADA_CHECKERS_INL_H @@ -1084,65 +1062,31 @@ ada_really_inline bool begins_with(std::string_view view, #ifndef ADA_LOG_H #define ADA_LOG_H -#include // To enable logging, set ADA_LOGGING to 1: #ifndef ADA_LOGGING #define ADA_LOGGING 0 #endif -namespace ada { - -/** - * Private function used for logging messages. - * @private - */ -template -ada_really_inline void inner_log([[maybe_unused]] T t) { -#if ADA_LOGGING - std::cout << t << std::endl; -#endif -} - -/** - * Private function used for logging messages. - * @private - */ -template -ada_really_inline void inner_log([[maybe_unused]] T t, - [[maybe_unused]] Args... args) { #if ADA_LOGGING - std::cout << t; - inner_log(args...); -#endif -} +#include +#endif // ADA_LOGGING -/** - * Log a message. - * @private - */ -template -ada_really_inline void log([[maybe_unused]] T t, - [[maybe_unused]] Args... args) { -#if ADA_LOGGING - std::cout << "ADA_LOG: " << t; - inner_log(args...); -#endif -} +namespace ada { /** - * Log a message. + * Log a message. If you want to have no overhead when logging is disabled, use + * the ada_log macro. * @private */ -template -ada_really_inline void log([[maybe_unused]] T t) { +template +constexpr ada_really_inline void log([[maybe_unused]] Args... args) { #if ADA_LOGGING - std::cout << "ADA_LOG: " << t << std::endl; -#endif + ((std::cout << "ADA_LOG: ") << ... << args) << std::endl; +#endif // ADA_LOGGING } } // namespace ada #if ADA_LOGGING - #ifndef ada_log #define ada_log(...) \ do { \ @@ -1182,7 +1126,7 @@ enum class encoding_type { /** * Convert a encoding_type to string. */ -ada_warn_unused std::string to_string(encoding_type type); +ada_warn_unused std::string_view to_string(encoding_type type); } // namespace ada @@ -1196,512 +1140,311 @@ ada_warn_unused std::string to_string(encoding_type type); #ifndef ADA_HELPERS_H #define ADA_HELPERS_H -/* begin file include/ada/state.h */ +/* begin file include/ada/url_base.h */ /** - * @file state.h - * @brief Definitions for the states of the URL state machine. + * @file url_base.h + * @brief Declaration for the basic URL definitions */ -#ifndef ADA_STATE_H -#define ADA_STATE_H +#ifndef ADA_URL_BASE_H +#define ADA_URL_BASE_H + +/* begin file include/ada/scheme.h */ +/** + * @file scheme.h + * @brief Declarations for the URL scheme. + */ +#ifndef ADA_SCHEME_H +#define ADA_SCHEME_H + + +#include + +/** + * @namespace ada::scheme + * @brief Includes the scheme declarations + */ +namespace ada::scheme { + +/** + * Type of the scheme as an enum. + * Using strings to represent a scheme type is not ideal because + * checking for types involves string comparisons. It is faster to use + * a simple integer. + * In C++11, we are allowed to specify the underlying type of the enum. + * We pick an 8-bit integer (which allows up to 256 types). Specifying the + * type of the enum may help integration with other systems if the type + * variable is exposed (since its value will not depend on the compiler). + */ +enum type : uint8_t { + HTTP = 0, + NOT_SPECIAL = 1, + HTTPS = 2, + WS = 3, + FTP = 4, + WSS = 5, + FILE = 6 +}; + +/** + * A special scheme is an ASCII string that is listed in the first column of the + * following table. The default port for a special scheme is listed in the + * second column on the same row. The default port for any other ASCII string is + * null. + * + * @see https://url.spec.whatwg.org/#url-miscellaneous + * @param scheme + * @return If scheme is a special scheme + */ +ada_really_inline constexpr bool is_special(std::string_view scheme); + +/** + * A special scheme is an ASCII string that is listed in the first column of the + * following table. The default port for a special scheme is listed in the + * second column on the same row. The default port for any other ASCII string is + * null. + * + * @see https://url.spec.whatwg.org/#url-miscellaneous + * @param scheme + * @return The special port + */ +constexpr uint16_t get_special_port(std::string_view scheme) noexcept; + +/** + * Returns the port number of a special scheme. + * @see https://url.spec.whatwg.org/#special-scheme + */ +constexpr uint16_t get_special_port(ada::scheme::type type) noexcept; +/** + * Returns the scheme of an input, or NOT_SPECIAL if it's not a special scheme + * defined by the spec. + */ +constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept; + +} // namespace ada::scheme + +#endif // ADA_SCHEME_H +/* end file include/ada/scheme.h */ #include +#include namespace ada { /** - * @see https://url.spec.whatwg.org/#url-parsing + * Type of URL host as an enum. */ -enum class state { +enum url_host_type : uint8_t { /** - * @see https://url.spec.whatwg.org/#authority-state + * Represents common URLs such as "https://www.google.com" */ - AUTHORITY, - + DEFAULT = 0, /** - * @see https://url.spec.whatwg.org/#scheme-start-state + * Represents ipv4 addresses such as "http://127.0.0.1" */ - SCHEME_START, - + IPV4 = 1, /** - * @see https://url.spec.whatwg.org/#scheme-state + * Represents ipv6 addresses such as + * "http://[2001:db8:3333:4444:5555:6666:7777:8888]" */ - SCHEME, + IPV6 = 2, +}; - /** - * @see https://url.spec.whatwg.org/#host-state - */ - HOST, +/** + * @brief Base class of URL implementations + * + * @details A url_base contains a few attributes: is_valid, has_opaque_path and + * type. All non-trivial implementation details are in derived classes such as + * ada::url and ada::url_aggregator. + * + * It is an abstract class that cannot be instantiated directly. + */ +struct url_base { + virtual ~url_base() = default; /** - * @see https://url.spec.whatwg.org/#no-scheme-state + * Used for returning the validity from the result of the URL parser. */ - NO_SCHEME, + bool is_valid{true}; /** - * @see https://url.spec.whatwg.org/#fragment-state + * A URL has an opaque path if its path is a string. */ - FRAGMENT, + bool has_opaque_path{false}; /** - * @see https://url.spec.whatwg.org/#relative-state + * URL hosts type */ - RELATIVE_SCHEME, + url_host_type host_type = url_host_type::DEFAULT; /** - * @see https://url.spec.whatwg.org/#relative-slash-state + * @private */ - RELATIVE_SLASH, + ada::scheme::type type{ada::scheme::type::NOT_SPECIAL}; /** - * @see https://url.spec.whatwg.org/#file-state + * A URL is special if its scheme is a special scheme. A URL is not special if + * its scheme is not a special scheme. */ - FILE, + [[nodiscard]] ada_really_inline constexpr bool is_special() const noexcept; /** - * @see https://url.spec.whatwg.org/#file-host-state + * The origin getter steps are to return the serialization of this's URL's + * origin. [HTML] + * @return a newly allocated string. + * @see https://url.spec.whatwg.org/#concept-url-origin */ - FILE_HOST, + [[nodiscard]] virtual std::string get_origin() const noexcept = 0; /** - * @see https://url.spec.whatwg.org/#file-slash-state + * Returns true if this URL has a valid domain as per RFC 1034 and + * corresponding specifications. Among other things, it requires + * that the domain string has fewer than 255 octets. */ - FILE_SLASH, + [[nodiscard]] virtual bool has_valid_domain() const noexcept = 0; /** - * @see https://url.spec.whatwg.org/#path-or-authority-state + * @private + * + * Return the 'special port' if the URL is special and not 'file'. + * Returns 0 otherwise. */ - PATH_OR_AUTHORITY, + [[nodiscard]] inline uint16_t get_special_port() const noexcept; /** - * @see https://url.spec.whatwg.org/#special-authority-ignore-slashes-state + * @private + * + * Get the default port if the url's scheme has one, returns 0 otherwise. */ - SPECIAL_AUTHORITY_IGNORE_SLASHES, + [[nodiscard]] ada_really_inline uint16_t scheme_default_port() const noexcept; /** - * @see https://url.spec.whatwg.org/#special-authority-slashes-state + * @private + * + * Parse a port (16-bit decimal digit) from the provided input. + * We assume that the input does not contain spaces or tabs + * within the ASCII digits. + * It returns how many bytes were consumed when a number is successfully + * parsed. + * @return On failure, it returns zero. + * @see https://url.spec.whatwg.org/#host-parsing */ - SPECIAL_AUTHORITY_SLASHES, + virtual size_t parse_port(std::string_view view, + bool check_trailing_content) noexcept = 0; - /** - * @see https://url.spec.whatwg.org/#special-relative-or-authority-state - */ - SPECIAL_RELATIVE_OR_AUTHORITY, + virtual ada_really_inline size_t parse_port(std::string_view view) noexcept { + return this->parse_port(view, false); + } /** - * @see https://url.spec.whatwg.org/#query-state + * Returns a JSON string representation of this URL. */ - QUERY, + [[nodiscard]] virtual std::string to_string() const = 0; - /** - * @see https://url.spec.whatwg.org/#path-state - */ - PATH, + /** @private */ + virtual inline void clear_pathname() = 0; - /** - * @see https://url.spec.whatwg.org/#path-start-state - */ - PATH_START, + /** @private */ + virtual inline void clear_search() = 0; - /** - * @see https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state - */ - OPAQUE_PATH, + /** @private */ + [[nodiscard]] virtual inline bool has_hash() const noexcept = 0; - /** - * @see https://url.spec.whatwg.org/#port-state - */ - PORT, -}; + /** @private */ + [[nodiscard]] virtual inline bool has_search() const noexcept = 0; -/** - * Stringify a URL state machine state. - */ -ada_warn_unused std::string to_string(ada::state s); +}; // url_base } // namespace ada -#endif // ADA_STATE_H -/* end file include/ada/state.h */ -/* begin file include/ada/url_base.h */ -/** - * @file url_base.h - * @brief Declaration for the basic URL definitions - */ -#ifndef ADA_URL_BASE_H -#define ADA_URL_BASE_H - -/* begin file include/ada/url_components.h */ -/** - * @file url_components.h - * @brief Declaration for the URL Components - */ -#ifndef ADA_URL_COMPONENTS_H -#define ADA_URL_COMPONENTS_H +#endif +/* end file include/ada/url_base.h */ -#include +#include #include +#include -namespace ada { +#if ADA_DEVELOPMENT_CHECKS +#include +#endif // ADA_DEVELOPMENT_CHECKS /** - * @brief URL Component representations using offsets. - * - * @details We design the url_components struct so that it is as small - * and simple as possible. This version uses 32 bytes. + * These functions are not part of our public API and may + * change at any time. * - * This struct is used to extract components from a single 'href'. + * @private + * @namespace ada::helpers + * @brief Includes the definitions for helper functions */ -struct url_components { - constexpr static uint32_t omitted = uint32_t(-1); - - url_components() = default; - url_components(const url_components& u) = default; - url_components(url_components&& u) noexcept = default; - url_components& operator=(url_components&& u) noexcept = default; - url_components& operator=(const url_components& u) = default; - ~url_components() = default; - - /* - * By using 32-bit integers, we implicitly assume that the URL string - * cannot exceed 4 GB. - * - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - */ - uint32_t protocol_end{0}; - /** - * Username end is not `omitted` by default to make username and password - * getters less costly to implement. - */ - uint32_t username_end{0}; - uint32_t host_start{0}; - uint32_t host_end{0}; - uint32_t port{omitted}; - uint32_t pathname_start{0}; - uint32_t search_start{omitted}; - uint32_t hash_start{omitted}; - - /** - * Check the following conditions: - * protocol_end < username_end < ... < hash_start, - * expect when a value is omitted. It also computes - * a lower bound on the possible string length that may match these - * offsets. - * @return true if the offset values are - * consistent with a possible URL string - */ - [[nodiscard]] bool check_offset_consistency() const noexcept; - - /** - * Converts a url_components to JSON stringified version. - */ - [[nodiscard]] std::string to_string() const; - -}; // struct url_components +namespace ada::helpers { -} // namespace ada -#endif -/* end file include/ada/url_components.h */ -/* begin file include/ada/scheme.h */ /** - * @file scheme.h - * @brief Declarations for the URL scheme. + * @private */ -#ifndef ADA_SCHEME_H -#define ADA_SCHEME_H - -#include -#include -#include +template +void encode_json(std::string_view view, out_iter out); /** - * @namespace ada::scheme - * @brief Includes the scheme declarations + * @private + * This function is used to prune a fragment from a url, and returning the + * removed string if input has fragment. + * + * @details prune_hash seeks the first '#' and returns everything after it + * as a string_view, and modifies (in place) the input so that it points at + * everything before the '#'. If no '#' is found, the input is left unchanged + * and std::nullopt is returned. + * + * @attention The function is non-allocating and it does not throw. + * @returns Note that the returned string_view might be empty! */ -namespace ada::scheme { +ada_really_inline std::optional prune_hash( + std::string_view& input) noexcept; /** - * Type of the scheme as an enum. - * Using strings to represent a scheme type is not ideal because - * checking for types involves string comparisons. It is faster to use - * a simple integer. - * In C++11, we are allowed to specify the underlying type of the enum. - * We pick an 8-bit integer (which allows up to 256 types). Specifying the - * type of the enum may help integration with other systems if the type - * variable is exposed (since its value will not depend on the compiler). + * @private + * Defined by the URL specification, shorten a URLs paths. + * @see https://url.spec.whatwg.org/#shorten-a-urls-path + * @returns Returns true if path is shortened. */ -enum type : uint8_t { - HTTP = 0, - NOT_SPECIAL = 1, - HTTPS = 2, - WS = 3, - FTP = 4, - WSS = 5, - FILE = 6 -}; +ada_really_inline bool shorten_path(std::string& path, + ada::scheme::type type) noexcept; /** - * A special scheme is an ASCII string that is listed in the first column of the - * following table. The default port for a special scheme is listed in the - * second column on the same row. The default port for any other ASCII string is - * null. - * - * @see https://url.spec.whatwg.org/#url-miscellaneous - * @param scheme - * @return If scheme is a special scheme + * @private + * Defined by the URL specification, shorten a URLs paths. + * @see https://url.spec.whatwg.org/#shorten-a-urls-path + * @returns Returns true if path is shortened. */ -ada_really_inline constexpr bool is_special(std::string_view scheme); +ada_really_inline bool shorten_path(std::string_view& path, + ada::scheme::type type) noexcept; /** - * A special scheme is an ASCII string that is listed in the first column of the - * following table. The default port for a special scheme is listed in the - * second column on the same row. The default port for any other ASCII string is - * null. + * @private * - * @see https://url.spec.whatwg.org/#url-miscellaneous - * @param scheme - * @return The special port + * Parse the path from the provided input and append to the existing + * (possibly empty) path. The input cannot contain tabs and spaces: it + * is the user's responsibility to check. + * + * The input is expected to be UTF-8. + * + * @see https://url.spec.whatwg.org/ */ -constexpr uint16_t get_special_port(std::string_view scheme) noexcept; +ada_really_inline void parse_prepared_path(std::string_view input, + ada::scheme::type type, + std::string& path); /** - * Returns the port number of a special scheme. - * @see https://url.spec.whatwg.org/#special-scheme + * @private + * Remove and mutate all ASCII tab or newline characters from an input. */ -constexpr uint16_t get_special_port(ada::scheme::type type) noexcept; +ada_really_inline void remove_ascii_tab_or_newline(std::string& input) noexcept; + /** - * Returns the scheme of an input, or NOT_SPECIAL if it's not a special scheme - * defined by the spec. + * @private + * Return the substring from input going from index pos to the end. + * This function cannot throw. */ -constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept; - -} // namespace ada::scheme - -#endif // ADA_SCHEME_H -/* end file include/ada/scheme.h */ - -#include - -namespace ada { - -/** - * Type of URL host as an enum. - */ -enum url_host_type : uint8_t { - /** - * Represents common URLs such as "https://www.google.com" - */ - DEFAULT = 0, - /** - * Represents ipv4 addresses such as "http://127.0.0.1" - */ - IPV4 = 1, - /** - * Represents ipv6 addresses such as - * "http://[2001:db8:3333:4444:5555:6666:7777:8888]" - */ - IPV6 = 2, -}; - -/** - * @brief Base class of URL implementations - * - * @details A url_base contains a few attributes: is_valid, has_opaque_path and - * type. All non-trivial implementation details are in derived classes such as - * ada::url and ada::url_aggregator. - * - * It is an abstract class that cannot be instantiated directly. - */ -struct url_base { - virtual ~url_base() = default; - - /** - * Used for returning the validity from the result of the URL parser. - */ - bool is_valid{true}; - - /** - * A URL has an opaque path if its path is a string. - */ - bool has_opaque_path{false}; - - /** - * URL hosts type - */ - url_host_type host_type = url_host_type::DEFAULT; - - /** - * @private - */ - ada::scheme::type type{ada::scheme::type::NOT_SPECIAL}; - - /** - * A URL is special if its scheme is a special scheme. A URL is not special if - * its scheme is not a special scheme. - */ - [[nodiscard]] ada_really_inline bool is_special() const noexcept; - - /** - * The origin getter steps are to return the serialization of this's URL's - * origin. [HTML] - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#concept-url-origin - */ - [[nodiscard]] virtual std::string get_origin() const noexcept = 0; - - /** - * Returns true if this URL has a valid domain as per RFC 1034 and - * corresponding specifications. Among other things, it requires - * that the domain string has fewer than 255 octets. - */ - [[nodiscard]] virtual bool has_valid_domain() const noexcept = 0; - - /** - * @private - * - * Return the 'special port' if the URL is special and not 'file'. - * Returns 0 otherwise. - */ - [[nodiscard]] inline uint16_t get_special_port() const noexcept; - - /** - * @private - * - * Get the default port if the url's scheme has one, returns 0 otherwise. - */ - [[nodiscard]] ada_really_inline uint16_t scheme_default_port() const noexcept; - - /** - * @private - * - * Parse a port (16-bit decimal digit) from the provided input. - * We assume that the input does not contain spaces or tabs - * within the ASCII digits. - * It returns how many bytes were consumed when a number is successfully - * parsed. - * @return On failure, it returns zero. - * @see https://url.spec.whatwg.org/#host-parsing - */ - virtual size_t parse_port(std::string_view view, - bool check_trailing_content) noexcept = 0; - - virtual ada_really_inline size_t parse_port(std::string_view view) noexcept { - return this->parse_port(view, false); - } - - /** - * Returns a JSON string representation of this URL. - */ - [[nodiscard]] virtual std::string to_string() const = 0; - - /** @private */ - virtual inline void clear_pathname() = 0; - - /** @private */ - virtual inline void clear_search() = 0; - - /** @private */ - [[nodiscard]] virtual inline bool has_hash() const noexcept = 0; - - /** @private */ - [[nodiscard]] virtual inline bool has_search() const noexcept = 0; - -}; // url_base - -} // namespace ada - -#endif -/* end file include/ada/url_base.h */ - -#include -#include - -/** - * These functions are not part of our public API and may - * change at any time. - * - * @private - * @namespace ada::helpers - * @brief Includes the definitions for helper functions - */ -namespace ada::helpers { - -/** - * @private - */ -template -void encode_json(std::string_view view, out_iter out); - -/** - * @private - * This function is used to prune a fragment from a url, and returning the - * removed string if input has fragment. - * - * @details prune_hash seeks the first '#' and returns everything after it - * as a string_view, and modifies (in place) the input so that it points at - * everything before the '#'. If no '#' is found, the input is left unchanged - * and std::nullopt is returned. - * - * @attention The function is non-allocating and it does not throw. - * @returns Note that the returned string_view might be empty! - */ -ada_really_inline std::optional prune_hash( - std::string_view& input) noexcept; - -/** - * @private - * Defined by the URL specification, shorten a URLs paths. - * @see https://url.spec.whatwg.org/#shorten-a-urls-path - * @returns Returns true if path is shortened. - */ -ada_really_inline bool shorten_path(std::string& path, - ada::scheme::type type) noexcept; - -/** - * @private - * Defined by the URL specification, shorten a URLs paths. - * @see https://url.spec.whatwg.org/#shorten-a-urls-path - * @returns Returns true if path is shortened. - */ -ada_really_inline bool shorten_path(std::string_view& path, - ada::scheme::type type) noexcept; - -/** - * @private - * - * Parse the path from the provided input and append to the existing - * (possibly empty) path. The input cannot contain tabs and spaces: it - * is the user's responsibility to check. - * - * The input is expected to be UTF-8. - * - * @see https://url.spec.whatwg.org/ - */ -ada_really_inline void parse_prepared_path(std::string_view input, - ada::scheme::type type, - std::string& path); - -/** - * @private - * Remove and mutate all ASCII tab or newline characters from an input. - */ -ada_really_inline void remove_ascii_tab_or_newline(std::string& input) noexcept; - -/** - * @private - * Return the substring from input going from index pos to the end. - * This function cannot throw. - */ -ada_really_inline std::string_view substring(std::string_view input, - size_t pos) noexcept; +ada_really_inline constexpr std::string_view substring(std::string_view input, + size_t pos) noexcept; /** * @private @@ -1714,9 +1457,9 @@ bool overlaps(std::string_view input1, const std::string& input2) noexcept; * Return the substring from input going from index pos1 to the pos2 (non * included). The length of the substring is pos2 - pos1. */ -ada_really_inline std::string_view substring(const std::string& input, - size_t pos1, - size_t pos2) noexcept { +ada_really_inline constexpr std::string_view substring(std::string_view input, + size_t pos1, + size_t pos2) noexcept { #if ADA_DEVELOPMENT_CHECKS if (pos2 < pos1) { std::cerr << "Negative-length substring: [" << pos1 << " to " << pos2 << ")" @@ -1724,7 +1467,7 @@ ada_really_inline std::string_view substring(const std::string& input, abort(); } #endif - return std::string_view(input.data() + pos1, pos2 - pos1); + return input.substr(pos1, pos2 - pos1); } /** @@ -1740,14 +1483,14 @@ ada_really_inline void resize(std::string_view& input, size_t pos) noexcept; * and whether a colon was found outside brackets. Used by the host parser. */ ada_really_inline std::pair get_host_delimiter_location( - const bool is_special, std::string_view& view) noexcept; + bool is_special, std::string_view& view) noexcept; /** * @private * Removes leading and trailing C0 control and whitespace characters from * string. */ -ada_really_inline void trim_c0_whitespace(std::string_view& input) noexcept; +void trim_c0_whitespace(std::string_view& input) noexcept; /** * @private @@ -1851,8 +1594,8 @@ inline int fast_digit_count(uint32_t x) noexcept { #ifndef ADA_PARSER_H #define ADA_PARSER_H -#include #include +#include /* begin file include/ada/expected.h */ /** @@ -2008,25 +1751,25 @@ class unexpected { static_assert(!std::is_same::value, "E must not be void"); unexpected() = delete; - constexpr explicit unexpected(const E& e) : m_val(e) {} + constexpr explicit unexpected(const E &e) : m_val(e) {} - constexpr explicit unexpected(E&& e) : m_val(std::move(e)) {} + constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} template ::value>::type* = nullptr> - constexpr explicit unexpected(Args&&... args) + E, Args &&...>::value>::type * = nullptr> + constexpr explicit unexpected(Args &&...args) : m_val(std::forward(args)...) {} template < class U, class... Args, typename std::enable_if&, Args&&...>::value>::type* = nullptr> - constexpr explicit unexpected(std::initializer_list l, Args&&... args) + E, std::initializer_list &, Args &&...>::value>::type * = nullptr> + constexpr explicit unexpected(std::initializer_list l, Args &&...args) : m_val(l, std::forward(args)...) {} - constexpr const E& value() const& { return m_val; } - TL_EXPECTED_11_CONSTEXPR E& value() & { return m_val; } - TL_EXPECTED_11_CONSTEXPR E&& value() && { return std::move(m_val); } - constexpr const E&& value() const&& { return std::move(m_val); } + constexpr const E &value() const & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } + constexpr const E &&value() const && { return std::move(m_val); } private: E m_val; @@ -2038,32 +1781,32 @@ unexpected(E) -> unexpected; #endif template -constexpr bool operator==(const unexpected& lhs, const unexpected& rhs) { +constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { return lhs.value() == rhs.value(); } template -constexpr bool operator!=(const unexpected& lhs, const unexpected& rhs) { +constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() != rhs.value(); } template -constexpr bool operator<(const unexpected& lhs, const unexpected& rhs) { +constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { return lhs.value() < rhs.value(); } template -constexpr bool operator<=(const unexpected& lhs, const unexpected& rhs) { +constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() <= rhs.value(); } template -constexpr bool operator>(const unexpected& lhs, const unexpected& rhs) { +constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { return lhs.value() > rhs.value(); } template -constexpr bool operator>=(const unexpected& lhs, const unexpected& rhs) { +constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() >= rhs.value(); } template -unexpected::type> make_unexpected(E&& e) { +unexpected::type> make_unexpected(E &&e) { return unexpected::type>(std::forward(e)); } @@ -2074,7 +1817,7 @@ static constexpr unexpect_t unexpect{}; namespace detail { template -[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E&& e) { +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED throw std::forward(e); #else @@ -2124,7 +1867,7 @@ template struct is_pointer_to_non_const_member_func : std::true_type {}; template -struct is_pointer_to_non_const_member_func +struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_pointer_to_non_const_member_func @@ -2133,16 +1876,16 @@ template struct is_pointer_to_non_const_member_func : std::true_type {}; template -struct is_pointer_to_non_const_member_func +struct is_pointer_to_non_const_member_func : std::true_type {}; template -struct is_pointer_to_non_const_member_func +struct is_pointer_to_non_const_member_func : std::true_type {}; template struct is_const_or_const_ref : std::false_type {}; template -struct is_const_or_const_ref : std::true_type {}; +struct is_const_or_const_ref : std::true_type {}; template struct is_const_or_const_ref : std::true_type {}; #endif @@ -2156,7 +1899,7 @@ template < is_const_or_const_ref::value)>, #endif typename = enable_if_t>::value>, int = 0> -constexpr auto invoke(Fn&& f, Args&&... args) noexcept( +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( noexcept(std::mem_fn(f)(std::forward(args)...))) -> decltype(std::mem_fn(f)(std::forward(args)...)) { return std::mem_fn(f)(std::forward(args)...); @@ -2164,7 +1907,7 @@ constexpr auto invoke(Fn&& f, Args&&... args) noexcept( template >::value>> -constexpr auto invoke(Fn&& f, Args&&... args) noexcept( +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( noexcept(std::forward(f)(std::forward(args)...))) -> decltype(std::forward(f)(std::forward(args)...)) { return std::forward(f)(std::forward(args)...); @@ -2204,7 +1947,7 @@ namespace swap_adl_tests { struct tag {}; template -tag swap(T&, T&); +tag swap(T &, T &); template tag swap(T (&a)[N], T (&b)[N]); @@ -2213,14 +1956,14 @@ tag swap(T (&a)[N], T (&b)[N]); template std::false_type can_swap(...) noexcept(false); template (), std::declval()))> -std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), - std::declval()))); + class = decltype(swap(std::declval(), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); template std::false_type uses_std(...); template -std::is_same(), std::declval())), tag> +std::is_same(), std::declval())), tag> uses_std(int); template @@ -2251,8 +1994,8 @@ struct is_swappable : std::integral_constant< bool, decltype(detail::swap_adl_tests::can_swap(0))::value && - (!decltype( - detail::swap_adl_tests::uses_std(0))::value || + (!decltype(detail::swap_adl_tests::uses_std( + 0))::value || is_swappable::value)> {}; template @@ -2260,12 +2003,10 @@ struct is_nothrow_swappable : std::integral_constant< bool, is_swappable::value && - ((decltype(detail::swap_adl_tests::uses_std(0))::value&& - detail::swap_adl_tests::is_std_swap_noexcept::value) || - (!decltype(detail::swap_adl_tests::uses_std(0))::value&& - detail::swap_adl_tests::is_adl_swap_noexcept::value))> { -}; + ((decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> {}; #endif #endif @@ -2279,7 +2020,7 @@ using is_expected = is_expected_impl>; template using expected_enable_forward_value = detail::enable_if_t< - std::is_constructible::value && + std::is_constructible::value && !std::is_same, in_place_t>::value && !std::is_same, detail::decay_t>::value && !std::is_same, detail::decay_t>::value>; @@ -2288,14 +2029,14 @@ template using expected_enable_from_other = detail::enable_if_t< std::is_constructible::value && std::is_constructible::value && - !std::is_constructible&>::value && - !std::is_constructible&&>::value && - !std::is_constructible&>::value && - !std::is_constructible&&>::value && - !std::is_convertible&, T>::value && - !std::is_convertible&&, T>::value && - !std::is_convertible&, T>::value && - !std::is_convertible&&, T>::value>; + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; template using is_void_or = conditional_t::value, std::true_type, U>; @@ -2333,29 +2074,29 @@ struct expected_storage_base { constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) + constexpr expected_storage_base(in_place_t, Args &&...args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args&&...>::value>* = nullptr> + T, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { @@ -2381,29 +2122,29 @@ struct expected_storage_base { constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) + constexpr expected_storage_base(in_place_t, Args &&...args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args&&...>::value>* = nullptr> + T, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() = default; @@ -2423,29 +2164,29 @@ struct expected_storage_base { : m_no_init(), m_has_val(false) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) + constexpr expected_storage_base(in_place_t, Args &&...args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args&&...>::value>* = nullptr> + T, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { @@ -2469,29 +2210,29 @@ struct expected_storage_base { constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr expected_storage_base(in_place_t, Args&&... args) + constexpr expected_storage_base(in_place_t, Args &&...args) : m_val(std::forward(args)...), m_has_val(true) {} template &, Args&&...>::value>* = nullptr> + T, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr expected_storage_base(in_place_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_val(il, std::forward(args)...), m_has_val(true) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { @@ -2522,17 +2263,17 @@ struct expected_storage_base { constexpr expected_storage_base(in_place_t) : m_has_val(true) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() = default; @@ -2553,17 +2294,17 @@ struct expected_storage_base { constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) : m_unexpect(std::forward(args)...), m_has_val(false) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : m_unexpect(il, std::forward(args)...), m_has_val(false) {} ~expected_storage_base() { @@ -2586,19 +2327,19 @@ struct expected_operations_base : expected_storage_base { using expected_storage_base::expected_storage_base; template - void construct(Args&&... args) noexcept { + void construct(Args &&...args) noexcept { new (std::addressof(this->m_val)) T(std::forward(args)...); this->m_has_val = true; } template - void construct_with(Rhs&& rhs) noexcept { + void construct_with(Rhs &&rhs) noexcept { new (std::addressof(this->m_val)) T(std::forward(rhs).get()); this->m_has_val = true; } template - void construct_error(Args&&... args) noexcept { + void construct_error(Args &&...args) noexcept { new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); this->m_has_val = false; @@ -2613,9 +2354,9 @@ struct expected_operations_base : expected_storage_base { // This overload handles the case where we can just copy-construct `T` // directly into place without throwing. template ::value>* = - nullptr> - void assign(const expected_operations_base& rhs) noexcept { + detail::enable_if_t::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(rhs.get()); @@ -2628,9 +2369,9 @@ struct expected_operations_base : expected_storage_base { // `T`, then no-throw move it into place if the copy was successful. template ::value && - std::is_nothrow_move_constructible::value>* = - nullptr> - void assign(const expected_operations_base& rhs) noexcept { + std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { T tmp = rhs.get(); geterr().~unexpected(); @@ -2646,10 +2387,10 @@ struct expected_operations_base : expected_storage_base { // then we move the old unexpected value back into place before rethrowing the // exception. template ::value && - !std::is_nothrow_move_constructible::value>* = nullptr> - void assign(const expected_operations_base& rhs) { + detail::enable_if_t::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) { if (!this->m_has_val && rhs.m_has_val) { auto tmp = std::move(geterr()); geterr().~unexpected(); @@ -2671,9 +2412,9 @@ struct expected_operations_base : expected_storage_base { // These overloads do the same as above, but for rvalues template ::value>* = - nullptr> - void assign(expected_operations_base&& rhs) noexcept { + detail::enable_if_t::value> + * = nullptr> + void assign(expected_operations_base &&rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(std::move(rhs).get()); @@ -2683,9 +2424,9 @@ struct expected_operations_base : expected_storage_base { } template ::value>* = nullptr> - void assign(expected_operations_base&& rhs) { + detail::enable_if_t::value> + * = nullptr> + void assign(expected_operations_base &&rhs) { if (!this->m_has_val && rhs.m_has_val) { auto tmp = std::move(geterr()); geterr().~unexpected(); @@ -2707,7 +2448,7 @@ struct expected_operations_base : expected_storage_base { #else // If exceptions are disabled then we can just copy-construct - void assign(const expected_operations_base& rhs) noexcept { + void assign(const expected_operations_base &rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(rhs.get()); @@ -2716,7 +2457,7 @@ struct expected_operations_base : expected_storage_base { } } - void assign(expected_operations_base&& rhs) noexcept { + void assign(expected_operations_base &&rhs) noexcept { if (!this->m_has_val && rhs.m_has_val) { geterr().~unexpected(); construct(std::move(rhs).get()); @@ -2729,7 +2470,7 @@ struct expected_operations_base : expected_storage_base { // The common part of move/copy assigning template - void assign_common(Rhs&& rhs) { + void assign_common(Rhs &&rhs) { if (this->m_has_val) { if (rhs.m_has_val) { get() = std::forward(rhs).get(); @@ -2746,22 +2487,22 @@ struct expected_operations_base : expected_storage_base { bool has_value() const { return this->m_has_val; } - TL_EXPECTED_11_CONSTEXPR T& get() & { return this->m_val; } - constexpr const T& get() const& { return this->m_val; } - TL_EXPECTED_11_CONSTEXPR T&& get() && { return std::move(this->m_val); } + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } #ifndef TL_EXPECTED_NO_CONSTRR - constexpr const T&& get() const&& { return std::move(this->m_val); } + constexpr const T &&get() const && { return std::move(this->m_val); } #endif - TL_EXPECTED_11_CONSTEXPR unexpected& geterr() & { + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { return this->m_unexpect; } - constexpr const unexpected& geterr() const& { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected&& geterr() && { + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { return std::move(this->m_unexpect); } #ifndef TL_EXPECTED_NO_CONSTRR - constexpr const unexpected&& geterr() const&& { + constexpr const unexpected &&geterr() const && { return std::move(this->m_unexpect); } #endif @@ -2783,19 +2524,19 @@ struct expected_operations_base : expected_storage_base { // This function doesn't use its argument, but needs it so that code in // levels above this can work independently of whether T is void template - void construct_with(Rhs&&) noexcept { + void construct_with(Rhs &&) noexcept { this->m_has_val = true; } template - void construct_error(Args&&... args) noexcept { + void construct_error(Args &&...args) noexcept { new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); this->m_has_val = false; } template - void assign(Rhs&& rhs) noexcept { + void assign(Rhs &&rhs) noexcept { if (!this->m_has_val) { if (rhs.m_has_val) { geterr().~unexpected(); @@ -2812,15 +2553,15 @@ struct expected_operations_base : expected_storage_base { bool has_value() const { return this->m_has_val; } - TL_EXPECTED_11_CONSTEXPR unexpected& geterr() & { + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { return this->m_unexpect; } - constexpr const unexpected& geterr() const& { return this->m_unexpect; } - TL_EXPECTED_11_CONSTEXPR unexpected&& geterr() && { + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { return std::move(this->m_unexpect); } #ifndef TL_EXPECTED_NO_CONSTRR - constexpr const unexpected&& geterr() const&& { + constexpr const unexpected &&geterr() const && { return std::move(this->m_unexpect); } #endif @@ -2833,8 +2574,9 @@ struct expected_operations_base : expected_storage_base { // This class manages conditionally having a trivial copy constructor // This specialization is for when T and E are trivially copy constructible template :: - value&& TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> + bool = is_void_or::value && + TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> struct expected_copy_base : expected_operations_base { using expected_operations_base::expected_operations_base; }; @@ -2845,7 +2587,7 @@ struct expected_copy_base : expected_operations_base { using expected_operations_base::expected_operations_base; expected_copy_base() = default; - expected_copy_base(const expected_copy_base& rhs) + expected_copy_base(const expected_copy_base &rhs) : expected_operations_base(no_init) { if (rhs.has_value()) { this->construct_with(rhs); @@ -2854,9 +2596,9 @@ struct expected_copy_base : expected_operations_base { } } - expected_copy_base(expected_copy_base&& rhs) = default; - expected_copy_base& operator=(const expected_copy_base& rhs) = default; - expected_copy_base& operator=(expected_copy_base&& rhs) = default; + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; }; // This class manages conditionally having a trivial move constructor @@ -2866,7 +2608,8 @@ struct expected_copy_base : expected_operations_base { // move constructible #ifndef TL_EXPECTED_GCC49 template >::value&& + bool = + is_void_or>::value && std::is_trivially_move_constructible::value> struct expected_move_base : expected_copy_base { using expected_copy_base::expected_copy_base; @@ -2880,9 +2623,9 @@ struct expected_move_base : expected_copy_base { using expected_copy_base::expected_copy_base; expected_move_base() = default; - expected_move_base(const expected_move_base& rhs) = default; + expected_move_base(const expected_move_base &rhs) = default; - expected_move_base(expected_move_base&& rhs) noexcept( + expected_move_base(expected_move_base &&rhs) noexcept( std::is_nothrow_move_constructible::value) : expected_copy_base(no_init) { if (rhs.has_value()) { @@ -2891,19 +2634,21 @@ struct expected_move_base : expected_copy_base { this->construct_error(std::move(rhs.geterr())); } } - expected_move_base& operator=(const expected_move_base& rhs) = default; - expected_move_base& operator=(expected_move_base&& rhs) = default; + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; }; // This class manages conditionally having a trivial copy assignment operator -template >::value&& - TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value&& - TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value&& - TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +template < + class T, class E, + bool = + is_void_or< + T, conjunction>::value && + TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value && + TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value && + TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> struct expected_copy_assign_base : expected_move_base { using expected_move_base::expected_move_base; }; @@ -2913,14 +2658,14 @@ struct expected_copy_assign_base : expected_move_base { using expected_move_base::expected_move_base; expected_copy_assign_base() = default; - expected_copy_assign_base(const expected_copy_assign_base& rhs) = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; - expected_copy_assign_base(expected_copy_assign_base&& rhs) = default; - expected_copy_assign_base& operator=(const expected_copy_assign_base& rhs) { + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { this->assign(rhs); return *this; } - expected_copy_assign_base& operator=(expected_copy_assign_base&& rhs) = + expected_copy_assign_base &operator=(expected_copy_assign_base &&rhs) = default; }; @@ -2930,14 +2675,15 @@ struct expected_copy_assign_base : expected_move_base { // to make do with a non-trivial move assignment operator even if T is trivially // move assignable #ifndef TL_EXPECTED_GCC49 -template , - std::is_trivially_move_constructible, - std::is_trivially_move_assignable>>:: - value&& std::is_trivially_destructible::value&& - std::is_trivially_move_constructible::value&& - std::is_trivially_move_assignable::value> +template < + class T, class E, + bool = is_void_or< + T, conjunction, + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>::value && + std::is_trivially_destructible::value && + std::is_trivially_move_constructible::value && + std::is_trivially_move_assignable::value> struct expected_move_assign_base : expected_copy_assign_base { using expected_copy_assign_base::expected_copy_assign_base; }; @@ -2952,17 +2698,17 @@ struct expected_move_assign_base using expected_copy_assign_base::expected_copy_assign_base; expected_move_assign_base() = default; - expected_move_assign_base(const expected_move_assign_base& rhs) = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; - expected_move_assign_base(expected_move_assign_base&& rhs) = default; + expected_move_assign_base(expected_move_assign_base &&rhs) = default; - expected_move_assign_base& operator=(const expected_move_assign_base& rhs) = + expected_move_assign_base &operator=(const expected_move_assign_base &rhs) = default; - expected_move_assign_base& - operator=(expected_move_assign_base&& rhs) noexcept( - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value) { + expected_move_assign_base &operator=( + expected_move_assign_base + &&rhs) noexcept(std::is_nothrow_move_constructible::value && + std::is_nothrow_move_assignable::value) { this->assign(std::move(rhs)); return *this; } @@ -2977,44 +2723,44 @@ template ::value)> struct expected_delete_ctor_base { expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = + expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept = default; }; template struct expected_delete_ctor_base { expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = + expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept = default; }; template struct expected_delete_ctor_base { expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = + expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept = default; }; template struct expected_delete_ctor_base { expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base &operator=(const expected_delete_ctor_base &) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = + expected_delete_ctor_base &operator=(expected_delete_ctor_base &&) noexcept = default; }; @@ -3032,45 +2778,49 @@ template ::value)> struct expected_delete_assign_base { expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base &operator=(const expected_delete_assign_base &) = default; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = default; + expected_delete_assign_base &operator=( + expected_delete_assign_base &&) noexcept = default; }; template struct expected_delete_assign_base { expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = default; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = delete; + expected_delete_assign_base &operator=(const expected_delete_assign_base &) = + default; + expected_delete_assign_base &operator=( + expected_delete_assign_base &&) noexcept = delete; }; template struct expected_delete_assign_base { expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base &operator=(const expected_delete_assign_base &) = delete; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = default; + expected_delete_assign_base &operator=( + expected_delete_assign_base &&) noexcept = default; }; template struct expected_delete_assign_base { expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base &operator=(const expected_delete_assign_base &) = delete; - expected_delete_assign_base& operator=( - expected_delete_assign_base&&) noexcept = delete; + expected_delete_assign_base &operator=( + expected_delete_assign_base &&) noexcept = delete; }; // This is needed to be able to construct the expected_default_ctor_base which @@ -3088,13 +2838,13 @@ template struct expected_default_ctor_base { constexpr expected_default_ctor_base() noexcept = delete; constexpr expected_default_ctor_base( - expected_default_ctor_base const&) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = - default; - expected_default_ctor_base& operator=( - expected_default_ctor_base const&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = default; + expected_default_ctor_base &operator=( + expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base &operator=( + expected_default_ctor_base &&) noexcept = default; constexpr explicit expected_default_ctor_base(default_constructor_tag) {} }; @@ -3121,14 +2871,14 @@ class bad_expected_access : public std::exception { public: explicit bad_expected_access(E e) : m_val(std::move(e)) {} - virtual const char* what() const noexcept override { + virtual const char *what() const noexcept override { return "Bad expected access"; } - const E& error() const& { return m_val; } - E& error() & { return m_val; } - const E&& error() const&& { return std::move(m_val); } - E&& error() && { return std::move(m_val); } + const E &error() const & { return m_val; } + E &error() & { return m_val; } + const E &&error() const && { return std::move(m_val); } + E &&error() && { return std::move(m_val); } private: E m_val; @@ -3156,26 +2906,26 @@ class expected : private detail::expected_move_assign_base, "T must not be unexpected"); static_assert(!std::is_reference::value, "E must not be a reference"); - T* valptr() { return std::addressof(this->m_val); } - const T* valptr() const { return std::addressof(this->m_val); } - unexpected* errptr() { return std::addressof(this->m_unexpect); } - const unexpected* errptr() const { + T *valptr() { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { return std::addressof(this->m_unexpect); } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U& val() { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &val() { return this->m_val; } - TL_EXPECTED_11_CONSTEXPR unexpected& err() { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } template ::value>* = nullptr> - constexpr const U& val() const { + detail::enable_if_t::value> * = nullptr> + constexpr const U &val() const { return this->m_val; } - constexpr const unexpected& err() const { return this->m_unexpect; } + constexpr const unexpected &err() const { return this->m_unexpect; } using impl_base = detail::expected_move_assign_base; using ctor_base = detail::expected_default_ctor_base; @@ -3188,46 +2938,46 @@ class expected : private detail::expected_move_assign_base, #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & { + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { return and_then_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && { + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { return and_then_impl(std::move(*this), std::forward(f)); } template - constexpr auto and_then(F&& f) const& { + constexpr auto and_then(F &&f) const & { return and_then_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr auto and_then(F&& f) const&& { + constexpr auto and_then(F &&f) const && { return and_then_impl(std::move(*this), std::forward(f)); } #endif #else template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) & -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & -> decltype(and_then_impl( + std::declval(), std::forward(f))) { return and_then_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto and_then(F&& f) && -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype(and_then_impl( + std::declval(), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } template - constexpr auto and_then(F&& f) const& -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + constexpr auto and_then(F &&f) const & -> decltype(and_then_impl( + std::declval(), std::forward(f))) { return and_then_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr auto and_then(F&& f) const&& -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( + std::declval(), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } #endif @@ -3236,46 +2986,46 @@ class expected : private detail::expected_move_assign_base, #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template - TL_EXPECTED_11_CONSTEXPR auto map(F&& f) & { + TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto map(F&& f) && { + TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template - constexpr auto map(F&& f) const& { + constexpr auto map(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } template - constexpr auto map(F&& f) const&& { + constexpr auto map(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #else template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - map(F&& f) & { + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + map(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - map(F&& f) && { + std::declval())) + map(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F&& f) const& { + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F&& f) const&& { + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #endif @@ -3284,46 +3034,46 @@ class expected : private detail::expected_move_assign_base, #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template - TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) & { + TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto transform(F&& f) && { + TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template - constexpr auto transform(F&& f) const& { + constexpr auto transform(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } template - constexpr auto transform(F&& f) const&& { + constexpr auto transform(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #else template - TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F&& f) & { + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + transform(F &&f) & { return expected_map_impl(*this, std::forward(f)); } template TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F&& f) && { + std::declval())) + transform(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F&& f) const& { + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const & { return expected_map_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F&& f) const&& { + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const && { return expected_map_impl(std::move(*this), std::forward(f)); } #endif @@ -3332,46 +3082,46 @@ class expected : private detail::expected_move_assign_base, #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template - TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) & { + TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { return map_error_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto map_error(F&& f) && { + TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { return map_error_impl(std::move(*this), std::forward(f)); } template - constexpr auto map_error(F&& f) const& { + constexpr auto map_error(F &&f) const & { return map_error_impl(*this, std::forward(f)); } template - constexpr auto map_error(F&& f) const&& { + constexpr auto map_error(F &&f) const && { return map_error_impl(std::move(*this), std::forward(f)); } #else template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F&& f) & { + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) & { return map_error_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F&& f) && { + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) && { return map_error_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F&& f) const& { + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const & { return map_error_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F&& f) const&& { + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const && { return map_error_impl(std::move(*this), std::forward(f)); } #endif @@ -3379,147 +3129,149 @@ class expected : private detail::expected_move_assign_base, #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template - TL_EXPECTED_11_CONSTEXPR auto transform_error(F&& f) & { + TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { return map_error_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto transform_error(F&& f) && { + TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { return map_error_impl(std::move(*this), std::forward(f)); } template - constexpr auto transform_error(F&& f) const& { + constexpr auto transform_error(F &&f) const & { return map_error_impl(*this, std::forward(f)); } template - constexpr auto transform_error(F&& f) const&& { + constexpr auto transform_error(F &&f) const && { return map_error_impl(std::move(*this), std::forward(f)); } #else template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F&& f) & { + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) & { return map_error_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F&& f) && { + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) && { return map_error_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F&& f) const& { + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const & { return map_error_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - transform_error(F&& f) const&& { + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const && { return map_error_impl(std::move(*this), std::forward(f)); } #endif #endif template - expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) & { + expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { return or_else_impl(*this, std::forward(f)); } template - expected TL_EXPECTED_11_CONSTEXPR or_else(F&& f) && { + expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { return or_else_impl(std::move(*this), std::forward(f)); } template - expected constexpr or_else(F&& f) const& { + expected constexpr or_else(F &&f) const & { return or_else_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR template - expected constexpr or_else(F&& f) const&& { + expected constexpr or_else(F &&f) const && { return or_else_impl(std::move(*this), std::forward(f)); } #endif constexpr expected() = default; - constexpr expected(const expected& rhs) = default; - constexpr expected(expected&& rhs) = default; - expected& operator=(const expected& rhs) = default; - expected& operator=(expected&& rhs) = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr expected(in_place_t, Args&&... args) + constexpr expected(in_place_t, Args &&...args) : impl_base(in_place, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template &, Args&&...>::value>* = nullptr> - constexpr expected(in_place_t, std::initializer_list il, Args&&... args) + T, std::initializer_list &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&...args) : impl_base(in_place, il, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} - template < - class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - explicit constexpr expected(const unexpected& e) + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) : impl_base(unexpect, e.value()), ctor_base(detail::default_constructor_tag{}) {} template < class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - constexpr expected(unexpected const& e) + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) : impl_base(unexpect, e.value()), ctor_base(detail::default_constructor_tag{}) {} template < class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - explicit constexpr expected(unexpected&& e) noexcept( - std::is_nothrow_constructible::value) + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) : impl_base(unexpect, std::move(e.value())), ctor_base(detail::default_constructor_tag{}) {} template < class G = E, - detail::enable_if_t::value>* = nullptr, - detail::enable_if_t::value>* = nullptr> - constexpr expected(unexpected&& e) noexcept( - std::is_nothrow_constructible::value) + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) : impl_base(unexpect, std::move(e.value())), ctor_base(detail::default_constructor_tag{}) {} template ::value>* = + detail::enable_if_t::value> * = nullptr> - constexpr explicit expected(unexpect_t, Args&&... args) + constexpr explicit expected(unexpect_t, Args &&...args) : impl_base(unexpect, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template &, Args&&...>::value>* = nullptr> + E, std::initializer_list &, Args &&...>::value> * = nullptr> constexpr explicit expected(unexpect_t, std::initializer_list il, - Args&&... args) + Args &&...args) : impl_base(unexpect, il, std::forward(args)...), ctor_base(detail::default_constructor_tag{}) {} template ::value && - std::is_convertible::value)>* = + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, - detail::expected_enable_from_other* = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(const expected& rhs) + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(*rhs); @@ -3528,13 +3280,13 @@ class expected : private detail::expected_move_assign_base, } } - template < - class U, class G, - detail::enable_if_t<(std::is_convertible::value && - std::is_convertible::value)>* = nullptr, - detail::expected_enable_from_other* = - nullptr> - TL_EXPECTED_11_CONSTEXPR expected(const expected& rhs) + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(*rhs); @@ -3545,10 +3297,10 @@ class expected : private detail::expected_move_assign_base, template < class U, class G, - detail::enable_if_t::value && - std::is_convertible::value)>* = nullptr, - detail::expected_enable_from_other* = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(expected&& rhs) + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(std::move(*rhs)); @@ -3559,10 +3311,10 @@ class expected : private detail::expected_move_assign_base, template < class U, class G, - detail::enable_if_t<(std::is_convertible::value && - std::is_convertible::value)>* = nullptr, - detail::expected_enable_from_other* = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(expected&& rhs) + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { this->construct(std::move(*rhs)); @@ -3571,31 +3323,33 @@ class expected : private detail::expected_move_assign_base, } } - template ::value>* = nullptr, - detail::expected_enable_forward_value* = nullptr> - explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v) + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) : expected(in_place, std::forward(v)) {} - template ::value>* = nullptr, - detail::expected_enable_forward_value* = nullptr> - TL_EXPECTED_MSVC2015_CONSTEXPR expected(U&& v) + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) : expected(in_place, std::forward(v)) {} template < class U = T, class G = T, - detail::enable_if_t::value>* = + detail::enable_if_t::value> * = nullptr, - detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value> * = nullptr, detail::enable_if_t< (!std::is_same, detail::decay_t>::value && !detail::conjunction, std::is_same>>::value && std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)>* = nullptr> - expected& operator=(U&& v) { + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); } else { @@ -3609,17 +3363,17 @@ class expected : private detail::expected_move_assign_base, template < class U = T, class G = T, - detail::enable_if_t::value>* = + detail::enable_if_t::value> * = nullptr, - detail::enable_if_t::value>* = nullptr, + detail::enable_if_t::value> * = nullptr, detail::enable_if_t< (!std::is_same, detail::decay_t>::value && !detail::conjunction, std::is_same>>::value && std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)>* = nullptr> - expected& operator=(U&& v) { + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); } else { @@ -3645,8 +3399,8 @@ class expected : private detail::expected_move_assign_base, template ::value && - std::is_assignable::value>* = nullptr> - expected& operator=(const unexpected& rhs) { + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { if (!has_value()) { err() = rhs; } else { @@ -3660,8 +3414,8 @@ class expected : private detail::expected_move_assign_base, template ::value && - std::is_move_assignable::value>* = nullptr> - expected& operator=(unexpected&& rhs) noexcept { + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { if (!has_value()) { err() = std::move(rhs); } else { @@ -3674,8 +3428,8 @@ class expected : private detail::expected_move_assign_base, } template ::value>* = nullptr> - void emplace(Args&&... args) { + T, Args &&...>::value> * = nullptr> + void emplace(Args &&...args) { if (has_value()) { val().~T(); } else { @@ -3686,8 +3440,8 @@ class expected : private detail::expected_move_assign_base, } template ::value>* = nullptr> - void emplace(Args&&... args) { + T, Args &&...>::value> * = nullptr> + void emplace(Args &&...args) { if (has_value()) { val().~T(); ::new (valptr()) T(std::forward(args)...); @@ -3712,8 +3466,8 @@ class expected : private detail::expected_move_assign_base, template &, Args&&...>::value>* = nullptr> - void emplace(std::initializer_list il, Args&&... args) { + T, std::initializer_list &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { if (has_value()) { T t(il, std::forward(args)...); val() = std::move(t); @@ -3726,8 +3480,8 @@ class expected : private detail::expected_move_assign_base, template &, Args&&...>::value>* = nullptr> - void emplace(std::initializer_list il, Args&&... args) { + T, std::initializer_list &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { if (has_value()) { T t(il, std::forward(args)...); val() = std::move(t); @@ -3758,30 +3512,30 @@ class expected : private detail::expected_move_assign_base, using e_is_nothrow_move_constructible = std::true_type; using move_constructing_e_can_throw = std::false_type; - void swap_where_both_have_value(expected& /*rhs*/, t_is_void) noexcept { + void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept { // swapping void is a no-op } - void swap_where_both_have_value(expected& rhs, t_is_not_void) { + void swap_where_both_have_value(expected &rhs, t_is_not_void) { using std::swap; swap(val(), rhs.val()); } - void swap_where_only_one_has_value(expected& rhs, t_is_void) noexcept( + void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( std::is_nothrow_move_constructible::value) { ::new (errptr()) unexpected_type(std::move(rhs.err())); rhs.err().~unexpected_type(); std::swap(this->m_has_val, rhs.m_has_val); } - void swap_where_only_one_has_value(expected& rhs, t_is_not_void) { + void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { swap_where_only_one_has_value_and_t_is_not_void( rhs, typename std::is_nothrow_move_constructible::type{}, typename std::is_nothrow_move_constructible::type{}); } void swap_where_only_one_has_value_and_t_is_not_void( - expected& rhs, t_is_nothrow_move_constructible, + expected &rhs, t_is_nothrow_move_constructible, e_is_nothrow_move_constructible) noexcept { auto temp = std::move(val()); val().~T(); @@ -3792,7 +3546,7 @@ class expected : private detail::expected_move_assign_base, } void swap_where_only_one_has_value_and_t_is_not_void( - expected& rhs, t_is_nothrow_move_constructible, + expected &rhs, t_is_nothrow_move_constructible, move_constructing_e_can_throw) { auto temp = std::move(val()); val().~T(); @@ -3815,7 +3569,7 @@ class expected : private detail::expected_move_assign_base, } void swap_where_only_one_has_value_and_t_is_not_void( - expected& rhs, move_constructing_t_can_throw, + expected &rhs, move_constructing_t_can_throw, e_is_nothrow_move_constructible) { auto temp = std::move(rhs.err()); rhs.err().~unexpected_type(); @@ -3843,11 +3597,10 @@ class expected : private detail::expected_move_assign_base, detail::is_swappable::value && (std::is_nothrow_move_constructible::value || std::is_nothrow_move_constructible::value)> - swap(expected& rhs) noexcept( - std::is_nothrow_move_constructible::value&& - detail::is_nothrow_swappable::value&& - std::is_nothrow_move_constructible::value&& - detail::is_nothrow_swappable::value) { + swap(expected &rhs) noexcept(std::is_nothrow_move_constructible::value && + detail::is_nothrow_swappable::value && + std::is_nothrow_move_constructible::value && + detail::is_nothrow_swappable::value) { if (has_value() && rhs.has_value()) { swap_where_both_have_value(rhs, typename std::is_void::type{}); } else if (!has_value() && rhs.has_value()) { @@ -3860,36 +3613,36 @@ class expected : private detail::expected_move_assign_base, } } - constexpr const T* operator->() const { + constexpr const T *operator->() const { TL_ASSERT(has_value()); return valptr(); } - TL_EXPECTED_11_CONSTEXPR T* operator->() { + TL_EXPECTED_11_CONSTEXPR T *operator->() { TL_ASSERT(has_value()); return valptr(); } template ::value>* = nullptr> - constexpr const U& operator*() const& { + detail::enable_if_t::value> * = nullptr> + constexpr const U &operator*() const & { TL_ASSERT(has_value()); return val(); } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U& operator*() & { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { TL_ASSERT(has_value()); return val(); } template ::value>* = nullptr> - constexpr const U&& operator*() const&& { + detail::enable_if_t::value> * = nullptr> + constexpr const U &&operator*() const && { TL_ASSERT(has_value()); return std::move(val()); } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U&& operator*() && { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { TL_ASSERT(has_value()); return std::move(val()); } @@ -3898,62 +3651,62 @@ class expected : private detail::expected_move_assign_base, constexpr explicit operator bool() const noexcept { return this->m_has_val; } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR const U& value() const& { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { if (!has_value()) detail::throw_exception(bad_expected_access(err().value())); return val(); } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U& value() & { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { if (!has_value()) detail::throw_exception(bad_expected_access(err().value())); return val(); } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR const U&& value() const&& { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { if (!has_value()) detail::throw_exception(bad_expected_access(std::move(err()).value())); return std::move(val()); } template ::value>* = nullptr> - TL_EXPECTED_11_CONSTEXPR U&& value() && { + detail::enable_if_t::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { if (!has_value()) detail::throw_exception(bad_expected_access(std::move(err()).value())); return std::move(val()); } - constexpr const E& error() const& { + constexpr const E &error() const & { TL_ASSERT(!has_value()); return err().value(); } - TL_EXPECTED_11_CONSTEXPR E& error() & { + TL_EXPECTED_11_CONSTEXPR E &error() & { TL_ASSERT(!has_value()); return err().value(); } - constexpr const E&& error() const&& { + constexpr const E &&error() const && { TL_ASSERT(!has_value()); return std::move(err().value()); } - TL_EXPECTED_11_CONSTEXPR E&& error() && { + TL_EXPECTED_11_CONSTEXPR E &&error() && { TL_ASSERT(!has_value()); return std::move(err().value()); } template - constexpr T value_or(U&& v) const& { + constexpr T value_or(U &&v) const & { static_assert(std::is_copy_constructible::value && - std::is_convertible::value, + std::is_convertible::value, "T must be copy-constructible and convertible to from U&&"); return bool(*this) ? **this : static_cast(std::forward(v)); } template - TL_EXPECTED_11_CONSTEXPR T value_or(U&& v) && { + TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { static_assert(std::is_move_constructible::value && - std::is_convertible::value, + std::is_convertible::value, "T must be move-constructible and convertible to from U&&"); return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); } @@ -3969,10 +3722,10 @@ using ret_t = expected>; #ifdef TL_EXPECTED_CXX14 template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval()))> -constexpr auto and_then_impl(Exp&& exp, F&& f) { +constexpr auto and_then_impl(Exp &&exp, F &&f) { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() @@ -3981,9 +3734,9 @@ constexpr auto and_then_impl(Exp&& exp, F&& f) { } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval()))> -constexpr auto and_then_impl(Exp&& exp, F&& f) { +constexpr auto and_then_impl(Exp &&exp, F &&f) { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? detail::invoke(std::forward(f)) @@ -3995,8 +3748,8 @@ struct TC; template (), *std::declval())), - detail::enable_if_t>::value>* = nullptr> -auto and_then_impl(Exp&& exp, F&& f) -> Ret { + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() @@ -4006,8 +3759,8 @@ auto and_then_impl(Exp&& exp, F&& f) -> Ret { template ())), - detail::enable_if_t>::value>* = nullptr> -constexpr auto and_then_impl(Exp&& exp, F&& f) -> Ret { + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? detail::invoke(std::forward(f)) @@ -4017,11 +3770,11 @@ constexpr auto and_then_impl(Exp&& exp, F&& f) -> Ret { #ifdef TL_EXPECTED_CXX14 template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), - detail::enable_if_t::value>* = nullptr> -constexpr auto expected_map_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) @@ -4029,11 +3782,11 @@ constexpr auto expected_map_impl(Exp&& exp, F&& f) { } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), - detail::enable_if_t::value>* = nullptr> -auto expected_map_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); @@ -4044,20 +3797,20 @@ auto expected_map_impl(Exp&& exp, F&& f) { } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> -constexpr auto expected_map_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { using result = ret_t>; return exp.has_value() ? result(detail::invoke(std::forward(f))) : result(unexpect, std::forward(exp).error()); } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> -auto expected_map_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f)); @@ -4068,12 +3821,12 @@ auto expected_map_impl(Exp&& exp, F&& f) { } #else template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), - detail::enable_if_t::value>* = nullptr> + detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp&& exp, F&& f) +constexpr auto expected_map_impl(Exp &&exp, F &&f) -> ret_t> { using result = ret_t>; @@ -4083,12 +3836,12 @@ constexpr auto expected_map_impl(Exp&& exp, F&& f) } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), - detail::enable_if_t::value>* = nullptr> + detail::enable_if_t::value> * = nullptr> -auto expected_map_impl(Exp&& exp, F&& f) -> expected> { +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); return {}; @@ -4098,11 +3851,11 @@ auto expected_map_impl(Exp&& exp, F&& f) -> expected> { } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> + detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp&& exp, F&& f) +constexpr auto expected_map_impl(Exp &&exp, F &&f) -> ret_t> { using result = ret_t>; @@ -4111,11 +3864,11 @@ constexpr auto expected_map_impl(Exp&& exp, F&& f) } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value>* = nullptr> + detail::enable_if_t::value> * = nullptr> -auto expected_map_impl(Exp&& exp, F&& f) -> expected> { +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { if (exp.has_value()) { detail::invoke(std::forward(f)); return {}; @@ -4128,11 +3881,11 @@ auto expected_map_impl(Exp&& exp, F&& f) -> expected> { #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { using result = expected, detail::decay_t>; return exp.has_value() ? result(*std::forward(exp)) @@ -4140,11 +3893,11 @@ constexpr auto map_error_impl(Exp&& exp, F&& f) { std::forward(exp).error())); } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { using result = expected, monostate>; if (exp.has_value()) { return result(*std::forward(exp)); @@ -4154,11 +3907,11 @@ auto map_error_impl(Exp&& exp, F&& f) { return result(unexpect, monostate{}); } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { using result = expected, detail::decay_t>; return exp.has_value() ? result() @@ -4166,11 +3919,11 @@ constexpr auto map_error_impl(Exp&& exp, F&& f) { std::forward(exp).error())); } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { using result = expected, monostate>; if (exp.has_value()) { return result(); @@ -4181,11 +3934,11 @@ auto map_error_impl(Exp&& exp, F&& f) { } #else template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected, detail::decay_t> { using result = expected, detail::decay_t>; @@ -4196,11 +3949,11 @@ constexpr auto map_error_impl(Exp&& exp, F&& f) } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> { + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { using result = expected, monostate>; if (exp.has_value()) { return result(*std::forward(exp)); @@ -4211,11 +3964,11 @@ auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> { } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto map_error_impl(Exp&& exp, F&& f) + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected, detail::decay_t> { using result = expected, detail::decay_t>; @@ -4226,11 +3979,11 @@ constexpr auto map_error_impl(Exp&& exp, F&& f) } template >::value>* = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> { + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { using result = expected, monostate>; if (exp.has_value()) { return result(); @@ -4245,8 +3998,8 @@ auto map_error_impl(Exp&& exp, F&& f) -> expected, monostate> { template (), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -constexpr auto or_else_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +constexpr auto or_else_impl(Exp &&exp, F &&f) { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? std::forward(exp) : detail::invoke(std::forward(f), @@ -4256,8 +4009,8 @@ constexpr auto or_else_impl(Exp&& exp, F&& f) { template (), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -detail::decay_t or_else_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { return exp.has_value() ? std::forward(exp) : (detail::invoke(std::forward(f), std::forward(exp).error()), @@ -4267,8 +4020,8 @@ detail::decay_t or_else_impl(Exp&& exp, F&& f) { template (), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -auto or_else_impl(Exp&& exp, F&& f) -> Ret { + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? std::forward(exp) : detail::invoke(std::forward(f), @@ -4278,8 +4031,8 @@ auto or_else_impl(Exp&& exp, F&& f) -> Ret { template (), std::declval().error())), - detail::enable_if_t::value>* = nullptr> -detail::decay_t or_else_impl(Exp&& exp, F&& f) { + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { return exp.has_value() ? std::forward(exp) : (detail::invoke(std::forward(f), std::forward(exp).error()), @@ -4289,65 +4042,65 @@ detail::decay_t or_else_impl(Exp&& exp, F&& f) { } // namespace detail template -constexpr bool operator==(const expected& lhs, - const expected& rhs) { +constexpr bool operator==(const expected &lhs, + const expected &rhs) { return (lhs.has_value() != rhs.has_value()) ? false : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); } template -constexpr bool operator!=(const expected& lhs, - const expected& rhs) { +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { return (lhs.has_value() != rhs.has_value()) ? true : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); } template -constexpr bool operator==(const expected& lhs, - const expected& rhs) { +constexpr bool operator==(const expected &lhs, + const expected &rhs) { return (lhs.has_value() != rhs.has_value()) ? false : (!lhs.has_value() ? lhs.error() == rhs.error() : true); } template -constexpr bool operator!=(const expected& lhs, - const expected& rhs) { +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { return (lhs.has_value() != rhs.has_value()) ? true : (!lhs.has_value() ? lhs.error() == rhs.error() : false); } template -constexpr bool operator==(const expected& x, const U& v) { +constexpr bool operator==(const expected &x, const U &v) { return x.has_value() ? *x == v : false; } template -constexpr bool operator==(const U& v, const expected& x) { +constexpr bool operator==(const U &v, const expected &x) { return x.has_value() ? *x == v : false; } template -constexpr bool operator!=(const expected& x, const U& v) { +constexpr bool operator!=(const expected &x, const U &v) { return x.has_value() ? *x != v : true; } template -constexpr bool operator!=(const U& v, const expected& x) { +constexpr bool operator!=(const U &v, const expected &x) { return x.has_value() ? *x != v : true; } template -constexpr bool operator==(const expected& x, const unexpected& e) { +constexpr bool operator==(const expected &x, const unexpected &e) { return x.has_value() ? false : x.error() == e.value(); } template -constexpr bool operator==(const unexpected& e, const expected& x) { +constexpr bool operator==(const unexpected &e, const expected &x) { return x.has_value() ? false : x.error() == e.value(); } template -constexpr bool operator!=(const expected& x, const unexpected& e) { +constexpr bool operator!=(const expected &x, const unexpected &e) { return x.has_value() ? true : x.error() != e.value(); } template -constexpr bool operator!=(const unexpected& e, const expected& x) { +constexpr bool operator!=(const unexpected &e, const expected &x) { return x.has_value() ? true : x.error() != e.value(); } @@ -4356,9 +4109,9 @@ template ::value) && detail::is_swappable::value && std::is_move_constructible::value && - detail::is_swappable::value>* = nullptr> -void swap(expected& lhs, - expected& rhs) noexcept(noexcept(lhs.swap(rhs))) { + detail::is_swappable::value> * = nullptr> +void swap(expected &lhs, + expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { lhs.swap(rhs); } } // namespace tl @@ -4366,16 +4119,236 @@ void swap(expected& lhs, #endif /* end file include/ada/expected.h */ +/* begin file include/ada/url_pattern_regex.h */ /** - * @private + * @file url_search_params.h + * @brief Declaration for the URL Search Params */ -namespace ada { -struct url_aggregator; -struct url; -} // namespace ada +#ifndef ADA_URL_PATTERN_REGEX_H +#define ADA_URL_PATTERN_REGEX_H -/** - * @namespace ada::parser +#include +#include + +#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER +#include +#endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER + +#if ADA_INCLUDE_URL_PATTERN +namespace ada::url_pattern_regex { + +template +concept regex_concept = requires(T t, std::string_view pattern, + bool ignore_case, std::string_view input) { + // Ensure the class has a type alias 'regex_type' + typename T::regex_type; + + // Function to create a regex instance + { + T::create_instance(pattern, ignore_case) + } -> std::same_as>; + + // Function to perform regex search + { + T::regex_search(input, std::declval()) + } -> std::same_as>>>; + + // Function to match regex pattern + { + T::regex_match(input, std::declval()) + } -> std::same_as; + + // Copy constructor + { T(std::declval()) } -> std::same_as; + + // Move constructor + { T(std::declval()) } -> std::same_as; +}; + +#ifdef ADA_USE_UNSAFE_STD_REGEX_PROVIDER +class std_regex_provider final { + public: + std_regex_provider() = default; + using regex_type = std::regex; + static std::optional create_instance(std::string_view pattern, + bool ignore_case); + static std::optional>> regex_search( + std::string_view input, const regex_type& pattern); + static bool regex_match(std::string_view input, const regex_type& pattern); +}; +#endif // ADA_USE_UNSAFE_STD_REGEX_PROVIDER + +} // namespace ada::url_pattern_regex +#endif // ADA_INCLUDE_URL_PATTERN +#endif // ADA_URL_PATTERN_REGEX_H +/* end file include/ada/url_pattern_regex.h */ +/* begin file include/ada/url_pattern_init.h */ +/** + * @file url_pattern_init.h + * @brief Declaration for the url_pattern_init implementation. + */ +#ifndef ADA_URL_PATTERN_INIT_H +#define ADA_URL_PATTERN_INIT_H + +/* begin file include/ada/errors.h */ +/** + * @file errors.h + * @brief Definitions for the errors. + */ +#ifndef ADA_ERRORS_H +#define ADA_ERRORS_H + +#include +namespace ada { +enum class errors : uint8_t { type_error }; +} // namespace ada +#endif // ADA_ERRORS_H +/* end file include/ada/errors.h */ + +#include +#include +#include +#include + +#if ADA_TESTING +#include +#endif // ADA_TESTING + +#if ADA_INCLUDE_URL_PATTERN +namespace ada { + +// Important: C++20 allows us to use concept rather than `using` or `typedef +// and allows functions with second argument, which is optional (using either +// std::nullopt or a parameter with default value) +template +concept url_pattern_encoding_callback = requires(F f, std::string_view sv) { + { f(sv) } -> std::same_as>; +}; + +// A structure providing matching patterns for individual components +// of a URL. When a URLPattern is created, or when a URLPattern is +// used to match or test against a URL, the input can be given as +// either a string or a URLPatternInit struct. If a string is given, +// it will be parsed to create a URLPatternInit. The URLPatternInit +// API is defined as part of the URLPattern specification. +// All provided strings must be valid UTF-8. +struct url_pattern_init { + enum class process_type : uint8_t { + url, + pattern, + }; + + friend std::ostream& operator<<(std::ostream& os, process_type type) { + switch (type) { + case process_type::url: + return os << "url"; + case process_type::pattern: + return os << "pattern"; + default: + return os << "unknown"; + } + } + + // All strings must be valid UTF-8. + // @see https://urlpattern.spec.whatwg.org/#process-a-urlpatterninit + static tl::expected process( + const url_pattern_init& init, process_type type, + std::optional protocol = std::nullopt, + std::optional username = std::nullopt, + std::optional password = std::nullopt, + std::optional hostname = std::nullopt, + std::optional port = std::nullopt, + std::optional pathname = std::nullopt, + std::optional search = std::nullopt, + std::optional hash = std::nullopt); + + // @see https://urlpattern.spec.whatwg.org/#process-protocol-for-init + static tl::expected process_protocol( + std::string_view value, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-username-for-init + static tl::expected process_username( + std::string_view value, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-password-for-init + static tl::expected process_password( + std::string_view value, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-hostname-for-init + static tl::expected process_hostname( + std::string_view value, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-port-for-init + static tl::expected process_port( + std::string_view port, std::string_view protocol, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-pathname-for-init + static tl::expected process_pathname( + std::string_view value, std::string_view protocol, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-search-for-init + static tl::expected process_search( + std::string_view value, process_type type); + + // @see https://urlpattern.spec.whatwg.org/#process-hash-for-init + static tl::expected process_hash(std::string_view value, + process_type type); + +#if ADA_TESTING + friend void PrintTo(const url_pattern_init& init, std::ostream* os) { + *os << "protocol: '" << init.protocol.value_or("undefined") << "', "; + *os << "username: '" << init.username.value_or("undefined") << "', "; + *os << "password: '" << init.password.value_or("undefined") << "', "; + *os << "hostname: '" << init.hostname.value_or("undefined") << "', "; + *os << "port: '" << init.port.value_or("undefined") << "', "; + *os << "pathname: '" << init.pathname.value_or("undefined") << "', "; + *os << "search: '" << init.search.value_or("undefined") << "', "; + *os << "hash: '" << init.hash.value_or("undefined") << "', "; + *os << "base_url: '" << init.base_url.value_or("undefined") << "', "; + } +#endif // ADA_TESTING + + bool operator==(const url_pattern_init&) const; + // If present, must be valid UTF-8. + std::optional protocol{}; + // If present, must be valid UTF-8. + std::optional username{}; + // If present, must be valid UTF-8. + std::optional password{}; + // If present, must be valid UTF-8. + std::optional hostname{}; + // If present, must be valid UTF-8. + std::optional port{}; + // If present, must be valid UTF-8. + std::optional pathname{}; + // If present, must be valid UTF-8. + std::optional search{}; + // If present, must be valid UTF-8. + std::optional hash{}; + // If present, must be valid UTF-8. + std::optional base_url{}; +}; +} // namespace ada +#endif // ADA_INCLUDE_URL_PATTERN +#endif // ADA_URL_PATTERN_INIT_H +/* end file include/ada/url_pattern_init.h */ + +/** + * @private + */ +namespace ada { +struct url_aggregator; +struct url; +#if ADA_INCLUDE_URL_PATTERN +template +class url_pattern; +struct url_pattern_options; +#endif // ADA_INCLUDE_URL_PATTERN +enum class errors : uint8_t; +} // namespace ada + +/** + * @namespace ada::parser * @brief Includes the definitions for supported parsers */ namespace ada::parser { @@ -4385,7 +4358,7 @@ namespace ada::parser { * parameter that can be used to resolve relative URLs. If the base_url is * provided, the user_input is resolved against the base_url. */ -template +template result_type parse_url(std::string_view user_input, const result_type* base_url = nullptr); @@ -4394,7 +4367,7 @@ extern template url_aggregator parse_url( extern template url parse_url(std::string_view user_input, const url* base_url); -template +template result_type parse_url_impl(std::string_view user_input, const result_type* base_url = nullptr); @@ -4402,507 +4375,496 @@ extern template url_aggregator parse_url_impl( std::string_view user_input, const url_aggregator* base_url); extern template url parse_url_impl(std::string_view user_input, const url* base_url); + +#if ADA_INCLUDE_URL_PATTERN +template +tl::expected, errors> parse_url_pattern_impl( + std::variant&& input, + const std::string_view* base_url, const url_pattern_options* options); +#endif // ADA_INCLUDE_URL_PATTERN + } // namespace ada::parser #endif // ADA_PARSER_H /* end file include/ada/parser.h */ -/* begin file include/ada/scheme-inl.h */ +/* begin file include/ada/parser-inl.h */ /** - * @file scheme-inl.h - * @brief Definitions for the URL scheme. + * @file parser-inl.h */ -#ifndef ADA_SCHEME_INL_H -#define ADA_SCHEME_INL_H - -namespace ada::scheme { +#ifndef ADA_PARSER_INL_H +#define ADA_PARSER_INL_H +/* begin file include/ada/url_pattern.h */ /** - * @namespace ada::scheme::details - * @brief Includes the definitions for scheme specific entities + * @file url_pattern.h + * @brief Declaration for the URLPattern implementation. */ -namespace details { -// for use with is_special and get_special_port -// Spaces, if present, are removed from URL. -constexpr std::string_view is_special_list[] = {"http", " ", "https", "ws", - "ftp", "wss", "file", " "}; -// for use with get_special_port -constexpr uint16_t special_ports[] = {80, 0, 443, 80, 21, 443, 0, 0}; -} // namespace details - -/**** - * @private - * In is_special, get_scheme_type, and get_special_port, we - * use a standard hashing technique to find the index of the scheme in - * the is_special_list. The hashing technique is based on the size of - * the scheme and the first character of the scheme. It ensures that we - * do at most one string comparison per call. If the protocol is - * predictible (e.g., it is always "http"), we can get a better average - * performance by using a simpler approach where we loop and compare - * scheme with all possible protocols starting with the most likely - * protocol. Doing multiple comparisons may have a poor worst case - * performance, however. In this instance, we choose a potentially - * slightly lower best-case performance for a better worst-case - * performance. We can revisit this choice at any time. - * - * Reference: - * Schmidt, Douglas C. "Gperf: A perfect hash function generator." - * More C++ gems 17 (2000). - * - * Reference: https://en.wikipedia.org/wiki/Perfect_hash_function - * - * Reference: https://github.com/ada-url/ada/issues/617 - ****/ +#ifndef ADA_URL_PATTERN_H +#define ADA_URL_PATTERN_H -ada_really_inline constexpr bool is_special(std::string_view scheme) { - if (scheme.empty()) { - return false; - } - int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; - const std::string_view target = details::is_special_list[hash_value]; - return (target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1)); -} -constexpr uint16_t get_special_port(std::string_view scheme) noexcept { - if (scheme.empty()) { - return 0; - } - int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; - const std::string_view target = details::is_special_list[hash_value]; - if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) { - return details::special_ports[hash_value]; - } else { - return 0; - } -} -constexpr uint16_t get_special_port(ada::scheme::type type) noexcept { - return details::special_ports[int(type)]; -} -constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept { - if (scheme.empty()) { - return ada::scheme::NOT_SPECIAL; - } - int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; - const std::string_view target = details::is_special_list[hash_value]; - if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) { - return ada::scheme::type(hash_value); - } else { - return ada::scheme::NOT_SPECIAL; - } -} +/* begin file include/ada/implementation.h */ +/** + * @file implementation.h + * @brief Definitions for user facing functions for parsing URL and it's + * components. + */ +#ifndef ADA_IMPLEMENTATION_H +#define ADA_IMPLEMENTATION_H -} // namespace ada::scheme +#include +#include +#include -#endif // ADA_SCHEME_INL_H -/* end file include/ada/scheme-inl.h */ -/* begin file include/ada/serializers.h */ +/* begin file include/ada/url.h */ /** - * @file serializers.h - * @brief Definitions for the URL serializers. + * @file url.h + * @brief Declaration for the URL */ -#ifndef ADA_SERIALIZERS_H -#define ADA_SERIALIZERS_H +#ifndef ADA_URL_H +#define ADA_URL_H -#include +#include #include +#include #include +#include +/* begin file include/ada/checkers.h */ /** - * @namespace ada::serializers - * @brief Includes the definitions for URL serializers + * @file checkers.h + * @brief Declarations for URL specific checkers used within Ada. */ -namespace ada::serializers { +#ifndef ADA_CHECKERS_H +#define ADA_CHECKERS_H + + +#include +#include /** - * Finds and returns the longest sequence of 0 values in a ipv6 input. + * These functions are not part of our public API and may + * change at any time. + * @private + * @namespace ada::checkers + * @brief Includes the definitions for validation functions */ -void find_longest_sequence_of_ipv6_pieces( - const std::array& address, size_t& compress, - size_t& compress_length) noexcept; +namespace ada::checkers { /** - * Serializes an ipv6 address. - * @details An IPv6 address is a 128-bit unsigned integer that identifies a - * network address. - * @see https://url.spec.whatwg.org/#concept-ipv6-serializer + * @private + * Assuming that x is an ASCII letter, this function returns the lower case + * equivalent. + * @details More likely to be inlined by the compiler and constexpr. */ -std::string ipv6(const std::array& address) noexcept; +constexpr char to_lower(char x) noexcept; /** - * Serializes an ipv4 address. - * @details An IPv4 address is a 32-bit unsigned integer that identifies a - * network address. - * @see https://url.spec.whatwg.org/#concept-ipv4-serializer + * @private + * Returns true if the character is an ASCII letter. Equivalent to std::isalpha + * but more likely to be inlined by the compiler. + * + * @attention std::isalpha is not constexpr generally. */ -std::string ipv4(uint64_t address) noexcept; - -} // namespace ada::serializers +constexpr bool is_alpha(char x) noexcept; -#endif // ADA_SERIALIZERS_H -/* end file include/ada/serializers.h */ -/* begin file include/ada/unicode.h */ /** - * @file unicode.h - * @brief Definitions for all unicode specific functions. + * @private + * Check whether a string starts with 0x or 0X. The function is only + * safe if input.size() >=2. + * + * @see has_hex_prefix */ -#ifndef ADA_UNICODE_H -#define ADA_UNICODE_H - -#include -#include +constexpr bool has_hex_prefix_unsafe(std::string_view input); +/** + * @private + * Check whether a string starts with 0x or 0X. + */ +constexpr bool has_hex_prefix(std::string_view input); /** - * Unicode operations. These functions are not part of our public API and may - * change at any time. - * * @private - * @namespace ada::unicode - * @brief Includes the definitions for unicode operations + * Check whether x is an ASCII digit. More likely to be inlined than + * std::isdigit. */ -namespace ada::unicode { +constexpr bool is_digit(char x) noexcept; /** * @private - * We receive a UTF-8 string representing a domain name. - * If the string is percent encoded, we apply percent decoding. - * - * Given a domain, we need to identify its labels. - * They are separated by label-separators: - * - * U+002E (.) FULL STOP - * U+FF0E FULLWIDTH FULL STOP - * U+3002 IDEOGRAPHIC FULL STOP - * U+FF61 HALFWIDTH IDEOGRAPHIC FULL STOP + * @details A string starts with a Windows drive letter if all of the following + * are true: * - * They are all mapped to U+002E. - * - * We process each label into a string that should not exceed 63 octets. - * If the string is already punycode (starts with "xn--"), then we must - * scan it to look for unallowed code points. - * Otherwise, if the string is not pure ASCII, we need to transcode it - * to punycode by following RFC 3454 which requires us to - * - Map characters (see section 3), - * - Normalize (see section 4), - * - Reject forbidden characters, - * - Check for right-to-left characters and if so, check all requirements (see - * section 6), - * - Optionally reject based on unassigned code points (section 7). - * - * The Unicode standard provides a table of code points with a mapping, a list - * of forbidden code points and so forth. This table is subject to change and - * will vary based on the implementation. For Unicode 15, the table is at - * https://www.unicode.org/Public/idna/15.0.0/IdnaMappingTable.txt - * If you use ICU, they parse this table and map it to code using a Python - * script. - * - * The resulting strings should not exceed 255 octets according to RFC 1035 - * section 2.3.4. ICU checks for label size and domain size, but these errors - * are ignored. - * - * @see https://url.spec.whatwg.org/#concept-domain-to-ascii + * - its length is greater than or equal to 2 + * - its first two code points are a Windows drive letter + * - its length is 2 or its third code point is U+002F (/), U+005C (\), U+003F + * (?), or U+0023 (#). * + * https://url.spec.whatwg.org/#start-with-a-windows-drive-letter */ -bool to_ascii(std::optional& out, std::string_view plain, - size_t first_percent); +inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept; /** * @private - * Checks if the input has tab or newline characters. - * - * @attention The has_tabs_or_newline function is a bottleneck and it is simple - * enough that compilers like GCC can 'autovectorize it'. + * @details A normalized Windows drive letter is a Windows drive letter of which + * the second code point is U+003A (:). */ -ada_really_inline bool has_tabs_or_newline( - std::string_view user_input) noexcept; +inline constexpr bool is_normalized_windows_drive_letter( + std::string_view input) noexcept; /** * @private - * Checks if the input is a forbidden host code point. - * @see https://url.spec.whatwg.org/#forbidden-host-code-point + * Returns true if an input is an ipv4 address. It is assumed that the string + * does not contain uppercase ASCII characters (the input should have been + * lowered cased before calling this function) and is not empty. */ -ada_really_inline constexpr bool is_forbidden_host_code_point(char c) noexcept; +ada_really_inline constexpr bool is_ipv4(std::string_view view) noexcept; /** * @private - * Checks if the input contains a forbidden domain code point. - * @see https://url.spec.whatwg.org/#forbidden-domain-code-point + * Returns a bitset. If the first bit is set, then at least one character needs + * percent encoding. If the second bit is set, a \\ is found. If the third bit + * is set then we have a dot. If the fourth bit is set, then we have a percent + * character. */ -ada_really_inline constexpr bool contains_forbidden_domain_code_point( - const char* input, size_t length) noexcept; +ada_really_inline constexpr uint8_t path_signature( + std::string_view input) noexcept; /** * @private - * Checks if the input contains a forbidden domain code point in which case - * the first bit is set to 1. If the input contains an upper case ASCII letter, - * then the second bit is set to 1. - * @see https://url.spec.whatwg.org/#forbidden-domain-code-point + * Returns true if the length of the domain name and its labels are according to + * the specifications. The length of the domain must be 255 octets (253 + * characters not including the last 2 which are the empty label reserved at the + * end). When the empty label is included (a dot at the end), the domain name + * can have 254 characters. The length of a label must be at least 1 and at most + * 63 characters. + * @see section 3.1. of https://www.rfc-editor.org/rfc/rfc1034 + * @see https://www.unicode.org/reports/tr46/#ToASCII */ -ada_really_inline constexpr uint8_t -contains_forbidden_domain_code_point_or_upper(const char* input, - size_t length) noexcept; +ada_really_inline constexpr bool verify_dns_length( + std::string_view input) noexcept; -/** - * @private - * Checks if the input is a forbidden domain code point. - * @see https://url.spec.whatwg.org/#forbidden-domain-code-point - */ -ada_really_inline constexpr bool is_forbidden_domain_code_point( - char c) noexcept; +} // namespace ada::checkers +#endif // ADA_CHECKERS_H +/* end file include/ada/checkers.h */ +/* begin file include/ada/url_components.h */ /** - * @private - * Checks if the input is alphanumeric, '+', '-' or '.' + * @file url_components.h + * @brief Declaration for the URL Components */ -ada_really_inline constexpr bool is_alnum_plus(char c) noexcept; +#ifndef ADA_URL_COMPONENTS_H +#define ADA_URL_COMPONENTS_H -/** - * @private - * @details An ASCII hex digit is an ASCII upper hex digit or ASCII lower hex - * digit. An ASCII upper hex digit is an ASCII digit or a code point in the - * range U+0041 (A) to U+0046 (F), inclusive. An ASCII lower hex digit is an - * ASCII digit or a code point in the range U+0061 (a) to U+0066 (f), inclusive. - */ -ada_really_inline constexpr bool is_ascii_hex_digit(char c) noexcept; +namespace ada { /** - * @private - * Checks if the input is a C0 control or space character. + * @brief URL Component representations using offsets. * - * @details A C0 control or space is a C0 control or U+0020 SPACE. - * A C0 control is a code point in the range U+0000 NULL to U+001F INFORMATION - * SEPARATOR ONE, inclusive. - */ -ada_really_inline constexpr bool is_c0_control_or_space(char c) noexcept; - -/** - * @private - * Checks if the input is a ASCII tab or newline character. + * @details We design the url_components struct so that it is as small + * and simple as possible. This version uses 32 bytes. * - * @details An ASCII tab or newline is U+0009 TAB, U+000A LF, or U+000D CR. - */ -ada_really_inline constexpr bool is_ascii_tab_or_newline(char c) noexcept; - -/** - * @private - * @details A double-dot path segment must be ".." or an ASCII case-insensitive - * match for ".%2e", "%2e.", or "%2e%2e". - */ -ada_really_inline ada_constexpr bool is_double_dot_path_segment( - std::string_view input) noexcept; - -/** - * @private - * @details A single-dot path segment must be "." or an ASCII case-insensitive - * match for "%2e". + * This struct is used to extract components from a single 'href'. */ -ada_really_inline constexpr bool is_single_dot_path_segment( - std::string_view input) noexcept; +struct url_components { + constexpr static uint32_t omitted = uint32_t(-1); -/** - * @private - * @details ipv4 character might contain 0-9 or a-f character ranges. - */ -ada_really_inline constexpr bool is_lowercase_hex(char c) noexcept; + url_components() = default; + url_components(const url_components &u) = default; + url_components(url_components &&u) noexcept = default; + url_components &operator=(url_components &&u) noexcept = default; + url_components &operator=(const url_components &u) = default; + ~url_components() = default; -/** - * @private - * @details Convert hex to binary. Caller is responsible to ensure that - * the parameter is an hexadecimal digit (0-9, A-F, a-f). - */ -ada_really_inline unsigned constexpr convert_hex_to_binary(char c) noexcept; + /* + * By using 32-bit integers, we implicitly assume that the URL string + * cannot exceed 4 GB. + * + * https://user:pass@example.com:1234/foo/bar?baz#quux + * | | | | ^^^^| | | + * | | | | | | | `----- hash_start + * | | | | | | `--------- search_start + * | | | | | `----------------- pathname_start + * | | | | `--------------------- port + * | | | `----------------------- host_end + * | | `---------------------------------- host_start + * | `--------------------------------------- username_end + * `--------------------------------------------- protocol_end + */ + uint32_t protocol_end{0}; + /** + * Username end is not `omitted` by default to make username and password + * getters less costly to implement. + */ + uint32_t username_end{0}; + uint32_t host_start{0}; + uint32_t host_end{0}; + uint32_t port{omitted}; + uint32_t pathname_start{0}; + uint32_t search_start{omitted}; + uint32_t hash_start{omitted}; -/** - * @private - * first_percent should be = input.find('%') - * - * @todo It would be faster as noexcept maybe, but it could be unsafe since. - * @author Node.js - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L245 - * @see https://encoding.spec.whatwg.org/#utf-8-decode-without-bom - */ -std::string percent_decode(std::string_view input, size_t first_percent); + /** + * Check the following conditions: + * protocol_end < username_end < ... < hash_start, + * expect when a value is omitted. It also computes + * a lower bound on the possible string length that may match these + * offsets. + * @return true if the offset values are + * consistent with a possible URL string + */ + [[nodiscard]] constexpr bool check_offset_consistency() const noexcept; -/** - * @private - * Returns a percent-encoding string whether percent encoding was needed or not. - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 - */ -std::string percent_encode(std::string_view input, - const uint8_t character_set[]); -/** - * @private - * Returns a percent-encoded string version of input, while starting the percent - * encoding at the provided index. - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 - */ -std::string percent_encode(std::string_view input, - const uint8_t character_set[], size_t index); -/** - * @private - * Returns true if percent encoding was needed, in which case, we store - * the percent-encoded content in 'out'. If the boolean 'append' is set to - * true, the content is appended to 'out'. - * If percent encoding is not needed, out is left unchanged. - * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 - */ -template -bool percent_encode(std::string_view input, const uint8_t character_set[], - std::string& out); -/** - * @private - * Returns the index at which percent encoding should start, or (equivalently), - * the length of the prefix that does not require percent encoding. - */ -ada_really_inline size_t percent_encode_index(std::string_view input, - const uint8_t character_set[]); -/** - * @private - * Lowers the string in-place, assuming that the content is ASCII. - * Return true if the content was ASCII. - */ -constexpr bool to_lower_ascii(char* input, size_t length) noexcept; -} // namespace ada::unicode + /** + * Converts a url_components to JSON stringified version. + */ + [[nodiscard]] std::string to_string() const; -#endif // ADA_UNICODE_H -/* end file include/ada/unicode.h */ -/* begin file include/ada/url_base-inl.h */ -/** - * @file url_base-inl.h - * @brief Inline functions for url base - */ -#ifndef ADA_URL_BASE_INL_H -#define ADA_URL_BASE_INL_H +}; // struct url_components +} // namespace ada +#endif +/* end file include/ada/url_components.h */ -/* begin file include/ada/url_aggregator.h */ -/** - * @file url_aggregator.h - * @brief Declaration for the basic URL definitions - */ -#ifndef ADA_URL_AGGREGATOR_H -#define ADA_URL_AGGREGATOR_H +namespace ada { -#include -#include +struct url_aggregator; -namespace ada { +// namespace parser { +// template +// result_type parse_url(std::string_view user_input, +// const result_type* base_url = nullptr); +// template +// result_type parse_url_impl(std::string_view user_input, +// const result_type* base_url = nullptr); +// } /** - * @brief Lightweight URL struct. + * @brief Generic URL struct reliant on std::string instantiation. * - * @details The url_aggregator class aims to minimize temporary memory - * allocation while representing a parsed URL. Internally, it contains a single - * normalized URL (the href), and it makes available the components, mostly - * using std::string_view. + * @details To disambiguate from a valid URL string it can also be referred to + * as a URL record. A URL is a struct that represents a universal identifier. + * Unlike the url_aggregator, the ada::url represents the different components + * of a parsed URL as independent std::string instances. This makes the + * structure heavier and more reliant on memory allocations. When getting + * components from the parsed URL, a new std::string is typically constructed. + * + * @see https://url.spec.whatwg.org/#url-representation */ -struct url_aggregator : url_base { - url_aggregator() = default; - url_aggregator(const url_aggregator& u) = default; - url_aggregator(url_aggregator&& u) noexcept = default; - url_aggregator& operator=(url_aggregator&& u) noexcept = default; - url_aggregator& operator=(const url_aggregator& u) = default; - ~url_aggregator() override = default; +struct url : url_base { + url() = default; + url(const url &u) = default; + url(url &&u) noexcept = default; + url &operator=(url &&u) noexcept = default; + url &operator=(const url &u) = default; + ~url() override = default; - bool set_href(std::string_view input); - bool set_host(std::string_view input); - bool set_hostname(std::string_view input); - bool set_protocol(std::string_view input); - bool set_username(std::string_view input); - bool set_password(std::string_view input); - bool set_port(std::string_view input); - bool set_pathname(std::string_view input); - void set_search(std::string_view input); - void set_hash(std::string_view input); + /** + * @private + * A URL's username is an ASCII string identifying a username. It is initially + * the empty string. + */ + std::string username{}; - [[nodiscard]] bool has_valid_domain() const noexcept override; /** - * The origin getter steps are to return the serialization of this's URL's - * origin. [HTML] - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#concept-url-origin + * @private + * A URL's password is an ASCII string identifying a password. It is initially + * the empty string. */ - [[nodiscard]] std::string get_origin() const noexcept override; + std::string password{}; + /** - * Return the normalized string. - * This function does not allocate memory. - * It is highly efficient. - * @return a constant reference to the underlying normalized URL. - * @see https://url.spec.whatwg.org/#dom-url-href - * @see https://url.spec.whatwg.org/#concept-url-serializer + * @private + * A URL's host is null or a host. It is initially null. */ - [[nodiscard]] inline std::string_view get_href() const noexcept; + std::optional host{}; + /** - * The username getter steps are to return this's URL's username. - * This function does not allocate memory. - * @return a lightweight std::string_view. - * @see https://url.spec.whatwg.org/#dom-url-username + * @private + * A URL's port is either null or a 16-bit unsigned integer that identifies a + * networking port. It is initially null. */ - [[nodiscard]] std::string_view get_username() const noexcept; + std::optional port{}; + /** - * The password getter steps are to return this's URL's password. - * This function does not allocate memory. - * @return a lightweight std::string_view. - * @see https://url.spec.whatwg.org/#dom-url-password + * @private + * A URL's path is either an ASCII string or a list of zero or more ASCII + * strings, usually identifying a location. */ - [[nodiscard]] std::string_view get_password() const noexcept; + std::string path{}; + /** - * Return this's URL's port, serialized. - * This function does not allocate memory. - * @return a lightweight std::string_view. - * @see https://url.spec.whatwg.org/#dom-url-port + * @private + * A URL's query is either null or an ASCII string. It is initially null. */ - [[nodiscard]] std::string_view get_port() const noexcept; + std::optional query{}; + /** - * Return U+0023 (#), followed by this's URL's fragment. - * This function does not allocate memory. - * @return a lightweight std::string_view.. - * @see https://url.spec.whatwg.org/#dom-url-hash + * @private + * A URL's fragment is either null or an ASCII string that can be used for + * further processing on the resource the URL's other components identify. It + * is initially null. + */ + std::optional hash{}; + + /** @return true if it has an host but it is the empty string */ + [[nodiscard]] inline bool has_empty_hostname() const noexcept; + /** @return true if the URL has a (non default) port */ + [[nodiscard]] inline bool has_port() const noexcept; + /** @return true if it has a host (included an empty host) */ + [[nodiscard]] inline bool has_hostname() const noexcept; + [[nodiscard]] bool has_valid_domain() const noexcept override; + + /** + * Returns a JSON string representation of this URL. + */ + [[nodiscard]] std::string to_string() const override; + + /** + * @see https://url.spec.whatwg.org/#dom-url-href + * @see https://url.spec.whatwg.org/#concept-url-serializer + */ + [[nodiscard]] ada_really_inline std::string get_href() const noexcept; + + /** + * The origin getter steps are to return the serialization of this's URL's + * origin. [HTML] + * @return a newly allocated string. + * @see https://url.spec.whatwg.org/#concept-url-origin + */ + [[nodiscard]] std::string get_origin() const noexcept override; + + /** + * The protocol getter steps are to return this's URL's scheme, followed by + * U+003A (:). + * @return a newly allocated string. + * @see https://url.spec.whatwg.org/#dom-url-protocol */ - [[nodiscard]] std::string_view get_hash() const noexcept; + [[nodiscard]] std::string get_protocol() const noexcept; + /** * Return url's host, serialized, followed by U+003A (:) and url's port, * serialized. - * This function does not allocate memory. - * When there is no host, this function returns the empty view. - * @return a lightweight std::string_view. + * When there is no host, this function returns the empty string. + * @return a newly allocated string. * @see https://url.spec.whatwg.org/#dom-url-host */ - [[nodiscard]] std::string_view get_host() const noexcept; + [[nodiscard]] std::string get_host() const noexcept; + /** * Return this's URL's host, serialized. - * This function does not allocate memory. - * When there is no host, this function returns the empty view. - * @return a lightweight std::string_view. + * When there is no host, this function returns the empty string. + * @return a newly allocated string. * @see https://url.spec.whatwg.org/#dom-url-hostname */ - [[nodiscard]] std::string_view get_hostname() const noexcept; + [[nodiscard]] std::string get_hostname() const noexcept; + /** * The pathname getter steps are to return the result of URL path serializing * this's URL. - * This function does not allocate memory. - * @return a lightweight std::string_view. + * @return a newly allocated string. * @see https://url.spec.whatwg.org/#dom-url-pathname */ - [[nodiscard]] std::string_view get_pathname() const noexcept; + [[nodiscard]] constexpr std::string_view get_pathname() const noexcept; + /** * Compute the pathname length in bytes without instantiating a view or a * string. * @return size of the pathname in bytes * @see https://url.spec.whatwg.org/#dom-url-pathname */ - [[nodiscard]] ada_really_inline uint32_t get_pathname_length() const noexcept; + [[nodiscard]] ada_really_inline size_t get_pathname_length() const noexcept; + /** * Return U+003F (?), followed by this's URL's query. - * This function does not allocate memory. - * @return a lightweight std::string_view. + * @return a newly allocated string. * @see https://url.spec.whatwg.org/#dom-url-search */ - [[nodiscard]] std::string_view get_search() const noexcept; + [[nodiscard]] std::string get_search() const noexcept; + /** - * The protocol getter steps are to return this's URL's scheme, followed by - * U+003A (:). - * This function does not allocate memory. - * @return a lightweight std::string_view. + * The username getter steps are to return this's URL's username. + * @return a constant reference to the underlying string. + * @see https://url.spec.whatwg.org/#dom-url-username + */ + [[nodiscard]] const std::string &get_username() const noexcept; + + /** + * @return Returns true on successful operation. + * @see https://url.spec.whatwg.org/#dom-url-username + */ + bool set_username(std::string_view input); + + /** + * @return Returns true on success. + * @see https://url.spec.whatwg.org/#dom-url-password + */ + bool set_password(std::string_view input); + + /** + * @return Returns true on success. + * @see https://url.spec.whatwg.org/#dom-url-port + */ + bool set_port(std::string_view input); + + /** + * This function always succeeds. + * @see https://url.spec.whatwg.org/#dom-url-hash + */ + void set_hash(std::string_view input); + + /** + * This function always succeeds. + * @see https://url.spec.whatwg.org/#dom-url-search + */ + void set_search(std::string_view input); + + /** + * @return Returns true on success. + * @see https://url.spec.whatwg.org/#dom-url-search + */ + bool set_pathname(std::string_view input); + + /** + * @return Returns true on success. + * @see https://url.spec.whatwg.org/#dom-url-host + */ + bool set_host(std::string_view input); + + /** + * @return Returns true on success. + * @see https://url.spec.whatwg.org/#dom-url-hostname + */ + bool set_hostname(std::string_view input); + + /** + * @return Returns true on success. * @see https://url.spec.whatwg.org/#dom-url-protocol */ - [[nodiscard]] std::string_view get_protocol() const noexcept; + bool set_protocol(std::string_view input); + + /** + * @see https://url.spec.whatwg.org/#dom-url-href + */ + bool set_href(std::string_view input); + + /** + * The password getter steps are to return this's URL's password. + * @return a constant reference to the underlying string. + * @see https://url.spec.whatwg.org/#dom-url-password + */ + [[nodiscard]] const std::string &get_password() const noexcept; + + /** + * Return this's URL's port, serialized. + * @return a newly constructed string representing the port. + * @see https://url.spec.whatwg.org/#dom-url-port + */ + [[nodiscard]] std::string get_port() const noexcept; + + /** + * Return U+0023 (#), followed by this's URL's fragment. + * @return a newly constructed string representing the hash. + * @see https://url.spec.whatwg.org/#dom-url-hash + */ + [[nodiscard]] std::string get_hash() const noexcept; /** * A URL includes credentials if its username or password is not the empty @@ -4926,92 +4888,54 @@ struct url_aggregator : url_base { * * Inspired after servo/url * - * @return a constant reference to the underlying component attribute. + * @return a newly constructed component. * * @see * https://github.com/servo/rust-url/blob/b65a45515c10713f6d212e6726719a020203cc98/url/src/quirks.rs#L31 */ - [[nodiscard]] ada_really_inline const ada::url_components& get_components() + [[nodiscard]] ada_really_inline ada::url_components get_components() const noexcept; - /** - * Returns a string representation of this URL. - */ - [[nodiscard]] std::string to_string() const override; - /** - * Returns a string diagram of this URL. - */ - [[nodiscard]] std::string to_diagram() const; + /** @return true if the URL has a hash component */ + [[nodiscard]] constexpr bool has_hash() const noexcept override; + /** @return true if the URL has a search component */ + [[nodiscard]] constexpr bool has_search() const noexcept override; - /** - * Verifies that the parsed URL could be valid. Useful for debugging purposes. - * @return true if the URL is valid, otherwise return true of the offsets are - * possible. - */ - [[nodiscard]] bool validate() const noexcept; + private: + friend ada::url ada::parser::parse_url(std::string_view, + const ada::url *); + friend ada::url_aggregator ada::parser::parse_url( + std::string_view, const ada::url_aggregator *); + friend void ada::helpers::strip_trailing_spaces_from_opaque_path( + ada::url &url) noexcept; - /** @return true if it has an host but it is the empty string */ - [[nodiscard]] inline bool has_empty_hostname() const noexcept; - /** @return true if it has a host (included an empty host) */ - [[nodiscard]] inline bool has_hostname() const noexcept; - /** @return true if the URL has a non-empty username */ - [[nodiscard]] inline bool has_non_empty_username() const noexcept; - /** @return true if the URL has a non-empty password */ - [[nodiscard]] inline bool has_non_empty_password() const noexcept; - /** @return true if the URL has a (non default) port */ - [[nodiscard]] inline bool has_port() const noexcept; - /** @return true if the URL has a password */ - [[nodiscard]] inline bool has_password() const noexcept; - /** @return true if the URL has a hash component */ - [[nodiscard]] inline bool has_hash() const noexcept override; - /** @return true if the URL has a search component */ - [[nodiscard]] inline bool has_search() const noexcept override; - - inline void clear_port(); - inline void clear_hash(); - inline void clear_search() override; - - private: - friend ada::url_aggregator ada::parser::parse_url( - std::string_view, const ada::url_aggregator*); - friend void ada::helpers::strip_trailing_spaces_from_opaque_path< - ada::url_aggregator>(ada::url_aggregator& url) noexcept; - friend ada::url_aggregator ada::parser::parse_url_impl< - ada::url_aggregator, true>(std::string_view, const ada::url_aggregator*); + friend ada::url ada::parser::parse_url_impl(std::string_view, + const ada::url *); friend ada::url_aggregator ada::parser::parse_url_impl< - ada::url_aggregator, false>(std::string_view, const ada::url_aggregator*); - - std::string buffer{}; - url_components components{}; - - /** - * Returns true if neither the search, nor the hash nor the pathname - * have been set. - * @return true if the buffer is ready to receive the path. - */ - [[nodiscard]] ada_really_inline bool is_at_path() const noexcept; + ada::url_aggregator, true>(std::string_view, const ada::url_aggregator *); - inline void add_authority_slashes_if_needed() noexcept; + inline void update_unencoded_base_hash(std::string_view input); + inline void update_base_hostname(std::string_view input); + inline void update_base_search(std::string_view input, + const uint8_t query_percent_encode_set[]); + inline void update_base_search(std::optional &&input); + inline void update_base_pathname(std::string_view input); + inline void update_base_username(std::string_view input); + inline void update_base_password(std::string_view input); + inline void update_base_port(std::optional input); /** - * To optimize performance, you may indicate how much memory to allocate - * within this instance. + * Sets the host or hostname according to override condition. + * Return true on success. + * @see https://url.spec.whatwg.org/#hostname-state */ - inline void reserve(uint32_t capacity); - - ada_really_inline size_t parse_port( - std::string_view view, bool check_trailing_content) noexcept override; - - ada_really_inline size_t parse_port(std::string_view view) noexcept override { - return this->parse_port(view, false); - } + template + bool set_host_or_hostname(std::string_view input); /** - * Return true on success. The 'in_place' parameter indicates whether the - * the string_view input is pointing in the buffer. When in_place is false, - * we must nearly always update the buffer. + * Return true on success. * @see https://url.spec.whatwg.org/#concept-ipv4-parser */ - [[nodiscard]] bool parse_ipv4(std::string_view input, bool in_place); + [[nodiscard]] bool parse_ipv4(std::string_view input); /** * Return true on success. @@ -5025,7 +4949,16 @@ struct url_aggregator : url_base { */ [[nodiscard]] bool parse_opaque_host(std::string_view input); - ada_really_inline void parse_path(std::string_view input); + /** + * A URL's scheme is an ASCII string that identifies the type of URL and can + * be used to dispatch a URL for further processing after parsing. It is + * initially the empty string. We only set non_special_scheme when the scheme + * is non-special, otherwise we avoid constructing string. + * + * Special schemes are stored in ada::scheme::details::is_special_list so we + * typically do not need to store them in each url instance. + */ + std::string non_special_scheme{}; /** * A URL cannot have a username/password/port if its host is null or the empty @@ -5033,729 +4966,1747 @@ struct url_aggregator : url_base { */ [[nodiscard]] inline bool cannot_have_credentials_or_port() const; - template - bool set_host_or_hostname(std::string_view input); + ada_really_inline size_t parse_port( + std::string_view view, bool check_trailing_content) noexcept override; - ada_really_inline bool parse_host(std::string_view input); + ada_really_inline size_t parse_port(std::string_view view) noexcept override { + return this->parse_port(view, false); + } + + /** + * Parse the host from the provided input. We assume that + * the input does not contain spaces or tabs. Control + * characters and spaces are not trimmed (they should have + * been removed if needed). + * Return true on success. + * @see https://url.spec.whatwg.org/#host-parsing + */ + [[nodiscard]] ada_really_inline bool parse_host(std::string_view input); - inline void update_base_authority(std::string_view base_buffer, - const ada::url_components& base); - inline void update_unencoded_base_hash(std::string_view input); - inline void update_base_hostname(std::string_view input); - inline void update_base_search(std::string_view input); - inline void update_base_search(std::string_view input, - const uint8_t* query_percent_encode_set); - inline void update_base_pathname(std::string_view input); - inline void update_base_username(std::string_view input); - inline void append_base_username(std::string_view input); - inline void update_base_password(std::string_view input); - inline void append_base_password(std::string_view input); - inline void update_base_port(uint32_t input); - inline void append_base_pathname(std::string_view input); - [[nodiscard]] inline uint32_t retrieve_base_port() const; - inline void clear_hostname(); - inline void clear_password(); - inline void clear_pathname() override; - [[nodiscard]] inline bool has_dash_dot() const noexcept; - void delete_dash_dot(); - inline void consume_prepared_path(std::string_view input); template - [[nodiscard]] ada_really_inline bool parse_scheme_with_colon( - std::string_view input); - ada_really_inline uint32_t replace_and_resize(uint32_t start, uint32_t end, - std::string_view input); - [[nodiscard]] inline bool has_authority() const noexcept; - inline void set_protocol_as_file(); - inline void set_scheme(std::string_view new_scheme) noexcept; + [[nodiscard]] ada_really_inline bool parse_scheme(std::string_view input); + + constexpr void clear_pathname() override; + constexpr void clear_search() override; + constexpr void set_protocol_as_file(); + /** - * Fast function to set the scheme from a view with a colon in the - * buffer, does not change type. + * Parse the path from the provided input. + * Return true on success. Control characters not + * trimmed from the ends (they should have + * been removed if needed). + * + * The input is expected to be UTF-8. + * + * @see https://url.spec.whatwg.org/ */ - inline void set_scheme_from_view_with_colon( - std::string_view new_scheme_with_colon) noexcept; - inline void copy_scheme(const url_aggregator& u) noexcept; + ada_really_inline void parse_path(std::string_view input); -}; // url_aggregator + /** + * Set the scheme for this URL. The provided scheme should be a valid + * scheme string, be lower-cased, not contain spaces or tabs. It should + * have no spurious trailing or leading content. + */ + inline void set_scheme(std::string &&new_scheme) noexcept; -inline std::ostream& operator<<(std::ostream& out, const ada::url& u); -} // namespace ada + /** + * Take the scheme from another URL. The scheme string is moved from the + * provided url. + */ + constexpr void copy_scheme(ada::url &&u) noexcept; -#endif -/* end file include/ada/url_aggregator.h */ -/* begin file include/ada/checkers.h */ -/** - * @file checkers.h - * @brief Declarations for URL specific checkers used within Ada. - */ -#ifndef ADA_CHECKERS_H -#define ADA_CHECKERS_H + /** + * Take the scheme from another URL. The scheme string is copied from the + * provided url. + */ + constexpr void copy_scheme(const ada::url &u); -#include -#include +}; // struct url -/** - * These functions are not part of our public API and may - * change at any time. - * @private - * @namespace ada::checkers - * @brief Includes the definitions for validation functions - */ -namespace ada::checkers { +inline std::ostream &operator<<(std::ostream &out, const ada::url &u); +} // namespace ada -/** - * @private - * Assuming that x is an ASCII letter, this function returns the lower case - * equivalent. - * @details More likely to be inlined by the compiler and constexpr. - */ -constexpr char to_lower(char x) noexcept; +#endif // ADA_URL_H +/* end file include/ada/url.h */ -/** - * @private - * Returns true if the character is an ASCII letter. Equivalent to std::isalpha - * but more likely to be inlined by the compiler. - * - * @attention std::isalpha is not constexpr generally. - */ -constexpr bool is_alpha(char x) noexcept; +namespace ada { + +template +using result = tl::expected; /** - * @private - * Check whether a string starts with 0x or 0X. The function is only - * safe if input.size() >=2. + * The URL parser takes a scalar value string input, with an optional null or + * base URL base (default null). The parser assumes the input is a valid ASCII + * or UTF-8 string. * - * @see has_hex_prefix - */ -inline bool has_hex_prefix_unsafe(std::string_view input); -/** - * @private - * Check whether a string starts with 0x or 0X. + * @param input the string input to analyze (must be valid ASCII or UTF-8) + * @param base_url the optional URL input to use as a base url. + * @return a parsed URL. */ -inline bool has_hex_prefix(std::string_view input); +template +ada_warn_unused ada::result parse( + std::string_view input, const result_type* base_url = nullptr); + +extern template ada::result parse(std::string_view input, + const url* base_url); +extern template ada::result parse( + std::string_view input, const url_aggregator* base_url); /** - * @private - * Check whether x is an ASCII digit. More likely to be inlined than - * std::isdigit. + * Verifies whether the URL strings can be parsed. The function assumes + * that the inputs are valid ASCII or UTF-8 strings. + * @see https://url.spec.whatwg.org/#dom-url-canparse + * @return If URL can be parsed or not. */ -constexpr bool is_digit(char x) noexcept; +bool can_parse(std::string_view input, + const std::string_view* base_input = nullptr); +#if ADA_INCLUDE_URL_PATTERN /** - * @private - * @details A string starts with a Windows drive letter if all of the following - * are true: + * Implementation of the URL pattern parsing algorithm. + * @see https://urlpattern.spec.whatwg.org * - * - its length is greater than or equal to 2 - * - its first two code points are a Windows drive letter - * - its length is 2 or its third code point is U+002F (/), U+005C (\), U+003F - * (?), or U+0023 (#). - * - * https://url.spec.whatwg.org/#start-with-a-windows-drive-letter + * @param input valid UTF-8 string or URLPatternInit struct + * @param base_url an optional valid UTF-8 string + * @param options an optional url_pattern_options struct + * @return url_pattern instance */ -inline constexpr bool is_windows_drive_letter(std::string_view input) noexcept; +template +ada_warn_unused tl::expected, errors> +parse_url_pattern(std::variant&& input, + const std::string_view* base_url = nullptr, + const url_pattern_options* options = nullptr); +#endif // ADA_INCLUDE_URL_PATTERN /** - * @private - * @details A normalized Windows drive letter is a Windows drive letter of which - * the second code point is U+003A (:). + * Computes a href string from a file path. The function assumes + * that the input is a valid ASCII or UTF-8 string. + * @return a href string (starts with file:://) */ -inline constexpr bool is_normalized_windows_drive_letter( - std::string_view input) noexcept; +std::string href_from_file(std::string_view path); +} // namespace ada -/** - * @private - * @warning Will be removed when Ada requires C++20. - */ -ada_really_inline bool begins_with(std::string_view view, - std::string_view prefix); +#endif // ADA_IMPLEMENTATION_H +/* end file include/ada/implementation.h */ -/** - * @private - * Returns true if an input is an ipv4 address. It is assumed that the string - * does not contain uppercase ASCII characters (the input should have been - * lowered cased before calling this function) and is not empty. - */ -ada_really_inline ada_constexpr bool is_ipv4(std::string_view view) noexcept; +#include +#include +#include +#include +#include +#include -/** - * @private - * Returns a bitset. If the first bit is set, then at least one character needs - * percent encoding. If the second bit is set, a \\ is found. If the third bit - * is set then we have a dot. If the fourth bit is set, then we have a percent - * character. - */ -ada_really_inline constexpr uint8_t path_signature( - std::string_view input) noexcept; +#if ADA_TESTING +#include +#endif // ADA_TESTING -/** - * @private - * Returns true if the length of the domain name and its labels are according to - * the specifications. The length of the domain must be 255 octets (253 - * characters not including the last 2 which are the empty label reserved at the - * end). When the empty label is included (a dot at the end), the domain name - * can have 254 characters. The length of a label must be at least 1 and at most - * 63 characters. - * @see section 3.1. of https://www.rfc-editor.org/rfc/rfc1034 - * @see https://www.unicode.org/reports/tr46/#ToASCII - */ -ada_really_inline constexpr bool verify_dns_length( - std::string_view input) noexcept; +#if ADA_INCLUDE_URL_PATTERN +namespace ada { -} // namespace ada::checkers +enum class url_pattern_part_type : uint8_t { + // The part represents a simple fixed text string. + FIXED_TEXT, + // The part represents a matching group with a custom regular expression. + REGEXP, + // The part represents a matching group that matches code points up to the + // next separator code point. This is typically used for a named group like + // ":foo" that does not have a custom regular expression. + SEGMENT_WILDCARD, + // The part represents a matching group that greedily matches all code points. + // This is typically used for the "*" wildcard matching group. + FULL_WILDCARD, +}; -#endif // ADA_CHECKERS_H -/* end file include/ada/checkers.h */ -/* begin file include/ada/url.h */ -/** - * @file url.h - * @brief Declaration for the URL - */ -#ifndef ADA_URL_H -#define ADA_URL_H +enum class url_pattern_part_modifier : uint8_t { + // The part does not have a modifier. + none, + // The part has an optional modifier indicated by the U+003F (?) code point. + optional, + // The part has a "zero or more" modifier indicated by the U+002A (*) code + // point. + zero_or_more, + // The part has a "one or more" modifier indicated by the U+002B (+) code + // point. + one_or_more, +}; -#include -#include -#include -#include -#include -#include +// @see https://urlpattern.spec.whatwg.org/#part +class url_pattern_part { + public: + url_pattern_part(url_pattern_part_type _type, std::string&& _value, + url_pattern_part_modifier _modifier) + : type(_type), value(std::move(_value)), modifier(_modifier) {} + + url_pattern_part(url_pattern_part_type _type, std::string&& _value, + url_pattern_part_modifier _modifier, std::string&& _name, + std::string&& _prefix, std::string&& _suffix) + : type(_type), + value(std::move(_value)), + modifier(_modifier), + name(std::move(_name)), + prefix(std::move(_prefix)), + suffix(std::move(_suffix)) {} + // A part has an associated type, a string, which must be set upon creation. + url_pattern_part_type type; + // A part has an associated value, a string, which must be set upon creation. + std::string value; + // A part has an associated modifier a string, which must be set upon + // creation. + url_pattern_part_modifier modifier; + // A part has an associated name, a string, initially the empty string. + std::string name{}; + // A part has an associated prefix, a string, initially the empty string. + std::string prefix{}; + // A part has an associated suffix, a string, initially the empty string. + std::string suffix{}; + + inline bool is_regexp() const noexcept; +}; -namespace ada { +// @see https://urlpattern.spec.whatwg.org/#options-header +struct url_pattern_compile_component_options { + url_pattern_compile_component_options() = default; + explicit url_pattern_compile_component_options( + std::optional new_delimiter = std::nullopt, + std::optional new_prefix = std::nullopt) + : delimiter(new_delimiter), prefix(new_prefix) {} -/** - * @brief Generic URL struct reliant on std::string instantiation. - * - * @details To disambiguate from a valid URL string it can also be referred to - * as a URL record. A URL is a struct that represents a universal identifier. - * Unlike the url_aggregator, the ada::url represents the different components - * of a parsed URL as independent std::string instances. This makes the - * structure heavier and more reliant on memory allocations. When getting - * components from the parsed URL, a new std::string is typically constructed. - * - * @see https://url.spec.whatwg.org/#url-representation - */ -struct url : url_base { - url() = default; - url(const url& u) = default; - url(url&& u) noexcept = default; - url& operator=(url&& u) noexcept = default; - url& operator=(const url& u) = default; - ~url() override = default; + inline std::string_view get_delimiter() const ada_warn_unused; + inline std::string_view get_prefix() const ada_warn_unused; + + // @see https://urlpattern.spec.whatwg.org/#options-ignore-case + bool ignore_case = false; + + static url_pattern_compile_component_options DEFAULT; + static url_pattern_compile_component_options HOSTNAME; + static url_pattern_compile_component_options PATHNAME; + + private: + // @see https://urlpattern.spec.whatwg.org/#options-delimiter-code-point + std::optional delimiter{}; + // @see https://urlpattern.spec.whatwg.org/#options-prefix-code-point + std::optional prefix{}; +}; + +// The default options is an options struct with delimiter code point set to +// the empty string and prefix code point set to the empty string. +inline url_pattern_compile_component_options + url_pattern_compile_component_options::DEFAULT(std::nullopt, std::nullopt); + +// The hostname options is an options struct with delimiter code point set +// "." and prefix code point set to the empty string. +inline url_pattern_compile_component_options + url_pattern_compile_component_options::HOSTNAME('.', std::nullopt); + +// The pathname options is an options struct with delimiter code point set +// "/" and prefix code point set to "/". +inline url_pattern_compile_component_options + url_pattern_compile_component_options::PATHNAME('/', '/'); + +// A struct providing the URLPattern matching results for a single +// URL component. The URLPatternComponentResult is only ever used +// as a member attribute of a URLPatternResult struct. The +// URLPatternComponentResult API is defined as part of the URLPattern +// specification. +struct url_pattern_component_result { + std::string input; + std::unordered_map> groups; + + bool operator==(const url_pattern_component_result&) const; + +#if ADA_TESTING + friend void PrintTo(const url_pattern_component_result& result, + std::ostream* os) { + *os << "input: '" << result.input << "', group: "; + for (const auto& group : result.groups) { + *os << "(" << group.first << ", " << group.second.value_or("undefined") + << ") "; + } + } +#endif // ADA_TESTING +}; + +template +class url_pattern_component { + public: + url_pattern_component() = default; + + // This function explicitly takes a std::string because it is moved. + // To avoid unnecessary copy, move each value while calling the constructor. + url_pattern_component(std::string&& new_pattern, + typename regex_provider::regex_type&& new_regexp, + std::vector&& new_group_name_list, + bool new_has_regexp_groups) + : regexp(std::move(new_regexp)), + pattern(std::move(new_pattern)), + group_name_list(std::move(new_group_name_list)), + has_regexp_groups(new_has_regexp_groups) {} + + // @see https://urlpattern.spec.whatwg.org/#compile-a-component + template + static tl::expected compile( + std::string_view input, F& encoding_callback, + url_pattern_compile_component_options& options); + + // @see https://urlpattern.spec.whatwg.org/#create-a-component-match-result + url_pattern_component_result create_component_match_result( + std::string&& input, + std::vector>&& exec_result); + +#if ADA_TESTING + friend void PrintTo(const url_pattern_component& component, + std::ostream* os) { + *os << "pattern: '" << component.pattern + << "', has_regexp_groups: " << component.has_regexp_groups + << "group_name_list: "; + for (const auto& name : component.group_name_list) { + *os << name << ", "; + } + } +#endif // ADA_TESTING + + typename regex_provider::regex_type regexp{}; + std::string pattern{}; + std::vector group_name_list{}; + bool has_regexp_groups = false; +}; + +// A URLPattern input can be either a string or a URLPatternInit object. +// If it is a string, it must be a valid UTF-8 string. +using url_pattern_input = std::variant; + +// A struct providing the URLPattern matching results for all +// components of a URL. The URLPatternResult API is defined as +// part of the URLPattern specification. +struct url_pattern_result { + std::vector inputs; + url_pattern_component_result protocol; + url_pattern_component_result username; + url_pattern_component_result password; + url_pattern_component_result hostname; + url_pattern_component_result port; + url_pattern_component_result pathname; + url_pattern_component_result search; + url_pattern_component_result hash; +}; + +struct url_pattern_options { + bool ignore_case = false; + +#if ADA_TESTING + friend void PrintTo(const url_pattern_options& options, std::ostream* os) { + *os << "ignore_case: '" << options.ignore_case; + } +#endif // ADA_TESTING +}; + +// URLPattern is a Web Platform standard API for matching URLs against a +// pattern syntax (think of it as a regular expression for URLs). It is +// defined in https://wicg.github.io/urlpattern. +// More information about the URL Pattern syntax can be found at +// https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API +// +// We require all strings to be valid UTF-8: it is the user's responsibility +// to ensure that the provided strings are valid UTF-8. +template +class url_pattern { + public: + url_pattern() = default; /** - * @private - * A URL's username is an ASCII string identifying a username. It is initially - * the empty string. + * If non-null, base_url must pointer at a valid UTF-8 string. + * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec */ - std::string username{}; + result> exec( + const url_pattern_input& input, + const std::string_view* base_url = nullptr); /** - * @private - * A URL's password is an ASCII string identifying a password. It is initially - * the empty string. + * If non-null, base_url must pointer at a valid UTF-8 string. + * @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-test */ - std::string password{}; + result test(const url_pattern_input& input, + const std::string_view* base_url = nullptr); /** - * @private - * A URL's host is null or a host. It is initially null. + * @see https://urlpattern.spec.whatwg.org/#url-pattern-match + * This function expects a valid UTF-8 string if input is a string. */ - std::optional host{}; + result> match( + const url_pattern_input& input, + const std::string_view* base_url_string = nullptr); + + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol + [[nodiscard]] std::string_view get_protocol() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-username + [[nodiscard]] std::string_view get_username() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-password + [[nodiscard]] std::string_view get_password() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-hostname + [[nodiscard]] std::string_view get_hostname() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-port + [[nodiscard]] std::string_view get_port() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-pathname + [[nodiscard]] std::string_view get_pathname() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-search + [[nodiscard]] std::string_view get_search() const ada_lifetime_bound; + // @see https://urlpattern.spec.whatwg.org/#dom-urlpattern-hash + [[nodiscard]] std::string_view get_hash() const ada_lifetime_bound; + + // If ignoreCase is true, the JavaScript regular expression created for each + // pattern must use the `vi` flag. Otherwise, they must use the `v` flag. + [[nodiscard]] bool ignore_case() const; + + // @see https://urlpattern.spec.whatwg.org/#url-pattern-has-regexp-groups + [[nodiscard]] bool has_regexp_groups() const; + +#if ADA_TESTING + friend void PrintTo(const url_pattern& c, std::ostream* os) { + *os << "protocol_component: '" << c.get_protocol() << ", "; + *os << "username_component: '" << c.get_username() << ", "; + *os << "password_component: '" << c.get_password() << ", "; + *os << "hostname_component: '" << c.get_hostname() << ", "; + *os << "port_component: '" << c.get_port() << ", "; + *os << "pathname_component: '" << c.get_pathname() << ", "; + *os << "search_component: '" << c.get_search() << ", "; + *os << "hash_component: '" << c.get_hash(); + } +#endif // ADA_TESTING + + template + friend tl::expected, errors> parser::parse_url_pattern_impl( + std::variant&& input, + const std::string_view* base_url, const url_pattern_options* options); /** * @private - * A URL's port is either null or a 16-bit unsigned integer that identifies a - * networking port. It is initially null. + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - std::optional port{}; - + url_pattern_component protocol_component{}; /** * @private - * A URL's path is either an ASCII string or a list of zero or more ASCII - * strings, usually identifying a location. + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - std::string path{}; - + url_pattern_component username_component{}; /** * @private - * A URL's query is either null or an ASCII string. It is initially null. + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - std::optional query{}; - + url_pattern_component password_component{}; /** * @private - * A URL's fragment is either null or an ASCII string that can be used for - * further processing on the resource the URL's other components identify. It - * is initially null. + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - std::optional hash{}; - - /** @return true if it has an host but it is the empty string */ - [[nodiscard]] inline bool has_empty_hostname() const noexcept; - /** @return true if the URL has a (non default) port */ - [[nodiscard]] inline bool has_port() const noexcept; - /** @return true if it has a host (included an empty host) */ - [[nodiscard]] inline bool has_hostname() const noexcept; - [[nodiscard]] bool has_valid_domain() const noexcept override; - + url_pattern_component hostname_component{}; /** - * Returns a JSON string representation of this URL. + * @private + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - [[nodiscard]] std::string to_string() const override; - + url_pattern_component port_component{}; /** - * @see https://url.spec.whatwg.org/#dom-url-href - * @see https://url.spec.whatwg.org/#concept-url-serializer + * @private + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - [[nodiscard]] ada_really_inline std::string get_href() const noexcept; - + url_pattern_component pathname_component{}; /** - * The origin getter steps are to return the serialization of this's URL's - * origin. [HTML] - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#concept-url-origin + * @private + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - [[nodiscard]] std::string get_origin() const noexcept override; - + url_pattern_component search_component{}; /** - * The protocol getter steps are to return this's URL's scheme, followed by - * U+003A (:). - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#dom-url-protocol + * @private + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - [[nodiscard]] std::string get_protocol() const noexcept; - + url_pattern_component hash_component{}; /** - * Return url's host, serialized, followed by U+003A (:) and url's port, - * serialized. - * When there is no host, this function returns the empty string. - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#dom-url-host + * @private + * We can not make this private due to a LLVM bug. + * Ref: https://github.com/ada-url/ada/pull/859 */ - [[nodiscard]] std::string get_host() const noexcept; + bool ignore_case_ = false; +}; +} // namespace ada +#endif // ADA_INCLUDE_URL_PATTERN +#endif +/* end file include/ada/url_pattern.h */ +/* begin file include/ada/url_pattern_helpers.h */ +/** + * @file url_pattern_helpers.h + * @brief Declaration for the URLPattern helpers. + */ +#ifndef ADA_URL_PATTERN_HELPERS_H +#define ADA_URL_PATTERN_HELPERS_H - /** - * Return this's URL's host, serialized. - * When there is no host, this function returns the empty string. - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#dom-url-hostname - */ - [[nodiscard]] std::string get_hostname() const noexcept; - /** - * The pathname getter steps are to return the result of URL path serializing - * this's URL. - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - [[nodiscard]] std::string_view get_pathname() const noexcept; +#include +#include +#include - /** - * Compute the pathname length in bytes without instantiating a view or a - * string. - * @return size of the pathname in bytes - * @see https://url.spec.whatwg.org/#dom-url-pathname - */ - [[nodiscard]] ada_really_inline size_t get_pathname_length() const noexcept; +#if ADA_INCLUDE_URL_PATTERN +namespace ada { +enum class errors : uint8_t; +} + +namespace ada::url_pattern_helpers { + +// @see https://urlpattern.spec.whatwg.org/#token +enum class token_type : uint8_t { + INVALID_CHAR, // 0 + OPEN, // 1 + CLOSE, // 2 + REGEXP, // 3 + NAME, // 4 + CHAR, // 5 + ESCAPED_CHAR, // 6 + OTHER_MODIFIER, // 7 + ASTERISK, // 8 + END, // 9 +}; - /** - * Return U+003F (?), followed by this's URL's query. - * @return a newly allocated string. - * @see https://url.spec.whatwg.org/#dom-url-search - */ - [[nodiscard]] std::string get_search() const noexcept; +#ifdef ADA_TESTING +std::string to_string(token_type type); +#endif // ADA_TESTING - /** - * The username getter steps are to return this's URL's username. - * @return a constant reference to the underlying string. - * @see https://url.spec.whatwg.org/#dom-url-username - */ - [[nodiscard]] const std::string& get_username() const noexcept; +// @see https://urlpattern.spec.whatwg.org/#tokenize-policy +enum class token_policy { + strict, + lenient, +}; - /** - * @return Returns true on successful operation. - * @see https://url.spec.whatwg.org/#dom-url-username - */ - bool set_username(std::string_view input); +// @see https://urlpattern.spec.whatwg.org/#tokens +class token { + public: + token(token_type _type, size_t _index, std::string&& _value) + : type(_type), index(_index), value(std::move(_value)) {} - /** - * @return Returns true on success. - * @see https://url.spec.whatwg.org/#dom-url-password - */ - bool set_password(std::string_view input); + // A token has an associated type, a string, initially "invalid-char". + token_type type = token_type::INVALID_CHAR; - /** - * @return Returns true on success. - * @see https://url.spec.whatwg.org/#dom-url-port - */ - bool set_port(std::string_view input); + // A token has an associated index, a number, initially 0. It is the position + // of the first code point in the pattern string represented by the token. + size_t index = 0; - /** - * This function always succeeds. - * @see https://url.spec.whatwg.org/#dom-url-hash - */ - void set_hash(std::string_view input); + // A token has an associated value, a string, initially the empty string. It + // contains the code points from the pattern string represented by the token. + std::string value{}; +}; - /** - * This function always succeeds. - * @see https://url.spec.whatwg.org/#dom-url-search - */ - void set_search(std::string_view input); +// @see https://urlpattern.spec.whatwg.org/#pattern-parser +template +class url_pattern_parser { + public: + url_pattern_parser(F& encoding_callback_, + std::string_view segment_wildcard_regexp_) + : encoding_callback(encoding_callback_), + segment_wildcard_regexp(segment_wildcard_regexp_) {} + + bool can_continue() const { return index < tokens.size(); } + + // @see https://urlpattern.spec.whatwg.org/#try-to-consume-a-token + token* try_consume_token(token_type type); + // @see https://urlpattern.spec.whatwg.org/#try-to-consume-a-modifier-token + token* try_consume_modifier_token(); + // @see + // https://urlpattern.spec.whatwg.org/#try-to-consume-a-regexp-or-wildcard-token + token* try_consume_regexp_or_wildcard_token(const token* name_token); + // @see https://urlpattern.spec.whatwg.org/#consume-text + std::string consume_text(); + // @see https://urlpattern.spec.whatwg.org/#consume-a-required-token + bool consume_required_token(token_type type); + // @see + // https://urlpattern.spec.whatwg.org/#maybe-add-a-part-from-the-pending-fixed-value + std::optional maybe_add_part_from_the_pending_fixed_value() + ada_warn_unused; + // @see https://urlpattern.spec.whatwg.org/#add-a-part + std::optional add_part(std::string_view prefix, token* name_token, + token* regexp_or_wildcard_token, + std::string_view suyffix, + token* modifier_token) ada_warn_unused; + + std::vector tokens{}; + F& encoding_callback; + std::string segment_wildcard_regexp; + std::vector parts{}; + std::string pending_fixed_value{}; + size_t index = 0; + size_t next_numeric_name = 0; +}; - /** - * @return Returns true on success. - * @see https://url.spec.whatwg.org/#dom-url-search - */ - bool set_pathname(std::string_view input); +// @see https://urlpattern.spec.whatwg.org/#tokenizer +class Tokenizer { + public: + explicit Tokenizer(std::string_view new_input, token_policy new_policy) + : input(new_input), policy(new_policy) {} - /** - * @return Returns true on success. - * @see https://url.spec.whatwg.org/#dom-url-host - */ - bool set_host(std::string_view input); + // @see https://urlpattern.spec.whatwg.org/#get-the-next-code-point + constexpr void get_next_code_point(); - /** - * @return Returns true on success. - * @see https://url.spec.whatwg.org/#dom-url-hostname - */ - bool set_hostname(std::string_view input); + // @see https://urlpattern.spec.whatwg.org/#seek-and-get-the-next-code-point + constexpr void seek_and_get_next_code_point(size_t index); - /** - * @return Returns true on success. - * @see https://url.spec.whatwg.org/#dom-url-protocol - */ - bool set_protocol(std::string_view input); + // @see https://urlpattern.spec.whatwg.org/#add-a-token - /** - * @see https://url.spec.whatwg.org/#dom-url-href - */ - bool set_href(std::string_view input); + void add_token(token_type type, size_t next_position, size_t value_position, + size_t value_length); - /** - * The password getter steps are to return this's URL's password. - * @return a constant reference to the underlying string. - * @see https://url.spec.whatwg.org/#dom-url-password - */ - [[nodiscard]] const std::string& get_password() const noexcept; + // @see https://urlpattern.spec.whatwg.org/#add-a-token-with-default-length + void add_token_with_default_length(token_type type, size_t next_position, + size_t value_position); - /** - * Return this's URL's port, serialized. - * @return a newly constructed string representing the port. - * @see https://url.spec.whatwg.org/#dom-url-port - */ - [[nodiscard]] std::string get_port() const noexcept; + // @see + // https://urlpattern.spec.whatwg.org/#add-a-token-with-default-position-and-length + void add_token_with_defaults(token_type type); - /** - * Return U+0023 (#), followed by this's URL's fragment. - * @return a newly constructed string representing the hash. - * @see https://url.spec.whatwg.org/#dom-url-hash - */ - [[nodiscard]] std::string get_hash() const noexcept; + // @see https://urlpattern.spec.whatwg.org/#process-a-tokenizing-error + std::optional process_tokenizing_error( + size_t next_position, size_t value_position) ada_warn_unused; - /** - * A URL includes credentials if its username or password is not the empty - * string. - */ - [[nodiscard]] ada_really_inline bool has_credentials() const noexcept; - - /** - * Useful for implementing efficient serialization for the URL. - * - * https://user:pass@example.com:1234/foo/bar?baz#quux - * | | | | ^^^^| | | - * | | | | | | | `----- hash_start - * | | | | | | `--------- search_start - * | | | | | `----------------- pathname_start - * | | | | `--------------------- port - * | | | `----------------------- host_end - * | | `---------------------------------- host_start - * | `--------------------------------------- username_end - * `--------------------------------------------- protocol_end - * - * Inspired after servo/url - * - * @return a newly constructed component. - * - * @see - * https://github.com/servo/rust-url/blob/b65a45515c10713f6d212e6726719a020203cc98/url/src/quirks.rs#L31 - */ - [[nodiscard]] ada_really_inline ada::url_components get_components() - const noexcept; - /** @return true if the URL has a hash component */ - [[nodiscard]] inline bool has_hash() const noexcept override; - /** @return true if the URL has a search component */ - [[nodiscard]] inline bool has_search() const noexcept override; + friend tl::expected, errors> tokenize( + std::string_view input, token_policy policy); private: - friend ada::url ada::parser::parse_url(std::string_view, - const ada::url*); - friend ada::url_aggregator ada::parser::parse_url( - std::string_view, const ada::url_aggregator*); - friend void ada::helpers::strip_trailing_spaces_from_opaque_path( - ada::url& url) noexcept; + // has an associated input, a pattern string, initially the empty string. + std::string input; + // has an associated policy, a tokenize policy, initially "strict". + token_policy policy; + // has an associated token list, a token list, initially an empty list. + std::vector token_list{}; + // has an associated index, a number, initially 0. + size_t index = 0; + // has an associated next index, a number, initially 0. + size_t next_index = 0; + // has an associated code point, a Unicode code point, initially null. + char32_t code_point{}; +}; - friend ada::url ada::parser::parse_url_impl(std::string_view, - const ada::url*); - friend ada::url_aggregator ada::parser::parse_url_impl< - ada::url_aggregator, true>(std::string_view, const ada::url_aggregator*); +// @see https://urlpattern.spec.whatwg.org/#constructor-string-parser +template +struct constructor_string_parser { + explicit constructor_string_parser(std::string_view new_input, + std::vector&& new_token_list) + : input(new_input), token_list(std::move(new_token_list)) {} + // @see https://urlpattern.spec.whatwg.org/#parse-a-constructor-string + static tl::expected parse(std::string_view input); + + // @see https://urlpattern.spec.whatwg.org/#constructor-string-parser-state + enum class State { + INIT, + PROTOCOL, + AUTHORITY, + USERNAME, + PASSWORD, + HOSTNAME, + PORT, + PATHNAME, + SEARCH, + HASH, + DONE, + }; - inline void update_unencoded_base_hash(std::string_view input); - inline void update_base_hostname(std::string_view input); - inline void update_base_search(std::string_view input); - inline void update_base_search(std::string_view input, - const uint8_t query_percent_encode_set[]); - inline void update_base_search(std::optional input); - inline void update_base_pathname(std::string_view input); - inline void update_base_username(std::string_view input); - inline void update_base_password(std::string_view input); - inline void update_base_port(std::optional input); + // @see + // https://urlpattern.spec.whatwg.org/#compute-protocol-matches-a-special-scheme-flag + std::optional compute_protocol_matches_special_scheme_flag(); - /** - * Sets the host or hostname according to override condition. - * Return true on success. - * @see https://url.spec.whatwg.org/#hostname-state - */ - template - bool set_host_or_hostname(std::string_view input); + private: + // @see https://urlpattern.spec.whatwg.org/#rewind + constexpr void rewind(); + + // @see https://urlpattern.spec.whatwg.org/#is-a-hash-prefix + constexpr bool is_hash_prefix(); + + // @see https://urlpattern.spec.whatwg.org/#is-a-search-prefix + constexpr bool is_search_prefix(); + + // @see https://urlpattern.spec.whatwg.org/#change-state + void change_state(State state, size_t skip); + + // @see https://urlpattern.spec.whatwg.org/#is-a-group-open + constexpr bool is_group_open() const; + + // @see https://urlpattern.spec.whatwg.org/#is-a-group-close + constexpr bool is_group_close() const; + + // @see https://urlpattern.spec.whatwg.org/#is-a-protocol-suffix + constexpr bool is_protocol_suffix() const; + + // @see https://urlpattern.spec.whatwg.org/#next-is-authority-slashes + constexpr bool next_is_authority_slashes() const; + + // @see https://urlpattern.spec.whatwg.org/#is-an-identity-terminator + constexpr bool is_an_identity_terminator() const; + + // @see https://urlpattern.spec.whatwg.org/#is-a-pathname-start + constexpr bool is_pathname_start() const; + + // @see https://urlpattern.spec.whatwg.org/#is-a-password-prefix + constexpr bool is_password_prefix() const; + + // @see https://urlpattern.spec.whatwg.org/#is-an-ipv6-open + constexpr bool is_an_ipv6_open() const; + + // @see https://urlpattern.spec.whatwg.org/#is-an-ipv6-close + constexpr bool is_an_ipv6_close() const; + + // @see https://urlpattern.spec.whatwg.org/#is-a-port-prefix + constexpr bool is_port_prefix() const; + + // @see https://urlpattern.spec.whatwg.org/#is-a-non-special-pattern-char + constexpr bool is_non_special_pattern_char(size_t index, + uint32_t value) const; + + // @see https://urlpattern.spec.whatwg.org/#get-a-safe-token + constexpr const token* get_safe_token(size_t index) const; + + // @see https://urlpattern.spec.whatwg.org/#make-a-component-string + std::string make_component_string(); + // has an associated input, a string, which must be set upon creation. + std::string input; + // has an associated token list, a token list, which must be set upon + // creation. + std::vector token_list; + // has an associated result, a URLPatternInit, initially set to a new + // URLPatternInit. + url_pattern_init result{}; + // has an associated component start, a number, initially set to 0. + size_t component_start = 0; + // has an associated token index, a number, initially set to 0. + size_t token_index = 0; + // has an associated token increment, a number, initially set to 1. + size_t token_increment = 1; + // has an associated group depth, a number, initially set to 0. + size_t group_depth = 0; + // has an associated hostname IPv6 bracket depth, a number, initially set to + // 0. + size_t hostname_ipv6_bracket_depth = 0; + // has an associated protocol matches a special scheme flag, a boolean, + // initially set to false. + bool protocol_matches_a_special_scheme_flag = false; + // has an associated state, a string, initially set to "init". + State state = State::INIT; +}; - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-ipv4-parser - */ - [[nodiscard]] bool parse_ipv4(std::string_view input); +// @see https://urlpattern.spec.whatwg.org/#canonicalize-a-protocol +tl::expected canonicalize_protocol(std::string_view input); - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-ipv6-parser - */ - [[nodiscard]] bool parse_ipv6(std::string_view input); +// @see https://wicg.github.io/urlpattern/#canonicalize-a-username +tl::expected canonicalize_username(std::string_view input); - /** - * Return true on success. - * @see https://url.spec.whatwg.org/#concept-opaque-host-parser - */ - [[nodiscard]] bool parse_opaque_host(std::string_view input); +// @see https://wicg.github.io/urlpattern/#canonicalize-a-password +tl::expected canonicalize_password(std::string_view input); - /** - * A URL's scheme is an ASCII string that identifies the type of URL and can - * be used to dispatch a URL for further processing after parsing. It is - * initially the empty string. We only set non_special_scheme when the scheme - * is non-special, otherwise we avoid constructing string. - * - * Special schemes are stored in ada::scheme::details::is_special_list so we - * typically do not need to store them in each url instance. - */ - std::string non_special_scheme{}; +// @see https://wicg.github.io/urlpattern/#canonicalize-a-password +tl::expected canonicalize_hostname(std::string_view input); - /** - * A URL cannot have a username/password/port if its host is null or the empty - * string, or its scheme is "file". - */ - [[nodiscard]] inline bool cannot_have_credentials_or_port() const; +// @see https://wicg.github.io/urlpattern/#canonicalize-an-ipv6-hostname +tl::expected canonicalize_ipv6_hostname( + std::string_view input); - ada_really_inline size_t parse_port( - std::string_view view, bool check_trailing_content) noexcept override; +// @see https://wicg.github.io/urlpattern/#canonicalize-a-port +tl::expected canonicalize_port(std::string_view input); - ada_really_inline size_t parse_port(std::string_view view) noexcept override { - return this->parse_port(view, false); - } +// @see https://wicg.github.io/urlpattern/#canonicalize-a-port +tl::expected canonicalize_port_with_protocol( + std::string_view input, std::string_view protocol); - /** - * Take the scheme from another URL. The scheme string is copied from the - * provided url. - */ - inline void copy_scheme(const ada::url& u); +// @see https://wicg.github.io/urlpattern/#canonicalize-a-pathname +tl::expected canonicalize_pathname(std::string_view input); - /** - * Parse the host from the provided input. We assume that - * the input does not contain spaces or tabs. Control - * characters and spaces are not trimmed (they should have - * been removed if needed). - * Return true on success. - * @see https://url.spec.whatwg.org/#host-parsing - */ - [[nodiscard]] ada_really_inline bool parse_host(std::string_view input); +// @see https://wicg.github.io/urlpattern/#canonicalize-an-opaque-pathname +tl::expected canonicalize_opaque_pathname( + std::string_view input); - template - [[nodiscard]] ada_really_inline bool parse_scheme(std::string_view input); +// @see https://wicg.github.io/urlpattern/#canonicalize-a-search +tl::expected canonicalize_search(std::string_view input); - inline void clear_pathname() override; - inline void clear_search() override; - inline void set_protocol_as_file(); +// @see https://wicg.github.io/urlpattern/#canonicalize-a-hash +tl::expected canonicalize_hash(std::string_view input); - /** - * Parse the path from the provided input. - * Return true on success. Control characters not - * trimmed from the ends (they should have - * been removed if needed). - * - * The input is expected to be UTF-8. - * - * @see https://url.spec.whatwg.org/ - */ - ada_really_inline void parse_path(std::string_view input); +// @see https://urlpattern.spec.whatwg.org/#tokenize +tl::expected, errors> tokenize(std::string_view input, + token_policy policy); - /** - * Set the scheme for this URL. The provided scheme should be a valid - * scheme string, be lower-cased, not contain spaces or tabs. It should - * have no spurious trailing or leading content. - */ - inline void set_scheme(std::string&& new_scheme) noexcept; +// @see https://urlpattern.spec.whatwg.org/#process-a-base-url-string +std::string process_base_url_string(std::string_view input, + url_pattern_init::process_type type); - /** - * Take the scheme from another URL. The scheme string is moved from the - * provided url. - */ - inline void copy_scheme(ada::url&& u) noexcept; +// @see https://urlpattern.spec.whatwg.org/#escape-a-pattern-string +std::string escape_pattern_string(std::string_view input); -}; // struct url +// @see https://urlpattern.spec.whatwg.org/#escape-a-regexp-string +std::string escape_regexp_string(std::string_view input); -inline std::ostream& operator<<(std::ostream& out, const ada::url& u); -} // namespace ada +// @see https://urlpattern.spec.whatwg.org/#is-an-absolute-pathname +constexpr bool is_absolute_pathname( + std::string_view input, url_pattern_init::process_type type) noexcept; -#endif // ADA_URL_H -/* end file include/ada/url.h */ +// @see https://urlpattern.spec.whatwg.org/#parse-a-pattern-string +template +tl::expected, errors> parse_pattern_string( + std::string_view input, url_pattern_compile_component_options& options, + F& encoding_callback); -#include -#include -#if ADA_REGULAR_VISUAL_STUDIO -#include -#endif // ADA_REGULAR_VISUAL_STUDIO +// @see https://urlpattern.spec.whatwg.org/#generate-a-pattern-string +std::string generate_pattern_string( + std::vector& part_list, + url_pattern_compile_component_options& options); -namespace ada { +// @see +// https://urlpattern.spec.whatwg.org/#generate-a-regular-expression-and-name-list +std::tuple> +generate_regular_expression_and_name_list( + const std::vector& part_list, + url_pattern_compile_component_options options); -[[nodiscard]] ada_really_inline bool url_base::is_special() const noexcept { - return type != ada::scheme::NOT_SPECIAL; -} +// @see https://urlpattern.spec.whatwg.org/#hostname-pattern-is-an-ipv6-address +bool is_ipv6_address(std::string_view input) noexcept; -[[nodiscard]] inline uint16_t url_base::get_special_port() const noexcept { - return ada::scheme::get_special_port(type); -} +// @see +// https://urlpattern.spec.whatwg.org/#protocol-component-matches-a-special-scheme +template +bool protocol_component_matches_special_scheme( + ada::url_pattern_component& input); -[[nodiscard]] ada_really_inline uint16_t -url_base::scheme_default_port() const noexcept { - return scheme::get_special_port(type); -} +// @see https://urlpattern.spec.whatwg.org/#convert-a-modifier-to-a-string +std::string convert_modifier_to_string(url_pattern_part_modifier modifier); -} // namespace ada +// @see https://urlpattern.spec.whatwg.org/#generate-a-segment-wildcard-regexp +std::string generate_segment_wildcard_regexp( + url_pattern_compile_component_options options); -#endif // ADA_URL_BASE_INL_H -/* end file include/ada/url_base-inl.h */ -/* begin file include/ada/url-inl.h */ -/** - * @file url-inl.h - * @brief Definitions for the URL - */ -#ifndef ADA_URL_INL_H -#define ADA_URL_INL_H +} // namespace ada::url_pattern_helpers +#endif // ADA_INCLUDE_URL_PATTERN +#endif +/* end file include/ada/url_pattern_helpers.h */ -#include #include -#if ADA_REGULAR_VISUAL_STUDIO -#include -#endif // ADA_REGULAR_VISUAL_STUDIO - -namespace ada { -[[nodiscard]] ada_really_inline bool url::has_credentials() const noexcept { - return !username.empty() || !password.empty(); -} -[[nodiscard]] ada_really_inline bool url::has_port() const noexcept { - return port.has_value(); -} -[[nodiscard]] inline bool url::cannot_have_credentials_or_port() const { - return !host.has_value() || host.value().empty() || - type == ada::scheme::type::FILE; -} -[[nodiscard]] inline bool url::has_empty_hostname() const noexcept { - if (!host.has_value()) { - return false; - } - return host.value().empty(); -} -[[nodiscard]] inline bool url::has_hostname() const noexcept { - return host.has_value(); -} -inline std::ostream& operator<<(std::ostream& out, const ada::url& u) { - return out << u.to_string(); -} - -[[nodiscard]] size_t url::get_pathname_length() const noexcept { - return path.size(); -} - -[[nodiscard]] ada_really_inline ada::url_components url::get_components() - const noexcept { - url_components out{}; - - // protocol ends with ':'. for example: "https:" - out.protocol_end = uint32_t(get_protocol().size()); - - // Trailing index is always the next character of the current one. - size_t running_index = out.protocol_end; - - if (host.has_value()) { - // 2 characters for "//" and 1 character for starting index - out.host_start = out.protocol_end + 2; - - if (has_credentials()) { - out.username_end = uint32_t(out.host_start + username.size()); - - out.host_start += uint32_t(username.size()); - - if (!password.empty()) { - out.host_start += uint32_t(password.size() + 1); - } - - out.host_end = uint32_t(out.host_start + host.value().size()); - } else { - out.username_end = out.host_start; +#include +#include - // Host does not start with "@" if it does not include credentials. - out.host_end = uint32_t(out.host_start + host.value().size()) - 1; +namespace ada::parser { +#if ADA_INCLUDE_URL_PATTERN +template +tl::expected, errors> parse_url_pattern_impl( + std::variant&& input, + const std::string_view* base_url, const url_pattern_options* options) { + // Let init be null. + url_pattern_init init; + + // If input is a scalar value string then: + if (std::holds_alternative(input)) { + // Set init to the result of running parse a constructor string given input. + auto parse_result = + url_pattern_helpers::constructor_string_parser::parse( + std::get(input)); + if (!parse_result) { + ada_log("constructor_string_parser::parse failed"); + return tl::unexpected(parse_result.error()); + } + init = std::move(*parse_result); + // If baseURL is null and init["protocol"] does not exist, then throw a + // TypeError. + if (!base_url && !init.protocol) { + ada_log("base url is null and protocol is not set"); + return tl::unexpected(errors::type_error); } - running_index = out.host_end + 1; + // If baseURL is not null, set init["baseURL"] to baseURL. + if (base_url) { + init.base_url = std::string(*base_url); + } } else { - // Update host start and end date to the same index, since it does not - // exist. - out.host_start = out.protocol_end; - out.host_end = out.host_start; - - if (!has_opaque_path && checkers::begins_with(path, "//")) { - // If url's host is null, url does not have an opaque path, url's path's - // size is greater than 1, and url's path[0] is the empty string, then - // append U+002F (/) followed by U+002E (.) to output. - running_index = out.protocol_end + 2; - } else { - running_index = out.protocol_end; + // Assert: input is a URLPatternInit. + ADA_ASSERT_TRUE(std::holds_alternative(input)); + // If baseURL is not null, then throw a TypeError. + if (base_url) { + ada_log("base url is not null"); + return tl::unexpected(errors::type_error); + } + // Optimization: Avoid copy by moving the input value. + // Set init to input. + init = std::move(std::get(input)); + } + + // Let processedInit be the result of process a URLPatternInit given init, + // "pattern", null, null, null, null, null, null, null, and null. + auto processed_init = + url_pattern_init::process(init, url_pattern_init::process_type::pattern); + if (!processed_init) { + ada_log("url_pattern_init::process failed for init and 'pattern'"); + return tl::unexpected(processed_init.error()); + } + + // For each componentName of "protocol", "username", "password", "hostname", + // "port", "pathname", "search", "hash" If processedInit[componentName] does + // not exist, then set processedInit[componentName] to "*". + ADA_ASSERT_TRUE(processed_init.has_value()); + if (!processed_init->protocol) processed_init->protocol = "*"; + if (!processed_init->username) processed_init->username = "*"; + if (!processed_init->password) processed_init->password = "*"; + if (!processed_init->hostname) processed_init->hostname = "*"; + if (!processed_init->port) processed_init->port = "*"; + if (!processed_init->pathname) processed_init->pathname = "*"; + if (!processed_init->search) processed_init->search = "*"; + if (!processed_init->hash) processed_init->hash = "*"; + + ada_log("-- processed_init->protocol: ", processed_init->protocol.value()); + ada_log("-- processed_init->username: ", processed_init->username.value()); + ada_log("-- processed_init->password: ", processed_init->password.value()); + ada_log("-- processed_init->hostname: ", processed_init->hostname.value()); + ada_log("-- processed_init->port: ", processed_init->port.value()); + ada_log("-- processed_init->pathname: ", processed_init->pathname.value()); + ada_log("-- processed_init->search: ", processed_init->search.value()); + ada_log("-- processed_init->hash: ", processed_init->hash.value()); + + // If processedInit["protocol"] is a special scheme and processedInit["port"] + // is a string which represents its corresponding default port in radix-10 + // using ASCII digits then set processedInit["port"] to the empty string. + // TODO: Optimization opportunity. + if (scheme::is_special(*processed_init->protocol)) { + std::string_view port = processed_init->port.value(); + if (std::to_string(scheme::get_special_port(*processed_init->protocol)) == + port) { + processed_init->port->clear(); } } - if (port.has_value()) { - out.port = *port; - running_index += helpers::fast_digit_count(*port) + 1; // Port omits ':' - } - - out.pathname_start = uint32_t(running_index); - - running_index += path.size(); + // Let urlPattern be a new URL pattern. + url_pattern url_pattern_{}; + + // Set urlPattern's protocol component to the result of compiling a component + // given processedInit["protocol"], canonicalize a protocol, and default + // options. + auto protocol_component = url_pattern_component::compile( + processed_init->protocol.value(), + url_pattern_helpers::canonicalize_protocol, + url_pattern_compile_component_options::DEFAULT); + if (!protocol_component) { + ada_log("url_pattern_component::compile failed for protocol ", + processed_init->protocol.value()); + return tl::unexpected(protocol_component.error()); + } + url_pattern_.protocol_component = std::move(*protocol_component); + + // Set urlPattern's username component to the result of compiling a component + // given processedInit["username"], canonicalize a username, and default + // options. + auto username_component = url_pattern_component::compile( + processed_init->username.value(), + url_pattern_helpers::canonicalize_username, + url_pattern_compile_component_options::DEFAULT); + if (!username_component) { + ada_log("url_pattern_component::compile failed for username ", + processed_init->username.value()); + return tl::unexpected(username_component.error()); + } + url_pattern_.username_component = std::move(*username_component); + + // Set urlPattern's password component to the result of compiling a component + // given processedInit["password"], canonicalize a password, and default + // options. + auto password_component = url_pattern_component::compile( + processed_init->password.value(), + url_pattern_helpers::canonicalize_password, + url_pattern_compile_component_options::DEFAULT); + if (!password_component) { + ada_log("url_pattern_component::compile failed for password ", + processed_init->password.value()); + return tl::unexpected(password_component.error()); + } + url_pattern_.password_component = std::move(*password_component); + + // TODO: Optimization opportunity. The following if statement can be + // simplified. + // If the result running hostname pattern is an IPv6 address given + // processedInit["hostname"] is true, then set urlPattern's hostname component + // to the result of compiling a component given processedInit["hostname"], + // canonicalize an IPv6 hostname, and hostname options. + if (url_pattern_helpers::is_ipv6_address(processed_init->hostname.value())) { + ada_log("processed_init->hostname is ipv6 address"); + // then set urlPattern's hostname component to the result of compiling a + // component given processedInit["hostname"], canonicalize an IPv6 hostname, + // and hostname options. + auto hostname_component = url_pattern_component::compile( + processed_init->hostname.value(), + url_pattern_helpers::canonicalize_ipv6_hostname, + url_pattern_compile_component_options::DEFAULT); + if (!hostname_component) { + ada_log("url_pattern_component::compile failed for ipv6 hostname ", + processed_init->hostname.value()); + return tl::unexpected(hostname_component.error()); + } + url_pattern_.hostname_component = std::move(*hostname_component); + } else { + // Otherwise, set urlPattern's hostname component to the result of compiling + // a component given processedInit["hostname"], canonicalize a hostname, and + // hostname options. + auto hostname_component = url_pattern_component::compile( + processed_init->hostname.value(), + url_pattern_helpers::canonicalize_hostname, + url_pattern_compile_component_options::HOSTNAME); + if (!hostname_component) { + ada_log("url_pattern_component::compile failed for hostname ", + processed_init->hostname.value()); + return tl::unexpected(hostname_component.error()); + } + url_pattern_.hostname_component = std::move(*hostname_component); + } + + // Set urlPattern's port component to the result of compiling a component + // given processedInit["port"], canonicalize a port, and default options. + auto port_component = url_pattern_component::compile( + processed_init->port.value(), url_pattern_helpers::canonicalize_port, + url_pattern_compile_component_options::DEFAULT); + if (!port_component) { + ada_log("url_pattern_component::compile failed for port ", + processed_init->port.value()); + return tl::unexpected(port_component.error()); + } + url_pattern_.port_component = std::move(*port_component); + + // Let compileOptions be a copy of the default options with the ignore case + // property set to options["ignoreCase"]. + auto compile_options = url_pattern_compile_component_options::DEFAULT; + if (options) { + compile_options.ignore_case = options->ignore_case; + } + + // TODO: Optimization opportunity: Simplify this if statement. + // If the result of running protocol component matches a special scheme given + // urlPattern's protocol component is true, then: + if (url_pattern_helpers::protocol_component_matches_special_scheme< + regex_provider>(url_pattern_.protocol_component)) { + // Let pathCompileOptions be copy of the pathname options with the ignore + // case property set to options["ignoreCase"]. + auto path_compile_options = url_pattern_compile_component_options::PATHNAME; + if (options) { + path_compile_options.ignore_case = options->ignore_case; + } - if (query.has_value()) { - out.search_start = uint32_t(running_index); - running_index += get_search().size(); - if (get_search().empty()) { - running_index++; + // Set urlPattern's pathname component to the result of compiling a + // component given processedInit["pathname"], canonicalize a pathname, and + // pathCompileOptions. + auto pathname_component = url_pattern_component::compile( + processed_init->pathname.value(), + url_pattern_helpers::canonicalize_pathname, path_compile_options); + if (!pathname_component) { + ada_log("url_pattern_component::compile failed for pathname ", + processed_init->pathname.value()); + return tl::unexpected(pathname_component.error()); + } + url_pattern_.pathname_component = std::move(*pathname_component); + } else { + // Otherwise set urlPattern's pathname component to the result of compiling + // a component given processedInit["pathname"], canonicalize an opaque + // pathname, and compileOptions. + auto pathname_component = url_pattern_component::compile( + processed_init->pathname.value(), + url_pattern_helpers::canonicalize_opaque_pathname, compile_options); + if (!pathname_component) { + ada_log("url_pattern_component::compile failed for opaque pathname ", + processed_init->pathname.value()); + return tl::unexpected(pathname_component.error()); } + url_pattern_.pathname_component = std::move(*pathname_component); } - if (hash.has_value()) { - out.hash_start = uint32_t(running_index); + // Set urlPattern's search component to the result of compiling a component + // given processedInit["search"], canonicalize a search, and compileOptions. + auto search_component = url_pattern_component::compile( + processed_init->search.value(), url_pattern_helpers::canonicalize_search, + compile_options); + if (!search_component) { + ada_log("url_pattern_component::compile failed for search ", + processed_init->search.value()); + return tl::unexpected(search_component.error()); } + url_pattern_.search_component = std::move(*search_component); - return out; + // Set urlPattern's hash component to the result of compiling a component + // given processedInit["hash"], canonicalize a hash, and compileOptions. + auto hash_component = url_pattern_component::compile( + processed_init->hash.value(), url_pattern_helpers::canonicalize_hash, + compile_options); + if (!hash_component) { + ada_log("url_pattern_component::compile failed for hash ", + processed_init->hash.value()); + return tl::unexpected(hash_component.error()); + } + url_pattern_.hash_component = std::move(*hash_component); + + // Return urlPattern. + return url_pattern_; } +#endif // ADA_INCLUDE_URL_PATTERN -inline void url::update_base_hostname(std::string_view input) { host = input; } +} // namespace ada::parser + +#endif // ADA_PARSER_INL_H +/* end file include/ada/parser-inl.h */ +/* begin file include/ada/scheme-inl.h */ +/** + * @file scheme-inl.h + * @brief Definitions for the URL scheme. + */ +#ifndef ADA_SCHEME_INL_H +#define ADA_SCHEME_INL_H + + +namespace ada::scheme { + +/** + * @namespace ada::scheme::details + * @brief Includes the definitions for scheme specific entities + */ +namespace details { +// for use with is_special and get_special_port +// Spaces, if present, are removed from URL. +constexpr std::string_view is_special_list[] = {"http", " ", "https", "ws", + "ftp", "wss", "file", " "}; +// for use with get_special_port +constexpr uint16_t special_ports[] = {80, 0, 443, 80, 21, 443, 0, 0}; +} // namespace details + +/**** + * @private + * In is_special, get_scheme_type, and get_special_port, we + * use a standard hashing technique to find the index of the scheme in + * the is_special_list. The hashing technique is based on the size of + * the scheme and the first character of the scheme. It ensures that we + * do at most one string comparison per call. If the protocol is + * predictible (e.g., it is always "http"), we can get a better average + * performance by using a simpler approach where we loop and compare + * scheme with all possible protocols starting with the most likely + * protocol. Doing multiple comparisons may have a poor worst case + * performance, however. In this instance, we choose a potentially + * slightly lower best-case performance for a better worst-case + * performance. We can revisit this choice at any time. + * + * Reference: + * Schmidt, Douglas C. "Gperf: A perfect hash function generator." + * More C++ gems 17 (2000). + * + * Reference: https://en.wikipedia.org/wiki/Perfect_hash_function + * + * Reference: https://github.com/ada-url/ada/issues/617 + ****/ + +ada_really_inline constexpr bool is_special(std::string_view scheme) { + if (scheme.empty()) { + return false; + } + int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; + const std::string_view target = details::is_special_list[hash_value]; + return (target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1)); +} +constexpr uint16_t get_special_port(std::string_view scheme) noexcept { + if (scheme.empty()) { + return 0; + } + int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; + const std::string_view target = details::is_special_list[hash_value]; + if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) { + return details::special_ports[hash_value]; + } else { + return 0; + } +} +constexpr uint16_t get_special_port(ada::scheme::type type) noexcept { + return details::special_ports[int(type)]; +} +constexpr ada::scheme::type get_scheme_type(std::string_view scheme) noexcept { + if (scheme.empty()) { + return ada::scheme::NOT_SPECIAL; + } + int hash_value = (2 * scheme.size() + (unsigned)(scheme[0])) & 7; + const std::string_view target = details::is_special_list[hash_value]; + if ((target[0] == scheme[0]) && (target.substr(1) == scheme.substr(1))) { + return ada::scheme::type(hash_value); + } else { + return ada::scheme::NOT_SPECIAL; + } +} + +} // namespace ada::scheme + +#endif // ADA_SCHEME_INL_H +/* end file include/ada/scheme-inl.h */ +/* begin file include/ada/serializers.h */ +/** + * @file serializers.h + * @brief Definitions for the URL serializers. + */ +#ifndef ADA_SERIALIZERS_H +#define ADA_SERIALIZERS_H + + +#include +#include + +/** + * @namespace ada::serializers + * @brief Includes the definitions for URL serializers + */ +namespace ada::serializers { + +/** + * Finds and returns the longest sequence of 0 values in a ipv6 input. + */ +void find_longest_sequence_of_ipv6_pieces( + const std::array& address, size_t& compress, + size_t& compress_length) noexcept; + +/** + * Serializes an ipv6 address. + * @details An IPv6 address is a 128-bit unsigned integer that identifies a + * network address. + * @see https://url.spec.whatwg.org/#concept-ipv6-serializer + */ +std::string ipv6(const std::array& address) noexcept; + +/** + * Serializes an ipv4 address. + * @details An IPv4 address is a 32-bit unsigned integer that identifies a + * network address. + * @see https://url.spec.whatwg.org/#concept-ipv4-serializer + */ +std::string ipv4(uint64_t address) noexcept; + +} // namespace ada::serializers + +#endif // ADA_SERIALIZERS_H +/* end file include/ada/serializers.h */ +/* begin file include/ada/state.h */ +/** + * @file state.h + * @brief Definitions for the states of the URL state machine. + */ +#ifndef ADA_STATE_H +#define ADA_STATE_H + + +#include + +namespace ada { + +/** + * @see https://url.spec.whatwg.org/#url-parsing + */ +enum class state { + /** + * @see https://url.spec.whatwg.org/#authority-state + */ + AUTHORITY, + + /** + * @see https://url.spec.whatwg.org/#scheme-start-state + */ + SCHEME_START, + + /** + * @see https://url.spec.whatwg.org/#scheme-state + */ + SCHEME, + + /** + * @see https://url.spec.whatwg.org/#host-state + */ + HOST, + + /** + * @see https://url.spec.whatwg.org/#no-scheme-state + */ + NO_SCHEME, + + /** + * @see https://url.spec.whatwg.org/#fragment-state + */ + FRAGMENT, + + /** + * @see https://url.spec.whatwg.org/#relative-state + */ + RELATIVE_SCHEME, + + /** + * @see https://url.spec.whatwg.org/#relative-slash-state + */ + RELATIVE_SLASH, + + /** + * @see https://url.spec.whatwg.org/#file-state + */ + FILE, + + /** + * @see https://url.spec.whatwg.org/#file-host-state + */ + FILE_HOST, + + /** + * @see https://url.spec.whatwg.org/#file-slash-state + */ + FILE_SLASH, + + /** + * @see https://url.spec.whatwg.org/#path-or-authority-state + */ + PATH_OR_AUTHORITY, + + /** + * @see https://url.spec.whatwg.org/#special-authority-ignore-slashes-state + */ + SPECIAL_AUTHORITY_IGNORE_SLASHES, + + /** + * @see https://url.spec.whatwg.org/#special-authority-slashes-state + */ + SPECIAL_AUTHORITY_SLASHES, + + /** + * @see https://url.spec.whatwg.org/#special-relative-or-authority-state + */ + SPECIAL_RELATIVE_OR_AUTHORITY, + + /** + * @see https://url.spec.whatwg.org/#query-state + */ + QUERY, + + /** + * @see https://url.spec.whatwg.org/#path-state + */ + PATH, + + /** + * @see https://url.spec.whatwg.org/#path-start-state + */ + PATH_START, + + /** + * @see https://url.spec.whatwg.org/#cannot-be-a-base-url-path-state + */ + OPAQUE_PATH, + + /** + * @see https://url.spec.whatwg.org/#port-state + */ + PORT, +}; + +/** + * Stringify a URL state machine state. + */ +ada_warn_unused std::string to_string(ada::state s); + +} // namespace ada + +#endif // ADA_STATE_H +/* end file include/ada/state.h */ +/* begin file include/ada/unicode.h */ +/** + * @file unicode.h + * @brief Definitions for all unicode specific functions. + */ +#ifndef ADA_UNICODE_H +#define ADA_UNICODE_H + + +#include +#include +#include + +/** + * Unicode operations. These functions are not part of our public API and may + * change at any time. + * + * @private + * @namespace ada::unicode + * @brief Includes the definitions for unicode operations + */ +namespace ada::unicode { + +/** + * @private + * We receive a UTF-8 string representing a domain name. + * If the string is percent encoded, we apply percent decoding. + * + * Given a domain, we need to identify its labels. + * They are separated by label-separators: + * + * U+002E (.) FULL STOP + * U+FF0E FULLWIDTH FULL STOP + * U+3002 IDEOGRAPHIC FULL STOP + * U+FF61 HALFWIDTH IDEOGRAPHIC FULL STOP + * + * They are all mapped to U+002E. + * + * We process each label into a string that should not exceed 63 octets. + * If the string is already punycode (starts with "xn--"), then we must + * scan it to look for unallowed code points. + * Otherwise, if the string is not pure ASCII, we need to transcode it + * to punycode by following RFC 3454 which requires us to + * - Map characters (see section 3), + * - Normalize (see section 4), + * - Reject forbidden characters, + * - Check for right-to-left characters and if so, check all requirements (see + * section 6), + * - Optionally reject based on unassigned code points (section 7). + * + * The Unicode standard provides a table of code points with a mapping, a list + * of forbidden code points and so forth. This table is subject to change and + * will vary based on the implementation. For Unicode 15, the table is at + * https://www.unicode.org/Public/idna/15.0.0/IdnaMappingTable.txt + * If you use ICU, they parse this table and map it to code using a Python + * script. + * + * The resulting strings should not exceed 255 octets according to RFC 1035 + * section 2.3.4. ICU checks for label size and domain size, but these errors + * are ignored. + * + * @see https://url.spec.whatwg.org/#concept-domain-to-ascii + * + */ +bool to_ascii(std::optional& out, std::string_view plain, + size_t first_percent); + +/** + * @private + * Checks if the input has tab or newline characters. + * + * @attention The has_tabs_or_newline function is a bottleneck and it is simple + * enough that compilers like GCC can 'autovectorize it'. + */ +ada_really_inline bool has_tabs_or_newline( + std::string_view user_input) noexcept; + +/** + * @private + * Checks if the input is a forbidden host code point. + * @see https://url.spec.whatwg.org/#forbidden-host-code-point + */ +ada_really_inline constexpr bool is_forbidden_host_code_point(char c) noexcept; + +/** + * @private + * Checks if the input contains a forbidden domain code point. + * @see https://url.spec.whatwg.org/#forbidden-domain-code-point + */ +ada_really_inline constexpr bool contains_forbidden_domain_code_point( + const char* input, size_t length) noexcept; + +/** + * @private + * Checks if the input contains a forbidden domain code point in which case + * the first bit is set to 1. If the input contains an upper case ASCII letter, + * then the second bit is set to 1. + * @see https://url.spec.whatwg.org/#forbidden-domain-code-point + */ +ada_really_inline constexpr uint8_t +contains_forbidden_domain_code_point_or_upper(const char* input, + size_t length) noexcept; + +/** + * @private + * Checks if the input is a forbidden domain code point. + * @see https://url.spec.whatwg.org/#forbidden-domain-code-point + */ +ada_really_inline constexpr bool is_forbidden_domain_code_point( + char c) noexcept; + +/** + * @private + * Checks if the input is alphanumeric, '+', '-' or '.' + */ +ada_really_inline constexpr bool is_alnum_plus(char c) noexcept; + +/** + * @private + * @details An ASCII hex digit is an ASCII upper hex digit or ASCII lower hex + * digit. An ASCII upper hex digit is an ASCII digit or a code point in the + * range U+0041 (A) to U+0046 (F), inclusive. An ASCII lower hex digit is an + * ASCII digit or a code point in the range U+0061 (a) to U+0066 (f), inclusive. + */ +ada_really_inline constexpr bool is_ascii_hex_digit(char c) noexcept; + +/** + * @private + * An ASCII digit is a code point in the range U+0030 (0) to U+0039 (9), + * inclusive. + */ +ada_really_inline constexpr bool is_ascii_digit(char c) noexcept; + +/** + * @private + * @details If a char is between U+0000 and U+007F inclusive, then it's an ASCII + * character. + */ +ada_really_inline constexpr bool is_ascii(char32_t c) noexcept; + +/** + * @private + * Checks if the input is a C0 control or space character. + * + * @details A C0 control or space is a C0 control or U+0020 SPACE. + * A C0 control is a code point in the range U+0000 NULL to U+001F INFORMATION + * SEPARATOR ONE, inclusive. + */ +ada_really_inline constexpr bool is_c0_control_or_space(char c) noexcept; + +/** + * @private + * Checks if the input is a ASCII tab or newline character. + * + * @details An ASCII tab or newline is U+0009 TAB, U+000A LF, or U+000D CR. + */ +ada_really_inline constexpr bool is_ascii_tab_or_newline(char c) noexcept; + +/** + * @private + * @details A double-dot path segment must be ".." or an ASCII case-insensitive + * match for ".%2e", "%2e.", or "%2e%2e". + */ +ada_really_inline constexpr bool is_double_dot_path_segment( + std::string_view input) noexcept; + +/** + * @private + * @details A single-dot path segment must be "." or an ASCII case-insensitive + * match for "%2e". + */ +ada_really_inline constexpr bool is_single_dot_path_segment( + std::string_view input) noexcept; + +/** + * @private + * @details ipv4 character might contain 0-9 or a-f character ranges. + */ +ada_really_inline constexpr bool is_lowercase_hex(char c) noexcept; + +/** + * @private + * @details Convert hex to binary. Caller is responsible to ensure that + * the parameter is an hexadecimal digit (0-9, A-F, a-f). + */ +ada_really_inline unsigned constexpr convert_hex_to_binary(char c) noexcept; + +/** + * @private + * first_percent should be = input.find('%') + * + * @todo It would be faster as noexcept maybe, but it could be unsafe since. + * @author Node.js + * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L245 + * @see https://encoding.spec.whatwg.org/#utf-8-decode-without-bom + */ +std::string percent_decode(std::string_view input, size_t first_percent); + +/** + * @private + * Returns a percent-encoding string whether percent encoding was needed or not. + * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 + */ +std::string percent_encode(std::string_view input, + const uint8_t character_set[]); +/** + * @private + * Returns a percent-encoded string version of input, while starting the percent + * encoding at the provided index. + * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 + */ +std::string percent_encode(std::string_view input, + const uint8_t character_set[], size_t index); +/** + * @private + * Returns true if percent encoding was needed, in which case, we store + * the percent-encoded content in 'out'. If the boolean 'append' is set to + * true, the content is appended to 'out'. + * If percent encoding is not needed, out is left unchanged. + * @see https://github.com/nodejs/node/blob/main/src/node_url.cc#L226 + */ +template +bool percent_encode(std::string_view input, const uint8_t character_set[], + std::string& out); +/** + * @private + * Returns the index at which percent encoding should start, or (equivalently), + * the length of the prefix that does not require percent encoding. + */ +ada_really_inline size_t percent_encode_index(std::string_view input, + const uint8_t character_set[]); +/** + * @private + * Lowers the string in-place, assuming that the content is ASCII. + * Return true if the content was ASCII. + */ +constexpr bool to_lower_ascii(char* input, size_t length) noexcept; +} // namespace ada::unicode + +#endif // ADA_UNICODE_H +/* end file include/ada/unicode.h */ +/* begin file include/ada/url_base-inl.h */ +/** + * @file url_base-inl.h + * @brief Inline functions for url base + */ +#ifndef ADA_URL_BASE_INL_H +#define ADA_URL_BASE_INL_H + + +#include +#if ADA_REGULAR_VISUAL_STUDIO +#include +#endif // ADA_REGULAR_VISUAL_STUDIO + +namespace ada { + +[[nodiscard]] ada_really_inline constexpr bool url_base::is_special() + const noexcept { + return type != ada::scheme::NOT_SPECIAL; +} + +[[nodiscard]] inline uint16_t url_base::get_special_port() const noexcept { + return ada::scheme::get_special_port(type); +} + +[[nodiscard]] ada_really_inline uint16_t +url_base::scheme_default_port() const noexcept { + return scheme::get_special_port(type); +} + +} // namespace ada + +#endif // ADA_URL_BASE_INL_H +/* end file include/ada/url_base-inl.h */ +/* begin file include/ada/url-inl.h */ +/** + * @file url-inl.h + * @brief Definitions for the URL + */ +#ifndef ADA_URL_INL_H +#define ADA_URL_INL_H + + +#include +#include +#include +#if ADA_REGULAR_VISUAL_STUDIO +#include +#endif // ADA_REGULAR_VISUAL_STUDIO + +namespace ada { +[[nodiscard]] ada_really_inline bool url::has_credentials() const noexcept { + return !username.empty() || !password.empty(); +} +[[nodiscard]] ada_really_inline bool url::has_port() const noexcept { + return port.has_value(); +} +[[nodiscard]] inline bool url::cannot_have_credentials_or_port() const { + return !host.has_value() || host.value().empty() || + type == ada::scheme::type::FILE; +} +[[nodiscard]] inline bool url::has_empty_hostname() const noexcept { + if (!host.has_value()) { + return false; + } + return host.value().empty(); +} +[[nodiscard]] inline bool url::has_hostname() const noexcept { + return host.has_value(); +} +inline std::ostream &operator<<(std::ostream &out, const ada::url &u) { + return out << u.to_string(); +} + +[[nodiscard]] size_t url::get_pathname_length() const noexcept { + return path.size(); +} + +[[nodiscard]] constexpr std::string_view url::get_pathname() const noexcept { + return path; +} + +[[nodiscard]] ada_really_inline ada::url_components url::get_components() + const noexcept { + url_components out{}; + + // protocol ends with ':'. for example: "https:" + out.protocol_end = uint32_t(get_protocol().size()); + + // Trailing index is always the next character of the current one. + // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores) + size_t running_index = out.protocol_end; + + if (host.has_value()) { + // 2 characters for "//" and 1 character for starting index + out.host_start = out.protocol_end + 2; + + if (has_credentials()) { + out.username_end = uint32_t(out.host_start + username.size()); + + out.host_start += uint32_t(username.size()); + + if (!password.empty()) { + out.host_start += uint32_t(password.size() + 1); + } + + out.host_end = uint32_t(out.host_start + host.value().size()); + } else { + out.username_end = out.host_start; + + // Host does not start with "@" if it does not include credentials. + out.host_end = uint32_t(out.host_start + host.value().size()) - 1; + } + + running_index = out.host_end + 1; + } else { + // Update host start and end date to the same index, since it does not + // exist. + out.host_start = out.protocol_end; + out.host_end = out.host_start; + + if (!has_opaque_path && path.starts_with("//")) { + // If url's host is null, url does not have an opaque path, url's path's + // size is greater than 1, and url's path[0] is the empty string, then + // append U+002F (/) followed by U+002E (.) to output. + running_index = out.protocol_end + 2; + } else { + running_index = out.protocol_end; + } + } + + if (port.has_value()) { + out.port = *port; + running_index += helpers::fast_digit_count(*port) + 1; // Port omits ':' + } + + out.pathname_start = uint32_t(running_index); + + running_index += path.size(); + + if (query.has_value()) { + out.search_start = uint32_t(running_index); + running_index += get_search().size(); + if (get_search().empty()) { + running_index++; + } + } + + if (hash.has_value()) { + out.hash_start = uint32_t(running_index); + } + + return out; +} + +inline void url::update_base_hostname(std::string_view input) { host = input; } inline void url::update_unencoded_base_hash(std::string_view input) { // We do the percent encoding @@ -5763,130 +6714,556 @@ inline void url::update_unencoded_base_hash(std::string_view input) { ada::character_sets::FRAGMENT_PERCENT_ENCODE); } -inline void url::update_base_search(std::string_view input, - const uint8_t query_percent_encode_set[]) { - query = ada::unicode::percent_encode(input, query_percent_encode_set); +inline void url::update_base_search(std::string_view input, + const uint8_t query_percent_encode_set[]) { + query = ada::unicode::percent_encode(input, query_percent_encode_set); +} + +inline void url::update_base_search(std::optional &&input) { + query = std::move(input); +} + +inline void url::update_base_pathname(const std::string_view input) { + path = input; +} + +inline void url::update_base_username(const std::string_view input) { + username = input; +} + +inline void url::update_base_password(const std::string_view input) { + password = input; +} + +inline void url::update_base_port(std::optional input) { + port = input; +} + +constexpr void url::clear_pathname() { path.clear(); } + +constexpr void url::clear_search() { query = std::nullopt; } + +[[nodiscard]] constexpr bool url::has_hash() const noexcept { + return hash.has_value(); +} + +[[nodiscard]] constexpr bool url::has_search() const noexcept { + return query.has_value(); +} + +constexpr void url::set_protocol_as_file() { type = ada::scheme::type::FILE; } + +inline void url::set_scheme(std::string &&new_scheme) noexcept { + type = ada::scheme::get_scheme_type(new_scheme); + // We only move the 'scheme' if it is non-special. + if (!is_special()) { + non_special_scheme = std::move(new_scheme); + } +} + +constexpr void url::copy_scheme(ada::url &&u) noexcept { + non_special_scheme = u.non_special_scheme; + type = u.type; +} + +constexpr void url::copy_scheme(const ada::url &u) { + non_special_scheme = u.non_special_scheme; + type = u.type; +} + +[[nodiscard]] ada_really_inline std::string url::get_href() const noexcept { + std::string output = get_protocol(); + + if (host.has_value()) { + output += "//"; + if (has_credentials()) { + output += username; + if (!password.empty()) { + output += ":" + get_password(); + } + output += "@"; + } + output += host.value(); + if (port.has_value()) { + output += ":" + get_port(); + } + } else if (!has_opaque_path && path.starts_with("//")) { + // If url's host is null, url does not have an opaque path, url's path's + // size is greater than 1, and url's path[0] is the empty string, then + // append U+002F (/) followed by U+002E (.) to output. + output += "/."; + } + output += path; + if (query.has_value()) { + output += "?" + query.value(); + } + if (hash.has_value()) { + output += "#" + hash.value(); + } + return output; +} + +ada_really_inline size_t url::parse_port(std::string_view view, + bool check_trailing_content) noexcept { + ada_log("parse_port('", view, "') ", view.size()); + if (!view.empty() && view[0] == '-') { + ada_log("parse_port: view[0] == '0' && view.size() > 1"); + is_valid = false; + return 0; + } + uint16_t parsed_port{}; + auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port); + if (r.ec == std::errc::result_out_of_range) { + ada_log("parse_port: r.ec == std::errc::result_out_of_range"); + is_valid = false; + return 0; + } + ada_log("parse_port: ", parsed_port); + const auto consumed = size_t(r.ptr - view.data()); + ada_log("parse_port: consumed ", consumed); + if (check_trailing_content) { + is_valid &= + (consumed == view.size() || view[consumed] == '/' || + view[consumed] == '?' || (is_special() && view[consumed] == '\\')); + } + ada_log("parse_port: is_valid = ", is_valid); + if (is_valid) { + // scheme_default_port can return 0, and we should allow 0 as a base port. + auto default_port = scheme_default_port(); + bool is_port_valid = (default_port == 0 && parsed_port == 0) || + (default_port != parsed_port); + port = (r.ec == std::errc() && is_port_valid) ? std::optional(parsed_port) + : std::nullopt; + } + return consumed; +} + +} // namespace ada + +#endif // ADA_URL_H +/* end file include/ada/url-inl.h */ +/* begin file include/ada/url_components-inl.h */ +/** + * @file url_components.h + * @brief Declaration for the URL Components + */ +#ifndef ADA_URL_COMPONENTS_INL_H +#define ADA_URL_COMPONENTS_INL_H + + +namespace ada { + +[[nodiscard]] constexpr bool url_components::check_offset_consistency() + const noexcept { + /** + * https://user:pass@example.com:1234/foo/bar?baz#quux + * | | | | ^^^^| | | + * | | | | | | | `----- hash_start + * | | | | | | `--------- search_start + * | | | | | `----------------- pathname_start + * | | | | `--------------------- port + * | | | `----------------------- host_end + * | | `---------------------------------- host_start + * | `--------------------------------------- username_end + * `--------------------------------------------- protocol_end + */ + // These conditions can be made more strict. + if (protocol_end == url_components::omitted) { + return false; + } + uint32_t index = protocol_end; + + if (username_end == url_components::omitted) { + return false; + } + if (username_end < index) { + return false; + } + index = username_end; + + if (host_start == url_components::omitted) { + return false; + } + if (host_start < index) { + return false; + } + index = host_start; + + if (port != url_components::omitted) { + if (port > 0xffff) { + return false; + } + uint32_t port_length = helpers::fast_digit_count(port) + 1; + if (index + port_length < index) { + return false; + } + index += port_length; + } + + if (pathname_start == url_components::omitted) { + return false; + } + if (pathname_start < index) { + return false; + } + index = pathname_start; + + if (search_start != url_components::omitted) { + if (search_start < index) { + return false; + } + index = search_start; + } + + if (hash_start != url_components::omitted) { + if (hash_start < index) { + return false; + } + } + + return true; } -inline void url::update_base_search(std::optional input) { - query = input; -} +} // namespace ada +#endif +/* end file include/ada/url_components-inl.h */ +/* begin file include/ada/url_aggregator.h */ +/** + * @file url_aggregator.h + * @brief Declaration for the basic URL definitions + */ +#ifndef ADA_URL_AGGREGATOR_H +#define ADA_URL_AGGREGATOR_H + +#include +#include +#include +#include + + +namespace ada { + +namespace parser {} + +/** + * @brief Lightweight URL struct. + * + * @details The url_aggregator class aims to minimize temporary memory + * allocation while representing a parsed URL. Internally, it contains a single + * normalized URL (the href), and it makes available the components, mostly + * using std::string_view. + */ +struct url_aggregator : url_base { + url_aggregator() = default; + url_aggregator(const url_aggregator &u) = default; + url_aggregator(url_aggregator &&u) noexcept = default; + url_aggregator &operator=(url_aggregator &&u) noexcept = default; + url_aggregator &operator=(const url_aggregator &u) = default; + ~url_aggregator() override = default; + + bool set_href(std::string_view input); + bool set_host(std::string_view input); + bool set_hostname(std::string_view input); + bool set_protocol(std::string_view input); + bool set_username(std::string_view input); + bool set_password(std::string_view input); + bool set_port(std::string_view input); + bool set_pathname(std::string_view input); + void set_search(std::string_view input); + void set_hash(std::string_view input); + + [[nodiscard]] bool has_valid_domain() const noexcept override; + /** + * The origin getter steps are to return the serialization of this's URL's + * origin. [HTML] + * @return a newly allocated string. + * @see https://url.spec.whatwg.org/#concept-url-origin + */ + [[nodiscard]] std::string get_origin() const noexcept override; + /** + * Return the normalized string. + * This function does not allocate memory. + * It is highly efficient. + * @return a constant reference to the underlying normalized URL. + * @see https://url.spec.whatwg.org/#dom-url-href + * @see https://url.spec.whatwg.org/#concept-url-serializer + */ + [[nodiscard]] constexpr std::string_view get_href() const noexcept + ada_lifetime_bound; + /** + * The username getter steps are to return this's URL's username. + * This function does not allocate memory. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-username + */ + [[nodiscard]] std::string_view get_username() const noexcept + ada_lifetime_bound; + /** + * The password getter steps are to return this's URL's password. + * This function does not allocate memory. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-password + */ + [[nodiscard]] std::string_view get_password() const noexcept + ada_lifetime_bound; + /** + * Return this's URL's port, serialized. + * This function does not allocate memory. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-port + */ + [[nodiscard]] std::string_view get_port() const noexcept ada_lifetime_bound; + /** + * Return U+0023 (#), followed by this's URL's fragment. + * This function does not allocate memory. + * @return a lightweight std::string_view.. + * @see https://url.spec.whatwg.org/#dom-url-hash + */ + [[nodiscard]] std::string_view get_hash() const noexcept ada_lifetime_bound; + /** + * Return url's host, serialized, followed by U+003A (:) and url's port, + * serialized. + * This function does not allocate memory. + * When there is no host, this function returns the empty view. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-host + */ + [[nodiscard]] std::string_view get_host() const noexcept ada_lifetime_bound; + /** + * Return this's URL's host, serialized. + * This function does not allocate memory. + * When there is no host, this function returns the empty view. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-hostname + */ + [[nodiscard]] std::string_view get_hostname() const noexcept + ada_lifetime_bound; + /** + * The pathname getter steps are to return the result of URL path serializing + * this's URL. + * This function does not allocate memory. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-pathname + */ + [[nodiscard]] constexpr std::string_view get_pathname() const noexcept + ada_lifetime_bound; + /** + * Compute the pathname length in bytes without instantiating a view or a + * string. + * @return size of the pathname in bytes + * @see https://url.spec.whatwg.org/#dom-url-pathname + */ + [[nodiscard]] ada_really_inline uint32_t get_pathname_length() const noexcept; + /** + * Return U+003F (?), followed by this's URL's query. + * This function does not allocate memory. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-search + */ + [[nodiscard]] std::string_view get_search() const noexcept ada_lifetime_bound; + /** + * The protocol getter steps are to return this's URL's scheme, followed by + * U+003A (:). + * This function does not allocate memory. + * @return a lightweight std::string_view. + * @see https://url.spec.whatwg.org/#dom-url-protocol + */ + [[nodiscard]] std::string_view get_protocol() const noexcept + ada_lifetime_bound; + + /** + * A URL includes credentials if its username or password is not the empty + * string. + */ + [[nodiscard]] ada_really_inline constexpr bool has_credentials() + const noexcept; + + /** + * Useful for implementing efficient serialization for the URL. + * + * https://user:pass@example.com:1234/foo/bar?baz#quux + * | | | | ^^^^| | | + * | | | | | | | `----- hash_start + * | | | | | | `--------- search_start + * | | | | | `----------------- pathname_start + * | | | | `--------------------- port + * | | | `----------------------- host_end + * | | `---------------------------------- host_start + * | `--------------------------------------- username_end + * `--------------------------------------------- protocol_end + * + * Inspired after servo/url + * + * @return a constant reference to the underlying component attribute. + * + * @see + * https://github.com/servo/rust-url/blob/b65a45515c10713f6d212e6726719a020203cc98/url/src/quirks.rs#L31 + */ + [[nodiscard]] ada_really_inline const url_components &get_components() + const noexcept; + /** + * Returns a string representation of this URL. + */ + [[nodiscard]] std::string to_string() const override; + /** + * Returns a string diagram of this URL. + */ + [[nodiscard]] std::string to_diagram() const; + + /** + * Verifies that the parsed URL could be valid. Useful for debugging purposes. + * @return true if the URL is valid, otherwise return true of the offsets are + * possible. + */ + [[nodiscard]] constexpr bool validate() const noexcept; + + /** @return true if it has an host but it is the empty string */ + [[nodiscard]] constexpr bool has_empty_hostname() const noexcept; + /** @return true if it has a host (included an empty host) */ + [[nodiscard]] constexpr bool has_hostname() const noexcept; + /** @return true if the URL has a non-empty username */ + [[nodiscard]] constexpr bool has_non_empty_username() const noexcept; + /** @return true if the URL has a non-empty password */ + [[nodiscard]] constexpr bool has_non_empty_password() const noexcept; + /** @return true if the URL has a (non default) port */ + [[nodiscard]] constexpr bool has_port() const noexcept; + /** @return true if the URL has a password */ + [[nodiscard]] constexpr bool has_password() const noexcept; + /** @return true if the URL has a hash component */ + [[nodiscard]] constexpr bool has_hash() const noexcept override; + /** @return true if the URL has a search component */ + [[nodiscard]] constexpr bool has_search() const noexcept override; + + inline void clear_port(); + inline void clear_hash(); + inline void clear_search() override; + + private: + // helper methods + friend void helpers::strip_trailing_spaces_from_opaque_path( + url_aggregator &url) noexcept; + // parse_url methods + friend url_aggregator parser::parse_url( + std::string_view, const url_aggregator *); + + friend url_aggregator parser::parse_url_impl( + std::string_view, const url_aggregator *); + friend url_aggregator parser::parse_url_impl( + std::string_view, const url_aggregator *); + +#if ADA_INCLUDE_URL_PATTERN + // url_pattern methods + template + friend tl::expected, errors> + parse_url_pattern_impl( + std::variant &&input, + const std::string_view *base_url, const url_pattern_options *options); +#endif // ADA_INCLUDE_URL_PATTERN + + std::string buffer{}; + url_components components{}; -inline void url::update_base_pathname(const std::string_view input) { - path = input; -} + /** + * Returns true if neither the search, nor the hash nor the pathname + * have been set. + * @return true if the buffer is ready to receive the path. + */ + [[nodiscard]] ada_really_inline bool is_at_path() const noexcept; -inline void url::update_base_username(const std::string_view input) { - username = input; -} + inline void add_authority_slashes_if_needed() noexcept; -inline void url::update_base_password(const std::string_view input) { - password = input; -} + /** + * To optimize performance, you may indicate how much memory to allocate + * within this instance. + */ + constexpr void reserve(uint32_t capacity); -inline void url::update_base_port(std::optional input) { - port = input; -} + ada_really_inline size_t parse_port( + std::string_view view, bool check_trailing_content) noexcept override; -inline void url::clear_pathname() { path.clear(); } + ada_really_inline size_t parse_port(std::string_view view) noexcept override { + return this->parse_port(view, false); + } -inline void url::clear_search() { query = std::nullopt; } + /** + * Return true on success. The 'in_place' parameter indicates whether the + * the string_view input is pointing in the buffer. When in_place is false, + * we must nearly always update the buffer. + * @see https://url.spec.whatwg.org/#concept-ipv4-parser + */ + [[nodiscard]] bool parse_ipv4(std::string_view input, bool in_place); -[[nodiscard]] inline bool url::has_hash() const noexcept { - return hash.has_value(); -} + /** + * Return true on success. + * @see https://url.spec.whatwg.org/#concept-ipv6-parser + */ + [[nodiscard]] bool parse_ipv6(std::string_view input); -[[nodiscard]] inline bool url::has_search() const noexcept { - return query.has_value(); -} + /** + * Return true on success. + * @see https://url.spec.whatwg.org/#concept-opaque-host-parser + */ + [[nodiscard]] bool parse_opaque_host(std::string_view input); -inline void url::set_protocol_as_file() { type = ada::scheme::type::FILE; } + ada_really_inline void parse_path(std::string_view input); -inline void url::set_scheme(std::string&& new_scheme) noexcept { - type = ada::scheme::get_scheme_type(new_scheme); - // We only move the 'scheme' if it is non-special. - if (!is_special()) { - non_special_scheme = new_scheme; - } -} + /** + * A URL cannot have a username/password/port if its host is null or the empty + * string, or its scheme is "file". + */ + [[nodiscard]] constexpr bool cannot_have_credentials_or_port() const; -inline void url::copy_scheme(ada::url&& u) noexcept { - non_special_scheme = u.non_special_scheme; - type = u.type; -} + template + bool set_host_or_hostname(std::string_view input); -inline void url::copy_scheme(const ada::url& u) { - non_special_scheme = u.non_special_scheme; - type = u.type; -} + ada_really_inline bool parse_host(std::string_view input); -[[nodiscard]] ada_really_inline std::string url::get_href() const noexcept { - std::string output = get_protocol(); + inline void update_base_authority(std::string_view base_buffer, + const url_components &base); + inline void update_unencoded_base_hash(std::string_view input); + inline void update_base_hostname(std::string_view input); + inline void update_base_search(std::string_view input); + inline void update_base_search(std::string_view input, + const uint8_t *query_percent_encode_set); + inline void update_base_pathname(std::string_view input); + inline void update_base_username(std::string_view input); + inline void append_base_username(std::string_view input); + inline void update_base_password(std::string_view input); + inline void append_base_password(std::string_view input); + inline void update_base_port(uint32_t input); + inline void append_base_pathname(std::string_view input); + [[nodiscard]] inline uint32_t retrieve_base_port() const; + constexpr void clear_hostname(); + constexpr void clear_password(); + constexpr void clear_pathname() override; + [[nodiscard]] constexpr bool has_dash_dot() const noexcept; + void delete_dash_dot(); + inline void consume_prepared_path(std::string_view input); + template + [[nodiscard]] ada_really_inline bool parse_scheme_with_colon( + std::string_view input); + ada_really_inline uint32_t replace_and_resize(uint32_t start, uint32_t end, + std::string_view input); + [[nodiscard]] constexpr bool has_authority() const noexcept; + constexpr void set_protocol_as_file(); + inline void set_scheme(std::string_view new_scheme) noexcept; + /** + * Fast function to set the scheme from a view with a colon in the + * buffer, does not change type. + */ + inline void set_scheme_from_view_with_colon( + std::string_view new_scheme_with_colon) noexcept; + inline void copy_scheme(const url_aggregator &u) noexcept; - if (host.has_value()) { - output += "//"; - if (has_credentials()) { - output += username; - if (!password.empty()) { - output += ":" + get_password(); - } - output += "@"; - } - output += host.value(); - if (port.has_value()) { - output += ":" + get_port(); - } - } else if (!has_opaque_path && checkers::begins_with(path, "//")) { - // If url's host is null, url does not have an opaque path, url's path's - // size is greater than 1, and url's path[0] is the empty string, then - // append U+002F (/) followed by U+002E (.) to output. - output += "/."; - } - output += path; - if (query.has_value()) { - output += "?" + query.value(); - } - if (hash.has_value()) { - output += "#" + hash.value(); - } - return output; -} + inline void update_host_to_base_host(const std::string_view input) noexcept; -ada_really_inline size_t url::parse_port(std::string_view view, - bool check_trailing_content) noexcept { - ada_log("parse_port('", view, "') ", view.size()); - uint16_t parsed_port{}; - auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port); - if (r.ec == std::errc::result_out_of_range) { - ada_log("parse_port: std::errc::result_out_of_range"); - is_valid = false; - return 0; - } - ada_log("parse_port: ", parsed_port); - const size_t consumed = size_t(r.ptr - view.data()); - ada_log("parse_port: consumed ", consumed); - if (check_trailing_content) { - is_valid &= - (consumed == view.size() || view[consumed] == '/' || - view[consumed] == '?' || (is_special() && view[consumed] == '\\')); - } - ada_log("parse_port: is_valid = ", is_valid); - if (is_valid) { - // scheme_default_port can return 0, and we should allow 0 as a base port. - auto default_port = scheme_default_port(); - bool is_port_valid = (default_port == 0 && parsed_port == 0) || - (default_port != parsed_port); - port = (r.ec == std::errc() && is_port_valid) - ? std::optional(parsed_port) - : std::nullopt; - } - return consumed; -} +}; // url_aggregator +inline std::ostream &operator<<(std::ostream &out, const url &u); } // namespace ada -#endif // ADA_URL_H -/* end file include/ada/url-inl.h */ +#endif +/* end file include/ada/url_aggregator.h */ /* begin file include/ada/url_aggregator-inl.h */ /** * @file url_aggregator-inl.h @@ -5902,7 +7279,6 @@ ada_really_inline size_t url::parse_port(std::string_view view, */ #ifndef ADA_UNICODE_INL_H #define ADA_UNICODE_INL_H -#include /** * Unicode operations. These functions are not part of our public API and may @@ -5915,29 +7291,51 @@ ada_really_inline size_t url::parse_port(std::string_view view, namespace ada::unicode { ada_really_inline size_t percent_encode_index(const std::string_view input, const uint8_t character_set[]) { - return std::distance( - input.begin(), - std::find_if(input.begin(), input.end(), [character_set](const char c) { - return character_sets::bit_at(character_set, c); - })); + const char* data = input.data(); + const size_t size = input.size(); + + // Process 8 bytes at a time using unrolled loop + size_t i = 0; + for (; i + 8 <= size; i += 8) { + unsigned char chunk[8]; + std::memcpy(&chunk, data + i, + 8); // entices compiler to unconditionally process 8 characters + + // Check 8 characters at once + for (size_t j = 0; j < 8; j++) { + if (character_sets::bit_at(character_set, chunk[j])) { + return i + j; + } + } + } + + // Handle remaining bytes + for (; i < size; i++) { + if (character_sets::bit_at(character_set, data[i])) { + return i; + } + } + + return size; } } // namespace ada::unicode #endif // ADA_UNICODE_INL_H /* end file include/ada/unicode-inl.h */ -#include +#include +#include #include namespace ada { inline void url_aggregator::update_base_authority( - std::string_view base_buffer, const ada::url_components& base) { + std::string_view base_buffer, const ada::url_components &base) { std::string_view input = base_buffer.substr( base.protocol_end, base.host_start - base.protocol_end); ada_log("url_aggregator::update_base_authority ", input); - bool input_starts_with_dash = checkers::begins_with(input, "//"); + bool input_starts_with_dash = input.starts_with("//"); uint32_t diff = components.host_start - components.protocol_end; buffer.erase(components.protocol_end, @@ -6177,9 +7575,8 @@ inline void url_aggregator::update_base_pathname(const std::string_view input) { ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); ADA_ASSERT_TRUE(validate()); - const bool begins_with_dashdash = checkers::begins_with(input, "//"); + const bool begins_with_dashdash = input.starts_with("//"); if (!begins_with_dashdash && has_dash_dot()) { - ada_log("url_aggregator::update_base_pathname has /.: \n", to_diagram()); // We must delete the ./ delete_dash_dot(); } @@ -6202,8 +7599,6 @@ inline void url_aggregator::update_base_pathname(const std::string_view input) { if (components.hash_start != url_components::omitted) { components.hash_start += difference; } - ada_log("url_aggregator::update_base_pathname end '", input, "' [", - input.size(), " bytes] \n", to_diagram()); ADA_ASSERT_TRUE(validate()); } @@ -6322,8 +7717,8 @@ inline void url_aggregator::append_base_username(const std::string_view input) { ADA_ASSERT_TRUE(validate()); } -inline void url_aggregator::clear_password() { - ada_log("url_aggregator::clear_password ", to_string(), "\n", to_diagram()); +constexpr void url_aggregator::clear_password() { + ada_log("url_aggregator::clear_password ", to_string()); ADA_ASSERT_TRUE(validate()); if (!has_password()) { return; @@ -6544,7 +7939,7 @@ inline void url_aggregator::clear_hash() { ADA_ASSERT_TRUE(validate()); } -inline void url_aggregator::clear_pathname() { +constexpr void url_aggregator::clear_pathname() { ada_log("url_aggregator::clear_pathname"); ADA_ASSERT_TRUE(validate()); uint32_t ending_index = uint32_t(buffer.size()); @@ -6579,7 +7974,7 @@ inline void url_aggregator::clear_pathname() { ada_log("url_aggregator::clear_pathname completed, running checks... ok"); } -inline void url_aggregator::clear_hostname() { +constexpr void url_aggregator::clear_hostname() { ada_log("url_aggregator::clear_hostname"); ADA_ASSERT_TRUE(validate()); if (!has_authority()) { @@ -6616,33 +8011,34 @@ inline void url_aggregator::clear_hostname() { ADA_ASSERT_TRUE(validate()); } -[[nodiscard]] inline bool url_aggregator::has_hash() const noexcept { +[[nodiscard]] constexpr bool url_aggregator::has_hash() const noexcept { ada_log("url_aggregator::has_hash"); return components.hash_start != url_components::omitted; } -[[nodiscard]] inline bool url_aggregator::has_search() const noexcept { +[[nodiscard]] constexpr bool url_aggregator::has_search() const noexcept { ada_log("url_aggregator::has_search"); return components.search_start != url_components::omitted; } -ada_really_inline bool url_aggregator::has_credentials() const noexcept { +constexpr bool url_aggregator::has_credentials() const noexcept { ada_log("url_aggregator::has_credentials"); return has_non_empty_username() || has_non_empty_password(); } -inline bool url_aggregator::cannot_have_credentials_or_port() const { +constexpr bool url_aggregator::cannot_have_credentials_or_port() const { ada_log("url_aggregator::cannot_have_credentials_or_port"); return type == ada::scheme::type::FILE || components.host_start == components.host_end; } -[[nodiscard]] ada_really_inline const ada::url_components& +[[nodiscard]] ada_really_inline const ada::url_components & url_aggregator::get_components() const noexcept { return components; } -[[nodiscard]] inline bool ada::url_aggregator::has_authority() const noexcept { +[[nodiscard]] constexpr bool ada::url_aggregator::has_authority() + const noexcept { ada_log("url_aggregator::has_authority"); // Performance: instead of doing this potentially expensive check, we could // have a boolean in the struct. @@ -6677,28 +8073,28 @@ inline void ada::url_aggregator::add_authority_slashes_if_needed() noexcept { ADA_ASSERT_TRUE(validate()); } -inline void ada::url_aggregator::reserve(uint32_t capacity) { +constexpr void ada::url_aggregator::reserve(uint32_t capacity) { buffer.reserve(capacity); } -inline bool url_aggregator::has_non_empty_username() const noexcept { +constexpr bool url_aggregator::has_non_empty_username() const noexcept { ada_log("url_aggregator::has_non_empty_username"); return components.protocol_end + 2 < components.username_end; } -inline bool url_aggregator::has_non_empty_password() const noexcept { +constexpr bool url_aggregator::has_non_empty_password() const noexcept { ada_log("url_aggregator::has_non_empty_password"); return components.host_start - components.username_end > 0; } -inline bool url_aggregator::has_password() const noexcept { +constexpr bool url_aggregator::has_password() const noexcept { ada_log("url_aggregator::has_password"); // This function does not care about the length of the password return components.host_start > components.username_end && buffer[components.username_end] == ':'; } -inline bool url_aggregator::has_empty_hostname() const noexcept { +constexpr bool url_aggregator::has_empty_hostname() const noexcept { if (!has_hostname()) { return false; } @@ -6711,18 +8107,18 @@ inline bool url_aggregator::has_empty_hostname() const noexcept { return components.username_end != components.host_start; } -inline bool url_aggregator::has_hostname() const noexcept { +constexpr bool url_aggregator::has_hostname() const noexcept { return has_authority(); } -inline bool url_aggregator::has_port() const noexcept { +constexpr bool url_aggregator::has_port() const noexcept { ada_log("url_aggregator::has_port"); // A URL cannot have a username/password/port if its host is null or the empty // string, or its scheme is "file". return has_hostname() && components.pathname_start != components.host_end; } -[[nodiscard]] inline bool url_aggregator::has_dash_dot() const noexcept { +[[nodiscard]] constexpr bool url_aggregator::has_dash_dot() const noexcept { // If url's host is null, url does not have an opaque path, url's path's size // is greater than 1, and url's path[0] is the empty string, then append // U+002F (/) followed by U+002E (.) to output. @@ -6754,8 +8150,8 @@ inline bool url_aggregator::has_port() const noexcept { buffer[components.host_end + 1] == '.'; } -[[nodiscard]] inline std::string_view url_aggregator::get_href() - const noexcept { +[[nodiscard]] constexpr std::string_view url_aggregator::get_href() + const noexcept ada_lifetime_bound { ada_log("url_aggregator::get_href"); return buffer; } @@ -6763,10 +8159,15 @@ inline bool url_aggregator::has_port() const noexcept { ada_really_inline size_t url_aggregator::parse_port( std::string_view view, bool check_trailing_content) noexcept { ada_log("url_aggregator::parse_port('", view, "') ", view.size()); + if (!view.empty() && view[0] == '-') { + ada_log("parse_port: view[0] == '0' && view.size() > 1"); + is_valid = false; + return 0; + } uint16_t parsed_port{}; auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port); if (r.ec == std::errc::result_out_of_range) { - ada_log("parse_port: std::errc::result_out_of_range"); + ada_log("parse_port: r.ec == std::errc::result_out_of_range"); is_valid = false; return 0; } @@ -6794,7 +8195,7 @@ ada_really_inline size_t url_aggregator::parse_port( return consumed; } -inline void url_aggregator::set_protocol_as_file() { +constexpr void url_aggregator::set_protocol_as_file() { ada_log("url_aggregator::set_protocol_as_file "); ADA_ASSERT_TRUE(validate()); type = ada::scheme::type::FILE; @@ -6822,11 +8223,218 @@ inline void url_aggregator::set_protocol_as_file() { components.hash_start += new_difference; } ADA_ASSERT_TRUE(validate()); -} - -inline std::ostream& operator<<(std::ostream& out, - const ada::url_aggregator& u) { - return out << u.to_string(); +} + +[[nodiscard]] constexpr bool url_aggregator::validate() const noexcept { + if (!is_valid) { + return true; + } + if (!components.check_offset_consistency()) { + ada_log("url_aggregator::validate inconsistent components \n", + to_diagram()); + return false; + } + // We have a credible components struct, but let us investivate more + // carefully: + /** + * https://user:pass@example.com:1234/foo/bar?baz#quux + * | | | | ^^^^| | | + * | | | | | | | `----- hash_start + * | | | | | | `--------- search_start + * | | | | | `----------------- pathname_start + * | | | | `--------------------- port + * | | | `----------------------- host_end + * | | `---------------------------------- host_start + * | `--------------------------------------- username_end + * `--------------------------------------------- protocol_end + */ + if (components.protocol_end == url_components::omitted) { + ada_log("url_aggregator::validate omitted protocol_end \n", to_diagram()); + return false; + } + if (components.username_end == url_components::omitted) { + ada_log("url_aggregator::validate omitted username_end \n", to_diagram()); + return false; + } + if (components.host_start == url_components::omitted) { + ada_log("url_aggregator::validate omitted host_start \n", to_diagram()); + return false; + } + if (components.host_end == url_components::omitted) { + ada_log("url_aggregator::validate omitted host_end \n", to_diagram()); + return false; + } + if (components.pathname_start == url_components::omitted) { + ada_log("url_aggregator::validate omitted pathname_start \n", to_diagram()); + return false; + } + + if (components.protocol_end > buffer.size()) { + ada_log("url_aggregator::validate protocol_end overflow \n", to_diagram()); + return false; + } + if (components.username_end > buffer.size()) { + ada_log("url_aggregator::validate username_end overflow \n", to_diagram()); + return false; + } + if (components.host_start > buffer.size()) { + ada_log("url_aggregator::validate host_start overflow \n", to_diagram()); + return false; + } + if (components.host_end > buffer.size()) { + ada_log("url_aggregator::validate host_end overflow \n", to_diagram()); + return false; + } + if (components.pathname_start > buffer.size()) { + ada_log("url_aggregator::validate pathname_start overflow \n", + to_diagram()); + return false; + } + + if (components.protocol_end > 0) { + if (buffer[components.protocol_end - 1] != ':') { + ada_log( + "url_aggregator::validate missing : at the end of the protocol \n", + to_diagram()); + return false; + } + } + + if (components.username_end != buffer.size() && + components.username_end > components.protocol_end + 2) { + if (buffer[components.username_end] != ':' && + buffer[components.username_end] != '@') { + ada_log( + "url_aggregator::validate missing : or @ at the end of the username " + "\n", + to_diagram()); + return false; + } + } + + if (components.host_start != buffer.size()) { + if (components.host_start > components.username_end) { + if (buffer[components.host_start] != '@') { + ada_log( + "url_aggregator::validate missing @ at the end of the password \n", + to_diagram()); + return false; + } + } else if (components.host_start == components.username_end && + components.host_end > components.host_start) { + if (components.host_start == components.protocol_end + 2) { + if (buffer[components.protocol_end] != '/' || + buffer[components.protocol_end + 1] != '/') { + ada_log( + "url_aggregator::validate missing // between protocol and host " + "\n", + to_diagram()); + return false; + } + } else { + if (components.host_start > components.protocol_end && + buffer[components.host_start] != '@') { + ada_log( + "url_aggregator::validate missing @ at the end of the username " + "\n", + to_diagram()); + return false; + } + } + } else { + if (components.host_end != components.host_start) { + ada_log("url_aggregator::validate expected omitted host \n", + to_diagram()); + return false; + } + } + } + if (components.host_end != buffer.size() && + components.pathname_start > components.host_end) { + if (components.pathname_start == components.host_end + 2 && + buffer[components.host_end] == '/' && + buffer[components.host_end + 1] == '.') { + if (components.pathname_start + 1 >= buffer.size() || + buffer[components.pathname_start] != '/' || + buffer[components.pathname_start + 1] != '/') { + ada_log( + "url_aggregator::validate expected the path to begin with // \n", + to_diagram()); + return false; + } + } else if (buffer[components.host_end] != ':') { + ada_log("url_aggregator::validate missing : at the port \n", + to_diagram()); + return false; + } + } + if (components.pathname_start != buffer.size() && + components.pathname_start < components.search_start && + components.pathname_start < components.hash_start && !has_opaque_path) { + if (buffer[components.pathname_start] != '/') { + ada_log("url_aggregator::validate missing / at the path \n", + to_diagram()); + return false; + } + } + if (components.search_start != url_components::omitted) { + if (buffer[components.search_start] != '?') { + ada_log("url_aggregator::validate missing ? at the search \n", + to_diagram()); + return false; + } + } + if (components.hash_start != url_components::omitted) { + if (buffer[components.hash_start] != '#') { + ada_log("url_aggregator::validate missing # at the hash \n", + to_diagram()); + return false; + } + } + + return true; +} + +[[nodiscard]] constexpr std::string_view url_aggregator::get_pathname() + const noexcept ada_lifetime_bound { + ada_log("url_aggregator::get_pathname pathname_start = ", + components.pathname_start, " buffer.size() = ", buffer.size(), + " components.search_start = ", components.search_start, + " components.hash_start = ", components.hash_start); + auto ending_index = uint32_t(buffer.size()); + if (components.search_start != url_components::omitted) { + ending_index = components.search_start; + } else if (components.hash_start != url_components::omitted) { + ending_index = components.hash_start; + } + return helpers::substring(buffer, components.pathname_start, ending_index); +} + +inline std::ostream &operator<<(std::ostream &out, + const ada::url_aggregator &u) { + return out << u.to_string(); +} + +void url_aggregator::update_host_to_base_host( + const std::string_view input) noexcept { + ada_log("url_aggregator::update_host_to_base_host ", input); + ADA_ASSERT_TRUE(validate()); + ADA_ASSERT_TRUE(!helpers::overlaps(input, buffer)); + if (type != ada::scheme::type::FILE) { + // Let host be the result of host parsing host_view with url is not special. + if (input.empty() && !is_special()) { + if (has_hostname()) { + clear_hostname(); + } else if (has_dash_dot()) { + add_authority_slashes_if_needed(); + delete_dash_dot(); + } + return; + } + } + update_base_hostname(input); + ADA_ASSERT_TRUE(validate()); + return; } } // namespace ada @@ -6868,6 +8476,8 @@ using url_search_params_entries_iter = url_search_params_iter_type::ENTRIES>; /** + * We require all strings to be valid UTF-8. It is the user's responsibility to + * ensure that the provided strings are valid UTF-8. * @see https://url.spec.whatwg.org/#interface-urlsearchparams */ struct url_search_params { @@ -6877,17 +8487,20 @@ struct url_search_params { * @see * https://github.com/web-platform-tests/wpt/blob/master/url/urlsearchparams-constructor.any.js */ - url_search_params(const std::string_view input) { initialize(input); } + explicit url_search_params(const std::string_view input) { + initialize(input); + } - url_search_params(const url_search_params& u) = default; - url_search_params(url_search_params&& u) noexcept = default; - url_search_params& operator=(url_search_params&& u) noexcept = default; - url_search_params& operator=(const url_search_params& u) = default; + url_search_params(const url_search_params &u) = default; + url_search_params(url_search_params &&u) noexcept = default; + url_search_params &operator=(url_search_params &&u) noexcept = default; + url_search_params &operator=(const url_search_params &u) = default; ~url_search_params() = default; [[nodiscard]] inline size_t size() const noexcept; /** + * Both key and value must be valid UTF-8. * @see https://url.spec.whatwg.org/#dom-urlsearchparams-append */ inline void append(std::string_view key, std::string_view value); @@ -6915,6 +8528,7 @@ struct url_search_params { inline bool has(std::string_view key, std::string_view value) noexcept; /** + * Both key and value must be valid UTF-8. * @see https://url.spec.whatwg.org/#dom-urlsearchparams-set */ inline void set(std::string_view key, std::string_view value); @@ -6978,6 +8592,7 @@ struct url_search_params { std::vector params{}; /** + * The init parameter must be valid UTF-8. * @see https://url.spec.whatwg.org/#concept-urlencoded-parser */ void initialize(std::string_view init); @@ -6995,11 +8610,11 @@ struct url_search_params { template struct url_search_params_iter { inline url_search_params_iter() : params(EMPTY) {} - url_search_params_iter(const url_search_params_iter& u) = default; - url_search_params_iter(url_search_params_iter&& u) noexcept = default; - url_search_params_iter& operator=(url_search_params_iter&& u) noexcept = + url_search_params_iter(const url_search_params_iter &u) = default; + url_search_params_iter(url_search_params_iter &&u) noexcept = default; + url_search_params_iter &operator=(url_search_params_iter &&u) noexcept = default; - url_search_params_iter& operator=(const url_search_params_iter& u) = default; + url_search_params_iter &operator=(const url_search_params_iter &u) = default; ~url_search_params_iter() = default; /** @@ -7007,13 +8622,13 @@ struct url_search_params_iter { */ inline std::optional next(); - inline bool has_next(); + inline bool has_next() const; private: static url_search_params EMPTY; - inline url_search_params_iter(url_search_params& params_) : params(params_) {} + inline url_search_params_iter(url_search_params ¶ms_) : params(params_) {} - url_search_params& params; + url_search_params ¶ms; size_t pos = 0; friend struct url_search_params; @@ -7030,8 +8645,10 @@ struct url_search_params_iter { #ifndef ADA_URL_SEARCH_PARAMS_INL_H #define ADA_URL_SEARCH_PARAMS_INL_H + #include #include +#include #include #include #include @@ -7057,14 +8674,14 @@ inline void url_search_params::initialize(std::string_view input) { if (equal == std::string_view::npos) { std::string name(current); - std::replace(name.begin(), name.end(), '+', ' '); + std::ranges::replace(name, '+', ' '); params.emplace_back(unicode::percent_decode(name, name.find('%')), ""); } else { std::string name(current.substr(0, equal)); std::string value(current.substr(equal + 1)); - std::replace(name.begin(), name.end(), '+', ' '); - std::replace(value.begin(), value.end(), '+', ' '); + std::ranges::replace(name, '+', ' '); + std::ranges::replace(value, '+', ' '); params.emplace_back(unicode::percent_decode(name, name.find('%')), unicode::percent_decode(value, value.find('%'))); @@ -7096,8 +8713,8 @@ inline size_t url_search_params::size() const noexcept { return params.size(); } inline std::optional url_search_params::get( const std::string_view key) { - auto entry = std::find_if(params.begin(), params.end(), - [&key](auto& param) { return param.first == key; }); + auto entry = std::ranges::find_if( + params, [&key](const auto ¶m) { return param.first == key; }); if (entry == params.end()) { return std::nullopt; @@ -7110,7 +8727,7 @@ inline std::vector url_search_params::get_all( const std::string_view key) { std::vector out{}; - for (auto& param : params) { + for (auto ¶m : params) { if (param.first == key) { out.emplace_back(param.second); } @@ -7120,17 +8737,16 @@ inline std::vector url_search_params::get_all( } inline bool url_search_params::has(const std::string_view key) noexcept { - auto entry = std::find_if(params.begin(), params.end(), - [&key](auto& param) { return param.first == key; }); + auto entry = std::ranges::find_if( + params, [&key](const auto ¶m) { return param.first == key; }); return entry != params.end(); } inline bool url_search_params::has(std::string_view key, std::string_view value) noexcept { - auto entry = - std::find_if(params.begin(), params.end(), [&key, &value](auto& param) { - return param.first == key && param.second == value; - }); + auto entry = std::ranges::find_if(params, [&key, &value](const auto ¶m) { + return param.first == key && param.second == value; + }); return entry != params.end(); } @@ -7142,8 +8758,8 @@ inline std::string url_search_params::to_string() const { auto value = ada::unicode::percent_encode(params[i].second, character_set); // Performance optimization: Move this inside percent_encode. - std::replace(key.begin(), key.end(), ' ', '+'); - std::replace(value.begin(), value.end(), ' ', '+'); + std::ranges::replace(key, ' ', '+'); + std::ranges::replace(value, ' ', '+'); if (i != 0) { out += "&"; @@ -7157,9 +8773,9 @@ inline std::string url_search_params::to_string() const { inline void url_search_params::set(const std::string_view key, const std::string_view value) { - const auto find = [&key](auto& param) { return param.first == key; }; + const auto find = [&key](const auto ¶m) { return param.first == key; }; - auto it = std::find_if(params.begin(), params.end(), find); + auto it = std::ranges::find_if(params, find); if (it == params.end()) { params.emplace_back(key, value); @@ -7171,27 +8787,92 @@ inline void url_search_params::set(const std::string_view key, } inline void url_search_params::remove(const std::string_view key) { - params.erase( - std::remove_if(params.begin(), params.end(), - [&key](auto& param) { return param.first == key; }), - params.end()); + std::erase_if(params, + [&key](const auto ¶m) { return param.first == key; }); } inline void url_search_params::remove(const std::string_view key, const std::string_view value) { - params.erase(std::remove_if(params.begin(), params.end(), - [&key, &value](auto& param) { - return param.first == key && - param.second == value; - }), - params.end()); + std::erase_if(params, [&key, &value](const auto ¶m) { + return param.first == key && param.second == value; + }); } inline void url_search_params::sort() { - std::stable_sort(params.begin(), params.end(), - [](const key_value_pair& lhs, const key_value_pair& rhs) { - return lhs.first < rhs.first; - }); + // We rely on the fact that the content is valid UTF-8. + std::ranges::stable_sort(params, [](const key_value_pair &lhs, + const key_value_pair &rhs) { + size_t i = 0, j = 0; + uint32_t low_surrogate1 = 0, low_surrogate2 = 0; + while ((i < lhs.first.size() || low_surrogate1 != 0) && + (j < rhs.first.size() || low_surrogate2 != 0)) { + uint32_t codePoint1 = 0, codePoint2 = 0; + + if (low_surrogate1 != 0) { + codePoint1 = low_surrogate1; + low_surrogate1 = 0; + } else { + uint8_t c1 = uint8_t(lhs.first[i]); + if (c1 <= 0x7F) { + codePoint1 = c1; + i++; + } else if (c1 <= 0xDF) { + codePoint1 = ((c1 & 0x1F) << 6) | (uint8_t(lhs.first[i + 1]) & 0x3F); + i += 2; + } else if (c1 <= 0xEF) { + codePoint1 = ((c1 & 0x0F) << 12) | + ((uint8_t(lhs.first[i + 1]) & 0x3F) << 6) | + (uint8_t(lhs.first[i + 2]) & 0x3F); + i += 3; + } else { + codePoint1 = ((c1 & 0x07) << 18) | + ((uint8_t(lhs.first[i + 1]) & 0x3F) << 12) | + ((uint8_t(lhs.first[i + 2]) & 0x3F) << 6) | + (uint8_t(lhs.first[i + 3]) & 0x3F); + i += 4; + + codePoint1 -= 0x10000; + uint16_t high_surrogate = uint16_t(0xD800 + (codePoint1 >> 10)); + low_surrogate1 = uint16_t(0xDC00 + (codePoint1 & 0x3FF)); + codePoint1 = high_surrogate; + } + } + + if (low_surrogate2 != 0) { + codePoint2 = low_surrogate2; + low_surrogate2 = 0; + } else { + uint8_t c2 = uint8_t(rhs.first[j]); + if (c2 <= 0x7F) { + codePoint2 = c2; + j++; + } else if (c2 <= 0xDF) { + codePoint2 = ((c2 & 0x1F) << 6) | (uint8_t(rhs.first[j + 1]) & 0x3F); + j += 2; + } else if (c2 <= 0xEF) { + codePoint2 = ((c2 & 0x0F) << 12) | + ((uint8_t(rhs.first[j + 1]) & 0x3F) << 6) | + (uint8_t(rhs.first[j + 2]) & 0x3F); + j += 3; + } else { + codePoint2 = ((c2 & 0x07) << 18) | + ((uint8_t(rhs.first[j + 1]) & 0x3F) << 12) | + ((uint8_t(rhs.first[j + 2]) & 0x3F) << 6) | + (uint8_t(rhs.first[j + 3]) & 0x3F); + j += 4; + codePoint2 -= 0x10000; + uint16_t high_surrogate = uint16_t(0xD800 + (codePoint2 >> 10)); + low_surrogate2 = uint16_t(0xDC00 + (codePoint2 & 0x3FF)); + codePoint2 = high_surrogate; + } + } + + if (codePoint1 != codePoint2) { + return (codePoint1 < codePoint2); + } + } + return (j < rhs.first.size() || low_surrogate2 != 0); + }); } inline url_search_params_keys_iter url_search_params::get_keys() { @@ -7213,7 +8894,7 @@ inline url_search_params_entries_iter url_search_params::get_entries() { } template -inline bool url_search_params_iter::has_next() { +inline bool url_search_params_iter::has_next() const { return pos < params.params.size(); } @@ -7247,6 +8928,1584 @@ url_search_params_entries_iter::next() { #endif // ADA_URL_SEARCH_PARAMS_INL_H /* end file include/ada/url_search_params-inl.h */ +/* begin file include/ada/url_pattern-inl.h */ +/** + * @file url_pattern-inl.h + * @brief Declaration for the URLPattern inline functions. + */ +#ifndef ADA_URL_PATTERN_INL_H +#define ADA_URL_PATTERN_INL_H + + +#include +#include +#include + +#if ADA_INCLUDE_URL_PATTERN +namespace ada { + +inline bool url_pattern_init::operator==(const url_pattern_init& other) const { + return protocol == other.protocol && username == other.username && + password == other.password && hostname == other.hostname && + port == other.port && search == other.search && hash == other.hash && + pathname == other.pathname; +} + +inline bool url_pattern_component_result::operator==( + const url_pattern_component_result& other) const { + return input == other.input && groups == other.groups; +} + +template +url_pattern_component_result +url_pattern_component::create_component_match_result( + std::string&& input, + std::vector>&& exec_result) { + // Let result be a new URLPatternComponentResult. + // Set result["input"] to input. + // Let groups be a record. + auto result = + url_pattern_component_result{.input = std::move(input), .groups = {}}; + + // Optimization: Let's reserve the size. + result.groups.reserve(exec_result.size()); + + // We explicitly start iterating from 0 even though the spec + // says we should start from 1. This case is handled by the + // std_regex_provider. + for (size_t index = 0; index < exec_result.size(); index++) { + result.groups.insert({ + group_name_list[index], + std::move(exec_result[index]), + }); + } + return result; +} + +template +std::string_view url_pattern::get_protocol() const + ada_lifetime_bound { + // Return this's associated URL pattern's protocol component's pattern string. + return protocol_component.pattern; +} +template +std::string_view url_pattern::get_username() const + ada_lifetime_bound { + // Return this's associated URL pattern's username component's pattern string. + return username_component.pattern; +} +template +std::string_view url_pattern::get_password() const + ada_lifetime_bound { + // Return this's associated URL pattern's password component's pattern string. + return password_component.pattern; +} +template +std::string_view url_pattern::get_hostname() const + ada_lifetime_bound { + // Return this's associated URL pattern's hostname component's pattern string. + return hostname_component.pattern; +} +template +std::string_view url_pattern::get_port() const + ada_lifetime_bound { + // Return this's associated URL pattern's port component's pattern string. + return port_component.pattern; +} +template +std::string_view url_pattern::get_pathname() const + ada_lifetime_bound { + // Return this's associated URL pattern's pathname component's pattern string. + return pathname_component.pattern; +} +template +std::string_view url_pattern::get_search() const + ada_lifetime_bound { + // Return this's associated URL pattern's search component's pattern string. + return search_component.pattern; +} +template +std::string_view url_pattern::get_hash() const + ada_lifetime_bound { + // Return this's associated URL pattern's hash component's pattern string. + return hash_component.pattern; +} +template +bool url_pattern::ignore_case() const { + return ignore_case_; +} +template +bool url_pattern::has_regexp_groups() const { + // If this's associated URL pattern's has regexp groups, then return true. + return protocol_component.has_regexp_groups || + username_component.has_regexp_groups || + password_component.has_regexp_groups || + hostname_component.has_regexp_groups || + port_component.has_regexp_groups || + pathname_component.has_regexp_groups || + search_component.has_regexp_groups || hash_component.has_regexp_groups; +} + +inline bool url_pattern_part::is_regexp() const noexcept { + return type == url_pattern_part_type::REGEXP; +} + +inline std::string_view url_pattern_compile_component_options::get_delimiter() + const { + if (delimiter) { + return {&delimiter.value(), 1}; + } + return {}; +} + +inline std::string_view url_pattern_compile_component_options::get_prefix() + const { + if (prefix) { + return {&prefix.value(), 1}; + } + return {}; +} + +template +template +tl::expected, errors> +url_pattern_component::compile( + std::string_view input, F& encoding_callback, + url_pattern_compile_component_options& options) { + ada_log("url_pattern_component::compile input: ", input); + // Let part list be the result of running parse a pattern string given input, + // options, and encoding callback. + auto part_list = url_pattern_helpers::parse_pattern_string(input, options, + encoding_callback); + + if (!part_list) { + ada_log("parse_pattern_string failed"); + return tl::unexpected(part_list.error()); + } + + // Let (regular expression string, name list) be the result of running + // generate a regular expression and name list given part list and options. + auto [regular_expression_string, name_list] = + url_pattern_helpers::generate_regular_expression_and_name_list(*part_list, + options); + + ada_log("regular expression string: ", regular_expression_string); + + // Let pattern string be the result of running generate a pattern + // string given part list and options. + auto pattern_string = + url_pattern_helpers::generate_pattern_string(*part_list, options); + + // Let regular expression be RegExpCreate(regular expression string, + // flags). If this throws an exception, catch it, and throw a + // TypeError. + std::optional regular_expression = + regex_provider::create_instance(regular_expression_string, + options.ignore_case); + + if (!regular_expression) { + return tl::unexpected(errors::type_error); + } + + // For each part of part list: + // - If part's type is "regexp", then set has regexp groups to true. + const auto has_regexp = [](const auto& part) { return part.is_regexp(); }; + const bool has_regexp_groups = std::ranges::any_of(*part_list, has_regexp); + + ada_log("has regexp groups: ", has_regexp_groups); + + // Return a new component whose pattern string is pattern string, regular + // expression is regular expression, group name list is name list, and has + // regexp groups is has regexp groups. + return url_pattern_component( + std::move(pattern_string), std::move(*regular_expression), + std::move(name_list), has_regexp_groups); +} + +template +result> url_pattern::exec( + const url_pattern_input& input, const std::string_view* base_url) { + // Return the result of match given this's associated URL pattern, input, and + // baseURL if given. + return match(input, base_url); +} + +template +result url_pattern::test( + const url_pattern_input& input, const std::string_view* base_url) { + // TODO: Optimization opportunity. Rather than returning `url_pattern_result` + // Implement a fast path just like `can_parse()` in ada_url. + // Let result be the result of match given this's associated URL pattern, + // input, and baseURL if given. + // If result is null, return false. + if (auto result = match(input, base_url); result.has_value()) { + return result->has_value(); + } + return tl::unexpected(errors::type_error); +} + +template +result> url_pattern::match( + const url_pattern_input& input, const std::string_view* base_url_string) { + std::string protocol{}; + std::string username{}; + std::string password{}; + std::string hostname{}; + std::string port{}; + std::string pathname{}; + std::string search{}; + std::string hash{}; + + // Let inputs be an empty list. + // Append input to inputs. + std::vector inputs{input}; + + // If input is a URLPatternInit then: + if (std::holds_alternative(input)) { + ada_log( + "url_pattern::match called with url_pattern_init and base_url_string=", + base_url_string); + // If baseURLString was given, throw a TypeError. + if (base_url_string) { + ada_log("failed to match because base_url_string was given"); + return tl::unexpected(errors::type_error); + } + + // Let applyResult be the result of process a URLPatternInit given input, + // "url", protocol, username, password, hostname, port, pathname, search, + // and hash. + auto apply_result = url_pattern_init::process( + std::get(input), url_pattern_init::process_type::url, + protocol, username, password, hostname, port, pathname, search, hash); + + // If this throws an exception, catch it, and return null. + if (!apply_result.has_value()) { + ada_log("match returned std::nullopt because process threw"); + return std::nullopt; + } + + // Set protocol to applyResult["protocol"]. + ADA_ASSERT_TRUE(apply_result->protocol.has_value()); + protocol = std::move(apply_result->protocol.value()); + + // Set username to applyResult["username"]. + ADA_ASSERT_TRUE(apply_result->username.has_value()); + username = std::move(apply_result->username.value()); + + // Set password to applyResult["password"]. + ADA_ASSERT_TRUE(apply_result->password.has_value()); + password = std::move(apply_result->password.value()); + + // Set hostname to applyResult["hostname"]. + ADA_ASSERT_TRUE(apply_result->hostname.has_value()); + hostname = std::move(apply_result->hostname.value()); + + // Set port to applyResult["port"]. + ADA_ASSERT_TRUE(apply_result->port.has_value()); + port = std::move(apply_result->port.value()); + + // Set pathname to applyResult["pathname"]. + ADA_ASSERT_TRUE(apply_result->pathname.has_value()); + pathname = std::move(apply_result->pathname.value()); + + // Set search to applyResult["search"]. + ADA_ASSERT_TRUE(apply_result->search.has_value()); + if (apply_result->search->starts_with("?")) { + search = apply_result->search->substr(1); + } else { + search = std::move(apply_result->search.value()); + } + + // Set hash to applyResult["hash"]. + ADA_ASSERT_TRUE(apply_result->hash.has_value()); + ADA_ASSERT_TRUE(!apply_result->hash->starts_with("#")); + hash = std::move(apply_result->hash.value()); + } else { + ADA_ASSERT_TRUE(std::holds_alternative(input)); + + // Let baseURL be null. + result base_url; + + // If baseURLString was given, then: + if (base_url_string) { + // Let baseURL be the result of parsing baseURLString. + base_url = ada::parse(*base_url_string, nullptr); + + // If baseURL is failure, return null. + if (!base_url) { + ada_log("match returned std::nullopt because failed to parse base_url=", + *base_url_string); + return std::nullopt; + } + + // Append baseURLString to inputs. + inputs.emplace_back(*base_url_string); + } + + url_aggregator* base_url_value = + base_url.has_value() ? &*base_url : nullptr; + + // Set url to the result of parsing input given baseURL. + auto url = ada::parse(std::get(input), + base_url_value); + + // If url is failure, return null. + if (!url) { + ada_log("match returned std::nullopt because url failed"); + return std::nullopt; + } + + // Set protocol to url's scheme. + // IMPORTANT: Not documented on the URLPattern spec, but protocol suffix ':' + // is removed. Similar work was done on workerd: + // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2038 + protocol = url->get_protocol().substr(0, url->get_protocol().size() - 1); + // Set username to url's username. + username = url->get_username(); + // Set password to url's password. + password = url->get_password(); + // Set hostname to url's host, serialized, or the empty string if the value + // is null. + hostname = url->get_hostname(); + // Set port to url's port, serialized, or the empty string if the value is + // null. + port = url->get_port(); + // Set pathname to the result of URL path serializing url. + pathname = url->get_pathname(); + // Set search to url's query or the empty string if the value is null. + // IMPORTANT: Not documented on the URLPattern spec, but search prefix '?' + // is removed. Similar work was done on workerd: + // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2232 + if (url->has_search()) { + auto view = url->get_search(); + search = view.starts_with("?") ? url->get_search().substr(1) : view; + } + // Set hash to url's fragment or the empty string if the value is null. + // IMPORTANT: Not documented on the URLPattern spec, but hash prefix '#' is + // removed. Similar work was done on workerd: + // https://github.com/cloudflare/workerd/blob/8620d14012513a6ce04d079e401d3becac3c67bd/src/workerd/jsg/url.c%2B%2B#L2242 + if (url->has_hash()) { + auto view = url->get_hash(); + hash = view.starts_with("#") ? url->get_hash().substr(1) : view; + } + } + + // Let protocolExecResult be RegExpBuiltinExec(urlPattern's protocol + // component's regular expression, protocol). + auto protocol_exec_result = + regex_provider::regex_search(protocol, protocol_component.regexp); + + if (!protocol_exec_result) { + return std::nullopt; + } + + // Let usernameExecResult be RegExpBuiltinExec(urlPattern's username + // component's regular expression, username). + auto username_exec_result = + regex_provider::regex_search(username, username_component.regexp); + + if (!username_exec_result) { + return std::nullopt; + } + + // Let passwordExecResult be RegExpBuiltinExec(urlPattern's password + // component's regular expression, password). + auto password_exec_result = + regex_provider::regex_search(password, password_component.regexp); + + if (!password_exec_result) { + return std::nullopt; + } + + // Let hostnameExecResult be RegExpBuiltinExec(urlPattern's hostname + // component's regular expression, hostname). + auto hostname_exec_result = + regex_provider::regex_search(hostname, hostname_component.regexp); + + if (!hostname_exec_result) { + return std::nullopt; + } + + // Let portExecResult be RegExpBuiltinExec(urlPattern's port component's + // regular expression, port). + auto port_exec_result = + regex_provider::regex_search(port, port_component.regexp); + + if (!port_exec_result) { + return std::nullopt; + } + + // Let pathnameExecResult be RegExpBuiltinExec(urlPattern's pathname + // component's regular expression, pathname). + auto pathname_exec_result = + regex_provider::regex_search(pathname, pathname_component.regexp); + + if (!pathname_exec_result) { + return std::nullopt; + } + + // Let searchExecResult be RegExpBuiltinExec(urlPattern's search component's + // regular expression, search). + auto search_exec_result = + regex_provider::regex_search(search, search_component.regexp); + + if (!search_exec_result) { + return std::nullopt; + } + + // Let hashExecResult be RegExpBuiltinExec(urlPattern's hash component's + // regular expression, hash). + auto hash_exec_result = + regex_provider::regex_search(hash, hash_component.regexp); + + if (!hash_exec_result) { + return std::nullopt; + } + + // Let result be a new URLPatternResult. + auto result = url_pattern_result{}; + // Set result["inputs"] to inputs. + result.inputs = std::move(inputs); + // Set result["protocol"] to the result of creating a component match result + // given urlPattern's protocol component, protocol, and protocolExecResult. + result.protocol = protocol_component.create_component_match_result( + std::move(protocol), std::move(*protocol_exec_result)); + + // Set result["username"] to the result of creating a component match result + // given urlPattern's username component, username, and usernameExecResult. + result.username = username_component.create_component_match_result( + std::move(username), std::move(*username_exec_result)); + + // Set result["password"] to the result of creating a component match result + // given urlPattern's password component, password, and passwordExecResult. + result.password = password_component.create_component_match_result( + std::move(password), std::move(*password_exec_result)); + + // Set result["hostname"] to the result of creating a component match result + // given urlPattern's hostname component, hostname, and hostnameExecResult. + result.hostname = hostname_component.create_component_match_result( + std::move(hostname), std::move(*hostname_exec_result)); + + // Set result["port"] to the result of creating a component match result given + // urlPattern's port component, port, and portExecResult. + result.port = port_component.create_component_match_result( + std::move(port), std::move(*port_exec_result)); + + // Set result["pathname"] to the result of creating a component match result + // given urlPattern's pathname component, pathname, and pathnameExecResult. + result.pathname = pathname_component.create_component_match_result( + std::move(pathname), std::move(*pathname_exec_result)); + + // Set result["search"] to the result of creating a component match result + // given urlPattern's search component, search, and searchExecResult. + result.search = search_component.create_component_match_result( + std::move(search), std::move(*search_exec_result)); + + // Set result["hash"] to the result of creating a component match result given + // urlPattern's hash component, hash, and hashExecResult. + result.hash = hash_component.create_component_match_result( + std::move(hash), std::move(*hash_exec_result)); + + return result; +} + +} // namespace ada +#endif // ADA_INCLUDE_URL_PATTERN +#endif +/* end file include/ada/url_pattern-inl.h */ +/* begin file include/ada/url_pattern_helpers-inl.h */ +/** + * @file url_pattern_helpers-inl.h + * @brief Declaration for the URLPattern helpers. + */ +#ifndef ADA_URL_PATTERN_HELPERS_INL_H +#define ADA_URL_PATTERN_HELPERS_INL_H + +#include +#include + + +#if ADA_INCLUDE_URL_PATTERN +namespace ada::url_pattern_helpers { +#if defined(ADA_TESTING) || defined(ADA_LOGGING) +inline std::string to_string(token_type type) { + switch (type) { + case token_type::INVALID_CHAR: + return "INVALID_CHAR"; + case token_type::OPEN: + return "OPEN"; + case token_type::CLOSE: + return "CLOSE"; + case token_type::REGEXP: + return "REGEXP"; + case token_type::NAME: + return "NAME"; + case token_type::CHAR: + return "CHAR"; + case token_type::ESCAPED_CHAR: + return "ESCAPED_CHAR"; + case token_type::OTHER_MODIFIER: + return "OTHER_MODIFIER"; + case token_type::ASTERISK: + return "ASTERISK"; + case token_type::END: + return "END"; + default: + ada::unreachable(); + } +} +#endif // defined(ADA_TESTING) || defined(ADA_LOGGING) + +template +constexpr void constructor_string_parser::rewind() { + // Set parser's token index to parser's component start. + token_index = component_start; + // Set parser's token increment to 0. + token_increment = 0; +} + +template +constexpr bool constructor_string_parser::is_hash_prefix() { + // Return the result of running is a non-special pattern char given parser, + // parser's token index and "#". + return is_non_special_pattern_char(token_index, '#'); +} + +template +constexpr bool constructor_string_parser::is_search_prefix() { + // If result of running is a non-special pattern char given parser, parser's + // token index and "?" is true, then return true. + if (is_non_special_pattern_char(token_index, '?')) { + return true; + } + + // If parser's token list[parser's token index]'s value is not "?", then + // return false. + if (token_list[token_index].value != "?") { + return false; + } + + // If previous index is less than 0, then return true. + if (token_index == 0) return true; + // Let previous index be parser's token index - 1. + auto previous_index = token_index - 1; + // Let previous token be the result of running get a safe token given parser + // and previous index. + auto previous_token = get_safe_token(previous_index); + ADA_ASSERT_TRUE(previous_token); + // If any of the following are true, then return false: + // - previous token's type is "name". + // - previous token's type is "regexp". + // - previous token's type is "close". + // - previous token's type is "asterisk". + return !(previous_token->type == token_type::NAME || + previous_token->type == token_type::REGEXP || + previous_token->type == token_type::CLOSE || + previous_token->type == token_type::ASTERISK); +} + +template +constexpr bool +constructor_string_parser::is_non_special_pattern_char( + size_t index, uint32_t value) const { + // Let token be the result of running get a safe token given parser and index. + auto token = get_safe_token(index); + ADA_ASSERT_TRUE(token); + + // If token's value is not value, then return false. + // TODO: Remove this once we make sure get_safe_token returns a non-empty + // string. + if (!token->value.empty() && + static_cast(token->value[0]) != value) { + return false; + } + + // If any of the following are true: + // - token's type is "char"; + // - token's type is "escaped-char"; or + // - token's type is "invalid-char", + // - then return true. + return token->type == token_type::CHAR || + token->type == token_type::ESCAPED_CHAR || + token->type == token_type::INVALID_CHAR; +} + +template +constexpr const token* +constructor_string_parser::get_safe_token(size_t index) const { + // If index is less than parser's token list's size, then return parser's + // token list[index]. + if (index < token_list.size()) [[likely]] { + return &token_list[index]; + } + + // Assert: parser's token list's size is greater than or equal to 1. + ADA_ASSERT_TRUE(!token_list.empty()); + + // Let token be parser's token list[last index]. + // Assert: token's type is "end". + ADA_ASSERT_TRUE(token_list.back().type == token_type::END); + + // Return token. + return &token_list.back(); +} + +template +constexpr bool constructor_string_parser::is_group_open() + const { + // If parser's token list[parser's token index]'s type is "open", then return + // true. + return token_list[token_index].type == token_type::OPEN; +} + +template +constexpr bool constructor_string_parser::is_group_close() + const { + // If parser's token list[parser's token index]'s type is "close", then return + // true. + return token_list[token_index].type == token_type::CLOSE; +} + +template +constexpr bool +constructor_string_parser::next_is_authority_slashes() const { + // If the result of running is a non-special pattern char given parser, + // parser's token index + 1, and "/" is false, then return false. + if (!is_non_special_pattern_char(token_index + 1, '/')) { + return false; + } + // If the result of running is a non-special pattern char given parser, + // parser's token index + 2, and "/" is false, then return false. + if (!is_non_special_pattern_char(token_index + 2, '/')) { + return false; + } + return true; +} + +template +constexpr bool constructor_string_parser::is_protocol_suffix() + const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and ":". + return is_non_special_pattern_char(token_index, ':'); +} + +template +void constructor_string_parser::change_state(State new_state, + size_t skip) { + // If parser's state is not "init", not "authority", and not "done", then set + // parser's result[parser's state] to the result of running make a component + // string given parser. + if (state != State::INIT && state != State::AUTHORITY && + state != State::DONE) { + auto value = make_component_string(); + // TODO: Simplify this. + switch (state) { + case State::PROTOCOL: { + result.protocol = value; + break; + } + case State::USERNAME: { + result.username = value; + break; + } + case State::PASSWORD: { + result.password = value; + break; + } + case State::HOSTNAME: { + result.hostname = value; + break; + } + case State::PORT: { + result.port = value; + break; + } + case State::PATHNAME: { + result.pathname = value; + break; + } + case State::SEARCH: { + result.search = value; + break; + } + case State::HASH: { + result.hash = value; + break; + } + default: + ada::unreachable(); + } + } + + // If parser's state is not "init" and new state is not "done", then: + if (state != State::INIT && new_state != State::DONE) { + // If parser's state is "protocol", "authority", "username", or "password"; + // new state is "port", "pathname", "search", or "hash"; and parser's + // result["hostname"] does not exist, then set parser's result["hostname"] + // to the empty string. + if ((state == State::PROTOCOL || state == State::AUTHORITY || + state == State::USERNAME || state == State::PASSWORD) && + (new_state == State::PORT || new_state == State::PATHNAME || + new_state == State::SEARCH || new_state == State::HASH) && + !result.hostname) + result.hostname = ""; + } + + // If parser's state is "protocol", "authority", "username", "password", + // "hostname", or "port"; new state is "search" or "hash"; and parser's + // result["pathname"] does not exist, then: + if ((state == State::PROTOCOL || state == State::AUTHORITY || + state == State::USERNAME || state == State::PASSWORD || + state == State::HOSTNAME || state == State::PORT) && + (new_state == State::SEARCH || new_state == State::HASH) && + !result.pathname) { + if (protocol_matches_a_special_scheme_flag) { + result.pathname = "/"; + } else { + // Otherwise, set parser's result["pathname"] to the empty string. + result.pathname = ""; + } + } + + // If parser's state is "protocol", "authority", "username", "password", + // "hostname", "port", or "pathname"; new state is "hash"; and parser's + // result["search"] does not exist, then set parser's result["search"] to + // the empty string. + if ((state == State::PROTOCOL || state == State::AUTHORITY || + state == State::USERNAME || state == State::PASSWORD || + state == State::HOSTNAME || state == State::PORT || + state == State::PATHNAME) && + new_state == State::HASH && !result.search) { + result.search = ""; + } + + // Set parser's state to new state. + state = new_state; + // Increment parser's token index by skip. + token_index += skip; + // Set parser's component start to parser's token index. + component_start = token_index; + // Set parser's token increment to 0. + token_increment = 0; +} + +template +std::string constructor_string_parser::make_component_string() { + // Assert: parser's token index is less than parser's token list's size. + ADA_ASSERT_TRUE(token_index < token_list.size()); + + // Let token be parser's token list[parser's token index]. + // Let end index be token's index. + const auto end_index = token_list[token_index].index; + // Let component start token be the result of running get a safe token given + // parser and parser's component start. + const auto component_start_token = get_safe_token(component_start); + ADA_ASSERT_TRUE(component_start_token); + // Let component start input index be component start token's index. + const auto component_start_input_index = component_start_token->index; + // Return the code point substring from component start input index to end + // index within parser's input. + return input.substr(component_start_input_index, + end_index - component_start_input_index); +} + +template +constexpr bool +constructor_string_parser::is_an_identity_terminator() const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and "@". + return is_non_special_pattern_char(token_index, '@'); +} + +template +constexpr bool constructor_string_parser::is_pathname_start() + const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and "/". + return is_non_special_pattern_char(token_index, '/'); +} + +template +constexpr bool constructor_string_parser::is_password_prefix() + const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and ":". + return is_non_special_pattern_char(token_index, ':'); +} + +template +constexpr bool constructor_string_parser::is_an_ipv6_open() + const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and "[". + return is_non_special_pattern_char(token_index, '['); +} + +template +constexpr bool constructor_string_parser::is_an_ipv6_close() + const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and "]". + return is_non_special_pattern_char(token_index, ']'); +} + +template +constexpr bool constructor_string_parser::is_port_prefix() + const { + // Return the result of running is a non-special pattern char given parser, + // parser's token index, and ":". + return is_non_special_pattern_char(token_index, ':'); +} + +constexpr void Tokenizer::get_next_code_point() { + ada_log("Tokenizer::get_next_code_point called with index=", next_index); + ADA_ASSERT_TRUE(next_index < input.size()); + // this assumes that we have a valid, non-truncated UTF-8 stream. + code_point = 0; + size_t number_bytes = 0; + unsigned char first_byte = input[next_index]; + + if ((first_byte & 0x80) == 0) { + // 1-byte character (ASCII) + next_index++; + code_point = first_byte; + ada_log("Tokenizer::get_next_code_point returning ASCII code point=", + uint32_t(code_point)); + ada_log("Tokenizer::get_next_code_point next_index =", next_index, + " input.size()=", input.size()); + return; + } + ada_log("Tokenizer::get_next_code_point read first byte=", + uint32_t(first_byte)); + if ((first_byte & 0xE0) == 0xC0) { + code_point = first_byte & 0x1F; + number_bytes = 2; + ada_log("Tokenizer::get_next_code_point two bytes"); + } else if ((first_byte & 0xF0) == 0xE0) { + code_point = first_byte & 0x0F; + number_bytes = 3; + ada_log("Tokenizer::get_next_code_point three bytes"); + } else if ((first_byte & 0xF8) == 0xF0) { + code_point = first_byte & 0x07; + number_bytes = 4; + ada_log("Tokenizer::get_next_code_point four bytes"); + } + ADA_ASSERT_TRUE(number_bytes + next_index <= input.size()); + + for (size_t i = 1 + next_index; i < number_bytes + next_index; ++i) { + unsigned char byte = input[i]; + ada_log("Tokenizer::get_next_code_point read byte=", uint32_t(byte)); + code_point = (code_point << 6) | (byte & 0x3F); + } + ada_log("Tokenizer::get_next_code_point returning non-ASCII code point=", + uint32_t(code_point)); + ada_log("Tokenizer::get_next_code_point next_index =", next_index, + " input.size()=", input.size()); + next_index += number_bytes; +} + +constexpr void Tokenizer::seek_and_get_next_code_point(size_t new_index) { + ada_log("Tokenizer::seek_and_get_next_code_point called with new_index=", + new_index); + // Set tokenizer's next index to index. + next_index = new_index; + // Run get the next code point given tokenizer. + get_next_code_point(); +} + +inline void Tokenizer::add_token(token_type type, size_t next_position, + size_t value_position, size_t value_length) { + ada_log("Tokenizer::add_token called with type=", to_string(type), + " next_position=", next_position, " value_position=", value_position); + ADA_ASSERT_TRUE(next_position >= value_position); + + // Let token be a new token. + // Set token's type to type. + // Set token's index to tokenizer's index. + // Set token's value to the code point substring from value position with + // length value length within tokenizer's input. + // Append token to the back of tokenizer's token list. + token_list.emplace_back(type, index, + input.substr(value_position, value_length)); + // Set tokenizer's index to next position. + index = next_position; +} + +inline void Tokenizer::add_token_with_default_length(token_type type, + size_t next_position, + size_t value_position) { + // Let computed length be next position - value position. + auto computed_length = next_position - value_position; + // Run add a token given tokenizer, type, next position, value position, and + // computed length. + add_token(type, next_position, value_position, computed_length); +} + +inline void Tokenizer::add_token_with_defaults(token_type type) { + ada_log("Tokenizer::add_token_with_defaults called with type=", + to_string(type)); + // Run add a token with default length given tokenizer, type, tokenizer's next + // index, and tokenizer's index. + add_token_with_default_length(type, next_index, index); +} + +inline ada_warn_unused std::optional +Tokenizer::process_tokenizing_error(size_t next_position, + size_t value_position) { + // If tokenizer's policy is "strict", then throw a TypeError. + if (policy == token_policy::strict) { + ada_log("process_tokenizing_error failed with next_position=", + next_position, " value_position=", value_position); + return errors::type_error; + } + // Assert: tokenizer's policy is "lenient". + ADA_ASSERT_TRUE(policy == token_policy::lenient); + // Run add a token with default length given tokenizer, "invalid-char", next + // position, and value position. + add_token_with_default_length(token_type::INVALID_CHAR, next_position, + value_position); + return std::nullopt; +} + +template +token* url_pattern_parser::try_consume_modifier_token() { + // Let token be the result of running try to consume a token given parser and + // "other-modifier". + auto token = try_consume_token(token_type::OTHER_MODIFIER); + // If token is not null, then return token. + if (token) return token; + // Set token to the result of running try to consume a token given parser and + // "asterisk". + // Return token. + return try_consume_token(token_type::ASTERISK); +} + +template +token* url_pattern_parser::try_consume_regexp_or_wildcard_token( + const token* name_token) { + // Let token be the result of running try to consume a token given parser and + // "regexp". + auto token = try_consume_token(token_type::REGEXP); + // If name token is null and token is null, then set token to the result of + // running try to consume a token given parser and "asterisk". + if (!name_token && !token) { + token = try_consume_token(token_type::ASTERISK); + } + // Return token. + return token; +} + +template +token* url_pattern_parser::try_consume_token(token_type type) { + ada_log("url_pattern_parser::try_consume_token called with type=", + to_string(type)); + // Assert: parser's index is less than parser's token list size. + ADA_ASSERT_TRUE(index < tokens.size()); + // Let next token be parser's token list[parser's index]. + auto& next_token = tokens[index]; + // If next token's type is not type return null. + if (next_token.type != type) return nullptr; + // Increase parser's index by 1. + index++; + // Return next token. + return &next_token; +} + +template +std::string url_pattern_parser::consume_text() { + // Let result be the empty string. + std::string result{}; + // While true: + while (true) { + // Let token be the result of running try to consume a token given parser + // and "char". + auto token = try_consume_token(token_type::CHAR); + // If token is null, then set token to the result of running try to consume + // a token given parser and "escaped-char". + if (!token) token = try_consume_token(token_type::ESCAPED_CHAR); + // If token is null, then break. + if (!token) break; + // Append token's value to the end of result. + result.append(token->value); + } + // Return result. + return result; +} + +template +bool url_pattern_parser::consume_required_token(token_type type) { + ada_log("url_pattern_parser::consume_required_token called with type=", + to_string(type)); + // Let result be the result of running try to consume a token given parser and + // type. + return try_consume_token(type) != nullptr; +} + +template +std::optional +url_pattern_parser::maybe_add_part_from_the_pending_fixed_value() { + // If parser's pending fixed value is the empty string, then return. + if (pending_fixed_value.empty()) { + ada_log("pending_fixed_value is empty"); + return std::nullopt; + } + // Let encoded value be the result of running parser's encoding callback given + // parser's pending fixed value. + auto encoded_value = encoding_callback(pending_fixed_value); + if (!encoded_value) { + ada_log("failed to encode pending_fixed_value: ", pending_fixed_value); + return encoded_value.error(); + } + // Set parser's pending fixed value to the empty string. + pending_fixed_value.clear(); + // Let part be a new part whose type is "fixed-text", value is encoded value, + // and modifier is "none". + // Append part to parser's part list. + parts.emplace_back(url_pattern_part_type::FIXED_TEXT, + std::move(*encoded_value), + url_pattern_part_modifier::none); + return std::nullopt; +} + +template +std::optional url_pattern_parser::add_part( + std::string_view prefix, token* name_token, token* regexp_or_wildcard_token, + std::string_view suffix, token* modifier_token) { + // Let modifier be "none". + auto modifier = url_pattern_part_modifier::none; + // If modifier token is not null: + if (modifier_token) { + // If modifier token's value is "?" then set modifier to "optional". + if (modifier_token->value == "?") { + modifier = url_pattern_part_modifier::optional; + } else if (modifier_token->value == "*") { + // Otherwise if modifier token's value is "*" then set modifier to + // "zero-or-more". + modifier = url_pattern_part_modifier::zero_or_more; + } else if (modifier_token->value == "+") { + // Otherwise if modifier token's value is "+" then set modifier to + // "one-or-more". + modifier = url_pattern_part_modifier::one_or_more; + } + } + // If name token is null and regexp or wildcard token is null and modifier + // is "none": + if (!name_token && !regexp_or_wildcard_token && + modifier == url_pattern_part_modifier::none) { + // Append prefix to the end of parser's pending fixed value. + pending_fixed_value.append(prefix); + return std::nullopt; + } + // Run maybe add a part from the pending fixed value given parser. + if (auto error = maybe_add_part_from_the_pending_fixed_value()) { + return *error; + } + // If name token is null and regexp or wildcard token is null: + if (!name_token && !regexp_or_wildcard_token) { + // Assert: suffix is the empty string. + ADA_ASSERT_TRUE(suffix.empty()); + // If prefix is the empty string, then return. + if (prefix.empty()) return std::nullopt; + // Let encoded value be the result of running parser's encoding callback + // given prefix. + auto encoded_value = encoding_callback(prefix); + if (!encoded_value) { + return encoded_value.error(); + } + // Let part be a new part whose type is "fixed-text", value is encoded + // value, and modifier is modifier. + // Append part to parser's part list. + parts.emplace_back(url_pattern_part_type::FIXED_TEXT, + std::move(*encoded_value), modifier); + return std::nullopt; + } + // Let regexp value be the empty string. + std::string regexp_value{}; + // If regexp or wildcard token is null, then set regexp value to parser's + // segment wildcard regexp. + if (!regexp_or_wildcard_token) { + regexp_value = segment_wildcard_regexp; + } else if (regexp_or_wildcard_token->type == token_type::ASTERISK) { + // Otherwise if regexp or wildcard token's type is "asterisk", then set + // regexp value to the full wildcard regexp value. + regexp_value = ".*"; + } else { + // Otherwise set regexp value to regexp or wildcard token's value. + regexp_value = regexp_or_wildcard_token->value; + } + // Let type be "regexp". + auto type = url_pattern_part_type::REGEXP; + // If regexp value is parser's segment wildcard regexp: + if (regexp_value == segment_wildcard_regexp) { + // Set type to "segment-wildcard". + type = url_pattern_part_type::SEGMENT_WILDCARD; + // Set regexp value to the empty string. + regexp_value.clear(); + } else if (regexp_value == ".*") { + // Otherwise if regexp value is the full wildcard regexp value: + // Set type to "full-wildcard". + type = url_pattern_part_type::FULL_WILDCARD; + // Set regexp value to the empty string. + regexp_value.clear(); + } + // Let name be the empty string. + std::string name{}; + // If name token is not null, then set name to name token's value. + if (name_token) { + name = name_token->value; + } else if (regexp_or_wildcard_token != nullptr) { + // Otherwise if regexp or wildcard token is not null: + // Set name to parser's next numeric name, serialized. + name = std::to_string(next_numeric_name); + // Increment parser's next numeric name by 1. + next_numeric_name++; + } + // If the result of running is a duplicate name given parser and name is + // true, then throw a TypeError. + if (std::ranges::any_of( + parts, [&name](const auto& part) { return part.name == name; })) { + return errors::type_error; + } + // Let encoded prefix be the result of running parser's encoding callback + // given prefix. + auto encoded_prefix = encoding_callback(prefix); + if (!encoded_prefix) return encoded_prefix.error(); + // Let encoded suffix be the result of running parser's encoding callback + // given suffix. + auto encoded_suffix = encoding_callback(suffix); + if (!encoded_suffix) return encoded_suffix.error(); + // Let part be a new part whose type is type, value is regexp value, + // modifier is modifier, name is name, prefix is encoded prefix, and suffix + // is encoded suffix. + // Append part to parser's part list. + parts.emplace_back(type, std::move(regexp_value), modifier, std::move(name), + std::move(*encoded_prefix), std::move(*encoded_suffix)); + return std::nullopt; +} + +template +tl::expected, errors> parse_pattern_string( + std::string_view input, url_pattern_compile_component_options& options, + F& encoding_callback) { + ada_log("parse_pattern_string input=", input); + // Let parser be a new pattern parser whose encoding callback is encoding + // callback and segment wildcard regexp is the result of running generate a + // segment wildcard regexp given options. + auto parser = url_pattern_parser( + encoding_callback, generate_segment_wildcard_regexp(options)); + // Set parser's token list to the result of running tokenize given input and + // "strict". + auto tokenize_result = tokenize(input, token_policy::strict); + if (!tokenize_result) { + ada_log("parse_pattern_string tokenize failed"); + return tl::unexpected(tokenize_result.error()); + } + parser.tokens = std::move(*tokenize_result); + + // While parser's index is less than parser's token list's size: + while (parser.can_continue()) { + // Let char token be the result of running try to consume a token given + // parser and "char". + auto char_token = parser.try_consume_token(token_type::CHAR); + // Let name token be the result of running try to consume a token given + // parser and "name". + auto name_token = parser.try_consume_token(token_type::NAME); + // Let regexp or wildcard token be the result of running try to consume a + // regexp or wildcard token given parser and name token. + auto regexp_or_wildcard_token = + parser.try_consume_regexp_or_wildcard_token(name_token); + // If name token is not null or regexp or wildcard token is not null: + if (name_token || regexp_or_wildcard_token) { + // Let prefix be the empty string. + std::string prefix{}; + // If char token is not null then set prefix to char token's value. + if (char_token) prefix = char_token->value; + // If prefix is not the empty string and not options's prefix code point: + if (!prefix.empty() && prefix != options.get_prefix()) { + // Append prefix to the end of parser's pending fixed value. + parser.pending_fixed_value.append(prefix); + // Set prefix to the empty string. + prefix.clear(); + } + // Run maybe add a part from the pending fixed value given parser. + if (auto error = parser.maybe_add_part_from_the_pending_fixed_value()) { + ada_log("maybe_add_part_from_the_pending_fixed_value failed"); + return tl::unexpected(*error); + } + // Let modifier token be the result of running try to consume a modifier + // token given parser. + auto modifier_token = parser.try_consume_modifier_token(); + // Run add a part given parser, prefix, name token, regexp or wildcard + // token, the empty string, and modifier token. + if (auto error = + parser.add_part(prefix, name_token, regexp_or_wildcard_token, "", + modifier_token)) { + ada_log("parser.add_part failed"); + return tl::unexpected(*error); + } + // Continue. + continue; + } + + // Let fixed token be char token. + auto fixed_token = char_token; + // If fixed token is null, then set fixed token to the result of running try + // to consume a token given parser and "escaped-char". + if (!fixed_token) + fixed_token = parser.try_consume_token(token_type::ESCAPED_CHAR); + // If fixed token is not null: + if (fixed_token) { + // Append fixed token's value to parser's pending fixed value. + parser.pending_fixed_value.append(fixed_token->value); + // Continue. + continue; + } + // Let open token be the result of running try to consume a token given + // parser and "open". + auto open_token = parser.try_consume_token(token_type::OPEN); + // If open token is not null: + if (open_token) { + // Set prefix be the result of running consume text given parser. + auto prefix_ = parser.consume_text(); + // Set name token to the result of running try to consume a token given + // parser and "name". + name_token = parser.try_consume_token(token_type::NAME); + // Set regexp or wildcard token to the result of running try to consume a + // regexp or wildcard token given parser and name token. + regexp_or_wildcard_token = + parser.try_consume_regexp_or_wildcard_token(name_token); + // Let suffix be the result of running consume text given parser. + auto suffix_ = parser.consume_text(); + // Run consume a required token given parser and "close". + if (!parser.consume_required_token(token_type::CLOSE)) { + ada_log("parser.consume_required_token failed"); + return tl::unexpected(errors::type_error); + } + // Set modifier token to the result of running try to consume a modifier + // token given parser. + auto modifier_token = parser.try_consume_modifier_token(); + // Run add a part given parser, prefix, name token, regexp or wildcard + // token, suffix, and modifier token. + if (auto error = + parser.add_part(prefix_, name_token, regexp_or_wildcard_token, + suffix_, modifier_token)) { + return tl::unexpected(*error); + } + // Continue. + continue; + } + // Run maybe add a part from the pending fixed value given parser. + if (auto error = parser.maybe_add_part_from_the_pending_fixed_value()) { + ada_log("maybe_add_part_from_the_pending_fixed_value failed on line 992"); + return tl::unexpected(*error); + } + // Run consume a required token given parser and "end". + if (!parser.consume_required_token(token_type::END)) { + return tl::unexpected(errors::type_error); + } + } + ada_log("parser.parts size is: ", parser.parts.size()); + // Return parser's part list. + return parser.parts; +} + +template +bool protocol_component_matches_special_scheme( + url_pattern_component& component) { + // let's avoid unnecessary copy here. + auto& regex = component.regexp; + return regex_provider::regex_match("http", regex) || + regex_provider::regex_match("https", regex) || + regex_provider::regex_match("ws", regex) || + regex_provider::regex_match("wss", regex) || + regex_provider::regex_match("ftp", regex); +} + +template +inline std::optional constructor_string_parser< + regex_provider>::compute_protocol_matches_special_scheme_flag() { + ada_log( + "constructor_string_parser::compute_protocol_matches_special_scheme_" + "flag"); + // Let protocol string be the result of running make a component string given + // parser. + auto protocol_string = make_component_string(); + // Let protocol component be the result of compiling a component given + // protocol string, canonicalize a protocol, and default options. + auto protocol_component = url_pattern_component::compile( + protocol_string, canonicalize_protocol, + url_pattern_compile_component_options::DEFAULT); + if (!protocol_component) { + ada_log("url_pattern_component::compile failed for protocol_string ", + protocol_string); + return protocol_component.error(); + } + // If the result of running protocol component matches a special scheme given + // protocol component is true, then set parser's protocol matches a special + // scheme flag to true. + if (protocol_component_matches_special_scheme(*protocol_component)) { + protocol_matches_a_special_scheme_flag = true; + } + return std::nullopt; +} + +template +tl::expected +constructor_string_parser::parse(std::string_view input) { + ada_log("constructor_string_parser::parse input=", input); + // Let parser be a new constructor string parser whose input is input and + // token list is the result of running tokenize given input and "lenient". + auto token_list = tokenize(input, token_policy::lenient); + if (!token_list) { + return tl::unexpected(token_list.error()); + } + auto parser = constructor_string_parser(input, std::move(*token_list)); + + // While parser's token index is less than parser's token list size: + while (parser.token_index < parser.token_list.size()) { + // Set parser's token increment to 1. + parser.token_increment = 1; + + // If parser's token list[parser's token index]'s type is "end" then: + if (parser.token_list[parser.token_index].type == token_type::END) { + // If parser's state is "init": + if (parser.state == State::INIT) { + // Run rewind given parser. + parser.rewind(); + // If the result of running is a hash prefix given parser is true, then + // run change state given parser, "hash" and 1. + if (parser.is_hash_prefix()) { + parser.change_state(State::HASH, 1); + } else if (parser.is_search_prefix()) { + // Otherwise if the result of running is a search prefix given parser + // is true: Run change state given parser, "search" and 1. + parser.change_state(State::SEARCH, 1); + } else { + // Run change state given parser, "pathname" and 0. + parser.change_state(State::PATHNAME, 0); + } + // Increment parser's token index by parser's token increment. + parser.token_index += parser.token_increment; + // Continue. + continue; + } + + if (parser.state == State::AUTHORITY) { + // If parser's state is "authority": + // Run rewind and set state given parser, and "hostname". + parser.rewind(); + parser.change_state(State::HOSTNAME, 0); + // Increment parser's token index by parser's token increment. + parser.token_index += parser.token_increment; + // Continue. + continue; + } + + // Run change state given parser, "done" and 0. + parser.change_state(State::DONE, 0); + // Break. + break; + } + + // If the result of running is a group open given parser is true: + if (parser.is_group_open()) { + // Increment parser's group depth by 1. + parser.group_depth += 1; + // Increment parser's token index by parser's token increment. + parser.token_index += parser.token_increment; + } + + // If parser's group depth is greater than 0: + if (parser.group_depth > 0) { + // If the result of running is a group close given parser is true, then + // decrement parser's group depth by 1. + if (parser.is_group_close()) { + parser.group_depth -= 1; + } else { + // Increment parser's token index by parser's token increment. + parser.token_index += parser.token_increment; + continue; + } + } + + // Switch on parser's state and run the associated steps: + switch (parser.state) { + case State::INIT: { + // If the result of running is a protocol suffix given parser is true: + if (parser.is_protocol_suffix()) { + // Run rewind and set state given parser and "protocol". + parser.rewind(); + parser.change_state(State::PROTOCOL, 0); + } + break; + } + case State::PROTOCOL: { + // If the result of running is a protocol suffix given parser is true: + if (parser.is_protocol_suffix()) { + // Run compute protocol matches a special scheme flag given parser. + if (const auto error = + parser.compute_protocol_matches_special_scheme_flag()) { + ada_log("compute_protocol_matches_special_scheme_flag failed"); + return tl::unexpected(*error); + } + // Let next state be "pathname". + auto next_state = State::PATHNAME; + // Let skip be 1. + auto skip = 1; + // If the result of running next is authority slashes given parser is + // true: + if (parser.next_is_authority_slashes()) { + // Set next state to "authority". + next_state = State::AUTHORITY; + // Set skip to 3. + skip = 3; + } else if (parser.protocol_matches_a_special_scheme_flag) { + // Otherwise if parser's protocol matches a special scheme flag is + // true, then set next state to "authority". + next_state = State::AUTHORITY; + } + + // Run change state given parser, next state, and skip. + parser.change_state(next_state, skip); + } + break; + } + case State::AUTHORITY: { + // If the result of running is an identity terminator given parser is + // true, then run rewind and set state given parser and "username". + if (parser.is_an_identity_terminator()) { + parser.rewind(); + parser.change_state(State::USERNAME, 0); + } else if (parser.is_pathname_start() || parser.is_search_prefix() || + parser.is_hash_prefix()) { + // Otherwise if any of the following are true: + // - the result of running is a pathname start given parser; + // - the result of running is a search prefix given parser; or + // - the result of running is a hash prefix given parser, + // then run rewind and set state given parser and "hostname". + parser.rewind(); + parser.change_state(State::HOSTNAME, 0); + } + break; + } + case State::USERNAME: { + // If the result of running is a password prefix given parser is true, + // then run change state given parser, "password", and 1. + if (parser.is_password_prefix()) { + parser.change_state(State::PASSWORD, 1); + } else if (parser.is_an_identity_terminator()) { + // Otherwise if the result of running is an identity terminator given + // parser is true, then run change state given parser, "hostname", + // and 1. + parser.change_state(State::HOSTNAME, 1); + } + break; + } + case State::PASSWORD: { + // If the result of running is an identity terminator given parser is + // true, then run change state given parser, "hostname", and 1. + if (parser.is_an_identity_terminator()) { + parser.change_state(State::HOSTNAME, 1); + } + break; + } + case State::HOSTNAME: { + // If the result of running is an IPv6 open given parser is true, then + // increment parser's hostname IPv6 bracket depth by 1. + if (parser.is_an_ipv6_open()) { + parser.hostname_ipv6_bracket_depth += 1; + } else if (parser.is_an_ipv6_close()) { + // Otherwise if the result of running is an IPv6 close given parser is + // true, then decrement parser's hostname IPv6 bracket depth by 1. + parser.hostname_ipv6_bracket_depth -= 1; + } else if (parser.is_port_prefix() && + parser.hostname_ipv6_bracket_depth == 0) { + // Otherwise if the result of running is a port prefix given parser is + // true and parser's hostname IPv6 bracket depth is zero, then run + // change state given parser, "port", and 1. + parser.change_state(State::PORT, 1); + } else if (parser.is_pathname_start()) { + // Otherwise if the result of running is a pathname start given parser + // is true, then run change state given parser, "pathname", and 0. + parser.change_state(State::PATHNAME, 0); + } else if (parser.is_search_prefix()) { + // Otherwise if the result of running is a search prefix given parser + // is true, then run change state given parser, "search", and 1. + parser.change_state(State::SEARCH, 1); + } else if (parser.is_hash_prefix()) { + // Otherwise if the result of running is a hash prefix given parser is + // true, then run change state given parser, "hash", and 1. + parser.change_state(State::HASH, 1); + } + + break; + } + case State::PORT: { + // If the result of running is a pathname start given parser is true, + // then run change state given parser, "pathname", and 0. + if (parser.is_pathname_start()) { + parser.change_state(State::PATHNAME, 0); + } else if (parser.is_search_prefix()) { + // Otherwise if the result of running is a search prefix given parser + // is true, then run change state given parser, "search", and 1. + parser.change_state(State::SEARCH, 1); + } else if (parser.is_hash_prefix()) { + // Otherwise if the result of running is a hash prefix given parser is + // true, then run change state given parser, "hash", and 1. + parser.change_state(State::HASH, 1); + } + break; + } + case State::PATHNAME: { + // If the result of running is a search prefix given parser is true, + // then run change state given parser, "search", and 1. + if (parser.is_search_prefix()) { + parser.change_state(State::SEARCH, 1); + } else if (parser.is_hash_prefix()) { + // Otherwise if the result of running is a hash prefix given parser is + // true, then run change state given parser, "hash", and 1. + parser.change_state(State::HASH, 1); + } + break; + } + case State::SEARCH: { + // If the result of running is a hash prefix given parser is true, then + // run change state given parser, "hash", and 1. + if (parser.is_hash_prefix()) { + parser.change_state(State::HASH, 1); + } + break; + } + case State::HASH: { + // Do nothing + break; + } + default: { + // Assert: This step is never reached. + unreachable(); + } + } + + // Increment parser's token index by parser's token increment. + parser.token_index += parser.token_increment; + } + + // If parser's result contains "hostname" and not "port", then set parser's + // result["port"] to the empty string. + if (parser.result.hostname && !parser.result.port) { + parser.result.port = ""; + } + + // Return parser's result. + return parser.result; +} + +} // namespace ada::url_pattern_helpers +#endif // ADA_INCLUDE_URL_PATTERN +#endif +/* end file include/ada/url_pattern_helpers-inl.h */ + // Public API /* begin file include/ada/ada_version.h */ /** @@ -7256,13 +10515,13 @@ url_search_params_entries_iter::next() { #ifndef ADA_ADA_VERSION_H #define ADA_ADA_VERSION_H -#define ADA_VERSION "2.9.0" +#define ADA_VERSION "3.3.0" namespace ada { enum { - ADA_VERSION_MAJOR = 2, - ADA_VERSION_MINOR = 9, + ADA_VERSION_MAJOR = 3, + ADA_VERSION_MINOR = 3, ADA_VERSION_REVISION = 0, }; @@ -7270,61 +10529,35 @@ enum { #endif // ADA_ADA_VERSION_H /* end file include/ada/ada_version.h */ -/* begin file include/ada/implementation.h */ +/* begin file include/ada/implementation-inl.h */ /** - * @file implementation.h - * @brief Definitions for user facing functions for parsing URL and it's - * components. + * @file implementation-inl.h */ -#ifndef ADA_IMPLEMENTATION_H -#define ADA_IMPLEMENTATION_H - -#include -#include +#ifndef ADA_IMPLEMENTATION_INL_H +#define ADA_IMPLEMENTATION_INL_H -namespace ada { -enum class errors { generic_error }; -template -using result = tl::expected; -/** - * The URL parser takes a scalar value string input, with an optional null or - * base URL base (default null). The parser assumes the input is a valid ASCII - * or UTF-8 string. - * - * @param input the string input to analyze (must be valid ASCII or UTF-8) - * @param base_url the optional URL input to use as a base url. - * @return a parsed URL. - */ -template -ada_warn_unused ada::result parse( - std::string_view input, const result_type* base_url = nullptr); +#include +#include -extern template ada::result parse(std::string_view input, - const url* base_url); -extern template ada::result parse( - std::string_view input, const url_aggregator* base_url); +namespace ada { -/** - * Verifies whether the URL strings can be parsed. The function assumes - * that the inputs are valid ASCII or UTF-8 strings. - * @see https://url.spec.whatwg.org/#dom-url-canparse - * @return If URL can be parsed or not. - */ -bool can_parse(std::string_view input, - const std::string_view* base_input = nullptr); +#if ADA_INCLUDE_URL_PATTERN +template +ada_warn_unused tl::expected, errors> +parse_url_pattern(std::variant&& input, + const std::string_view* base_url, + const url_pattern_options* options) { + return parser::parse_url_pattern_impl(std::move(input), + base_url, options); +} +#endif // ADA_INCLUDE_URL_PATTERN -/** - * Computes a href string from a file path. The function assumes - * that the input is a valid ASCII or UTF-8 string. - * @return a href string (starts with file:://) - */ -std::string href_from_file(std::string_view path); } // namespace ada -#endif // ADA_IMPLEMENTATION_H -/* end file include/ada/implementation.h */ +#endif // ADA_IMPLEMENTATION_INL_H +/* end file include/ada/implementation-inl.h */ #endif // ADA_H /* end file include/ada.h */ diff --git a/NativeScript/runtime/modules/worker/ConcurrentQueue.h b/NativeScript/runtime/modules/worker/ConcurrentQueue.h deleted file mode 100644 index 74aaf9c81..000000000 --- a/NativeScript/runtime/modules/worker/ConcurrentQueue.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ConcurrentQueue_h -#define ConcurrentQueue_h - -#include - -#include -#include -#include - -#include "runtime/modules/worker/Message.h" - -namespace nativescript { - -struct ConcurrentQueue { - public: - void Initialize(CFRunLoopRef runLoop, void (*performWork)(void*), void* info); - void Push(std::shared_ptr message); - std::vector> PopAll(); - void Terminate(); - - private: - std::queue> messagesQueue_; - CFRunLoopSourceRef runLoopTasksSource_ = nullptr; - CFRunLoopRef runLoop_ = nullptr; - bool terminated = false; - std::mutex mutex_; - std::mutex initializationMutex_; - void SignalAndWakeUp(); -}; - -} // namespace nativescript - -#endif /* ConcurrentQueue_h */ diff --git a/NativeScript/runtime/modules/worker/MessageJSON.cpp b/NativeScript/runtime/modules/worker/MessageJSON.cpp deleted file mode 100644 index 39c1e5f00..000000000 --- a/NativeScript/runtime/modules/worker/MessageJSON.cpp +++ /dev/null @@ -1,273 +0,0 @@ -// Message.cpp -// NativeScript -// -// Created by Eduardo Speroni on 11/22/23. -// Copyright © 2023 Progress. All rights reserved. - -#ifndef TARGET_ENGINE_V8 - -#include "js_native_api.h" -#include "MessageJSON.h" - -namespace nativescript { - -#include -#include -#include -#include - -typedef enum { - TYPE_UNDEFINED = 0x00, - TYPE_NULL = 0x01, - TYPE_BOOLEAN = 0x02, - TYPE_NUMBER = 0x03, - TYPE_STRING = 0x04, - TYPE_ARRAY = 0x05, - TYPE_OBJECT = 0x06, - TYPE_DATE = 0x07, - TYPE_ARRAYBUFFER = 0x08 -} TypeTag; - -#define CHECK(expr) \ - do { \ - napi_status status = (expr); \ - assert(status == napi_ok); \ - } while (0) - -typedef struct { - uint8_t* data; - size_t size; - size_t capacity; -} Buffer; - -#define INITIAL_CAPACITY 1024 - -static void buffer_init(Buffer* buf) { - buf->data = (uint8_t*)malloc(INITIAL_CAPACITY); - buf->size = 0; - buf->capacity = INITIAL_CAPACITY; -} - -static void buffer_free(Buffer* buf) { - if (buf->data != NULL) { - free(buf->data); - buf->data = NULL; - } - buf->size = 0; - buf->capacity = 0; -} - -static void buffer_ensure_capacity(Buffer* buf, size_t additional) { - while (buf->size + additional > buf->capacity) { - buf->capacity *= 2; - buf->data = (uint8_t*)realloc(buf->data, buf->capacity); - } -} - -static void buffer_write(Buffer* buf, const void* src, size_t len) { - buffer_ensure_capacity(buf, len); - memcpy(buf->data + buf->size, src, len); - buf->size += len; -} - -static void write_uint32(Buffer* buf, uint32_t val) { - buffer_write(buf, &val, 4); -} - -static void write_double(Buffer* buf, double val) { - buffer_write(buf, &val, 8); -} - -static void write_string(napi_env env, Buffer* buf, napi_value str) { - size_t len; - CHECK(napi_get_value_string_utf8(env, str, NULL, 0, &len)); - write_uint32(buf, (uint32_t)len); - buffer_ensure_capacity(buf, len); - CHECK(napi_get_value_string_utf8(env, str, (char*)(buf->data + buf->size), - len + 1, NULL)); - buf->size += len; -} - -static uint32_t read_uint32(const uint8_t** buf) { - uint32_t val = 0; - for (int i = 0; i < 4; ++i) val |= (uint32_t)(*(*buf)++) << (i * 8); - return val; -} - -static void write_double(uint8_t** buf, double val) { - memcpy(*buf, &val, 8); - *buf += 8; -} - -static double read_double(const uint8_t** buf) { - double val; - memcpy(&val, *buf, 8); - *buf += 8; - return val; -} - -static napi_value read_string(napi_env env, const uint8_t** buf) { - uint32_t len = read_uint32(buf); - napi_value result; - CHECK(napi_create_string_utf8(env, (const char*)(*buf), len, &result)); - *buf += len; - return result; -} - -static napi_value deserialize(napi_env env, const uint8_t** buf) { - uint8_t tag = *(*buf)++; - napi_value result; - - switch ((TypeTag)tag) { - case TYPE_UNDEFINED: - CHECK(napi_get_undefined(env, &result)); - break; - case TYPE_NULL: - CHECK(napi_get_null(env, &result)); - break; - case TYPE_BOOLEAN: { - bool b = *(*buf)++ != 0; - CHECK(napi_get_boolean(env, b, &result)); - break; - } - case TYPE_NUMBER: { - double num = read_double(buf); - CHECK(napi_create_double(env, num, &result)); - break; - } - case TYPE_STRING: - result = read_string(env, buf); - break; - case TYPE_ARRAY: { - uint32_t len = read_uint32(buf); - CHECK(napi_create_array_with_length(env, len, &result)); - for (uint32_t i = 0; i < len; ++i) { - napi_value elem = deserialize(env, buf); - CHECK(napi_set_element(env, result, i, elem)); - } - break; - } - case TYPE_OBJECT: { - CHECK(napi_create_object(env, &result)); - uint32_t len = read_uint32(buf); - for (uint32_t i = 0; i < len; ++i) { - napi_value key = read_string(env, buf); - napi_value val = deserialize(env, buf); - CHECK(napi_set_property(env, result, key, val)); - } - break; - } - case TYPE_DATE: { - double t = read_double(buf); - CHECK(napi_create_date(env, t, &result)); - break; - } - default: - assert(0 && "Unknown tag"); - } - return result; -} - -static void serialize(napi_env env, napi_value val, Buffer* buf) { - napi_valuetype type; - CHECK(napi_typeof(env, val, &type)); - - uint8_t tag; - - if (type == napi_undefined) { - tag = TYPE_UNDEFINED; - buffer_write(buf, &tag, 1); - } else if (type == napi_null) { - tag = TYPE_NULL; - buffer_write(buf, &tag, 1); - } else if (type == napi_boolean) { - tag = TYPE_BOOLEAN; - buffer_write(buf, &tag, 1); - bool b; - CHECK(napi_get_value_bool(env, val, &b)); - uint8_t bv = b ? 1 : 0; - buffer_write(buf, &bv, 1); - } else if (type == napi_number) { - tag = TYPE_NUMBER; - buffer_write(buf, &tag, 1); - double num; - CHECK(napi_get_value_double(env, val, &num)); - write_double(buf, num); - } else if (type == napi_string) { - tag = TYPE_STRING; - buffer_write(buf, &tag, 1); - write_string(env, buf, val); - } else { - bool is_array; - CHECK(napi_is_array(env, val, &is_array)); - if (is_array) { - tag = TYPE_ARRAY; - buffer_write(buf, &tag, 1); - uint32_t len; - CHECK(napi_get_array_length(env, val, &len)); - write_uint32(buf, len); - for (uint32_t i = 0; i < len; ++i) { - napi_value elem; - CHECK(napi_get_element(env, val, i, &elem)); - serialize(env, elem, buf); - } - } else { - napi_value ctor; - CHECK(napi_get_named_property(env, val, "constructor", &ctor)); - napi_value ctor_name; - CHECK(napi_get_named_property(env, ctor, "name", &ctor_name)); - - size_t len; - char cname[32]; - CHECK(napi_get_value_string_utf8(env, ctor_name, cname, sizeof(cname), - &len)); - - if (strcmp(cname, "Date") == 0) { - tag = TYPE_DATE; - buffer_write(buf, &tag, 1); - napi_value time; - CHECK(napi_coerce_to_number(env, val, &time)); - double t; - CHECK(napi_get_value_double(env, time, &t)); - write_double(buf, t); - } else { - tag = TYPE_OBJECT; - buffer_write(buf, &tag, 1); - napi_value keys; - CHECK(napi_get_property_names(env, val, &keys)); - uint32_t len; - CHECK(napi_get_array_length(env, keys, &len)); - write_uint32(buf, len); - for (uint32_t i = 0; i < len; ++i) { - napi_value key, value; - CHECK(napi_get_element(env, keys, i, &key)); - write_string(env, buf, key); - CHECK(napi_get_property(env, val, key, &value)); - serialize(env, value, buf); - } - } - } - } -} - -namespace worker { - -bool Message::Serialize(napi_env env, napi_value input) { - Buffer buf; - buffer_init(&buf); - serialize(env, input, &buf); - main_message_buf_ = MallocedBuffer((char*)buf.data, buf.size); - return true; -} - -napi_value Message::Deserialize(napi_env env) { - const uint8_t* buf = (const uint8_t*)main_message_buf_.data; - return deserialize(env, &buf); -} - -Message::Message(MallocedBuffer&& payload) - : main_message_buf_(std::move(payload)) {} -}; // namespace worker -}; // namespace nativescript - -#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/runtime/modules/worker/Worker.h b/NativeScript/runtime/modules/worker/Worker.h deleted file mode 100644 index 8e951b670..000000000 --- a/NativeScript/runtime/modules/worker/Worker.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef WORKER_H -#define WORKER_H - -#include -#include -#include - -#include "native_api_util.h" -#include "runtime/modules/worker/Message.h" - -namespace nativescript { - -class Worker { - public: - static void Init(napi_env env, napi_value global, bool isWorkerThread); - static void Init(napi_env env, napi_value global); - - private: - static JS_METHOD(Constructor); - static void Finalize(napi_env env, void* nativeObject, - void* finalize_hint); - static JS_METHOD(PostMessage); - static JS_METHOD(Terminate); - static void OnMessage(napi_env env, napi_value jsThis, - std::shared_ptr); - static JS_METHOD(PostMessageToMain); - static JS_METHOD(CloseWorker); - - static napi_value Serialize(napi_env env, napi_value value); - static void SetWorkerId(napi_env env, int workerId); - static int GetWorkerId(napi_env env, napi_value global); -}; - -} // namespace nativescript - -#endif // WORKER_H diff --git a/README.md b/README.md index 6540d1606..665ad411a 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,8 @@ The core backbone that powers [NativeScript](https://nativescript.org/) apps and - `NativeScript/`: The core C++ runtime source code handling FFI (Foreign Function Interface) operations and JS engine bindings. - `metadata-generator/`: Clang-based utility that parses C/Obj-C to generate the metadata consumed by the FFI - `packages/`: The NPM packages published from this repo (e.g., `@nativescript/ios`, `@nativescript/macos`, `@nativescript/visionos`, and specific engine combinations). -- `examples/`: Sample JS/TS test beds demonstrating platform APIs (AppKit, Metal, SpriteKit, etc.) driven purely over the runtime FFI. +- `platforms/apple/`: Apple platform project files, templates, tests, examples, and TKLiveSync sources. - `scripts/`: Shell and Node utilities for building frameworks, packaging runtimes, and driving CI/CD. -- `TKLiveSync/`: Provides fast LiveSync capability and Hot Module Replacement (HMR) implementation. ## Development & Building @@ -30,6 +29,8 @@ The core backbone that powers [NativeScript](https://nativescript.org/) apps and - `npm run build-macos`: Builds the macOS runtime platforms. - `npm run build-vision`: Builds the visionOS runtime platforms. - `npm run build-node-api`: Compiles the Node-API (N-API) addon. + - `npm run build-android`: Builds the Android runtime for one JS engine (prompts, or `-- --engine=`). + - `npm run setup-android`: Prepares an Android checkout (QuickJS patches + jsparser deps). - `npm run build-rn-turbomodule`: Compiles the React Native TurboModules compatibility layer. 4. **Core Dependencies**: - `npm run build-metagen`: Builds the Clang metadata generator binary. diff --git a/benchmarks/objc-dispatch/README.md b/benchmarks/objc-dispatch/README.md deleted file mode 100644 index 6a2951bd2..000000000 --- a/benchmarks/objc-dispatch/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Objective-C Dispatch Benchmarks - -This benchmark compares hot Objective-C dispatch shapes across the generated -signature dispatch runtime and the PR #366 AOT direct-call runtime. - -The benchmark body is plain NativeScript JavaScript: - -- `objc-dispatch-benchmarks.js` - -The runner can execute it in three modes: - -- `napi-node`: fastest smoke run using the packaged macOS Node-API runtime. -- `napi-ios`: builds a temporary iOS app from the packaged `@nativescript/ios` - template and runs it in Simulator. -- `legacy-ios`: temporarily injects the benchmark into the PR branch - `TestRunner` app, builds it, runs it in Simulator, then restores the app - entry point. - -For V8 builds, `gsd-off` still uses the V8-native callback/marshalling path, -but generated signature dispatch lookup is disabled so Objective-C calls fall -back to the dynamic prepared/`ffi_call` path. This keeps the comparison focused -on the generated dispatch win instead of accidentally measuring a hand-written -direct `objc_msgSend` fast path. - -For JSC, QuickJS, and Hermes builds, `gsd-off` follows the same rule: the -engine-native callback and marshalling layer remains active, while only the -generated typed invoker lookup is disabled. - -Examples: - -```sh -npm run benchmark:objc-dispatch -- --runtime napi-node --iterations 100000 -npm run benchmark:objc-dispatch -- --runtime napi-ios,legacy-ios --iterations 250000 -npm run benchmark:objc-dispatch -- --runtime all --include-napi-gsd-off -``` - -Useful options: - -```sh ---legacy-repo /path/to/NativeScript/ios ---destination "platform=iOS Simulator,id=" ---napi-package-tgz /path/to/nativescript-ios.tgz ---iterations 250000 ---include-napi-gsd-off -``` diff --git a/docs/AGENT_MEMORY.md b/docs/AGENT_MEMORY.md new file mode 100644 index 000000000..28159f1b3 --- /dev/null +++ b/docs/AGENT_MEMORY.md @@ -0,0 +1,9 @@ +# Agent Memory + +## NativeScript React Native Module Porting + +- When the active ask is source parity for a NativeScript TypeScript port, do not keep re-debugging an already-root-caused simulator symptom first. Clean the mechanical port, deviation comments, source tests, and docs before using simulator stress as the final verification gate. +- Simulator AX visibility is not UIKit transition/readiness proof. A visible button can still fail because the app scene is stale, the wrong bundle is foreground, or a detached UIKit/RN touch host is out of lifecycle. Verify with deterministic dev-client launch and focused stress after source cleanup. +- If simulator behavior gets flaky, shut down existing simulators and verify on a fresh iOS 26.5 iPhone 17 simulator. Do not stack source changes on stale SpringBoard/app lifecycle evidence. +- Keep generic runtime capability generic. Do not add library-specific TurboModule helpers for `react-native-screens` or React Navigation; expose reusable UIKit/ObjC interop primitives in NativeScript runtime when a TypeScript port needs native capability. +- SimDeck AX frame fallbacks must be conditional. Prefer a plausible AX element center; reject only impossible frames (for example giant full-screen frames). Do not replace plausible AX with screenshot-guessed coordinates, since the screenshot can be visually misleading while AX remains the actual tappable point. diff --git a/examples/expo-demo/App.tsx b/examples/expo-demo/App.tsx deleted file mode 100644 index 657b6aacb..000000000 --- a/examples/expo-demo/App.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React, {useEffect, useMemo, useState} from 'react'; -import {Pressable, ScrollView, Text, View} from 'react-native'; -import NativeScript, {defineUIKitView} from '@nativescript/react-native'; - -NativeScript.init(); - -type BadgeProps = { - title: string; - tone: 'blue' | 'green'; -}; - -const NativeBadge = defineUIKitView({ - displayName: 'NativeBadge', - create() { - const view = UIView.alloc().initWithFrame(CGRectZero); - view.clipsToBounds = true; - - const label = UILabel.alloc().initWithFrame(CGRectZero); - label.tag = 1; - label.textAlignment = NSTextAlignment.Center; - label.textColor = UIColor.whiteColor; - label.autoresizingMask = - UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight; - view.addSubview(label); - - return view; - }, - mounted(view) { - view.layer.cornerRadius = 14; - }, - update(view, props) { - view.backgroundColor = - props.tone === 'green' ? UIColor.systemGreenColor : UIColor.systemBlueColor; - const label = view.viewWithTag(1) as UILabel; - label.text = props.title; - }, -}); - -async function readNativeSummary() { - const api = (globalThis as any).__nativeScriptNativeApi; - let ranOnMainThread = false; - - await NativeScript.runOnUI(() => { - ranOnMainThread = NSThread.isMainThread === true; - UIApplication.sharedApplication.keyWindow.tintColor = - UIColor.systemPinkColor; - }); - - return { - backend: api?.backend, - classes: api?.metadata?.classes ?? 0, - constants: api?.metadata?.constants ?? 0, - enums: api?.metadata?.enums ?? 0, - ranOnMainThread, - timeoutConstant: NSURLErrorTimedOut, - darkStyle: UIUserInterfaceStyle.Dark, - }; -} -export default function App() { - const [tone, setTone] = useState<'blue' | 'green'>('blue'); - const [summary, setSummary] = useState('Loading NativeScript...'); - - useEffect(() => { - readNativeSummary() - .then((value) => setSummary(JSON.stringify(value, null, 2))) - .catch((error) => { - setSummary(error instanceof Error ? error.message : String(error)); - }); - }, []); - - const title = useMemo( - () => (tone === 'blue' ? 'UIKit view from Expo' : 'Updated from React state'), - [tone], - ); - - return ( - - - - NativeScript Expo - - - Define UIKit views directly in JavaScript and mount them in an Expo - development build. - - - - - - setTone((value) => (value === 'blue' ? 'green' : 'blue'))} - style={{ - alignItems: 'center', - borderRadius: 8, - backgroundColor: '#111827', - minHeight: 48, - justifyContent: 'center', - paddingHorizontal: 16, - }}> - - Toggle Native Badge - - - - - {summary} - - - ); -} diff --git a/examples/expo-demo/README.md b/examples/expo-demo/README.md deleted file mode 100644 index 5ebc12c6e..000000000 --- a/examples/expo-demo/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# NativeScript Expo Demo - -This example is meant to be copied into a generated Expo app after installing -`@nativescript/react-native`. - -```sh -npx create-expo-app NativeScriptExpoDemo --template blank-typescript -cd NativeScriptExpoDemo -npm install /path/to/nativescript-react-native-0.0.1.tgz -cp /path/to/napi-ios/examples/expo-demo/app.config.js ./app.config.js -cp /path/to/napi-ios/examples/expo-demo/App.tsx ./App.tsx -npx expo prebuild --platform ios -npx expo run:ios -``` - -The package config plugin enables the iOS New Architecture and Hermes during -prebuild. This custom native module cannot run inside Expo Go. diff --git a/examples/react-native-demo/App.tsx b/examples/react-native-demo/App.tsx deleted file mode 100644 index f03cf6b48..000000000 --- a/examples/react-native-demo/App.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import { - Platform, - Pressable, - SafeAreaView, - StyleSheet, - Text, - View, -} from 'react-native'; -import NativeScript from '@nativescript/react-native'; - -type NativeApiHost = { - backend?: string; - metadata?: { - classes?: number; - functions?: number; - constants?: number; - enums?: number; - }; -}; - -function installNativeScriptGlobals(): NativeApiHost { - NativeScript.init(); - const api = (globalThis as any).__nativeScriptNativeApi as - | NativeApiHost - | undefined; - if (!api) { - throw new Error('NativeScript Native API JSI host object was not installed'); - } - return api; -} - -function getActiveUIKitWindow() { - const app = UIApplication.sharedApplication; - const scenes = app.connectedScenes?.allObjects; - if (scenes) { - for (let i = 0; i < scenes.count; i++) { - const scene = scenes.objectAtIndex(i); - if ( - scene instanceof UIWindowScene && - scene.activationState === UISceneActivationState.ForegroundActive - ) { - const windows = scene.windows; - for (let j = 0; j < windows.count; j++) { - const window = windows.objectAtIndex(j); - if (window.isKeyWindow) { - return window; - } - } - if (windows.count > 0) { - return windows.objectAtIndex(0); - } - } - } - } - return app.keyWindow; -} - -async function applyUIKitTweaks() { - if (Platform.OS !== 'ios') { - throw new Error('This demo uses UIKit and must run on iOS'); - } - - const api = installNativeScriptGlobals(); - - let nativeCallsRanOnMainThread = false; - await NativeScript.runOnUI(() => { - nativeCallsRanOnMainThread = NSThread.isMainThread === true; - if (!nativeCallsRanOnMainThread) { - throw new Error('runOnUI did not dispatch native calls to the main thread'); - } - - const window = getActiveUIKitWindow(); - if (!window) { - throw new Error('No key UIWindow is available yet'); - } - - const nativeAccent = UIColor.systemPinkColor ?? UIColor.magentaColor; - const nativeBackdrop = UIColor.colorWithRedGreenBlueAlpha( - 0.04, - 0.08, - 0.12, - 1, - ); - - window.tintColor = nativeAccent; - window.backgroundColor = nativeBackdrop; - window.overrideUserInterfaceStyle = UIUserInterfaceStyle.Dark; - - const rootView = window.rootViewController?.view; - if (rootView) { - rootView.tintColor = nativeAccent; - rootView.backgroundColor = nativeBackdrop; - } - }); - - return { - backend: api.backend, - turboBackend: NativeScript.getRuntimeBackend(), - classes: api.metadata?.classes ?? 0, - functions: api.metadata?.functions ?? 0, - constants: api.metadata?.constants ?? 0, - enums: api.metadata?.enums ?? 0, - timeoutConstant: NSURLErrorTimedOut, - darkStyle: UIUserInterfaceStyle.Dark, - nativeCallsRanOnMainThread, - }; -} - -export default function App(): React.JSX.Element { - const [status, setStatus] = useState('Ready'); - const [details, setDetails] = useState(''); - const [busy, setBusy] = useState(false); - - const runDemo = useCallback(async () => { - setBusy(true); - setStatus('Applying UIKit tweaks'); - try { - const result = await applyUIKitTweaks(); - setStatus('UIKit updated from JavaScript'); - setDetails(JSON.stringify(result, null, 2)); - } catch (error) { - setStatus('Demo failed'); - setDetails(error instanceof Error ? error.message : String(error)); - } finally { - setBusy(false); - } - }, []); - - useEffect(() => { - runDemo(); - }, [runDemo]); - - const buttonLabel = useMemo( - () => (busy ? 'Working...' : 'Run UIKit Tweak Again'), - [busy], - ); - - return ( - - - NativeScript for React Native - Hermes JSI UIKit Demo - {status} - - {details} - - [ - styles.button, - busy && styles.buttonDisabled, - pressed && !busy && styles.buttonPressed, - ]}> - {buttonLabel} - - - - ); -} - -const styles = StyleSheet.create({ - screen: { - flex: 1, - backgroundColor: 'transparent', - justifyContent: 'center', - padding: 24, - }, - panel: { - gap: 14, - borderRadius: 8, - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(255,255,255,0.26)', - backgroundColor: 'rgba(8,14,22,0.82)', - padding: 20, - }, - kicker: { - color: '#9bd4ff', - fontSize: 13, - fontWeight: '700', - letterSpacing: 0, - textTransform: 'uppercase', - }, - title: { - color: '#ffffff', - fontSize: 28, - fontWeight: '800', - letterSpacing: 0, - }, - status: { - color: '#f7d276', - fontSize: 17, - fontWeight: '700', - letterSpacing: 0, - }, - details: { - minHeight: 112, - color: '#d8e7f4', - fontFamily: Platform.select({ios: 'Menlo', default: 'monospace'}), - fontSize: 13, - lineHeight: 18, - letterSpacing: 0, - }, - button: { - minHeight: 46, - alignItems: 'center', - justifyContent: 'center', - borderRadius: 8, - backgroundColor: '#ff4fa3', - paddingHorizontal: 16, - }, - buttonPressed: { - backgroundColor: '#e23f90', - }, - buttonDisabled: { - opacity: 0.6, - }, - buttonText: { - color: '#ffffff', - fontSize: 16, - fontWeight: '800', - letterSpacing: 0, - }, -}); diff --git a/examples/react-native-demo/README.md b/examples/react-native-demo/README.md deleted file mode 100644 index 651f18a06..000000000 --- a/examples/react-native-demo/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# React Native NativeScript Demo - -This demo is generated into `build/react-native-demo` so the -repository does not need to commit React Native boilerplate. - -Run it from the repository root: - -```sh -npm run demo-rn-turbomodule -``` - -The generated app installs the local -`@nativescript/react-native` tarball, enables Hermes and the New -Architecture, then launches an iOS simulator app. The app installs the -NativeScript Native API JSI host object with `NativeScript.init()`, installs -NativeScript-style globals such as `UIApplication` and `UIColor`, and uses -`runOnUI` to execute a small UIKit tweak from JavaScript while dispatching the -native UIKit calls to the main thread. The script waits for a simulator marker -after the tweak succeeds. diff --git a/metadata-generator/CMakeLists.txt b/metadata-generator/CMakeLists.txt index 59707dff2..12f4a04aa 100644 --- a/metadata-generator/CMakeLists.txt +++ b/metadata-generator/CMakeLists.txt @@ -23,11 +23,10 @@ include_directories( set(EXEC_SOURCE_FILES src/main.cpp src/SignatureDispatchEmitter.cpp - src/SignatureDispatchEmitter/EngineDirect.cpp - src/SignatureDispatchEmitter/Hermes.cpp src/SignatureDispatchEmitter/Napi.cpp + src/SignatureDispatchEmitter/Prepared.cpp src/SignatureDispatchEmitter/Shared.cpp - src/SignatureDispatchEmitter/V8.cpp + src/SignatureDispatchEmitter/Gsd.cpp src/Umbrella.cpp src/IR/Category.cpp src/IR/Class.cpp @@ -73,10 +72,26 @@ add_executable( ${EXEC_SOURCE_FILES} ) +set(LIBCLANG_DIR "/Library/Developer/CommandLineTools/usr/lib") +execute_process( + COMMAND xcrun --find clang + OUTPUT_VARIABLE XCRUN_CLANG + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET +) +if(XCRUN_CLANG) + get_filename_component(XCRUN_CLANG_BIN_DIR "${XCRUN_CLANG}" DIRECTORY) + get_filename_component(XCRUN_TOOLCHAIN_USR_DIR "${XCRUN_CLANG_BIN_DIR}" DIRECTORY) + if(EXISTS "${XCRUN_TOOLCHAIN_USR_DIR}/lib/libclang.dylib") + set(LIBCLANG_DIR "${XCRUN_TOOLCHAIN_USR_DIR}/lib") + endif() +endif() +message(STATUS "LIBCLANG_DIR = ${LIBCLANG_DIR}") + target_link_directories( ${NAME} PRIVATE - /Library/Developer/CommandLineTools/usr/lib + "${LIBCLANG_DIR}" ) target_link_libraries( @@ -88,7 +103,7 @@ target_link_libraries( # Set runtime path for libclang set_target_properties(${NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE - INSTALL_RPATH "/Library/Developer/CommandLineTools/usr/lib" + INSTALL_RPATH "${LIBCLANG_DIR}" ) install(TARGETS ${NAME} diff --git a/metadata-generator/README.md b/metadata-generator/README.md index d8f11c4f1..388ac6ebb 100644 --- a/metadata-generator/README.md +++ b/metadata-generator/README.md @@ -42,7 +42,7 @@ Example command line arguments: ```bash # replace NSV8RUNTIMEPATH with the path to the ns-v8ios-runtime path, ex: /Users/you/ns-v8ios-runtime # replace YOU with your username --verbose -output-typescript /tmp/tsdeclarations/ -output-bin NSV8RUNTIMEPATH/build/Debug-iphonesimulator/metadata-arm64.bin -output-umbrella NSV8RUNTIMEPATH/build/Debug-iphonesimulator/umbrella-arm64.h -docset-path /Users/YOU/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset Xclang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.2.sdk -mios-simulator-version-min=9.0 -std=gnu99 -target arm64-apple-ios15.2-simulator -INSV8RUNTIMEPATH/build/Debug-iphonesimulator/include -INSV8RUNTIMEPATH/NativeScript -INSV8RUNTIMEPATH/test/runtime/fixtures -FNSV8RUNTIMEPATH/build/Debug-iphonesimulator -DCOCOAPODS=1 -DDEBUG=1 -I. -fmodules +-verbose -output-typescript /tmp/tsdeclarations/ -output-bin NSV8RUNTIMEPATH/build/Debug-iphonesimulator/metadata-arm64.bin -output-umbrella NSV8RUNTIMEPATH/build/Debug-iphonesimulator/umbrella-arm64.h -docset-path /Users/YOU/Library/Developer/Shared/Documentation/DocSets/com.apple.adc.documentation.iOS.docset Xclang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.2.sdk -mios-simulator-version-min=9.0 -std=gnu99 -target arm64-apple-ios15.2-simulator -INSV8RUNTIMEPATH/build/Debug-iphonesimulator/include -INSV8RUNTIMEPATH/NativeScript -INSV8RUNTIMEPATH/platforms/apple/test/runtime/fixtures -FNSV8RUNTIMEPATH/build/Debug-iphonesimulator -DCOCOAPODS=1 -DDEBUG=1 -I. -fmodules ``` For a better way of generating these arguments, just run the TestRunner scheme on the v8ios-runtime project and get the arguments from the log. diff --git a/metadata-generator/build-step-metadata-generator.py b/metadata-generator/build-step-metadata-generator.py index f00ead7fd..444b37d9d 100755 --- a/metadata-generator/build-step-metadata-generator.py +++ b/metadata-generator/build-step-metadata-generator.py @@ -172,7 +172,7 @@ def is_nativescript_source_root(search_path): strict_includes = env_or_none("NS_DEBUG_METADATA_STRICT_INCLUDES") or env_or_none("TNS_DEBUG_METADATA_STRICT_INCLUDES") signature_bindings_cpp_path = env_or_none("NS_SIGNATURE_BINDINGS_CPP_PATH") or env_or_none("TNS_SIGNATURE_BINDINGS_CPP_PATH") if signature_bindings_cpp_path is None: - default_signature_bindings_path = os.path.join(src_root, "NativeScript", "ffi", "napi", "GeneratedSignatureDispatch.inc") + default_signature_bindings_path = os.path.join(src_root, "NativeScript", "ffi", "objc", "napi", "GeneratedSignatureDispatch.inc") if os.path.isdir(os.path.dirname(default_signature_bindings_path)): signature_bindings_cpp_path = default_signature_bindings_path diff --git a/metadata-generator/include/IR.h b/metadata-generator/include/IR.h index eb05dc7fc..774ec9ecd 100644 --- a/metadata-generator/include/IR.h +++ b/metadata-generator/include/IR.h @@ -97,6 +97,7 @@ enum VariableConstEvalKind { struct EnumConstDecl { std::string name; + std::string cName; int64_t value; AvailabilityInfo availability; }; @@ -131,6 +132,10 @@ class EnumDecl { std::string name; std::vector constants; + // Stripped from every member's cName to form its name, except members + // whose name is their cName. + std::string memberPrefix; + std::string memberSuffix; MDSectionOffset mdOffset = MD_SECTION_OFFSET_NULL; }; diff --git a/metadata-generator/include/Metadata.h b/metadata-generator/include/Metadata.h index 8ebc6e9dd..70398f3c1 100644 --- a/metadata-generator/include/Metadata.h +++ b/metadata-generator/include/Metadata.h @@ -18,6 +18,9 @@ typedef uint32_t MDSectionOffset; enum MDSectionOffsetFlag : uint32_t { mdSectionOffsetNext = 1u << 31, + // Enum member names only: the stored name is the full C name rather than + // one shortened by the enum's member prefix/suffix. + mdSectionOffsetEnumMemberCName = 1u << 30, }; // Bits 0-1: flags (2 flags) @@ -113,7 +116,11 @@ enum MDFunctionFlag : uint8_t { #define MD_HEADER_MAGIC_SIZE 4 #define MD_HEADER_MAGIC "NSMD" #define MD_HEADER_VERSION_SIZE 2 -#define MD_HEADER_VERSION 1 +#define MD_HEADER_VERSION 2 +#define MD_HEADER_MIN_VERSION 1 +// From this version, enum entries carry the member prefix/suffix and member +// names carry mdSectionOffsetEnumMemberCName. +#define MD_HEADER_VERSION_ENUM_MEMBER_C_NAMES 2 #define MD_HEADER_SIZE (MD_HEADER_MAGIC_SIZE + MD_HEADER_VERSION_SIZE) #define MD_NUM_SECTIONS 9 diff --git a/metadata-generator/include/MetadataReader.h b/metadata-generator/include/MetadataReader.h index 4a7595322..a1f444582 100644 --- a/metadata-generator/include/MetadataReader.h +++ b/metadata-generator/include/MetadataReader.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Metadata.h" @@ -14,6 +15,7 @@ class MDMetadataReader { public: void* data; bool ownsData = false; + uint16_t version = 0; MDSectionOffset stringsOffset; MDSectionOffset constantsOffset; @@ -35,9 +37,8 @@ class MDMetadataReader { } ptr_add(&data, MD_HEADER_MAGIC_SIZE); - uint16_t version; memcpy(&version, data, MD_HEADER_VERSION_SIZE); - if (version != MD_HEADER_VERSION) { + if (version < MD_HEADER_MIN_VERSION || version > MD_HEADER_VERSION) { fprintf(stderr, "Invalid metadata header version\n"); exit(1); } @@ -95,6 +96,66 @@ class MDMetadataReader { return *(int64_t*)((char*)data + offset); } + inline bool hasEnumMemberCNames() const { + return version >= MD_HEADER_VERSION_ENUM_MEMBER_C_NAMES; + } + + // Visits fn(name, cName, value) for each member of the enum entry at + // enumOffset and returns the offset just past the entry. cName is empty when + // the metadata predates stored C names; it cannot be derived from name. + template + inline MDSectionOffset forEachEnumMember(MDSectionOffset enumOffset, + Fn&& fn) { + MDSectionOffset offset = enumOffset + sizeof(MDSectionOffset); + const bool hasCNames = hasEnumMemberCNames(); + const char* prefix = ""; + const char* suffix = ""; + if (hasCNames) { + prefix = getString(offset); + offset += sizeof(MDSectionOffset); + suffix = getString(offset); + offset += sizeof(MDSectionOffset); + } + + bool next = true; + while (next) { + MDSectionOffset nameOffset = getOffset(offset); + next = (nameOffset & mdSectionOffsetNext) != 0; + const bool nameIsCName = + hasCNames && (nameOffset & mdSectionOffsetEnumMemberCName) != 0; + if (hasCNames) { + nameOffset &= ~mdSectionOffsetEnumMemberCName; + } + nameOffset &= ~mdSectionOffsetNext; + offset += sizeof(MDSectionOffset); + int64_t value = getEnumValue(offset); + offset += sizeof(int64_t); + + const char* name = resolveString(nameOffset); + std::string cName; + if (nameIsCName) { + cName = name; + } else if (hasCNames) { + cName.append(prefix).append(name).append(suffix); + } + fn(name, cName, value); + } + return offset; + } + + inline MDSectionOffset skipEnum(MDSectionOffset enumOffset) { + MDSectionOffset offset = enumOffset + sizeof(MDSectionOffset); + if (hasEnumMemberCNames()) { + offset += 2 * sizeof(MDSectionOffset); + } + bool next = true; + while (next) { + next = (getOffset(offset) & mdSectionOffsetNext) != 0; + offset += sizeof(MDSectionOffset) + sizeof(int64_t); + } + return offset; + } + inline MDMemberFlag getMemberFlag(MDSectionOffset offset) { return *(MDMemberFlag*)((char*)data + offset); } diff --git a/metadata-generator/include/MetadataWriter.h b/metadata-generator/include/MetadataWriter.h index 711b4d2e8..fe69b860e 100644 --- a/metadata-generator/include/MetadataWriter.h +++ b/metadata-generator/include/MetadataWriter.h @@ -40,11 +40,14 @@ struct MDVariable { struct MDEnumMember { MDSectionOffset name; + bool nameIsCName; int64_t value; }; struct MDEnum { MDSectionOffset name; + MDSectionOffset memberPrefix; + MDSectionOffset memberSuffix; std::vector members; }; diff --git a/metadata-generator/src/IR/Enum.cpp b/metadata-generator/src/IR/Enum.cpp index ba046c583..e82429221 100644 --- a/metadata-generator/src/IR/Enum.cpp +++ b/metadata-generator/src/IR/Enum.cpp @@ -13,7 +13,8 @@ namespace metagen { // - Deferred // Basically, split the name into parts (assuming camel case), and then remove // the repeating parts from the beginning and end of the name. -void transformEnumMemberNames(std::vector& members) { +void transformEnumMemberNames(std::vector& members, + std::string& prefix, std::string& suffix) { std::vector> parts; parts.reserve(members.size()); size_t skip_begin = 0, skip_end = 0; @@ -69,6 +70,13 @@ void transformEnumMemberNames(std::vector& members) { } } + for (size_t j = 0; j < skip_begin; j++) { + prefix += largestPart[j]; + } + for (size_t j = largestPart.size() - skip_end; j < largestPart.size(); j++) { + suffix += largestPart[j]; + } + for (size_t i = 0; i < members.size(); i++) { const auto& part = parts[i]; std::string name; @@ -119,6 +127,7 @@ EnumDecl::EnumDecl(CXCursor cursor) { auto value = clang_getEnumConstantDeclValue(cursor); EnumConstDecl decl; decl.name = nameStr; + decl.cName = nameStr; decl.value = value; if (gCaptureAvailability) { decl.availability = getAvailabilityInfo(cursor); @@ -135,7 +144,8 @@ EnumDecl::EnumDecl(CXCursor cursor) { }, this); - if (!name.empty()) transformEnumMemberNames(constants); + if (!name.empty()) + transformEnumMemberNames(constants, memberPrefix, memberSuffix); } void MetadataFactory::processEnumRefs() { diff --git a/metadata-generator/src/IR/Factory.cpp b/metadata-generator/src/IR/Factory.cpp index 932bf5e44..b0c25efdc 100644 --- a/metadata-generator/src/IR/Factory.cpp +++ b/metadata-generator/src/IR/Factory.cpp @@ -366,6 +366,8 @@ void MetadataFactory::processEnum(CXCursor cursor, bool required) { if (enumIt != enums.end()) { if (enumIt->second.constants.size() < decl.constants.size()) { enumIt->second.constants = std::move(decl.constants); + enumIt->second.memberPrefix = std::move(decl.memberPrefix); + enumIt->second.memberSuffix = std::move(decl.memberSuffix); } return; } diff --git a/metadata-generator/src/MetadataWriter/Enum.cpp b/metadata-generator/src/MetadataWriter/Enum.cpp index a4abe3109..48256137e 100644 --- a/metadata-generator/src/MetadataWriter/Enum.cpp +++ b/metadata-generator/src/MetadataWriter/Enum.cpp @@ -10,11 +10,25 @@ void MDMetadataWriter::write(EnumDecl &decl) { MDEnum *mdEnum = new MDEnum(); mdEnum->name = strings.add(decl.name, decl.name); + mdEnum->memberPrefix = strings.add(decl.memberPrefix, decl.memberPrefix); + mdEnum->memberSuffix = strings.add(decl.memberSuffix, decl.memberSuffix); mdEnum->members.reserve(decl.constants.size()); for (const EnumConstDecl &c : decl.constants) { MDEnumMember member; member.name = strings.add(c.name, c.name); + if ((member.name & mdSectionOffsetEnumMemberCName) != 0) { + std::cerr << "Strings section too large to flag enum member names" + << std::endl; + exit(1); + } + member.nameIsCName = c.name == c.cName; + if (!member.nameIsCName && + decl.memberPrefix + c.name + decl.memberSuffix != c.cName) { + std::cerr << "Enum " << decl.name << ": cannot encode the C name of " + << c.cName << std::endl; + exit(1); + } member.value = c.value; mdEnum->members.push_back(member); } @@ -33,7 +47,11 @@ size_t MDEnumMemberSerde::size(MDEnumMember *value) { void MDEnumMemberSerde::serialize(MDEnumMember *value, void *data) { // Name - binwrite(value->name); + MDSectionOffset name = value->name; + if (value->nameIsCName) { + name |= mdSectionOffsetEnumMemberCName; + } + binwrite(name); // Value binwrite(value->value); } @@ -42,6 +60,8 @@ size_t MDEnumSerde::size(MDEnum *value) { size_t size = 0; // Name addsize(value->name); + addsize(value->memberPrefix); + addsize(value->memberSuffix); // Members MDEnumMemberSerde memberSerde; if (!value->members.empty()) { @@ -53,6 +73,8 @@ size_t MDEnumSerde::size(MDEnum *value) { void MDEnumSerde::serialize(MDEnum *value, void *data) { // Name binwrite(value->name); + binwrite(value->memberPrefix); + binwrite(value->memberSuffix); // Members size_t membersSize = value->members.size(); MDEnumMemberSerde memberSerde; diff --git a/metadata-generator/src/SignatureDispatchEmitter.cpp b/metadata-generator/src/SignatureDispatchEmitter.cpp index da50820e9..0c18d5796 100644 --- a/metadata-generator/src/SignatureDispatchEmitter.cpp +++ b/metadata-generator/src/SignatureDispatchEmitter.cpp @@ -59,18 +59,14 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, wrappersByKey; std::unordered_map> preparedWrappersByKey; + // Engine-neutral GSD wrappers keyed by signature shape. + std::unordered_map gsdWrappersByKey; + std::unordered_map objcPreparedEntries; + std::unordered_map cFunctionPreparedEntries; + std::unordered_map blockPreparedEntries; std::unordered_map objcNapiEntries; std::unordered_map cFunctionNapiEntries; - std::unordered_map objcEngineDirectEntries; - std::unordered_map cFunctionEngineDirectEntries; - std::unordered_map objcV8Entries; - std::unordered_map cFunctionV8Entries; - std::unordered_map objcHermesDirectReturnEntries; - std::unordered_map cFunctionHermesDirectReturnEntries; - std::unordered_map objcHermesFrameDirectReturnEntries; - std::unordered_map cFunctionHermesFrameDirectReturnEntries; - std::unordered_map blockHermesFrameDirectReturnEntries; - std::unordered_map blockPreparedEntries; + std::unordered_map objcGsdEntries; std::unordered_map dispatchEncoding; std::unordered_set collidedDispatchIds; @@ -98,18 +94,12 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, if (encodedIt != dispatchEncoding.end() && encodedIt->second != canonicalSignatureKey) { collidedDispatchIds.insert(dispatchId); + objcPreparedEntries.erase(dispatchId); + cFunctionPreparedEntries.erase(dispatchId); + blockPreparedEntries.erase(dispatchId); objcNapiEntries.erase(dispatchId); cFunctionNapiEntries.erase(dispatchId); - objcEngineDirectEntries.erase(dispatchId); - cFunctionEngineDirectEntries.erase(dispatchId); - objcV8Entries.erase(dispatchId); - cFunctionV8Entries.erase(dispatchId); - objcHermesDirectReturnEntries.erase(dispatchId); - cFunctionHermesDirectReturnEntries.erase(dispatchId); - objcHermesFrameDirectReturnEntries.erase(dispatchId); - cFunctionHermesFrameDirectReturnEntries.erase(dispatchId); - blockHermesFrameDirectReturnEntries.erase(dispatchId); - blockPreparedEntries.erase(dispatchId); + objcGsdEntries.erase(dispatchId); dispatchEncoding.erase(dispatchId); continue; } @@ -125,38 +115,28 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, if (use.kind == DispatchKind::ObjCMethod) { wrappersByKey.emplace(wrapperKey, std::make_pair(use.kind, signature)); + preparedWrappersByKey.emplace(wrapperKey, + std::make_pair(use.kind, signature)); + objcPreparedEntries.emplace(dispatchId, wrapperKey); objcNapiEntries.emplace(dispatchId, wrapperKey); - objcEngineDirectEntries.emplace(dispatchId, wrapperKey); - objcV8Entries.emplace(dispatchId, wrapperKey); - if (canUseHermesDirectReturnWrapper( - use.kind, signature, HermesDirectReturnCallSite::FastCallback)) { - objcHermesDirectReturnEntries.emplace(dispatchId, wrapperKey); - } - if (canUseHermesDirectReturnWrapper( - use.kind, signature, HermesDirectReturnCallSite::Frame)) { - objcHermesFrameDirectReturnEntries.emplace(dispatchId, wrapperKey); + // Engine-neutral GSD invokers for supported signatures. + if (isGsdSignatureSupported(signature)) { + const std::string gsdKey = makeGsdWrapperShapeKey(signature); + if (!gsdKey.empty()) { + gsdWrappersByKey.emplace(gsdKey, signature); + objcGsdEntries.emplace(dispatchId, gsdKey); + } } } else if (use.kind == DispatchKind::CFunction) { wrappersByKey.emplace(wrapperKey, std::make_pair(use.kind, signature)); + preparedWrappersByKey.emplace(wrapperKey, + std::make_pair(use.kind, signature)); + cFunctionPreparedEntries.emplace(dispatchId, wrapperKey); cFunctionNapiEntries.emplace(dispatchId, wrapperKey); - cFunctionEngineDirectEntries.emplace(dispatchId, wrapperKey); - cFunctionV8Entries.emplace(dispatchId, wrapperKey); - if (canUseHermesDirectReturnWrapper( - use.kind, signature, HermesDirectReturnCallSite::FastCallback)) { - cFunctionHermesDirectReturnEntries.emplace(dispatchId, wrapperKey); - } - if (canUseHermesDirectReturnWrapper( - use.kind, signature, HermesDirectReturnCallSite::Frame)) { - cFunctionHermesFrameDirectReturnEntries.emplace(dispatchId, wrapperKey); - } } else if (use.kind == DispatchKind::BlockInvoke) { preparedWrappersByKey.emplace(wrapperKey, std::make_pair(use.kind, signature)); blockPreparedEntries.emplace(dispatchId, wrapperKey); - if (canUseHermesDirectReturnWrapper( - use.kind, signature, HermesDirectReturnCallSite::Frame)) { - blockHermesFrameDirectReturnEntries.emplace(dispatchId, wrapperKey); - } } } @@ -184,56 +164,6 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, makeNapiWrapperName(wrapper.second.first, wrapperIndex++)); } - std::unordered_map v8WrapperNameByKey; - v8WrapperNameByKey.reserve(wrappers.size()); - size_t v8WrapperIndex = 0; - for (const auto& wrapper : wrappers) { - v8WrapperNameByKey.emplace( - wrapper.first, - makeV8WrapperName(wrapper.second.first, v8WrapperIndex++)); - } - - std::unordered_map hermesDirectReturnWrapperNameByKey; - hermesDirectReturnWrapperNameByKey.reserve(wrappers.size()); - size_t hermesDirectReturnWrapperIndex = 0; - for (const auto& wrapper : wrappers) { - hermesDirectReturnWrapperNameByKey.emplace( - wrapper.first, - makeHermesDirectReturnWrapperName(wrapper.second.first, - hermesDirectReturnWrapperIndex++)); - } - - std::unordered_map - hermesFrameDirectReturnWrapperNameByKey; - hermesFrameDirectReturnWrapperNameByKey.reserve(wrappers.size()); - size_t hermesFrameDirectReturnWrapperIndex = 0; - for (const auto& wrapper : wrappers) { - hermesFrameDirectReturnWrapperNameByKey.emplace( - wrapper.first, - makeHermesFrameDirectReturnWrapperName( - wrapper.second.first, hermesFrameDirectReturnWrapperIndex++)); - } - - std::unordered_map - hermesBlockFrameDirectReturnWrapperNameByKey; - hermesBlockFrameDirectReturnWrapperNameByKey.reserve(preparedWrappers.size()); - for (const auto& wrapper : preparedWrappers) { - hermesBlockFrameDirectReturnWrapperNameByKey.emplace( - wrapper.first, - makeHermesFrameDirectReturnWrapperName( - wrapper.second.first, hermesFrameDirectReturnWrapperIndex++)); - } - - std::unordered_map engineDirectWrapperNameByKey; - engineDirectWrapperNameByKey.reserve(wrappers.size()); - size_t engineDirectWrapperIndex = 0; - for (const auto& wrapper : wrappers) { - engineDirectWrapperNameByKey.emplace( - wrapper.first, - makeEngineDirectWrapperName(wrapper.second.first, - engineDirectWrapperIndex++)); - } - std::unordered_map preparedWrapperNameByKey; preparedWrapperNameByKey.reserve(preparedWrappers.size()); size_t preparedWrapperIndex = 0; @@ -243,6 +173,22 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, makePreparedWrapperName(wrapper.second.first, preparedWrapperIndex++)); } + // Engine-neutral GSD wrappers + std::vector> gsdWrappers( + gsdWrappersByKey.begin(), gsdWrappersByKey.end()); + std::sort(gsdWrappers.begin(), gsdWrappers.end(), + [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + + std::unordered_map gsdWrapperNameByKey; + gsdWrapperNameByKey.reserve(gsdWrappers.size()); + size_t gsdWrapperIndex = 0; + for (const auto& wrapper : gsdWrappers) { + gsdWrapperNameByKey.emplace(wrapper.first, + makeGsdWrapperName(gsdWrapperIndex++)); + } + std::vector> sortedObjCNapiEntries( objcNapiEntries.begin(), objcNapiEntries.end()); std::sort( @@ -255,81 +201,16 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, sortedCFunctionNapiEntries.begin(), sortedCFunctionNapiEntries.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); - std::vector> sortedObjCEngineDirectEntries( - objcEngineDirectEntries.begin(), objcEngineDirectEntries.end()); - std::sort(sortedObjCEngineDirectEntries.begin(), - sortedObjCEngineDirectEntries.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector> - sortedCFunctionEngineDirectEntries(cFunctionEngineDirectEntries.begin(), - cFunctionEngineDirectEntries.end()); - std::sort(sortedCFunctionEngineDirectEntries.begin(), - sortedCFunctionEngineDirectEntries.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector> sortedObjCV8Entries( - objcV8Entries.begin(), objcV8Entries.end()); - std::sort( - sortedObjCV8Entries.begin(), sortedObjCV8Entries.end(), - [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); - - std::vector> sortedCFunctionV8Entries( - cFunctionV8Entries.begin(), cFunctionV8Entries.end()); + std::vector> sortedObjCPreparedEntries( + objcPreparedEntries.begin(), objcPreparedEntries.end()); std::sort( - sortedCFunctionV8Entries.begin(), sortedCFunctionV8Entries.end(), + sortedObjCPreparedEntries.begin(), sortedObjCPreparedEntries.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); - std::vector> - sortedObjCHermesDirectReturnEntries( - objcHermesDirectReturnEntries.begin(), - objcHermesDirectReturnEntries.end()); - std::sort(sortedObjCHermesDirectReturnEntries.begin(), - sortedObjCHermesDirectReturnEntries.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector> - sortedCFunctionHermesDirectReturnEntries( - cFunctionHermesDirectReturnEntries.begin(), - cFunctionHermesDirectReturnEntries.end()); - std::sort(sortedCFunctionHermesDirectReturnEntries.begin(), - sortedCFunctionHermesDirectReturnEntries.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector> - sortedObjCHermesFrameDirectReturnEntries( - objcHermesFrameDirectReturnEntries.begin(), - objcHermesFrameDirectReturnEntries.end()); - std::sort(sortedObjCHermesFrameDirectReturnEntries.begin(), - sortedObjCHermesFrameDirectReturnEntries.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector> - sortedCFunctionHermesFrameDirectReturnEntries( - cFunctionHermesFrameDirectReturnEntries.begin(), - cFunctionHermesFrameDirectReturnEntries.end()); - std::sort(sortedCFunctionHermesFrameDirectReturnEntries.begin(), - sortedCFunctionHermesFrameDirectReturnEntries.end(), - [](const auto& lhs, const auto& rhs) { - return lhs.first < rhs.first; - }); - - std::vector> - sortedBlockHermesFrameDirectReturnEntries( - blockHermesFrameDirectReturnEntries.begin(), - blockHermesFrameDirectReturnEntries.end()); - std::sort(sortedBlockHermesFrameDirectReturnEntries.begin(), - sortedBlockHermesFrameDirectReturnEntries.end(), + std::vector> sortedCFunctionPreparedEntries( + cFunctionPreparedEntries.begin(), cFunctionPreparedEntries.end()); + std::sort(sortedCFunctionPreparedEntries.begin(), + sortedCFunctionPreparedEntries.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); @@ -340,11 +221,17 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, sortedBlockPreparedEntries.begin(), sortedBlockPreparedEntries.end(), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + std::vector> sortedObjCGsdEntries( + objcGsdEntries.begin(), objcGsdEntries.end()); + std::sort( + sortedObjCGsdEntries.begin(), sortedObjCGsdEntries.end(), + [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); + std::ostringstream generated; generated << "#ifndef NS_GENERATED_SIGNATURE_DISPATCH_INC\n"; generated << "#define NS_GENERATED_SIGNATURE_DISPATCH_INC\n\n"; - generated << "#if NS_GSD_BACKEND_V8 || NS_GSD_BACKEND_NAPI || " - "NS_GSD_BACKEND_ENGINE_DIRECT\n"; + generated << "#if NS_GSD_BACKEND_NAPI || " + "NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_PREPARED\n"; generated << "#undef NS_HAS_GENERATED_SIGNATURE_DISPATCH\n"; generated << "#define NS_HAS_GENERATED_SIGNATURE_DISPATCH 1\n"; generated << "#endif\n"; @@ -352,30 +239,10 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, generated << "#undef NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH\n"; generated << "#define NS_HAS_GENERATED_SIGNATURE_NAPI_DISPATCH 1\n"; generated << "#endif\n"; - generated << "#if NS_GSD_BACKEND_V8\n"; - generated << "#undef NS_HAS_GENERATED_SIGNATURE_V8_DISPATCH\n"; - generated << "#define NS_HAS_GENERATED_SIGNATURE_V8_DISPATCH 1\n"; - generated << "#endif\n"; - generated << "#if NS_GSD_BACKEND_ENGINE_DIRECT\n"; - generated << "#undef NS_HAS_GENERATED_SIGNATURE_ENGINE_DIRECT_DISPATCH\n"; - generated << "#define NS_HAS_GENERATED_SIGNATURE_ENGINE_DIRECT_DISPATCH 1\n"; - generated << "#endif\n\n"; - generated << "#if NS_GSD_BACKEND_HERMES\n"; - generated << "#undef NS_HAS_GENERATED_SIGNATURE_HERMES_DIRECT_RETURN_DISPATCH\n"; - generated << "#define NS_HAS_GENERATED_SIGNATURE_HERMES_DIRECT_RETURN_DISPATCH 1\n"; - generated << "#undef " - "NS_HAS_GENERATED_SIGNATURE_HERMES_FRAME_DIRECT_RETURN_DISPATCH\n"; - generated << "#define " - "NS_HAS_GENERATED_SIGNATURE_HERMES_FRAME_DIRECT_RETURN_DISPATCH 1\n"; - generated << "#undef " - "NS_HAS_GENERATED_SIGNATURE_HERMES_BLOCK_FRAME_DIRECT_RETURN_DISPATCH\n"; - generated << "#define " - "NS_HAS_GENERATED_SIGNATURE_HERMES_BLOCK_FRAME_DIRECT_RETURN_DISPATCH 1\n"; - generated << "#endif\n\n"; generated << "namespace nativescript {\n\n"; - generated << "#if NS_GSD_BACKEND_V8 || NS_GSD_BACKEND_NAPI || " - "NS_GSD_BACKEND_ENGINE_DIRECT\n"; + generated << "#if NS_GSD_BACKEND_NAPI || " + "NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_PREPARED\n"; for (const auto& wrapper : preparedWrappers) { writePreparedWrapper(generated, wrapper.second.first, preparedWrapperNameByKey.at(wrapper.first), @@ -390,56 +257,24 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, } generated << "#endif\n\n"; - generated << "#if NS_GSD_BACKEND_ENGINE_DIRECT\n"; - writeEngineDirectConverterMacros(generated); - for (const auto& wrapper : wrappers) { - writeEngineDirectWrapper(generated, wrapper.second.first, - engineDirectWrapperNameByKey.at(wrapper.first), - wrapper.second.second); - } - writeEngineDirectConverterUndefs(generated); - generated << "#endif\n\n"; - - generated << "#if NS_GSD_BACKEND_HERMES\n"; - writeHermesEngineDirectConverterMacros(generated); - for (const auto& wrapper : wrappers) { - writeHermesDirectReturnWrapper( - generated, wrapper.second.first, - hermesDirectReturnWrapperNameByKey.at(wrapper.first), - wrapper.second.second); - } - for (const auto& wrapper : wrappers) { - writeHermesFrameDirectReturnWrapper( - generated, wrapper.second.first, - hermesFrameDirectReturnWrapperNameByKey.at(wrapper.first), - wrapper.second.second); - } - for (const auto& wrapper : preparedWrappers) { - writeHermesFrameDirectReturnWrapper( - generated, wrapper.second.first, - hermesBlockFrameDirectReturnWrapperNameByKey.at(wrapper.first), - wrapper.second.second); - } - writeEngineDirectConverterUndefs(generated); - generated << "#endif\n\n"; - - generated << "#if NS_GSD_BACKEND_V8\n"; - for (const auto& wrapper : wrappers) { - writeV8Wrapper(generated, wrapper.second.first, - v8WrapperNameByKey.at(wrapper.first), wrapper.second.second); - } - generated << "#endif\n\n"; - - generated << "#if NS_GSD_BACKEND_V8 || NS_GSD_BACKEND_NAPI || " - "NS_GSD_BACKEND_ENGINE_DIRECT\n"; + generated << "#if NS_GSD_BACKEND_NAPI || " + "NS_GSD_BACKEND_HERMES || NS_GSD_BACKEND_PREPARED\n"; generated << "inline constexpr ObjCDispatchEntry " "kGeneratedObjCDispatchEntries[] = {\n"; generated << " {0, nullptr},\n"; + for (const auto& entry : sortedObjCPreparedEntries) { + generated << " {" << toHexLiteral(entry.first) << ", &" + << preparedWrapperNameByKey.at(entry.second) << "},\n"; + } generated << "};\n\n"; generated << "inline constexpr CFunctionDispatchEntry " "kGeneratedCFunctionDispatchEntries[] = {\n"; generated << " {0, nullptr},\n"; + for (const auto& entry : sortedCFunctionPreparedEntries) { + generated << " {" << toHexLiteral(entry.first) << ", &" + << preparedWrapperNameByKey.at(entry.second) << "},\n"; + } generated << "};\n\n"; generated << "inline constexpr BlockDispatchEntry " @@ -452,78 +287,6 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, generated << "};\n\n"; generated << "#endif\n\n"; - generated << "#if NS_GSD_BACKEND_ENGINE_DIRECT\n"; - generated << "inline constexpr ObjCEngineDirectDispatchEntry " - "kGeneratedObjCEngineDirectDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedObjCEngineDirectEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << engineDirectWrapperNameByKey.at(entry.second) << "},\n"; - } - generated << "};\n\n"; - - generated << "inline constexpr CFunctionEngineDirectDispatchEntry " - "kGeneratedCFunctionEngineDirectDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedCFunctionEngineDirectEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << engineDirectWrapperNameByKey.at(entry.second) << "},\n"; - } - generated << "};\n"; - generated << "#endif\n\n"; - - generated << "#if NS_GSD_BACKEND_HERMES\n"; - generated << "inline constexpr ObjCHermesDirectReturnDispatchEntry " - "kGeneratedObjCHermesDirectReturnDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedObjCHermesDirectReturnEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << hermesDirectReturnWrapperNameByKey.at(entry.second) - << "},\n"; - } - generated << "};\n\n"; - - generated << "inline constexpr CFunctionHermesDirectReturnDispatchEntry " - "kGeneratedCFunctionHermesDirectReturnDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedCFunctionHermesDirectReturnEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << hermesDirectReturnWrapperNameByKey.at(entry.second) - << "},\n"; - } - generated << "};\n"; - - generated << "inline constexpr ObjCHermesFrameDirectReturnDispatchEntry " - "kGeneratedObjCHermesFrameDirectReturnDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedObjCHermesFrameDirectReturnEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << hermesFrameDirectReturnWrapperNameByKey.at(entry.second) - << "},\n"; - } - generated << "};\n\n"; - - generated << "inline constexpr CFunctionHermesFrameDirectReturnDispatchEntry " - "kGeneratedCFunctionHermesFrameDirectReturnDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedCFunctionHermesFrameDirectReturnEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << hermesFrameDirectReturnWrapperNameByKey.at(entry.second) - << "},\n"; - } - generated << "};\n"; - - generated << "inline constexpr BlockHermesFrameDirectReturnDispatchEntry " - "kGeneratedBlockHermesFrameDirectReturnDispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedBlockHermesFrameDirectReturnEntries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << hermesBlockFrameDirectReturnWrapperNameByKey.at(entry.second) - << "},\n"; - } - generated << "};\n"; - generated << "#endif\n\n"; - generated << "#if NS_GSD_BACKEND_NAPI\n"; generated << "inline constexpr ObjCNapiDispatchEntry " "kGeneratedObjCNapiDispatchEntries[] = {\n"; @@ -544,31 +307,55 @@ void writeSignatureDispatchBindings(const MDMetadataWriter& writer, generated << "};\n\n"; generated << "#endif\n\n"; - generated << "#if NS_GSD_BACKEND_V8\n"; - generated << "inline constexpr ObjCV8DispatchEntry " - "kGeneratedObjCV8DispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedObjCV8Entries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << v8WrapperNameByKey.at(entry.second) << "},\n"; - } - generated << "};\n\n"; - - generated << "inline constexpr CFunctionV8DispatchEntry " - "kGeneratedCFunctionV8DispatchEntries[] = {\n"; - generated << " {0, nullptr},\n"; - for (const auto& entry : sortedCFunctionV8Entries) { - generated << " {" << toHexLiteral(entry.first) << ", &" - << v8WrapperNameByKey.at(entry.second) << "},\n"; - } - generated << "};\n"; - generated << "#endif\n\n"; - generated << "} // namespace nativescript\n\n"; generated << "#endif // NS_GENERATED_SIGNATURE_DISPATCH_INC\n"; std::ofstream outFile(outputPath, std::ios::trunc | std::ios::binary); outFile << generated.str(); + + // Write engine-neutral GSD invokers + dispatch table to a separate file. + // It is included from each engine backend after that engine's + // GsdObjCContext struct is defined (avoiding namespace ordering issues). + // The generated code only references the engine-neutral GsdObjCContext + // interface, so the same file compiles unchanged in every backend. + if (!sortedObjCGsdEntries.empty()) { + std::string gsdOutputPath = outputPath; + auto lastSlash = gsdOutputPath.rfind('/'); + if (lastSlash != std::string::npos) { + gsdOutputPath = gsdOutputPath.substr(0, lastSlash + 1) + + "GeneratedGsdSignatureDispatch.inc"; + } else { + gsdOutputPath = "GeneratedGsdSignatureDispatch.inc"; + } + + std::ostringstream gsdGenerated; + gsdGenerated << "#ifndef NS_GENERATED_GSD_SIGNATURE_DISPATCH_INC\n"; + gsdGenerated << "#define NS_GENERATED_GSD_SIGNATURE_DISPATCH_INC\n\n"; + gsdGenerated << "#undef NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH\n"; + gsdGenerated << "#define NS_HAS_GENERATED_SIGNATURE_GSD_DISPATCH 1\n\n"; + gsdGenerated << "#include \n\n"; + // No namespace wrapper — included from within namespace nativescript in + // each engine backend, after GsdObjCContext is defined. + + for (const auto& wrapper : gsdWrappers) { + writeGsdWrapper(gsdGenerated, gsdWrapperNameByKey.at(wrapper.first), + wrapper.second); + } + + gsdGenerated << "inline constexpr ObjCGsdDispatchEntry " + "kGeneratedObjCGsdDispatchEntries[] = {\n"; + gsdGenerated << " {0, nullptr},\n"; + for (const auto& entry : sortedObjCGsdEntries) { + gsdGenerated << " {" << toHexLiteral(entry.first) << ", &" + << gsdWrapperNameByKey.at(entry.second) << "},\n"; + } + gsdGenerated << "};\n\n"; + + gsdGenerated << "#endif // NS_GENERATED_GSD_SIGNATURE_DISPATCH_INC\n"; + + std::ofstream gsdOutFile(gsdOutputPath, std::ios::trunc | std::ios::binary); + gsdOutFile << gsdGenerated.str(); + } } } // namespace metagen diff --git a/metadata-generator/src/SignatureDispatchEmitter/EngineDirect.cpp b/metadata-generator/src/SignatureDispatchEmitter/EngineDirect.cpp deleted file mode 100644 index 820eb3fb7..000000000 --- a/metadata-generator/src/SignatureDispatchEmitter/EngineDirect.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include "SignatureDispatchEmitter/Shared.h" - -#include -#include -#include - -namespace metagen::signature_dispatch { - -std::string makeEngineDirectWrapperName(DispatchKind kind, size_t index) { - std::ostringstream stream; - stream << "de"; - switch (kind) { - case DispatchKind::ObjCMethod: - stream << "o"; - break; - case DispatchKind::CFunction: - stream << "c"; - break; - case DispatchKind::BlockInvoke: - stream << "b"; - break; - } - stream << toBase36(index); - return stream.str(); -} -const char* engineDirectConverterMacroForKind(MDTypeKind kind) { - switch (kind) { - case mdTypeBool: - return "NS_GSD_ENGINE_DIRECT_CONVERT_BOOL_ARGUMENT"; - case mdTypeChar: - return "NS_GSD_ENGINE_DIRECT_CONVERT_INT8_ARGUMENT"; - case mdTypeUChar: - case mdTypeUInt8: - return "NS_GSD_ENGINE_DIRECT_CONVERT_UINT8_ARGUMENT"; - case mdTypeSShort: - return "NS_GSD_ENGINE_DIRECT_CONVERT_INT16_ARGUMENT"; - case mdTypeUShort: - return "NS_GSD_ENGINE_DIRECT_CONVERT_UINT16_ARGUMENT"; - case mdTypeSInt: - return "NS_GSD_ENGINE_DIRECT_CONVERT_INT32_ARGUMENT"; - case mdTypeUInt: - return "NS_GSD_ENGINE_DIRECT_CONVERT_UINT32_ARGUMENT"; - case mdTypeSLong: - case mdTypeSInt64: - return "NS_GSD_ENGINE_DIRECT_CONVERT_INT64_ARGUMENT"; - case mdTypeULong: - case mdTypeUInt64: - return "NS_GSD_ENGINE_DIRECT_CONVERT_UINT64_ARGUMENT"; - case mdTypeFloat: - return "NS_GSD_ENGINE_DIRECT_CONVERT_FLOAT_ARGUMENT"; - case mdTypeDouble: - return "NS_GSD_ENGINE_DIRECT_CONVERT_DOUBLE_ARGUMENT"; - case mdTypeSelector: - return "NS_GSD_ENGINE_DIRECT_CONVERT_SELECTOR_ARGUMENT"; - case mdTypeClass: - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return "NS_GSD_ENGINE_DIRECT_CONVERT_OBJECT_ARGUMENT"; - default: - return "NS_GSD_ENGINE_DIRECT_CONVERT_ARGUMENT"; - } -} - -bool engineDirectConverterTakesKind(MDTypeKind kind) { - switch (kind) { - case mdTypeClass: - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return true; - default: - return engineDirectConverterMacroForKind(kind) == - std::string("NS_GSD_ENGINE_DIRECT_CONVERT_ARGUMENT"); - } -} -void writeEngineDirectArgConversion(std::ostringstream& out, - const MDTypeInfo* type, size_t index, - const std::string& valueExpr = "") { - if (type == nullptr) { - out << " return false;\n"; - return; - } - - const std::string argValue = - valueExpr.empty() ? "argv[" + std::to_string(index) + "]" : valueExpr; - const char* converter = engineDirectConverterMacroForKind(type->kind); - out << " if (!" << converter << "(env, "; - if (engineDirectConverterTakesKind(type->kind)) { - out << "static_cast(" << static_cast(type->kind) - << "), "; - } - out << argValue << ", &arg" << index << ")) {\n"; - if (argKindMayNeedCleanup(type->kind)) { - out << " cif->argTypes[" << index << "]->toNative(env, " << argValue - << ", &arg" << index << ", &shouldFree" << index - << ", &shouldFreeAny);\n"; - } else { - out << " bool ignoredShouldFree = false;\n"; - out << " bool ignoredShouldFreeAny = false;\n"; - out << " cif->argTypes[" << index << "]->toNative(env, " << argValue - << ", &arg" << index - << ", &ignoredShouldFree, &ignoredShouldFreeAny);\n"; - } - out << " }\n"; -} - -void writeEngineDirectConverterMacros(std::ostringstream& out) { - out << "#if NS_GSD_BACKEND_JSC\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_ARGUMENT " - "TryFastConvertJSCArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_BOOL_ARGUMENT " - "TryFastConvertJSCBoolArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT8_ARGUMENT " - "TryFastConvertJSCInt8Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT8_ARGUMENT " - "TryFastConvertJSCUInt8Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT16_ARGUMENT " - "TryFastConvertJSCInt16Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT16_ARGUMENT " - "TryFastConvertJSCUInt16Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT32_ARGUMENT " - "TryFastConvertJSCInt32Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT32_ARGUMENT " - "TryFastConvertJSCUInt32Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT64_ARGUMENT " - "TryFastConvertJSCInt64Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT64_ARGUMENT " - "TryFastConvertJSCUInt64Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_FLOAT_ARGUMENT " - "TryFastConvertJSCFloatArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_DOUBLE_ARGUMENT " - "TryFastConvertJSCDoubleArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_SELECTOR_ARGUMENT " - "TryFastConvertJSCSelectorArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_OBJECT_ARGUMENT " - "TryFastConvertJSCObjectArgument\n"; - out << "#elif NS_GSD_BACKEND_QUICKJS\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_ARGUMENT " - "TryFastConvertQuickJSArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_BOOL_ARGUMENT " - "TryFastConvertQuickJSBoolArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT8_ARGUMENT " - "TryFastConvertQuickJSInt8Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT8_ARGUMENT " - "TryFastConvertQuickJSUInt8Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT16_ARGUMENT " - "TryFastConvertQuickJSInt16Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT16_ARGUMENT " - "TryFastConvertQuickJSUInt16Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT32_ARGUMENT " - "TryFastConvertQuickJSInt32Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT32_ARGUMENT " - "TryFastConvertQuickJSUInt32Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT64_ARGUMENT " - "TryFastConvertQuickJSInt64Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT64_ARGUMENT " - "TryFastConvertQuickJSUInt64Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_FLOAT_ARGUMENT " - "TryFastConvertQuickJSFloatArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_DOUBLE_ARGUMENT " - "TryFastConvertQuickJSDoubleArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_SELECTOR_ARGUMENT " - "TryFastConvertQuickJSSelectorArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_OBJECT_ARGUMENT " - "TryFastConvertQuickJSObjectArgument\n"; - out << "#elif NS_GSD_BACKEND_HERMES\n"; - writeHermesEngineDirectConverterMacros(out); - out << "#else\n"; - out << "#error \"No generated signature engine-direct converter selected\"\n"; - out << "#endif\n"; -} - -void writeHermesEngineDirectConverterMacros(std::ostringstream& out) { - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_ARGUMENT " - "TryFastConvertHermesArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_BOOL_ARGUMENT " - "TryFastConvertHermesGeneratedBoolArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT8_ARGUMENT " - "TryFastConvertHermesGeneratedInt8Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT8_ARGUMENT " - "TryFastConvertHermesGeneratedUInt8Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT16_ARGUMENT " - "TryFastConvertHermesGeneratedInt16Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT16_ARGUMENT " - "TryFastConvertHermesGeneratedUInt16Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT32_ARGUMENT " - "TryFastConvertHermesGeneratedInt32Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT32_ARGUMENT " - "TryFastConvertHermesGeneratedUInt32Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_INT64_ARGUMENT " - "TryFastConvertHermesGeneratedInt64Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_UINT64_ARGUMENT " - "TryFastConvertHermesGeneratedUInt64Argument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_FLOAT_ARGUMENT " - "TryFastConvertHermesGeneratedFloatArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_DOUBLE_ARGUMENT " - "TryFastConvertHermesGeneratedDoubleArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_SELECTOR_ARGUMENT " - "TryFastConvertHermesSelectorArgument\n"; - out << "#define NS_GSD_ENGINE_DIRECT_CONVERT_OBJECT_ARGUMENT " - "TryFastConvertHermesObjectArgument\n"; -} - -void writeEngineDirectConverterUndefs(std::ostringstream& out) { - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_BOOL_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_INT8_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_UINT8_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_INT16_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_UINT16_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_INT32_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_UINT32_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_INT64_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_UINT64_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_FLOAT_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_DOUBLE_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_SELECTOR_ARGUMENT\n"; - out << "#undef NS_GSD_ENGINE_DIRECT_CONVERT_OBJECT_ARGUMENT\n"; -} - -void writeEngineDirectWrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature) { - if (kind == DispatchKind::BlockInvoke) { - return; - } - - std::string returnType; - if (!mapTypeToCpp(signature->returnType, &returnType, true)) { - return; - } - - std::vector argTypeInfos; - std::vector argTypes; - argTypes.reserve(signature->arguments.size()); - argTypeInfos.reserve(signature->arguments.size()); - for (const auto* arg : signature->arguments) { - std::string argType; - if (!mapTypeToCpp(arg, &argType, false)) { - return; - } - argTypeInfos.push_back(arg); - argTypes.push_back(argType); - } - - out << "static inline bool " << wrapperName - << "(napi_env env, Cif* cif, void* fnptr, "; - if (kind == DispatchKind::ObjCMethod) { - out << "id self, SEL selector, "; - } - out << "const napi_value* argv, void* rvalue) {\n"; - - out << " using Fn = " << returnType << " (*)("; - bool first = true; - if (kind == DispatchKind::ObjCMethod) { - out << "id, SEL"; - first = false; - } - for (const auto& argType : argTypes) { - if (!first) { - out << ", "; - } - out << argType; - first = false; - } - out << ");\n"; - out << " auto fn = reinterpret_cast(fnptr);\n"; - - std::vector cleanupArgIndexes; - cleanupArgIndexes.reserve(argTypes.size()); - for (size_t i = 0; i < argTypes.size(); i++) { - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - cleanupArgIndexes.push_back(i); - } - } - const bool hasCleanupArgs = !cleanupArgIndexes.empty(); - if (hasCleanupArgs) { - out << " bool shouldFreeAny = false;\n"; - } - if (returnType != "void") { - out << " " << returnType << " nativeResult{};\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - out << " " << argTypes[i] << " arg" << i << "{};\n"; - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - out << " bool shouldFree" << i << " = false;\n"; - } - } - - if (hasCleanupArgs) { - out << " auto cleanupManagedArgs = [&]() {\n"; - out << " if (shouldFreeAny) {\n"; - if (kind == DispatchKind::CFunction && returnType != "void") { - out << " void* returnPointerValue = nullptr;\n"; - out << " if (cif->returnType != nullptr && cif->returnType->type == " - "&ffi_type_pointer) {\n"; - out << " returnPointerValue = " - "*reinterpret_cast(&nativeResult);\n"; - out << " }\n"; - } - for (const auto i : cleanupArgIndexes) { - out << " if (shouldFree" << i << ") {\n"; - if (kind == DispatchKind::CFunction && returnType != "void") { - out << " if (returnPointerValue != nullptr && " - "*reinterpret_cast(&arg" - << i << ") == returnPointerValue) {\n"; - out << " } else {\n"; - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - out << " }\n"; - } else { - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - } - out << " }\n"; - } - out << " }\n"; - out << " };\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - writeEngineDirectArgConversion(out, argTypeInfos[i], i); - } - - std::ostringstream callExpr; - callExpr << "fn("; - bool hasAnyCallArg = false; - if (kind == DispatchKind::ObjCMethod) { - callExpr << "self, selector"; - hasAnyCallArg = true; - } - for (size_t i = 0; i < argTypes.size(); i++) { - if (hasAnyCallArg) { - callExpr << ", "; - } - callExpr << "arg" << i; - hasAnyCallArg = true; - } - callExpr << ")"; - - if (returnType == "void") { - out << " " << callExpr.str() << ";\n"; - } else { - out << " nativeResult = " << callExpr.str() << ";\n"; - out << " *reinterpret_cast<" << returnType - << "*>(rvalue) = nativeResult;\n"; - } - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return true;\n"; - out << "}\n\n"; -} - -} // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/SignatureDispatchEmitter/Gsd.cpp b/metadata-generator/src/SignatureDispatchEmitter/Gsd.cpp new file mode 100644 index 000000000..5bc041aab --- /dev/null +++ b/metadata-generator/src/SignatureDispatchEmitter/Gsd.cpp @@ -0,0 +1,265 @@ +#include "SignatureDispatchEmitter/Shared.h" + +#include +#include + +// Engine-neutral Generated Signature Dispatch (GSD) emitter. +// +// Emits one set of invoker functions and a dispatch table that are shared by +// every embedded engine backend (V8, JSC, QuickJS, Hermes). Each invoker takes +// a single `GsdObjCContext&` argument. The context is a small concrete struct +// defined per engine that knows how to read JS arguments and write the JS +// return value using that engine's native value API. Native calls are routed +// through ctx.invokeNative so backends that must release/coordinate their JS +// runtime around Objective-C dispatch can do so without moving JS conversion +// outside the engine thread. Backends without that requirement take the direct +// exception-safe path. The generated code only references the engine-neutral +// `GsdObjCContext` interface, so the same `.inc` compiles unchanged in every +// backend translation unit with inline context calls. +// +// GSD handles primitives, SEL, Class, and already-native Objective-C object +// values. Any value that does not match the fast representation makes the +// relevant reader return false, which makes the whole invoker fall back to the +// fully correct generic marshalling path. + +namespace metagen::signature_dispatch { + +std::string makeGsdWrapperName(size_t index) { + return "gsd" + toBase36(index); +} + +static void writeGsdArgConversion(std::ostringstream& out, + const MDTypeInfo* type, size_t index) { + switch (type->kind) { + case mdTypeBool: + out << " if (!ctx.readBool(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeChar: + case mdTypeSShort: + case mdTypeSInt: + case mdTypeSLong: + case mdTypeSInt64: + out << " if (!ctx.readSigned(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeUChar: + case mdTypeUInt8: + case mdTypeUShort: + case mdTypeUInt: + case mdTypeULong: + case mdTypeUInt64: + out << " if (!ctx.readUnsigned(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeFloat: + out << " if (!ctx.readFloat(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeDouble: + out << " if (!ctx.readDouble(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeSelector: + out << " if (!ctx.readSelector(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeClass: + out << " if (!ctx.readClass(" << index << ", &arg" << index + << ")) return false;\n"; + break; + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + // Object arguments are accepted only when the JS value is already a + // native host object (cheap pointer unwrap); anything that would require + // allocation/conversion makes readObject return false and the whole + // invoker falls back to the generic path. + out << " if (!ctx.readObject(" << index << ", &arg" << index + << ")) return false;\n"; + break; + default: + out << " return false;\n"; + break; + } +} + +static void writeGsdReturnConversion(std::ostringstream& out, + const MDTypeInfo* type) { + switch (type->kind) { + case mdTypeVoid: + out << " ctx.setVoid();\n"; + break; + case mdTypeBool: + out << " ctx.setBool(nativeResult != 0);\n"; + break; + case mdTypeChar: + case mdTypeSShort: + case mdTypeSInt: + out << " ctx.setInt32(static_cast(nativeResult));\n"; + break; + case mdTypeUChar: + case mdTypeUInt8: + case mdTypeUInt: + out << " ctx.setUInt32(static_cast(nativeResult));\n"; + break; + case mdTypeUShort: + out << " ctx.setUInt16(static_cast(nativeResult));\n"; + break; + case mdTypeSLong: + case mdTypeSInt64: + out << " ctx.setInt64(static_cast(nativeResult));\n"; + break; + case mdTypeULong: + case mdTypeUInt64: + out << " ctx.setUInt64(static_cast(nativeResult));\n"; + break; + case mdTypeFloat: + case mdTypeDouble: + out << " ctx.setDouble(static_cast(nativeResult));\n"; + break; + case mdTypeSelector: + out << " ctx.setSelector(nativeResult);\n"; + break; + case mdTypeClass: + out << " ctx.setClass(nativeResult);\n"; + break; + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + // Object returns use the exact metadata return type (carried by the + // context) so JS conversion + ownership match the generic path exactly. + out << " ctx.setObject(nativeResult);\n"; + break; + default: + out << " return false;\n"; + break; + } +} + +static bool isGsdFastScalarType(const MDTypeInfo* type) { + if (type == nullptr) return false; + switch (type->kind) { + case mdTypeBool: + case mdTypeChar: + case mdTypeUChar: + case mdTypeUInt8: + case mdTypeSShort: + case mdTypeUShort: + case mdTypeSInt: + case mdTypeUInt: + case mdTypeSLong: + case mdTypeULong: + case mdTypeSInt64: + case mdTypeUInt64: + case mdTypeFloat: + case mdTypeDouble: + case mdTypeSelector: + case mdTypeClass: + return true; + default: + return false; + } +} + +static bool isGsdObjectType(const MDTypeInfo* type) { + if (type == nullptr) return false; + switch (type->kind) { + case mdTypeAnyObject: + case mdTypeProtocolObject: + case mdTypeClassObject: + case mdTypeInstanceObject: + case mdTypeNSStringObject: + case mdTypeNSMutableStringObject: + return true; + default: + return false; + } +} + +// Arguments accept scalars + Objective-C object types (the reader unwraps a +// native host object's backing pointer or falls back). Returns additionally +// accept void; object returns route through setObject with the exact metadata +// return type so JS conversion and ownership match the generic path. +static bool isGsdFastArgType(const MDTypeInfo* type) { + return isGsdFastScalarType(type) || isGsdObjectType(type); +} + +static bool isGsdFastReturnType(const MDTypeInfo* type) { + if (type != nullptr && type->kind == mdTypeVoid) return true; + return isGsdFastScalarType(type) || isGsdObjectType(type); +} + +bool isGsdSignatureSupported(const MDSignature* signature) { + if (signature == nullptr || signature->isVariadic) return false; + if (signature->arguments.size() > 8) return false; + if (!isGsdFastReturnType(signature->returnType)) return false; + for (const auto* arg : signature->arguments) { + if (!isGsdFastArgType(arg)) return false; + } + return true; +} + +void writeGsdWrapper(std::ostringstream& out, const std::string& wrapperName, + const MDSignature* signature) { + std::string returnType; + if (!mapTypeToCpp(signature->returnType, &returnType, true)) return; + + std::vector argTypes; + for (const auto* arg : signature->arguments) { + std::string argType; + if (!mapTypeToCpp(arg, &argType, false)) return; + argTypes.push_back(argType); + } + + out << "static inline bool " << wrapperName << "(GsdObjCContext& ctx) {\n"; + + out << " using Fn = " << returnType << " (*)(id, SEL"; + for (const auto& argType : argTypes) { + out << ", " << argType; + } + out << ");\n"; + out << " auto fn = reinterpret_cast(objc_msgSend);\n"; + + for (size_t i = 0; i < argTypes.size(); i++) { + out << " " << argTypes[i] << " arg" << i << "{};\n"; + } + for (size_t i = 0; i < argTypes.size(); i++) { + writeGsdArgConversion(out, signature->arguments[i], i); + } + + if (returnType == "void") { + out << " ctx.invokeNative([&]() {\n"; + out << " fn(ctx.self, ctx.selector"; + for (size_t i = 0; i < argTypes.size(); i++) out << ", arg" << i; + out << ");\n"; + out << " });\n"; + } else { + out << " " << returnType << " nativeResult{};\n"; + out << " ctx.invokeNative([&]() {\n"; + out << " nativeResult = fn(ctx.self, ctx.selector"; + for (size_t i = 0; i < argTypes.size(); i++) out << ", arg" << i; + out << ");\n"; + out << " });\n"; + } + + writeGsdReturnConversion(out, signature->returnType); + + out << " return true;\n"; + out << "}\n\n"; +} + +std::string makeGsdWrapperShapeKey(const MDSignature* signature) { + if (signature == nullptr) return {}; + std::string base = makeWrapperShapeKey(DispatchKind::ObjCMethod, signature); + if (base.empty()) return {}; + return "gsd|" + base; +} + +} // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/SignatureDispatchEmitter/Hermes.cpp b/metadata-generator/src/SignatureDispatchEmitter/Hermes.cpp deleted file mode 100644 index 74feb009b..000000000 --- a/metadata-generator/src/SignatureDispatchEmitter/Hermes.cpp +++ /dev/null @@ -1,548 +0,0 @@ -#include "SignatureDispatchEmitter/Shared.h" - -#include -#include - -namespace metagen::signature_dispatch { - -std::string makeHermesDirectReturnWrapperName(DispatchKind kind, size_t index) { - std::ostringstream stream; - stream << "dh"; - switch (kind) { - case DispatchKind::ObjCMethod: - stream << "o"; - break; - case DispatchKind::CFunction: - stream << "c"; - break; - case DispatchKind::BlockInvoke: - stream << "b"; - break; - } - stream << toBase36(index); - return stream.str(); -} - -std::string makeHermesFrameDirectReturnWrapperName(DispatchKind kind, - size_t index) { - std::ostringstream stream; - stream << "hf"; - switch (kind) { - case DispatchKind::ObjCMethod: - stream << "o"; - break; - case DispatchKind::CFunction: - stream << "c"; - break; - case DispatchKind::BlockInvoke: - stream << "b"; - break; - } - stream << toBase36(index); - return stream.str(); -} - -bool canSetHermesReturnDirectly(MDTypeKind kind) { - switch (kind) { - case mdTypeVoid: - case mdTypeBool: - case mdTypeChar: - case mdTypeUChar: - case mdTypeUInt8: - case mdTypeSShort: - case mdTypeUShort: - case mdTypeSInt: - case mdTypeUInt: - case mdTypeSLong: - case mdTypeULong: - case mdTypeSInt64: - case mdTypeUInt64: - case mdTypeFloat: - case mdTypeDouble: - return true; - default: - return false; - } -} - -bool canSetHermesObjCReturnDirectly(MDTypeKind kind) { - if (canSetHermesReturnDirectly(kind)) { - return true; - } - - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return true; - default: - return false; - } -} - -void writeHermesDirectReturnValue(std::ostringstream& out, DispatchKind dispatchKind, - MDTypeKind kind, - const std::string& valueExpr) { - // Emits an open failure branch; the caller appends cleanup and `return false`. - switch (kind) { - case mdTypeVoid: - out << " if (!SetHermesGeneratedVoidReturn(env, result)) {\n"; - break; - case mdTypeBool: - out << " if (!SetHermesGeneratedBoolReturn(cif, result, " << valueExpr - << " != 0)) {\n"; - break; - case mdTypeChar: - out << " if (!SetHermesGeneratedInt8Return(cif, result, static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeUChar: - case mdTypeUInt8: - out << " if (!SetHermesGeneratedUInt8Return(cif, result, static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeSShort: - out << " if (!SetHermesGeneratedInt16Return(cif, result, static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeUShort: - out << " if (!SetHermesGeneratedUInt16Return(env, cif, result, " - "static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeSInt: - out << " if (!SetHermesGeneratedInt32Return(cif, result, static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeUInt: - out << " if (!SetHermesGeneratedUInt32Return(cif, result, static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeSLong: - case mdTypeSInt64: - out << " if (!SetHermesGeneratedInt64Return(env, cif, result, " - "static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeULong: - case mdTypeUInt64: - out << " if (!SetHermesGeneratedUInt64Return(env, cif, result, " - "static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeFloat: - case mdTypeDouble: - out << " if (!SetHermesGeneratedDoubleReturn(cif, result, static_cast(" - << valueExpr << "))) {\n"; - break; - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - if (dispatchKind == DispatchKind::ObjCMethod) { - out << " if (!TryFastSetHermesGeneratedObjCObjectReturnValue(" - "env, cif, returnContext, selector, cif->returnType->kind, " - << valueExpr << ", result)) {\n"; - } else { - out << " if (!TryFastConvertHermesReturnValue(env, cif, " - "cif->returnType->kind, &" - << valueExpr << ", result)) {\n"; - } - break; - default: - out << " if (!TryFastConvertHermesReturnValue(env, cif, static_cast(" - << static_cast(kind) << "), &" << valueExpr << ", result)) {\n"; - break; - } -} -bool canUseHermesDirectReturnWrapper(DispatchKind kind, - const MDSignature* signature, - HermesDirectReturnCallSite callSite) { - if (callSite == HermesDirectReturnCallSite::FastCallback && - kind == DispatchKind::BlockInvoke) { - return false; - } - - if (signature == nullptr || signature->returnType == nullptr) { - return false; - } - - const bool canSetReturnDirectly = - kind == DispatchKind::ObjCMethod - ? canSetHermesObjCReturnDirectly(signature->returnType->kind) - : canSetHermesReturnDirectly(signature->returnType->kind); - if (!canSetReturnDirectly) { - return false; - } - - for (const auto* arg : signature->arguments) { - if (arg == nullptr || argKindMayNeedCleanup(arg->kind)) { - return false; - } - } - - return true; -} -const char* hermesFrameRawConverterForKind(MDTypeKind kind) { - switch (kind) { - case mdTypeBool: - return "TryFastConvertHermesGeneratedBoolRawArgument"; - case mdTypeChar: - return "TryFastConvertHermesGeneratedInt8RawArgument"; - case mdTypeUChar: - case mdTypeUInt8: - return "TryFastConvertHermesGeneratedUInt8RawArgument"; - case mdTypeSShort: - return "TryFastConvertHermesGeneratedInt16RawArgument"; - case mdTypeUShort: - return "TryFastConvertHermesGeneratedUInt16RawArgument"; - case mdTypeSInt: - return "TryFastConvertHermesGeneratedInt32RawArgument"; - case mdTypeUInt: - return "TryFastConvertHermesGeneratedUInt32RawArgument"; - case mdTypeSLong: - case mdTypeSInt64: - return "TryFastConvertHermesGeneratedInt64RawArgument"; - case mdTypeULong: - case mdTypeUInt64: - return "TryFastConvertHermesGeneratedUInt64RawArgument"; - case mdTypeFloat: - return "TryFastConvertHermesGeneratedFloatRawArgument"; - case mdTypeDouble: - return "TryFastConvertHermesGeneratedDoubleRawArgument"; - default: - return nullptr; - } -} -void writeHermesFrameArgConversion(std::ostringstream& out, - const MDTypeInfo* type, size_t index) { - if (type == nullptr) { - out << " return false;\n"; - return; - } - - if (const char* rawConverter = hermesFrameRawConverterForKind(type->kind)) { - out << " if (!" << rawConverter << "(argRaw" << index << ", &arg" - << index << ")) {\n"; - out << " napi_value argValue" << index - << " = hermesDispatchFrameArg(argsBase, " << index << ");\n"; - out << " bool ignoredShouldFree = false;\n"; - out << " bool ignoredShouldFreeAny = false;\n"; - out << " cif->argTypes[" << index << "]->toNative(env, argValue" - << index << ", &arg" << index - << ", &ignoredShouldFree, &ignoredShouldFreeAny);\n"; - out << " }\n"; - return; - } - - writeEngineDirectArgConversion( - out, type, index, "argValue" + std::to_string(index)); -} -void writeHermesDirectReturnWrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature) { - if (!canUseHermesDirectReturnWrapper( - kind, signature, HermesDirectReturnCallSite::FastCallback)) { - return; - } - - std::string returnType; - if (!mapTypeToCpp(signature->returnType, &returnType, true)) { - return; - } - - std::vector argTypeInfos; - std::vector argTypes; - argTypes.reserve(signature->arguments.size()); - argTypeInfos.reserve(signature->arguments.size()); - for (const auto* arg : signature->arguments) { - std::string argType; - if (!mapTypeToCpp(arg, &argType, false)) { - return; - } - argTypeInfos.push_back(arg); - argTypes.push_back(argType); - } - - out << "static inline bool " << wrapperName - << "(napi_env env, Cif* cif, void* fnptr, "; - if (kind == DispatchKind::ObjCMethod) { - out << "id self, SEL selector, " - "const HermesObjCReturnContext* returnContext, "; - } - out << "const napi_value* argv, napi_value* result) {\n"; - - out << " using Fn = " << returnType << " (*)("; - bool first = true; - if (kind == DispatchKind::ObjCMethod) { - out << "id, SEL"; - first = false; - } - for (const auto& argType : argTypes) { - if (!first) { - out << ", "; - } - out << argType; - first = false; - } - out << ");\n"; - out << " auto fn = reinterpret_cast(fnptr);\n"; - - std::vector cleanupArgIndexes; - cleanupArgIndexes.reserve(argTypes.size()); - for (size_t i = 0; i < argTypes.size(); i++) { - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - cleanupArgIndexes.push_back(i); - } - } - const bool hasCleanupArgs = !cleanupArgIndexes.empty(); - if (hasCleanupArgs) { - out << " bool shouldFreeAny = false;\n"; - } - if (returnType != "void") { - out << " " << returnType << " nativeResult{};\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - out << " " << argTypes[i] << " arg" << i << "{};\n"; - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - out << " bool shouldFree" << i << " = false;\n"; - } - } - - if (hasCleanupArgs) { - out << " auto cleanupManagedArgs = [&]() {\n"; - out << " if (shouldFreeAny) {\n"; - if (kind == DispatchKind::CFunction && returnType != "void") { - out << " void* returnPointerValue = nullptr;\n"; - out << " if (cif->returnType != nullptr && cif->returnType->type == " - "&ffi_type_pointer) {\n"; - out << " returnPointerValue = " - "*reinterpret_cast(&nativeResult);\n"; - out << " }\n"; - } - for (const auto i : cleanupArgIndexes) { - out << " if (shouldFree" << i << ") {\n"; - if (kind == DispatchKind::CFunction && returnType != "void") { - out << " if (returnPointerValue != nullptr && " - "*reinterpret_cast(&arg" - << i << ") == returnPointerValue) {\n"; - out << " } else {\n"; - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - out << " }\n"; - } else { - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - } - out << " }\n"; - } - out << " }\n"; - out << " };\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - writeEngineDirectArgConversion(out, argTypeInfos[i], i, ""); - } - - std::ostringstream callExpr; - callExpr << "fn("; - bool hasAnyCallArg = false; - if (kind == DispatchKind::ObjCMethod) { - callExpr << "self, selector"; - hasAnyCallArg = true; - } - for (size_t i = 0; i < argTypes.size(); i++) { - if (hasAnyCallArg) { - callExpr << ", "; - } - callExpr << "arg" << i; - hasAnyCallArg = true; - } - callExpr << ")"; - - if (returnType == "void") { - out << " " << callExpr.str() << ";\n"; - writeHermesDirectReturnValue(out, kind, signature->returnType->kind, ""); - } else { - out << " nativeResult = " << callExpr.str() << ";\n"; - writeHermesDirectReturnValue(out, kind, signature->returnType->kind, - "nativeResult"); - } - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return true;\n"; - out << "}\n\n"; -} - -void writeHermesFrameDirectReturnWrapper(std::ostringstream& out, - DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature) { - if (!canUseHermesDirectReturnWrapper(kind, signature, - HermesDirectReturnCallSite::Frame)) { - return; - } - - std::string returnType; - if (!mapTypeToCpp(signature->returnType, &returnType, true)) { - return; - } - - std::vector argTypeInfos; - std::vector argTypes; - argTypes.reserve(signature->arguments.size()); - argTypeInfos.reserve(signature->arguments.size()); - for (const auto* arg : signature->arguments) { - std::string argType; - if (!mapTypeToCpp(arg, &argType, false)) { - return; - } - argTypeInfos.push_back(arg); - argTypes.push_back(argType); - } - - out << "static inline bool " << wrapperName - << "(napi_env env, Cif* cif, void* fnptr, "; - if (kind == DispatchKind::ObjCMethod) { - out << "id self, SEL selector, " - "const HermesObjCReturnContext* returnContext, "; - } else if (kind == DispatchKind::BlockInvoke) { - out << "void* block, "; - } - out << "const uint64_t* argsBase, napi_value* result) {\n"; - - out << " using Fn = " << returnType << " (*)("; - bool first = true; - if (kind == DispatchKind::ObjCMethod) { - out << "id, SEL"; - first = false; - } else if (kind == DispatchKind::BlockInvoke) { - out << "void*"; - first = false; - } - for (const auto& argType : argTypes) { - if (!first) { - out << ", "; - } - out << argType; - first = false; - } - out << ");\n"; - out << " auto fn = reinterpret_cast(fnptr);\n"; - - std::vector cleanupArgIndexes; - cleanupArgIndexes.reserve(argTypes.size()); - for (size_t i = 0; i < argTypes.size(); i++) { - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - cleanupArgIndexes.push_back(i); - } - } - const bool hasCleanupArgs = !cleanupArgIndexes.empty(); - if (hasCleanupArgs) { - out << " bool shouldFreeAny = false;\n"; - } - if (returnType != "void") { - out << " " << returnType << " nativeResult{};\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - if (hermesFrameRawConverterForKind(argTypeInfos[i]->kind) != nullptr) { - out << " uint64_t argRaw" << i - << " = hermesDispatchFrameRawArg(argsBase, " << i << ");\n"; - } else { - out << " napi_value argValue" << i - << " = hermesDispatchFrameArg(argsBase, " << i << ");\n"; - } - out << " " << argTypes[i] << " arg" << i << "{};\n"; - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - out << " bool shouldFree" << i << " = false;\n"; - } - } - - if (hasCleanupArgs) { - out << " auto cleanupManagedArgs = [&]() {\n"; - out << " if (shouldFreeAny) {\n"; - if (kind != DispatchKind::ObjCMethod && returnType != "void") { - out << " void* returnPointerValue = nullptr;\n"; - out << " if (cif->returnType != nullptr && cif->returnType->type == " - "&ffi_type_pointer) {\n"; - out << " returnPointerValue = " - "*reinterpret_cast(&nativeResult);\n"; - out << " }\n"; - } - for (const auto i : cleanupArgIndexes) { - out << " if (shouldFree" << i << ") {\n"; - if (kind != DispatchKind::ObjCMethod && returnType != "void") { - out << " if (returnPointerValue != nullptr && " - "*reinterpret_cast(&arg" - << i << ") == returnPointerValue) {\n"; - out << " } else {\n"; - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - out << " }\n"; - } else { - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - } - out << " }\n"; - } - out << " }\n"; - out << " };\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - writeHermesFrameArgConversion(out, argTypeInfos[i], i); - } - - std::ostringstream callExpr; - callExpr << "fn("; - bool hasAnyCallArg = false; - if (kind == DispatchKind::ObjCMethod) { - callExpr << "self, selector"; - hasAnyCallArg = true; - } else if (kind == DispatchKind::BlockInvoke) { - callExpr << "block"; - hasAnyCallArg = true; - } - for (size_t i = 0; i < argTypes.size(); i++) { - if (hasAnyCallArg) { - callExpr << ", "; - } - callExpr << "arg" << i; - hasAnyCallArg = true; - } - callExpr << ")"; - - if (returnType == "void") { - out << " " << callExpr.str() << ";\n"; - writeHermesDirectReturnValue(out, kind, signature->returnType->kind, ""); - } else { - out << " nativeResult = " << callExpr.str() << ";\n"; - writeHermesDirectReturnValue(out, kind, signature->returnType->kind, - "nativeResult"); - } - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return true;\n"; - out << "}\n\n"; -} - -} // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/SignatureDispatchEmitter/Napi.cpp b/metadata-generator/src/SignatureDispatchEmitter/Napi.cpp index efcd6a408..2de2c8efd 100644 --- a/metadata-generator/src/SignatureDispatchEmitter/Napi.cpp +++ b/metadata-generator/src/SignatureDispatchEmitter/Napi.cpp @@ -22,23 +22,6 @@ std::string makeNapiWrapperName(DispatchKind kind, size_t index) { stream << toBase36(index); return stream.str(); } -std::string makePreparedWrapperName(DispatchKind kind, size_t index) { - std::ostringstream stream; - stream << "dp"; - switch (kind) { - case DispatchKind::ObjCMethod: - stream << "o"; - break; - case DispatchKind::CFunction: - stream << "c"; - break; - case DispatchKind::BlockInvoke: - stream << "b"; - break; - } - stream << toBase36(index); - return stream.str(); -} void writeFastNapiArgConversion(std::ostringstream& out, const MDTypeInfo* type, size_t index, bool hasCleanupArgs) { const char* failCleanup = hasCleanupArgs ? " cleanupManagedArgs();\n" : ""; @@ -249,7 +232,7 @@ void writeNapiWrapper(std::ostringstream& out, DispatchKind kind, cleanupArgIndexes.reserve(argTypes.size()); noCleanupManagedArgIndexes.reserve(argTypes.size()); for (size_t i = 0; i < argTypes.size(); i++) { - if (!isFastDirectNapiKind(argTypeInfos[i]->kind)) { + if (!isFastPrimitiveDispatchKind(argTypeInfos[i]->kind)) { if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { cleanupArgIndexes.push_back(i); } else { @@ -271,7 +254,7 @@ void writeNapiWrapper(std::ostringstream& out, DispatchKind kind, for (size_t i = 0; i < argTypes.size(); i++) { out << " " << argTypes[i] << " arg" << i << "{};\n"; - if (!isFastDirectNapiKind(argTypeInfos[i]->kind) && + if (!isFastPrimitiveDispatchKind(argTypeInfos[i]->kind) && argKindMayNeedCleanup(argTypeInfos[i]->kind)) { out << " bool shouldFree" << i << " = false;\n"; } @@ -312,9 +295,9 @@ void writeNapiWrapper(std::ostringstream& out, DispatchKind kind, } for (size_t i = 0; i < argTypes.size(); i++) { - if (isFastDirectNapiKind(argTypeInfos[i]->kind)) { + if (isFastPrimitiveDispatchKind(argTypeInfos[i]->kind)) { writeFastNapiArgConversion(out, argTypeInfos[i], i, hasCleanupArgs); - } else if (isFastManagedNapiKind(argTypeInfos[i]->kind)) { + } else if (isFastReferenceDispatchKind(argTypeInfos[i]->kind)) { out << " if (!TryFastConvertNapiArgument(env, static_cast(" << static_cast(argTypeInfos[i]->kind) << "), argv[" << i << "], &arg" << i << ")) {\n"; @@ -373,56 +356,5 @@ void writeNapiWrapper(std::ostringstream& out, DispatchKind kind, out << " return true;\n"; out << "}\n\n"; } -void writePreparedWrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature) { - if (kind != DispatchKind::BlockInvoke) { - return; - } - - std::string returnType; - if (!mapTypeToCpp(signature->returnType, &returnType, true)) { - return; - } - - std::vector argTypes; - argTypes.reserve(signature->arguments.size()); - for (const auto* arg : signature->arguments) { - std::string argType; - if (!mapTypeToCpp(arg, &argType, false)) { - return; - } - argTypes.push_back(argType); - } - - out << "static inline void " << wrapperName - << "(void* fnptr, void** avalues, void* rvalue) {\n"; - out << " using Fn = " << returnType << " (*)(void*"; - for (const auto& argType : argTypes) { - out << ", " << argType; - } - out << ");\n"; - out << " auto fn = reinterpret_cast(fnptr);\n"; - out << " void* block = *reinterpret_cast(avalues[0]);\n"; - for (size_t i = 0; i < argTypes.size(); i++) { - out << " " << argTypes[i] << " arg" << i << " = *reinterpret_cast<" - << argTypes[i] << "*>(avalues[" << (i + 1) << "]);\n"; - } - - std::ostringstream callExpr; - callExpr << "fn(block"; - for (size_t i = 0; i < argTypes.size(); i++) { - callExpr << ", arg" << i; - } - callExpr << ")"; - - if (returnType == "void") { - out << " " << callExpr.str() << ";\n"; - } else { - out << " *reinterpret_cast<" << returnType - << "*>(rvalue) = " << callExpr.str() << ";\n"; - } - out << "}\n\n"; -} } // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/SignatureDispatchEmitter/Prepared.cpp b/metadata-generator/src/SignatureDispatchEmitter/Prepared.cpp new file mode 100644 index 000000000..adec48e9d --- /dev/null +++ b/metadata-generator/src/SignatureDispatchEmitter/Prepared.cpp @@ -0,0 +1,107 @@ +#include "SignatureDispatchEmitter/Shared.h" + +#include +#include + +namespace metagen::signature_dispatch { + +std::string makePreparedWrapperName(DispatchKind kind, size_t index) { + std::ostringstream stream; + stream << "dp"; + switch (kind) { + case DispatchKind::ObjCMethod: + stream << "o"; + break; + case DispatchKind::CFunction: + stream << "c"; + break; + case DispatchKind::BlockInvoke: + stream << "b"; + break; + } + stream << toBase36(index); + return stream.str(); +} + +void writePreparedWrapper(std::ostringstream& out, DispatchKind kind, + const std::string& wrapperName, + const MDSignature* signature) { + std::string returnType; + if (!mapTypeToCpp(signature->returnType, &returnType, true)) { + return; + } + + std::vector argTypes; + argTypes.reserve(signature->arguments.size()); + for (const auto* arg : signature->arguments) { + std::string argType; + if (!mapTypeToCpp(arg, &argType, false)) { + return; + } + argTypes.push_back(argType); + } + + out << "static inline void " << wrapperName + << "(void* fnptr, void** avalues, void* rvalue) {\n"; + out << " using Fn = " << returnType << " (*)("; + bool first = true; + if (kind == DispatchKind::ObjCMethod) { + out << "id, SEL"; + first = false; + } else if (kind == DispatchKind::BlockInvoke) { + out << "void*"; + first = false; + } + for (const auto& argType : argTypes) { + if (!first) { + out << ", "; + } + out << argType; + first = false; + } + out << ");\n"; + out << " auto fn = reinterpret_cast(fnptr);\n"; + size_t implicitArgumentCount = 0; + if (kind == DispatchKind::ObjCMethod) { + out << " id self = *reinterpret_cast(avalues[0]);\n"; + out << " SEL selector = *reinterpret_cast(avalues[1]);\n"; + implicitArgumentCount = 2; + } else if (kind == DispatchKind::BlockInvoke) { + out << " void* block = *reinterpret_cast(avalues[0]);\n"; + implicitArgumentCount = 1; + } + for (size_t i = 0; i < argTypes.size(); i++) { + out << " " << argTypes[i] << " arg" << i << " = *reinterpret_cast<" + << argTypes[i] << "*>(avalues[" << (i + implicitArgumentCount) + << "]);\n"; + } + + std::ostringstream callExpr; + callExpr << "fn("; + bool hasAnyCallArg = false; + if (kind == DispatchKind::ObjCMethod) { + callExpr << "self, selector"; + hasAnyCallArg = true; + } else if (kind == DispatchKind::BlockInvoke) { + callExpr << "block"; + hasAnyCallArg = true; + } + for (size_t i = 0; i < argTypes.size(); i++) { + if (hasAnyCallArg) { + callExpr << ", "; + } + callExpr << "arg" << i; + hasAnyCallArg = true; + } + callExpr << ")"; + + if (returnType == "void") { + out << " " << callExpr.str() << ";\n"; + } else { + out << " *reinterpret_cast<" << returnType + << "*>(rvalue) = " << callExpr.str() << ";\n"; + } + out << "}\n\n"; +} + +} // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/SignatureDispatchEmitter/Shared.cpp b/metadata-generator/src/SignatureDispatchEmitter/Shared.cpp index 86d38637b..c4233cff6 100644 --- a/metadata-generator/src/SignatureDispatchEmitter/Shared.cpp +++ b/metadata-generator/src/SignatureDispatchEmitter/Shared.cpp @@ -364,7 +364,7 @@ std::string toBase36(size_t value) { return stream.str(); } -bool isFastDirectNapiKind(MDTypeKind kind) { +bool isFastPrimitiveDispatchKind(MDTypeKind kind) { switch (kind) { case mdTypeBool: case mdTypeChar: @@ -386,7 +386,7 @@ bool isFastDirectNapiKind(MDTypeKind kind) { } } -bool isFastManagedNapiKind(MDTypeKind kind) { +bool isFastReferenceDispatchKind(MDTypeKind kind) { switch (kind) { case mdTypeAnyObject: case mdTypeProtocolObject: @@ -413,7 +413,7 @@ bool argKindMayNeedCleanup(MDTypeKind kind) { case mdTypeSelector: return false; default: - return !isFastDirectNapiKind(kind); + return !isFastPrimitiveDispatchKind(kind); } } @@ -436,9 +436,9 @@ std::string makeWrapperShapeKey(DispatchKind kind, return {}; } - if (isFastDirectNapiKind(arg->kind)) { + if (isFastPrimitiveDispatchKind(arg->kind)) { key << "F" << static_cast(arg->kind); - } else if (isFastManagedNapiKind(arg->kind)) { + } else if (isFastReferenceDispatchKind(arg->kind)) { key << "H" << static_cast(arg->kind); } else { key << "M" << argType; diff --git a/metadata-generator/src/SignatureDispatchEmitter/Shared.h b/metadata-generator/src/SignatureDispatchEmitter/Shared.h index 873723d74..9af96eda3 100644 --- a/metadata-generator/src/SignatureDispatchEmitter/Shared.h +++ b/metadata-generator/src/SignatureDispatchEmitter/Shared.h @@ -25,11 +25,6 @@ struct SignatureUse { uint8_t flags; }; -enum class HermesDirectReturnCallSite { - FastCallback, - Frame, -}; - using SignatureMap = std::unordered_map; uint64_t composeDispatchId(uint64_t signatureHash, DispatchKind kind, @@ -40,8 +35,8 @@ uint64_t signatureHash(const MDSignature* signature, std::string* canonicalKeyOut); bool mapTypeToCpp(const MDTypeInfo* type, std::string* out, bool allowVoid); bool isSignatureSupported(const MDSignature* signature); -bool isFastDirectNapiKind(MDTypeKind kind); -bool isFastManagedNapiKind(MDTypeKind kind); +bool isFastPrimitiveDispatchKind(MDTypeKind kind); +bool isFastReferenceDispatchKind(MDTypeKind kind); bool argKindMayNeedCleanup(MDTypeKind kind); std::string toHexLiteral(uint64_t value); std::string toBase36(size_t value); @@ -56,41 +51,16 @@ void collectMethodUses(const std::vector& members, std::string makeNapiWrapperName(DispatchKind kind, size_t index); std::string makePreparedWrapperName(DispatchKind kind, size_t index); +std::string makeGsdWrapperName(size_t index); void writeNapiWrapper(std::ostringstream& out, DispatchKind kind, const std::string& wrapperName, const MDSignature* signature); void writePreparedWrapper(std::ostringstream& out, DispatchKind kind, const std::string& wrapperName, const MDSignature* signature); - -std::string makeEngineDirectWrapperName(DispatchKind kind, size_t index); -void writeEngineDirectArgConversion(std::ostringstream& out, - const MDTypeInfo* type, size_t index, - const std::string& valueExpr); -void writeEngineDirectConverterMacros(std::ostringstream& out); -void writeHermesEngineDirectConverterMacros(std::ostringstream& out); -void writeEngineDirectConverterUndefs(std::ostringstream& out); -void writeEngineDirectWrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature); - -std::string makeHermesDirectReturnWrapperName(DispatchKind kind, size_t index); -std::string makeHermesFrameDirectReturnWrapperName(DispatchKind kind, - size_t index); -bool canUseHermesDirectReturnWrapper(DispatchKind kind, - const MDSignature* signature, - HermesDirectReturnCallSite callSite); -void writeHermesDirectReturnWrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature); -void writeHermesFrameDirectReturnWrapper(std::ostringstream& out, - DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature); - -std::string makeV8WrapperName(DispatchKind kind, size_t index); -void writeV8Wrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature); +void writeGsdWrapper(std::ostringstream& out, const std::string& wrapperName, + const MDSignature* signature); +bool isGsdSignatureSupported(const MDSignature* signature); +std::string makeGsdWrapperShapeKey(const MDSignature* signature); } // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/SignatureDispatchEmitter/V8.cpp b/metadata-generator/src/SignatureDispatchEmitter/V8.cpp deleted file mode 100644 index 544cd6c64..000000000 --- a/metadata-generator/src/SignatureDispatchEmitter/V8.cpp +++ /dev/null @@ -1,500 +0,0 @@ -#include "SignatureDispatchEmitter/Shared.h" - -#include -#include - -namespace metagen::signature_dispatch { - -std::string makeV8WrapperName(DispatchKind kind, size_t index) { - std::ostringstream stream; - stream << "dv"; - switch (kind) { - case DispatchKind::ObjCMethod: - stream << "o"; - break; - case DispatchKind::CFunction: - stream << "c"; - break; - case DispatchKind::BlockInvoke: - stream << "b"; - break; - } - stream << toBase36(index); - return stream.str(); -} - -bool fastV8ArgConversionNeedsContext(MDTypeKind kind) { - switch (kind) { - case mdTypeChar: - case mdTypeUChar: - case mdTypeUInt8: - case mdTypeSShort: - case mdTypeSInt: - case mdTypeUInt: - case mdTypeSLong: - case mdTypeULong: - case mdTypeSInt64: - case mdTypeUInt64: - case mdTypeFloat: - case mdTypeDouble: - return true; - default: - return false; - } -} - -bool canSetV8ReturnDirectly(MDTypeKind kind) { - switch (kind) { - case mdTypeVoid: - case mdTypeBool: - case mdTypeChar: - case mdTypeUChar: - case mdTypeUInt8: - case mdTypeSShort: - case mdTypeUShort: - case mdTypeSInt: - case mdTypeUInt: - case mdTypeSLong: - case mdTypeULong: - case mdTypeSInt64: - case mdTypeUInt64: - case mdTypeFloat: - case mdTypeDouble: - return true; - default: - return false; - } -} - -bool canTrySetV8ObjectReturnDirectly(MDTypeKind kind) { - switch (kind) { - case mdTypeAnyObject: - case mdTypeProtocolObject: - case mdTypeClassObject: - case mdTypeInstanceObject: - case mdTypeNSStringObject: - case mdTypeNSMutableStringObject: - return true; - default: - return false; - } -} - -void writeV8DirectReturnValue(std::ostringstream& out, MDTypeKind kind, - const std::string& valueExpr) { - switch (kind) { - case mdTypeBool: - out << " info.GetReturnValue().Set(" << valueExpr << " != 0);\n"; - break; - case mdTypeChar: - case mdTypeSShort: - case mdTypeSInt: - out << " info.GetReturnValue().Set(static_cast(" << valueExpr - << "));\n"; - break; - case mdTypeUChar: - case mdTypeUInt8: - case mdTypeUInt: - out << " info.GetReturnValue().Set(static_cast(" << valueExpr - << "));\n"; - break; - case mdTypeUShort: - out << " setV8DispatchUInt16ReturnValue(info.GetIsolate(), info, " - << "static_cast(" << valueExpr << "));\n"; - break; - case mdTypeSLong: - case mdTypeSInt64: - out << " setV8DispatchInt64ReturnValue(info.GetIsolate(), info, " - << valueExpr << ");\n"; - break; - case mdTypeULong: - case mdTypeUInt64: - out << " setV8DispatchUInt64ReturnValue(info.GetIsolate(), info, " - << valueExpr << ");\n"; - break; - case mdTypeFloat: - case mdTypeDouble: - out << " info.GetReturnValue().Set(static_cast(" << valueExpr - << "));\n"; - break; - default: - break; - } -} - -void writeFastV8ArgConversion(std::ostringstream& out, const MDTypeInfo* type, - size_t index, bool hasCleanupArgs) { - const char* failCleanup = hasCleanupArgs ? " cleanupManagedArgs();\n" : ""; - if (type == nullptr) { - out << failCleanup; - out << " return false;\n"; - return; - } - - switch (type->kind) { - case mdTypeChar: { - out << " int32_t tmpArg" << index << " = 0;\n"; - out << " if (!info[" << index << "]->Int32Value(context).To(&tmpArg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " arg" << index << " = static_cast(tmpArg" << index - << ");\n"; - break; - } - case mdTypeUChar: - case mdTypeUInt8: { - out << " uint32_t tmpArg" << index << " = 0;\n"; - out << " if (!info[" << index << "]->Uint32Value(context).To(&tmpArg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " arg" << index << " = static_cast(tmpArg" << index - << ");\n"; - break; - } - case mdTypeSShort: { - out << " int32_t tmpArg" << index << " = 0;\n"; - out << " if (!info[" << index << "]->Int32Value(context).To(&tmpArg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " arg" << index << " = static_cast(tmpArg" << index - << ");\n"; - break; - } - case mdTypeUShort: { - out << " if (!TryFastConvertV8UInt16Argument(env, info[" << index - << "], &arg" << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - break; - } - case mdTypeSInt: { - out << " if (!info[" << index << "]->Int32Value(context).To(&arg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - break; - } - case mdTypeUInt: { - out << " if (!info[" << index << "]->Uint32Value(context).To(&arg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - break; - } - case mdTypeSLong: - case mdTypeSInt64: { - out << " if (info[" << index << "]->IsBigInt()) {\n"; - out << " bool lossless" << index << " = false;\n"; - out << " arg" << index << " = info[" << index - << "].As()->Int64Value(&lossless" << index << ");\n"; - out << " } else if (!info[" << index - << "]->IntegerValue(context).To(&arg" << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - break; - } - case mdTypeULong: - case mdTypeUInt64: { - out << " if (info[" << index << "]->IsBigInt()) {\n"; - out << " bool lossless" << index << " = false;\n"; - out << " arg" << index << " = info[" << index - << "].As()->Uint64Value(&lossless" << index << ");\n"; - out << " } else {\n"; - out << " int64_t signedValue" << index << " = 0;\n"; - out << " if (!info[" << index - << "]->IntegerValue(context).To(&signedValue" << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " arg" << index << " = static_cast(signedValue" - << index << ");\n"; - out << " }\n"; - break; - } - case mdTypeFloat: { - out << " double tmpArg" << index << " = 0.0;\n"; - out << " if (!info[" << index << "]->NumberValue(context).To(&tmpArg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " arg" << index << " = static_cast(tmpArg" << index - << ");\n"; - break; - } - case mdTypeDouble: { - out << " if (!info[" << index << "]->NumberValue(context).To(&arg" - << index << ")) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " if (std::isnan(arg" << index << ") || std::isinf(arg" << index - << ")) {\n"; - out << " arg" << index << " = 0.0;\n"; - out << " }\n"; - break; - } - case mdTypeBool: { - out << " if (!info[" << index << "]->IsBoolean()) {\n"; - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - out << " return false;\n"; - out << " }\n"; - out << " arg" << index << " = static_cast(info[" << index - << "]->BooleanValue(info.GetIsolate()) ? 1 : 0);\n"; - break; - } - default: - out << failCleanup; - out << " return false;\n"; - break; - } -} -void writeV8Wrapper(std::ostringstream& out, DispatchKind kind, - const std::string& wrapperName, - const MDSignature* signature) { - if (kind == DispatchKind::BlockInvoke) { - return; - } - - std::string returnType; - if (!mapTypeToCpp(signature->returnType, &returnType, true)) { - return; - } - - std::vector argTypeInfos; - std::vector argTypes; - argTypes.reserve(signature->arguments.size()); - argTypeInfos.reserve(signature->arguments.size()); - for (const auto* arg : signature->arguments) { - std::string argType; - if (!mapTypeToCpp(arg, &argType, false)) { - return; - } - argTypeInfos.push_back(arg); - argTypes.push_back(argType); - } - - out << "static inline bool " << wrapperName - << "(napi_env env, Cif* cif, void* fnptr, "; - if (kind == DispatchKind::ObjCMethod) { - out << "id self, SEL selector, void* bridgeState, bool returnOwned, " - "bool receiverIsClass, bool propertyAccess, "; - } - out << "const v8::FunctionCallbackInfo& info, void* rvalue, " - "bool* didSetReturnValue) {\n"; - if (!argTypes.empty()) { - out << " if (info.Length() < " << argTypes.size() << ") {\n"; - out << " return false;\n"; - out << " }\n"; - } - bool needsContext = false; - for (const auto* arg : argTypeInfos) { - if (arg != nullptr && fastV8ArgConversionNeedsContext(arg->kind)) { - needsContext = true; - break; - } - } - if (needsContext) { - out << " v8::Local context = info.GetIsolate()->GetCurrentContext();\n"; - } - - out << " using Fn = " << returnType << " (*)("; - bool first = true; - if (kind == DispatchKind::ObjCMethod) { - out << "id, SEL"; - first = false; - } - for (const auto& argType : argTypes) { - if (!first) { - out << ", "; - } - out << argType; - first = false; - } - out << ");\n"; - out << " auto fn = reinterpret_cast(fnptr);\n"; - - std::vector cleanupArgIndexes; - std::vector noCleanupManagedArgIndexes; - cleanupArgIndexes.reserve(argTypes.size()); - noCleanupManagedArgIndexes.reserve(argTypes.size()); - for (size_t i = 0; i < argTypes.size(); i++) { - if (!isFastDirectNapiKind(argTypeInfos[i]->kind)) { - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - cleanupArgIndexes.push_back(i); - } else { - noCleanupManagedArgIndexes.push_back(i); - } - } - } - const bool hasCleanupArgs = !cleanupArgIndexes.empty(); - const bool setsReturnDirectly = - canSetV8ReturnDirectly(signature->returnType->kind); - const bool triesObjectReturnDirectly = - kind == DispatchKind::ObjCMethod && - canTrySetV8ObjectReturnDirectly(signature->returnType->kind); - if (hasCleanupArgs) { - out << " bool shouldFreeAny = false;\n"; - } - if (!noCleanupManagedArgIndexes.empty()) { - out << " bool ignoredShouldFree = false;\n"; - out << " bool ignoredShouldFreeAny = false;\n"; - } - if (returnType != "void") { - out << " " << returnType << " nativeResult{};\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - out << " " << argTypes[i] << " arg" << i << "{};\n"; - if (!isFastDirectNapiKind(argTypeInfos[i]->kind) && - argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - out << " bool shouldFree" << i << " = false;\n"; - } - } - - if (hasCleanupArgs) { - out << " auto cleanupManagedArgs = [&]() {\n"; - out << " if (shouldFreeAny) {\n"; - if (kind == DispatchKind::CFunction && returnType != "void") { - out << " void* returnPointerValue = nullptr;\n"; - out << " if (cif->returnType != nullptr && cif->returnType->type == " - "&ffi_type_pointer) {\n"; - out << " returnPointerValue = " - "*reinterpret_cast(&nativeResult);\n"; - out << " }\n"; - } - for (const auto i : cleanupArgIndexes) { - out << " if (shouldFree" << i << ") {\n"; - if (kind == DispatchKind::CFunction && returnType != "void") { - out << " if (returnPointerValue != nullptr && " - "*reinterpret_cast(&arg" - << i << ") == returnPointerValue) {\n"; - out << " } else {\n"; - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - out << " }\n"; - } else { - out << " cif->argTypes[" << i - << "]->free(env, *reinterpret_cast(&arg" << i << "));\n"; - } - out << " }\n"; - } - out << " }\n"; - out << " };\n"; - } - - for (size_t i = 0; i < argTypes.size(); i++) { - if (isFastDirectNapiKind(argTypeInfos[i]->kind)) { - writeFastV8ArgConversion(out, argTypeInfos[i], i, hasCleanupArgs); - } else if (isFastManagedNapiKind(argTypeInfos[i]->kind)) { - out << " if (!TryFastConvertV8Argument(env, static_cast(" - << static_cast(argTypeInfos[i]->kind) << "), info[" << i - << "], &arg" << i << ")) {\n"; - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - out << " cif->argTypes[" << i - << "]->toNative(env, v8LocalValueToNapiValue(info[" << i - << "]), &arg" << i << ", &shouldFree" << i - << ", &shouldFreeAny);\n"; - } else { - out << " ignoredShouldFree = false;\n"; - out << " ignoredShouldFreeAny = false;\n"; - out << " cif->argTypes[" << i - << "]->toNative(env, v8LocalValueToNapiValue(info[" << i - << "]), &arg" << i - << ", &ignoredShouldFree, &ignoredShouldFreeAny);\n"; - } - out << " }\n"; - } else { - if (argKindMayNeedCleanup(argTypeInfos[i]->kind)) { - out << " cif->argTypes[" << i - << "]->toNative(env, v8LocalValueToNapiValue(info[" << i - << "]), &arg" << i << ", &shouldFree" << i - << ", &shouldFreeAny);\n"; - } else { - out << " ignoredShouldFree = false;\n"; - out << " ignoredShouldFreeAny = false;\n"; - out << " cif->argTypes[" << i - << "]->toNative(env, v8LocalValueToNapiValue(info[" << i - << "]), &arg" << i - << ", &ignoredShouldFree, &ignoredShouldFreeAny);\n"; - } - } - } - - std::ostringstream callExpr; - callExpr << "fn("; - bool hasAnyCallArg = false; - if (kind == DispatchKind::ObjCMethod) { - callExpr << "self, selector"; - hasAnyCallArg = true; - } - for (size_t i = 0; i < argTypes.size(); i++) { - if (hasAnyCallArg) { - callExpr << ", "; - } - callExpr << "arg" << i; - hasAnyCallArg = true; - } - callExpr << ")"; - - if (returnType == "void") { - out << " " << callExpr.str() << ";\n"; - out << " *didSetReturnValue = true;\n"; - } else if (setsReturnDirectly) { - out << " nativeResult = " << callExpr.str() << ";\n"; - writeV8DirectReturnValue(out, signature->returnType->kind, "nativeResult"); - out << " *didSetReturnValue = true;\n"; - } else if (triesObjectReturnDirectly) { - out << " nativeResult = " << callExpr.str() << ";\n"; - out << " *reinterpret_cast<" << returnType - << "*>(rvalue) = nativeResult;\n"; - out << " if (TryFastSetV8GeneratedObjCObjectReturnValue(env, info, cif, bridgeState, self, " - "selector, nativeResult, returnOwned, receiverIsClass, propertyAccess)) {\n"; - out << " *didSetReturnValue = true;\n"; - out << " }\n"; - } else { - out << " nativeResult = " << callExpr.str() << ";\n"; - out << " *reinterpret_cast<" << returnType - << "*>(rvalue) = nativeResult;\n"; - } - if (hasCleanupArgs) { - out << " cleanupManagedArgs();\n"; - } - - out << " return true;\n"; - out << "}\n\n"; -} - -} // namespace metagen::signature_dispatch diff --git a/metadata-generator/src/TSEmitter/TypeSpec.cpp b/metadata-generator/src/TSEmitter/TypeSpec.cpp index ad65bbb62..3dcdee653 100644 --- a/metadata-generator/src/TSEmitter/TypeSpec.cpp +++ b/metadata-generator/src/TSEmitter/TypeSpec.cpp @@ -66,7 +66,8 @@ std::string TSFile::typeToString(const TypeSpec& type, bool isStatic, bool isRet result += ") => "; result += typeToString(*type.callbackReturn, false, true); if (type.isNullable) { - result += " | null"; + // `() => T | null` would make the return type nullable instead. + result = "(" + result + ") | null"; } break; } diff --git a/package.json b/package.json index a43a29705..52afbad9b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios" + "url": "git+https://github.com/NativeScript/runtimes.git" }, "author": { "name": "NativeScript Team", @@ -18,7 +18,7 @@ }, "workspaces": [ "packages/*", - "examples", + "platforms/apple/examples", "scripts" ], "scripts": { @@ -27,14 +27,18 @@ "build-vision": "./scripts/build_all_vision.sh", "build-visionos": "./scripts/build_all_vision.sh", "build-node-api": "./scripts/build_all_react_native.sh", + "build-android": "./scripts/build_all_android.sh", "build-rn-turbomodule": "./scripts/build_react_native_turbomodule.sh", "check:ffi-boundaries": "./scripts/check_ffi_boundaries.sh", + "check:jsi-neutral": "./scripts/check_jsi_layer_neutral.sh", "test-rn-turbomodule": "./scripts/test_react_native_turbomodule.sh", "test-rn-ffi": "./scripts/test_react_native_ffi_compat.sh", "demo-rn-turbomodule": "./scripts/create_react_native_demo.sh", "pack:ios": "./scripts/build_npm_ios.sh", "pack:macos": "./scripts/build_npm_macos.sh", "pack:visionos": "./scripts/build_npm_vision.sh", + "patch-quickjs": "node ./scripts/apply_quickjs_patches.js", + "setup-android": "./scripts/setup_android.sh", "setup-ci": "./scripts/build_metadata_generator.sh", "update-version": "./scripts/update_version.sh ios", "update-version:ios": "./scripts/update_version.sh ios", @@ -54,7 +58,7 @@ "nsr": "./dist/nsr", "test:macos": "node ./scripts/run-tests-macos.js ./build/test-results/macos-junit.xml", "test:ios": "node ./scripts/run-tests-ios.js", - "benchmark:objc-dispatch": "node ./benchmarks/objc-dispatch/run.js" + "benchmark:objc-dispatch": "node ./platforms/apple/benchmarks/objc-dispatch/run.js" }, "license": "Apache-2.0", "devDependencies": { @@ -65,7 +69,7 @@ "semver": "^7.5.0" }, "lint-staged": { - "{NativeScript,metadata-generator/src,test/runtime/fixtures}/**/*.{h,hpp,c,cpp,mm,m}": [ + "{NativeScript,metadata-generator/src,platforms/apple/test/runtime/fixtures}/**/*.{h,hpp,c,cpp,mm,m}": [ "clang-format -i" ] } diff --git a/packages/android-hermes/LICENSE b/packages/android-hermes/LICENSE new file mode 100755 index 000000000..451412229 --- /dev/null +++ b/packages/android-hermes/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2020, nStudio, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/android-hermes/README.md b/packages/android-hermes/README.md new file mode 100644 index 000000000..7cd56d66a --- /dev/null +++ b/packages/android-hermes/README.md @@ -0,0 +1,15 @@ +# @nativescript/android-hermes + +NativeScript runtime package for Android, built with the Hermes JavaScript engine. + +## Build + +From the repo root: + +```sh +npm run build-android -- --engine=HERMES +``` + +The staged package lands in `dist/android_hermes_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_hermes_jsi/`). diff --git a/packages/android-hermes/package.json b/packages/android-hermes/package.json new file mode 100644 index 000000000..2bc9be6ae --- /dev/null +++ b/packages/android-hermes/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nativescript/android-hermes", + "version": "0.0.1", + "description": "NativeScript Runtime for Android (Hermes engine)", + "keywords": [ + "NativeScript", + "Android", + "runtime", + "hermes" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/NativeScript/runtimes.git", + "directory": "packages/android-hermes" + }, + "author": { + "name": "NativeScript Team", + "email": "oss@nativescript.org" + }, + "license": "Apache-2.0" +} diff --git a/packages/android-jsc/LICENSE b/packages/android-jsc/LICENSE new file mode 100755 index 000000000..451412229 --- /dev/null +++ b/packages/android-jsc/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2020, nStudio, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/android-jsc/README.md b/packages/android-jsc/README.md new file mode 100644 index 000000000..699053d91 --- /dev/null +++ b/packages/android-jsc/README.md @@ -0,0 +1,15 @@ +# @nativescript/android-jsc + +NativeScript runtime package for Android, built with the JavaScriptCore engine. + +## Build + +From the repo root: + +```sh +npm run build-android -- --engine=JSC +``` + +The staged package lands in `dist/android_jsc_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_jsc_jsi/`). diff --git a/packages/android-jsc/package.json b/packages/android-jsc/package.json new file mode 100644 index 000000000..3c8800d16 --- /dev/null +++ b/packages/android-jsc/package.json @@ -0,0 +1,22 @@ +{ + "name": "@nativescript/android-jsc", + "version": "0.0.1", + "description": "NativeScript Runtime for Android (JavaScriptCore engine)", + "keywords": [ + "NativeScript", + "Android", + "runtime", + "jsc", + "javascriptcore" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/NativeScript/runtimes.git", + "directory": "packages/android-jsc" + }, + "author": { + "name": "NativeScript Team", + "email": "oss@nativescript.org" + }, + "license": "Apache-2.0" +} diff --git a/packages/android-primjs/LICENSE b/packages/android-primjs/LICENSE new file mode 100755 index 000000000..451412229 --- /dev/null +++ b/packages/android-primjs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2020, nStudio, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/android-primjs/README.md b/packages/android-primjs/README.md new file mode 100644 index 000000000..edc986cbb --- /dev/null +++ b/packages/android-primjs/README.md @@ -0,0 +1,15 @@ +# @nativescript/android-primjs + +NativeScript runtime package for Android, built with the PrimJS JavaScript engine. + +## Build + +From the repo root: + +```sh +npm run build-android -- --engine=PRIMJS +``` + +The staged package lands in `dist/android_primjs_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_primjs_jsi/`). diff --git a/packages/android-primjs/package.json b/packages/android-primjs/package.json new file mode 100644 index 000000000..68835c2dd --- /dev/null +++ b/packages/android-primjs/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nativescript/android-primjs", + "version": "0.0.1", + "description": "NativeScript Runtime for Android (PrimJS engine)", + "keywords": [ + "NativeScript", + "Android", + "runtime", + "primjs" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/NativeScript/runtimes.git", + "directory": "packages/android-primjs" + }, + "author": { + "name": "NativeScript Team", + "email": "oss@nativescript.org" + }, + "license": "Apache-2.0" +} diff --git a/packages/android-quickjs-ng/LICENSE b/packages/android-quickjs-ng/LICENSE new file mode 100755 index 000000000..451412229 --- /dev/null +++ b/packages/android-quickjs-ng/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2020, nStudio, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/android-quickjs-ng/README.md b/packages/android-quickjs-ng/README.md new file mode 100644 index 000000000..d27086dd2 --- /dev/null +++ b/packages/android-quickjs-ng/README.md @@ -0,0 +1,15 @@ +# @nativescript/android-quickjs-ng + +NativeScript runtime package for Android, built with the QuickJS-ng JavaScript engine. + +## Build + +From the repo root: + +```sh +npm run build-android -- --engine=QUICKJS_NG +``` + +The staged package lands in `dist/android_quickjs_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_quickjs_jsi/`). diff --git a/packages/android-quickjs-ng/package.json b/packages/android-quickjs-ng/package.json new file mode 100644 index 000000000..fa9bd7af4 --- /dev/null +++ b/packages/android-quickjs-ng/package.json @@ -0,0 +1,22 @@ +{ + "name": "@nativescript/android-quickjs-ng", + "version": "0.0.1", + "description": "NativeScript Runtime for Android (QuickJS-ng engine)", + "keywords": [ + "NativeScript", + "Android", + "runtime", + "quickjs", + "quickjs-ng" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/NativeScript/runtimes.git", + "directory": "packages/android-quickjs-ng" + }, + "author": { + "name": "NativeScript Team", + "email": "oss@nativescript.org" + }, + "license": "Apache-2.0" +} diff --git a/packages/android-v8/LICENSE b/packages/android-v8/LICENSE new file mode 100755 index 000000000..451412229 --- /dev/null +++ b/packages/android-v8/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2020, nStudio, LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/android-v8/README.md b/packages/android-v8/README.md new file mode 100644 index 000000000..ae37c3ef1 --- /dev/null +++ b/packages/android-v8/README.md @@ -0,0 +1,15 @@ +# @nativescript/android-v8 + +NativeScript runtime package for Android, built with the V8 JavaScript engine. + +## Build + +From the repo root: + +```sh +npm run build-android -- --engine=V8-13 +``` + +The staged package lands in `dist/android_v8_13_napi/` and the tarball in +`build/npm-tarballs/`. Add `--binding=jsi` for the jsi runtime tree +(`dist/android_v8_13_jsi/`). diff --git a/packages/android-v8/package.json b/packages/android-v8/package.json new file mode 100644 index 000000000..0850d864c --- /dev/null +++ b/packages/android-v8/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nativescript/android-v8", + "version": "0.0.1", + "description": "NativeScript Runtime for Android (V8 engine)", + "keywords": [ + "NativeScript", + "Android", + "runtime", + "v8" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/NativeScript/runtimes.git", + "directory": "packages/android-v8" + }, + "author": { + "name": "NativeScript Team", + "email": "oss@nativescript.org" + }, + "license": "Apache-2.0" +} diff --git a/packages/ios-hermes/package.json b/packages/ios-hermes/package.json index e4a7bc3b3..b08a1c868 100644 --- a/packages/ios-hermes/package.json +++ b/packages/ios-hermes/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-hermes" }, "author": { @@ -18,7 +18,7 @@ "email": "oss@nativescript.org" }, "scripts": { - "build": "cd ../.. && ./scripts/build_all_ios.sh --hermes" + "build": "cd ../.. && IOS_VARIANT=ios-hermes ./scripts/build_all_ios.sh --hermes" }, "files": [ "framework" diff --git a/packages/ios-jsc/package.json b/packages/ios-jsc/package.json index 4966ca511..8d2cb1c17 100644 --- a/packages/ios-jsc/package.json +++ b/packages/ios-jsc/package.json @@ -11,7 +11,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-jsc" }, "author": { @@ -19,7 +19,7 @@ "email": "oss@nativescript.org" }, "scripts": { - "build": "cd ../.. && ./scripts/build_all_ios.sh --jsc" + "build": "cd ../.. && IOS_VARIANT=ios-jsc ./scripts/build_all_ios.sh --jsc" }, "files": [ "framework" diff --git a/packages/ios-node-api/package.json b/packages/ios-node-api/package.json index 5984a78d8..89dd746da 100644 --- a/packages/ios-node-api/package.json +++ b/packages/ios-node-api/package.json @@ -4,7 +4,7 @@ "description": "An embeddable, engine-agnostic NativeScript runtime for iOS based on Node-API", "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-node-api" }, "typings": "index.d.ts", diff --git a/packages/ios-quickjs/package.json b/packages/ios-quickjs/package.json index 757d1668a..958eb380b 100644 --- a/packages/ios-quickjs/package.json +++ b/packages/ios-quickjs/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-quickjs" }, "author": { @@ -18,7 +18,7 @@ "email": "oss@nativescript.org" }, "scripts": { - "build": "cd ../.. && ./scripts/build_all_ios.sh --quickjs" + "build": "cd ../.. && IOS_VARIANT=ios-quickjs ./scripts/build_all_ios.sh --quickjs" }, "files": [ "framework" diff --git a/packages/ios-v8/package.json b/packages/ios-v8/package.json index 198236be6..ce971ec85 100644 --- a/packages/ios-v8/package.json +++ b/packages/ios-v8/package.json @@ -10,7 +10,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios-v8" }, "author": { @@ -18,7 +18,7 @@ "email": "oss@nativescript.org" }, "scripts": { - "build": "cd ../.. && ./scripts/build_all_ios.sh --v8" + "build": "cd ../.. && IOS_VARIANT=ios-v8 ./scripts/build_all_ios.sh --v8" }, "files": [ "framework" diff --git a/packages/ios/package.json b/packages/ios/package.json index f96f7163d..a5ba0bc5d 100644 --- a/packages/ios/package.json +++ b/packages/ios/package.json @@ -9,7 +9,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/ios" }, "author": { diff --git a/packages/macos-node-api/README.md b/packages/macos-node-api/README.md index ae9c94689..12ed94ccf 100644 --- a/packages/macos-node-api/README.md +++ b/packages/macos-node-api/README.md @@ -10,7 +10,7 @@ npm install @nativescript/macos-node-api ## Usage -See [examples](https://github.com/NativeScript/runtime-node-api/tree/main/examples) in the repo. Best run on Node.js for now. +See [examples](https://github.com/NativeScript/runtime-node-api/tree/main/platforms/apple/examples) in the repo. Best run on Node.js for now. ## Contributing diff --git a/packages/macos-node-api/package.json b/packages/macos-node-api/package.json index 3f715914f..2f8e0e11e 100644 --- a/packages/macos-node-api/package.json +++ b/packages/macos-node-api/package.json @@ -4,7 +4,7 @@ "description": "An embeddable, engine-agnostic NativeScript runtime for macOS based on Node-API", "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/macos-node-api" }, "main": "index.js", @@ -19,7 +19,7 @@ "types" ], "scripts": { - "test": "node ../../examples/foundation.js", + "test": "node ../../platforms/apple/examples/foundation.js", "build": "cd ../.. && ./scripts/build_all_react_native.sh" }, "keywords": [ diff --git a/packages/macos/package.json b/packages/macos/package.json index 1c2a48ad8..331220286 100644 --- a/packages/macos/package.json +++ b/packages/macos/package.json @@ -9,7 +9,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/macos" }, "author": { diff --git a/packages/objc-node-api/index.d.ts b/packages/objc-node-api/index.d.ts index 021f11abb..f6b1e610f 100644 --- a/packages/objc-node-api/index.d.ts +++ b/packages/objc-node-api/index.d.ts @@ -9,6 +9,27 @@ declare global { class NativeObject implements interop.PointerObject { readonly __brand__: interop.PointerObject["__brand__"]; + + /** + * Registers an Objective-C subclass of this class. Functions in `methods` + * override the same-named members of this class and of + * `options.protocols`; other names are only callable from Objective-C + * when listed in `options.exposedMethods`. The returned class cannot + * itself be extended. + */ + static extend< + // deno-lint-ignore no-explicit-any + This extends abstract new (...args: any) => any, + // Inferred rather than annotated so that names absent from the class + // (protocol and exposed methods) are not excess properties. `& object` + // keeps literals sharing no name with the class from failing the weak + // type check against the all-optional Partial. + Methods extends Partial> & object, + >( + this: This, + methods: Methods & ThisType & Methods>, + options?: objc.ExtendOptions, + ): This; } namespace objc { @@ -25,6 +46,19 @@ declare global { ptr: interop.Pointer, size: number, ): ArrayBuffer; + + export interface ExposedMethod { + returns?: unknown; + params?: unknown[]; + } + + export interface ExtendOptions { + /** Objective-C class name; a numeric suffix is added if it is taken. */ + name?: string; + protocols?: unknown[]; + /** Keyed by method name in `methods`. */ + exposedMethods?: Record; + } } namespace interop { diff --git a/packages/objc-node-api/package.json b/packages/objc-node-api/package.json index 58bf92fd1..5fb13925a 100644 --- a/packages/objc-node-api/package.json +++ b/packages/objc-node-api/package.json @@ -4,7 +4,7 @@ "description": "Typings for the common Objective-C runtime APIs used by both @nativescript/ios-node-api and @nativescript/macos-node-api", "repository": { "type": "git", - "url": "https://github.com/NativeScript/runtime-node-api.git", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/objc-node-api" }, "main": "index.js", diff --git a/packages/react-native/NativeScriptNativeApi.podspec b/packages/react-native/NativeScriptNativeApi.podspec index d65543a7c..56a5a7c79 100644 --- a/packages/react-native/NativeScriptNativeApi.podspec +++ b/packages/react-native/NativeScriptNativeApi.podspec @@ -18,7 +18,9 @@ Pod::Spec.new do |s| s.source_files = [ "ios/**/*.{h,mm}", - "native-api-jsi/**/*.{h,mm}" + "native-api/ffi/objc/hermes/**/*.h", + "native-api/ffi/objc/shared/**/*.h", + "native-api/ffi/objc/hermes/NativeApiJsi.mm" ] s.exclude_files = "ios/Fabric/**/*" unless fabric_enabled s.public_header_files = "ios/**/*.h" @@ -31,16 +33,21 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", "CLANG_CXX_LIBRARY" => "libc++", - "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) TARGET_ENGINE_HERMES=1", + # NATIVESCRIPT_REACT_NATIVE tells the shared bridge sources that they are + # being compiled against a React Native-owned jsi::Runtime rather than the + # standalone runtime, so the parts that reach into napi/ (which this package + # does not ship) compile out. See NativeApiJsi.mm. + "GCC_PREPROCESSOR_DEFINITIONS" => "$(inherited) TARGET_ENGINE_HERMES=1 NS_GSD_BACKEND_HERMES=1 NATIVESCRIPT_REACT_NATIVE=1", "HEADER_SEARCH_PATHS" => [ "\"$(PODS_TARGET_SRCROOT)/ios\"", - "\"$(PODS_TARGET_SRCROOT)/native-api-jsi\"", - "\"$(PODS_TARGET_SRCROOT)/native-api-jsi/metadata/include\"", + "\"$(PODS_TARGET_SRCROOT)/native-api\"", + "\"$(PODS_TARGET_SRCROOT)/native-api/metadata/include\"", "\"$(PODS_TARGET_SRCROOT)/ios/vendor/libffi/include\"", "\"$(PODS_ROOT)/Headers/Public/React-Codegen\"", "\"$(PODS_ROOT)/Headers/Private/React-Codegen\"", "\"$(PODS_ROOT)/Headers/Public/ReactCommon\"", - "\"$(PODS_ROOT)/Headers/Private/ReactCommon\"" + "\"$(PODS_ROOT)/Headers/Private/ReactCommon\"", + "\"$(PODS_ROOT)/Headers/Public/RNWorklets\"" ].join(" ") } @@ -51,4 +58,5 @@ Pod::Spec.new do |s| s.dependency "React-jsi" s.dependency "ReactCommon/turbomodule/core" end + s.dependency "RNWorklets" end diff --git a/packages/react-native/README.md b/packages/react-native/README.md index 80c70d207..4a38a7a1d 100644 --- a/packages/react-native/README.md +++ b/packages/react-native/README.md @@ -16,28 +16,48 @@ NativeScript.init(); const object = NSObject.new(); ``` -For UIKit work that must happen on the main thread, pass a callback to the JSI -host object's `runOnUI()` helper. The callback itself stays on React Native's JS -thread; NativeScript native calls made inside the callback are synchronously -performed on UIKit's main thread. +`NativeScript.init()` also installs the Native API into the +`react-native-worklets` UI runtime. `NativeScript.runOnUI()` only accepts +Worklets callbacks; running React Native's JS-thread runtime as a UI-thread shim +is not supported. ```ts await NativeScript.runOnUI(() => { + "worklet"; UIApplication.sharedApplication.keyWindow.tintColor = UIColor.systemPinkColor; }); ``` +Install `react-native-worklets`, add its Babel plugin, and run `pod install` so +the `RNWorklets` pod is linked: + +```sh +npm install react-native-worklets +``` + +```js +module.exports = { + presets: ["module:@react-native/babel-preset"], + plugins: [ + "@nativescript/react-native/babel-plugin", + "react-native-worklets/plugin", + ], +}; +``` + +`installWorklets()` is still exported for custom initialization, but it throws +when Worklets is unavailable or incompatible. `runOnUI()` throws when the +callback was not transformed into a Worklets function. + Obj-C blocks and JS-backed Obj-C method callbacks, including `NSObject.extend` -subclass overrides and delegates created with `createDelegate()`, default to the -thread that invoked them. Wrap callbacks when you want an explicit thread -policy: +subclass overrides and delegates created with `createDelegate()`, should return +to React Native's JS thread for JS work. Use `jsInvoker()` when a callback can be +reached from a native caller thread: ```ts UIView.animateWithDurationAnimationsCompletion( 0.25, - NativeScript.uiInvoker(() => { - view.alpha = 0.5; - }), + null, NativeScript.jsInvoker((finished) => { console.log("animation finished", finished); }), @@ -47,24 +67,20 @@ UIView.animateWithDurationAnimationsCompletion( Delegate, data-source, target/action, and `UIAction` callbacks are JS-side callbacks. Treat their bodies as JS work. If a callback can be reached from a background native thread and needs to mutate UIKit, wrap the mutation in -`NativeScript.runOnUI()` or create the callback with `NativeScript.uiInvoker()`. +`NativeScript.runOnUI()` with a Worklets callback. -The package also includes a Babel plugin for directive-style callbacks: +The package also includes a Babel plugin for directive-style JS callbacks: ```ts -someNativeApi(() => { - "use ui"; - view.alpha = 1; -}); - someNativeApi(() => { "use js"; console.log("back on JS"); }); ``` -The transform rewrites those callbacks to `NativeScript.uiInvoker(fn)` and -`NativeScript.jsInvoker(fn)`. +The transform rewrites those callbacks to `NativeScript.jsInvoker(fn)`. +`"use ui"` is rejected in React Native; use a Worklets `"worklet"` callback with +`NativeScript.runOnUI()` instead. ## Defining native UIKit views in JS @@ -119,6 +135,7 @@ Forward a ref when you need imperative access: const badgeRef = useRef>(null); await badgeRef.current?.runOnUI((view) => { + "worklet"; view.alpha = 0.8; }); @@ -183,6 +200,7 @@ property setters still win first, and unsupported names fall back to JS state: ```ts NativeScript.runOnUI(() => { + "worklet"; const view = UIView.new(); view.ownerState = { selected: false }; view.tag = 42; // still calls UIKit's native tag setter @@ -204,6 +222,7 @@ const delegate = NativeScript.createDelegate( { scrollViewDidScroll(scrollView) { NativeScript.runOnUI(() => { + "worklet"; scrollView.indicatorStyle = UIScrollViewIndicatorStyle.White; }); }, @@ -398,6 +417,7 @@ function topVisibleViewController( } await NativeScript.runOnUI(() => { + "worklet"; const presenter = topVisibleViewController(); if (!presenter || presenter.presentedViewController) { return; @@ -466,7 +486,7 @@ npm run test-rn-turbomodule 2. Install it in an RN app that has Hermes and the New Architecture enabled: ```sh - npm install /path/to/nativescript-react-native-0.0.1.tgz + npm install /path/to/nativescript-react-native-0.0.1.tgz react-native-worklets cd ios RCT_NEW_ARCH_ENABLED=1 USE_HERMES=1 pod install ``` @@ -479,18 +499,21 @@ npm run test-rn-turbomodule NativeScript.init(); await NativeScript.runOnUI(() => { + "worklet"; UIApplication.sharedApplication.keyWindow.tintColor = UIColor.systemPinkColor; }); ``` -4. To use directive-style callbacks in a bare React Native app, add the bundled - Babel plugin: +4. Add the bundled NativeScript Babel plugin and the Worklets Babel plugin: ```js module.exports = { presets: ["module:@react-native/babel-preset"], - plugins: ["@nativescript/react-native/babel-plugin"], + plugins: [ + "@nativescript/react-native/babel-plugin", + "react-native-worklets/plugin", + ], }; ``` @@ -502,7 +525,7 @@ Expo development build, EAS Build, or `npx expo run:ios`. 1. Install the package: ```sh - npx expo install @nativescript/react-native + npx expo install @nativescript/react-native react-native-worklets ``` When testing a local tarball: @@ -523,8 +546,9 @@ Expo development build, EAS Build, or `npx expo run:ios`. The plugin configures iOS for Hermes and the React Native New Architecture, which are required by this JSI TurboModule. It also adds the - `@nativescript/react-native/babel-plugin` transform to `babel.config.js` so - `"use ui"` and `"use js"` callback directives work in Expo bundles. + `@nativescript/react-native/babel-plugin` and `react-native-worklets/plugin` + transforms to `babel.config.js` so `"use js"` and worklet callbacks work in + Expo bundles. 3. Prebuild and run the iOS development build: @@ -559,7 +583,7 @@ Expo development build, EAS Build, or `npx expo run:ios`. ``` Set `{ "babelPlugin": false }` in the config plugin options if you prefer to add -the Babel plugin manually. +the NativeScript and Worklets Babel plugins manually. The plugin also writes `nativescript.react-native.json` so metadata options are visible to native builds. You can pass metadata inputs when the app uses @@ -594,7 +618,167 @@ cd ios RCT_NEW_ARCH_ENABLED=1 USE_HERMES=1 pod install ``` -`configure` adds the bundled Babel plugin when missing, writes +`configure` adds the bundled NativeScript and Worklets Babel plugins when +missing, writes `nativescript.react-native.json`, and warns when the app is not configured for Hermes and the New Architecture. The command is intentionally conservative and does not make destructive native project edits. + +## Android + +The same package runs on Android. Where iOS gives you Objective-C and UIKit, +Android gives you Java and the Android SDK — plus every library your app +depends on, because the metadata is generated from your app's own classpath. + +```ts +import NativeScriptNativeApi from "@nativescript/react-native/src/NativeScriptNativeApi"; + +NativeScriptNativeApi.install(""); + +const now = java.lang.System.currentTimeMillis(); +const sdk = android.os.Build.VERSION.SDK_INT; +``` + +### How it works + +The runtime does not bring a JavaScript engine. It binds to the +`jsi::Runtime` React Native already created, which is why the same build works +whether your app runs Hermes or JSC. The Android module compiles from source in +your app the way every React Native module with C++ does — `jsi::Runtime` is an +abstract C++ class whose layout changes between React Native versions, so a +prebuilt binary could not be safe across them. + +### Setup + +`npx nativescript-rn configure` adds the Gradle script and the typings entries. +To do it by hand, in `android/app/build.gradle`, after the React plugin: + +```groovy +apply from: "../../node_modules/@nativescript/react-native/android/nativescript.gradle" +``` + +That adds two things to your build: + +* **Metadata** — a description of every class on the app's resolved classpath, + generated into the APK's assets on every build. This is what makes + `android.widget.Toast` and any third-party library resolvable from JavaScript. + `whitelist.mdg` / `blacklist.mdg` next to `android/app/build.gradle` filter it, + with the same format the NativeScript CLI uses. +* **TypeScript declarations** — off by default because they take a while: + + ```bash + cd android && ./gradlew generateNativeScriptTypingsDebug -PnsGenerateTypings=true + ``` + + which writes `types/android/` and, with `configure` having run, is already + referenced from `tsconfig.json`. + +### Extending Java classes + +Every class that extends a native class carries `@NativeClass`: + +```ts +import { NativeClass } from "@nativescript/react-native"; + +@NativeClass() +class Ticker extends java.lang.Object { + run() { + console.log("tick"); + } +} +``` + +`@NativeClass` is a plain runtime decorator. It reads nothing at build time and +behaves identically under Babel, SWC or tsc, in either the legacy or the TC39 +decorator dialect. It is not optional: `class X extends java.util.ArrayList {}` +on its own produces a class whose overrides resolve in JavaScript but which Java +still sees as an unmodified `ArrayList`. The decorator is what generates the +real Java subclass, so that Java calling `size()` reaches your implementation. + +A JavaScript class has one base, so several interfaces are declared on the +decorator: + +```ts +@NativeClass({ + interfaces: [java.lang.Runnable, java.util.concurrent.Callable], +}) +class Job extends java.lang.Object { + run() {} + call() { + return new java.lang.String("done"); + } +} +``` + +Decorators are *syntax*, so your bundler has to parse them — that means +`@babel/plugin-proposal-decorators` (or the SWC equivalent) in your Babel +config. That is a standard plugin, not a NativeScript-specific transform. + +#### Naming a class + +`@NativeClass()` on its own generates the Java class on the device. The class is +named after what it contains — its base class, its interfaces, and the names it +overrides — rather than after the source position of the call, because a guest +runs inside React Native's bundle and no source position computed here would +match one a build-time tool computed from your source tree. + +One consequence is worth knowing: two classes that extend the same type, declare +the same interfaces and override the same method names **share one generated +Java class**. That is safe, because the generated Java is identical in both +cases and only forwards into JavaScript — each instance still runs its own +implementation. + +It stops being safe when *Java* constructs the class by name, because then the +class name is all the runtime has to find the JavaScript side with. So: + +> **Name any class that Java instantiates for you** — an `Activity`, a +> `Fragment`, a `Service`, a `Worker`, anything reached by reflection or named +> in `AndroidManifest.xml`. Naming makes it unique. Classes you construct +> yourself with `new` never need it. + +A class that has to exist *before* the app runs — one named in +`AndroidManifest.xml`, for instance — needs a fixed name instead: + +```ts +import { JavaProxy } from "@nativescript/react-native"; + +@JavaProxy("com.example.Ticker") +@NativeClass() +class Ticker extends java.lang.Object {} +``` + +A fully-qualified name is taken verbatim by the runtime, so it is reproducible +at build time. The runtime then *looks the class up* rather than generating it, +which means a named class only resolves once the static binding generator has +emitted it — see the note below. + +> **Release builds only.** The generator reads a bundle, so it needs one on +> disk. On a release build the Gradle script bundles your JavaScript a second +> time -- separately from the one that ships, and with classes preserved -- and +> pre-generates the Java from that. In debug there is no bundle to read: Metro +> serves it over HTTP. Unnamed `@NativeClass()` classes are generated on the +> device and work in both, but `@JavaProxy` names resolve only in release. + +If you would rather not use decorators, the runtime API is always available and +needs nothing from the bundler: + +```ts +const Ticker = java.lang.Object.extend("Ticker", { run() {} }); +const listener = new android.view.View.OnClickListener({ onClick(v) {} }); +``` + +### Threads + +React Native owns the JavaScript thread, and JSI may only be touched there. A +Java callback arriving on another thread is marshalled onto the JS thread for +you. The one thing to avoid is blocking the JS thread while waiting for such a +callback — `thread.join()` immediately after `thread.start()` deadlocks, because +the callback cannot run until the JS thread is free again. + +### What a guest does not install + +React Native already provides `console`, timers, `queueMicrotask`, +`performance` and a module loader, so the runtime leaves all of them alone and +installs only the metadata globals, the object manager and the interop +callbacks. `Worker` is not installed either: a worker needs its own VM, which a +guest has no way to create. diff --git a/packages/react-native/android/.npmignore b/packages/react-native/android/.npmignore new file mode 100644 index 000000000..9b4702f5f --- /dev/null +++ b/packages/react-native/android/.npmignore @@ -0,0 +1,4 @@ +# Gradle/CMake output from building the module in a checkout. The package ships +# sources; the consuming app compiles them. +build/ +.cxx/ diff --git a/packages/react-native/android/CMakeLists.txt b/packages/react-native/android/CMakeLists.txt new file mode 100644 index 000000000..37d178984 --- /dev/null +++ b/packages/react-native/android/CMakeLists.txt @@ -0,0 +1,129 @@ +cmake_minimum_required(VERSION 3.22.1) +project(nativescript_rn) + +# The guest build of the NativeScript Android runtime. See ../build.gradle for +# why this compiles from source rather than shipping a prebuilt .aar. + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(NS_RUNTIME_DIR "${NS_ROOT}/runtime") +set(NS_ANDROID_RUNTIME_DIR "${NS_RUNTIME_DIR}/android/jsi") +set(NS_JNI_DIR "${NS_ROOT}/ffi/jni/jsi") +set(NS_JSI_DIR "${NS_ROOT}/jsi") + +# NS_JSI_HOST_RUNTIME: the jsi::Runtime belongs to React Native. +# TARGET_ENGINE_HERMES selects jsi/hermes, which despite the name is the +# adapter over plain facebook::jsi and holds nothing Hermes-specific -- under a +# host runtime the VM underneath may equally be JSC. +# NS_NO_INSPECTOR: the inspector is written against v8_inspector. +add_compile_definitions( + NS_JSI_HOST_RUNTIME + TARGET_ENGINE_HERMES + NS_NO_INSPECTOR + NATIVESCRIPT_REACT_NATIVE + __HERMES__ +) + +include_directories( + # Rooted includes ("jsi/hermes/HermesRuntime.h") resolve from here. Note + # this shadows an angle-include of a jsi/ header from the engine itself, + # so the shared layer's own includes stay quoted and rooted. + ${NS_ROOT} + ${NS_ANDROID_RUNTIME_DIR} + ${NS_ANDROID_RUNTIME_DIR}/instrumentation + ${NS_ANDROID_RUNTIME_DIR}/messageloop + ${NS_ANDROID_RUNTIME_DIR}/modules + ${NS_ANDROID_RUNTIME_DIR}/modules/console + ${NS_ANDROID_RUNTIME_DIR}/modules/module + ${NS_ANDROID_RUNTIME_DIR}/modules/performance + ${NS_ANDROID_RUNTIME_DIR}/modules/timers + ${NS_ANDROID_RUNTIME_DIR}/profiler + ${NS_ANDROID_RUNTIME_DIR}/sighandler + ${NS_ANDROID_RUNTIME_DIR}/util + ${NS_ANDROID_RUNTIME_DIR}/version + ${NS_ANDROID_RUNTIME_DIR}/workers + ${NS_JNI_DIR} + ${NS_JNI_DIR}/callbackhandlers + ${NS_JNI_DIR}/constants + ${NS_JNI_DIR}/conversion + ${NS_JNI_DIR}/exceptions + ${NS_JNI_DIR}/finalizer + ${NS_JNI_DIR}/global + ${NS_JNI_DIR}/jni + ${NS_JNI_DIR}/jsonhelper + ${NS_JNI_DIR}/metadata + ${NS_JNI_DIR}/objectmanager + ${NS_JNI_DIR}/weakref + # ConcurrentMap.h, robin_hood.h, spinmutex.h. The directory is named for + # Node-API because that is what else lives in it, but these three are + # plain containers with no Node-API in them, and the runtime uses them on + # both binding layers. + ${NS_ROOT}/napi/common + ${NS_METADATA_INCLUDE} + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp +) + +file(GLOB_RECURSE RUNTIME_SOURCES "${NS_ANDROID_RUNTIME_DIR}/*.cpp") +file(GLOB_RECURSE JNI_SOURCES "${NS_JNI_DIR}/*.cpp") +list(FILTER JNI_SOURCES EXCLUDE REGEX "_test\\.cpp$") + +# Parts of the standalone runtime a guest can never reach, dropped rather than +# merely left uninitialised so they are not carried in an app that cannot call +# them. Each has a matching NS_JSI_HOST_RUNTIME guard at its call sites. +# +# assetextractor unpacks a standalone app's app/ directory from the APK with +# libzip. Metro owns the JavaScript here, and the one asset +# needed -- the metadata -- is staged by the Java side. +# Dropping this takes libzip with it. +# WorkerWrapper a Worker gets its own VM, which a guest cannot build. React +# Native has its own answer for background work. Only the +# wrapper goes: LooperTasks and ConcurrentQueue live in the +# same directory but LooperTasks is the main-thread task +# queue -- how a Java callback arriving on another thread gets +# back onto the JS thread, which is core to a guest and not +# worker-specific. ConcurrentQueue has no other user, so it +# goes with the wrapper. +# modules/module require(). The embedder's bundler resolves modules; nothing +# in a guest ever loads a file. +# modules/console React Native installs console before this runtime attaches. +# modules/timers likewise setTimeout and friends. +# modules/performance likewise performance. +# profiler, instrumentation developer tooling for the standalone runtime, +# driven by JNI entry points a guest never calls. +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/assetextractor/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/workers/(WorkerWrapper|ConcurrentQueue)\\.cpp$") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/module/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/console/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/timers/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/modules/performance/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/profiler/") +list(FILTER RUNTIME_SOURCES EXCLUDE REGEX "/instrumentation/") + +add_library(nativescript_rn SHARED + ${RUNTIME_SOURCES} + ${JNI_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/NativeScriptRnInstall.cpp +) + +find_package(ReactAndroid REQUIRED CONFIG) +find_package(fbjni REQUIRED CONFIG) + +find_library(LOG_LIB log) +find_library(ANDROID_LIB android) + +# jsi and nothing else. React Native 0.76+ merges most of its C++ into +# libreactnative.so while keeping jsi separate; both names are tried so the +# module spans the versions where that consolidation happened. +if (TARGET ReactAndroid::reactnative) + set(RN_JSI_TARGETS ReactAndroid::jsi ReactAndroid::reactnative) +else () + set(RN_JSI_TARGETS ReactAndroid::jsi ReactAndroid::turbomodulejsijni ReactAndroid::react_nativemodule_core) +endif () + +target_link_libraries(nativescript_rn + ${RN_JSI_TARGETS} + fbjni::fbjni + ${LOG_LIB} + ${ANDROID_LIB} +) diff --git a/packages/react-native/android/build.gradle b/packages/react-native/android/build.gradle new file mode 100644 index 000000000..59a8f2cab --- /dev/null +++ b/packages/react-native/android/build.gradle @@ -0,0 +1,114 @@ +// @nativescript/react-native -- Android +// +// Built from source in the consuming app, the way every React Native module +// with C++ is. That is not a packaging preference: jsi::Runtime is an abstract +// class whose vtable changes shape between React Native versions, so a binary +// compiled against one version's can call through the wrong slot on +// another. Compiling here means the runtime is always built against exactly the +// jsi the app ships. +// +// Nothing links a JavaScript engine. The runtime speaks nativescript::engine, +// which on this flavour is a header-only adapter over facebook::jsi, so the +// only JS symbols in libnativescript_rn.so come from React Native's own +// libjsi.so -- which is why the same binary works under Hermes and JSC alike. + +apply plugin: "com.android.library" + +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + +// The C++ and Java the runtime is made of. Two layouts are supported: this +// repository, where the sources are the originals, and the published npm +// package, which carries a staged copy under native-api/. +// +// The repository wins when both are present. A checkout also contains a staged +// copy (scripts/build_react_native_android.sh leaves one behind), and preferring +// it would mean edits to NativeScript/ silently did not reach the build. +// +// Canonicalise before walking up: in a checkout the app reaches this package +// through a symlink in node_modules, and file("...") normalises ".." lexically +// without ever following it, so the walk has to start from the real path or it +// lands in node_modules instead of at the repository root. +def repoRoot = projectDir.getCanonicalFile().parentFile.parentFile.parentFile +def packagedRoot = file("${projectDir}/../native-api") +def fromRepo = file("${repoRoot}/NativeScript/runtime/android/jsi").exists() +def fromPackage = !fromRepo + +def nsRoot = fromPackage ? packagedRoot : file("${repoRoot}/NativeScript") +// Two trees, exactly as the standalone runtime module composes them: com.tns.* +// proper, plus com.tns.bindings.* (the proxy generator that stands a Java class +// up for a JS subclass), which lives in its own project but is compiled into +// the runtime rather than shipped separately. +def runtimeJava = fromPackage + ? [file("${projectDir}/src/main/java-runtime")] + : [file("${repoRoot}/platforms/android/test-app/runtime/src/main/java"), + file("${repoRoot}/platforms/android/test-app/runtime-binding-generator/src/main/java")] +def metadataInclude = fromPackage + ? file("${packagedRoot}/metadata/include") + : file("${repoRoot}/metadata-generator/include") + +android { + namespace "org.nativescript.reactnative" + compileSdk safeExtGet("compileSdkVersion", 35) + // Follow the app rather than AGP's default, which points at whichever NDK the + // plugin was built against and need not be the one installed. + ndkVersion safeExtGet("ndkVersion", "27.1.12297006") + + defaultConfig { + minSdk safeExtGet("minSdkVersion", 24) + + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared", + "-DNS_ROOT=${nsRoot.absolutePath}", + "-DNS_METADATA_INCLUDE=${metadataInclude.absolutePath}" + cppFlags "-std=c++20", "-frtti", "-fexceptions" + } + } + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + buildFeatures { + prefab true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + sourceSets { + main { + // com.tns.* is the runtime's Java half -- the class resolver, the object + // maps, the dex factory. It is plain Java with no engine dependency, so + // the guest ships the same copy the standalone runtime uses. + java.srcDirs += runtimeJava + } + } + + packagingOptions { + // React Native and the NDK both contribute one; either is the NDK's. + pickFirst "**/libc++_shared.so" + // Never ship an engine. If one of these ever turns up here it means a + // dependency pulled a VM in behind our back, which would defeat the whole + // arrangement described at the top of this file. + exclude "**/libhermes.so" + exclude "**/libhermesvm.so" + exclude "**/libjsc.so" + exclude "**/libv8android.so" + } +} + +dependencies { + implementation "com.facebook.react:react-android" +} + +// Generates NativeScriptNativeApiSpec from src/NativeScriptNativeApi.ts, using +// the codegenConfig block in package.json. The same spec drives iOS. +apply plugin: "com.facebook.react" diff --git a/packages/react-native/android/nativescript.gradle b/packages/react-native/android/nativescript.gradle new file mode 100644 index 000000000..7182e75a5 --- /dev/null +++ b/packages/react-native/android/nativescript.gradle @@ -0,0 +1,1125 @@ +// NativeScript build integration for a React Native app. +// +// Apply from android/app/build.gradle: +// +// apply from: "../../node_modules/@nativescript/react-native/android/nativescript.gradle" +// +// It adds two things: +// +// * metadata generation -- the description of every Java/Kotlin class the app +// can see, which is what makes `android.widget.Toast` resolvable from JS. +// Generated from the app's own resolved classpath, so a library the app +// installs is callable without anyone publishing bindings for it. +// +// * TypeScript declarations for the same classpath, on by default, generated +// on the first build and refreshed only when the classpath changes. +// -PnsGenerateTypings=false opts out. +// +// Both are keyed to the variant being built, so debug and release get metadata +// for the dependencies they actually resolve. + +import java.nio.file.Paths + +def nsPackageDir = file("${buildscript.sourceFile.parentFile}") +def toolsDir = file("${nsPackageDir}/tools") + +def metadataGeneratorJar = file("${toolsDir}/android-metadata-generator.jar") +def dtsGeneratorJar = file("${toolsDir}/dts-generator.jar") +def sbgGeneratorJar = file("${toolsDir}/static-binding-generator.jar") + +// Bundler settings, taken from the React extension the app already configures +// so that a project with a custom root, entry file or CLI is bundled the same +// way React Native bundles it. The defaults mirror the plugin's own. +def reactExt = project.extensions.findByName("react") +def reactRoot = reactExt?.root?.getOrNull()?.asFile ?: file("${project.projectDir}/../..") +def reactCliFile = reactExt?.cliFile?.getOrNull()?.asFile?.absolutePath + ?: new File(reactRoot, "node_modules/react-native/cli.js").absolutePath +// Node, as a full command rather than just its head: nodeExecutableAndArgs is a +// list because an app may need flags with it (--max-old-space-size, a wrapper), +// and dropping everything after [0] silently ignores them. +// +// -PnsNodePath wins over everything. A bare "node" is resolved through the +// Gradle daemon's PATH, which is not the PATH of the shell that launched the +// build: version managers (volta, nvm, fnm, asdf) install a shim there that +// fails unless a default version is pinned, and the daemon outlives the +// terminal it was started from. An absolute path sidesteps all of it. +def nsNodeCommand = { + if (project.hasProperty("nsNodePath")) { + return [project.property("nsNodePath").toString()] + } + def fromReact = reactExt?.nodeExecutableAndArgs?.getOrNull() + if (fromReact != null && !fromReact.isEmpty()) { + return fromReact.collect { it.toString() } + } + return ["node"] +}() +def nodeExecutable = nsNodeCommand[0] + +// Shared by every step that shells out to node, so one broken toolchain reads +// the same way wherever it surfaces. +def nsNodeHelp = "Set -PnsNodePath=/absolute/path/to/node (or react { " + + "nodeExecutableAndArgs = [\"/absolute/path/to/node\"] } in " + + "android/app/build.gradle). Version managers such as volta, nvm, fnm " + + "and asdf put a shim on PATH that the Gradle daemon inherits without " + + "the shell state that makes it work." +def nsEntryFile = reactExt?.entryFile?.getOrNull()?.asFile?.absolutePath + ?: (new File(reactRoot, "index.js").exists() + ? new File(reactRoot, "index.js").absolutePath + : new File(reactRoot, "index.android.js").absolutePath) + +// Restrict metadata to what the app's bundle can reach. +// +// On by default, and release-only: a debug build has no bundle on disk to +// analyse, and guessing from a partial view is what breaks apps. Shipping +// metadata for the whole compile classpath means an app that touches 5% of the +// +// The seed is a lower bound that the generator widens (supertypes, nested +// classes, signature types) before dropping anything, because usage alone is +// not enough: android.os.Bundle is never named in JS and never resolved as a +// node, yet removing it makes Activity.onCreate(Bundle) unbuildable and the app +// crashes at launch. +// +// -PnsFilterMetadata=false opts out and ships the whole classpath, which is the +// escape hatch if a filtered build misses something. +def nsFilterMetadata = !project.hasProperty("nsFilterMetadata") || + Boolean.parseBoolean(project.property("nsFilterMetadata").toString()) + +// Classes the app declares it calls from JS, from nativescript.react-native.json +// at the project root -- the same file the iOS side already uses: +// +// { "reactNative": { "metadata": { "android": { +// "include": ["com.ammarahmed.reactnativeworkers.WorkerTurboModules"] } } } } +// +// Entries are added to the metadata whitelist, and a package named here is kept +// out of the autolinked-module exclusion below. Accepts "pkg.Class", "pkg.*" or +// raw .mdg form ("pkg:Class"). +def nsMetadataInclude = { + def configFile = new File(reactRoot, "nativescript.react-native.json") + if (!configFile.exists()) return [] + try { + def parsed = new groovy.json.JsonSlurper().parse(configFile) + def declared = parsed?.reactNative?.metadata?.android?.include + if (declared instanceof List) { + return declared.findAll { it instanceof String && !it.trim().isEmpty() } + .collect { it.toString().trim() } + } + } catch (Exception e) { + // Falling back to a different filter than the author asked for would be + // silent and wrong; a filtered build is not the place to guess. + throw new GradleException("NativeScript: could not read ${configFile}: ${e.message}") + } + return [] +}() + +// "com.foo.Bar" -> "com.foo:Bar", "com.foo.*" -> "com.foo:*", "a:b" unchanged. +def nsToMdgEntry = { String raw -> + def value = raw.trim() + if (value.isEmpty()) return null + if (value.contains(":")) return value + if (value.endsWith(".*")) return value[0..-3] + ":*" + def segments = value.tokenize(".") + for (int i = 0; i < segments.size(); i++) { + if (segments[i] && Character.isUpperCase(segments[i].charAt(0))) { + return segments[0.. + def entry = nsToMdgEntry(raw) + entry == null ? null : entry.substring(0, entry.indexOf(":")) +}.findAll { it != null && !it.isEmpty() } + +// React Native's own native modules are reached from JS through the TurboModule +// bridge, not through the interop, so describing them in metadata buys nothing +// and costs bytes and startup parse time. Their packages are read from the +// PackageList React Native's autolinking generates, so the exclusion tracks +// whatever the app installs without anyone maintaining a list. +// +// This does NOT affect the R8 keep rules: those are written from what metadata +// retains, so a module that is never described is simply never mentioned -- +// and it stays in the app because the Java that registers it references it, +// which is what R8 follows. Excluded from metadata, untouched in the dex. +// +// A package the app named in `include` is left out of the exclusion: the +// generator applies the blacklist *over* the whitelist +// (`isSeed(d) && !isBlacklisted(d)` in MetadataClosure), so blacklisting a +// package the author asked for would silently win over the request. +def nsAutolinkedModulePackages = { variantName -> + def packageList = new File(buildDir, + "generated/autolinking/src/main/java/com/facebook/react/PackageList.java") + if (!packageList.exists()) return [] + + def packages = new LinkedHashSet() + def entry = ~/new\s+([a-zA-Z0-9_.]+)\s*\(/ + packageList.eachLine { line -> + def matcher = entry.matcher(line) + while (matcher.find()) { + def fqcn = matcher.group(1) + def lastDot = fqcn.lastIndexOf('.') + if (lastDot <= 0) return + def pkg = fqcn.substring(0, lastDot) + // The bridge's own packages are described by the runtime keep-list + // and by whatever the JS names; leave that judgement to the seed. + if (pkg.startsWith("com.facebook.react")) return + packages.add(pkg) + } + } + return packages.toList() +} + +// Typings are on by default: without them TypeScript cannot type a single +// native call. The task declares the classpath as an input and the typings +// directory as its output, so an unchanged classpath skips it entirely and +// only a dependency change pays for a run. -PnsGenerateTypings=false opts out. +def nsTypingsEnabled = !project.hasProperty("nsGenerateTypings") + || Boolean.parseBoolean(project.property("nsGenerateTypings").toString()) + +// Where the .dat files land. This is added to the asset source set rather than +// written into src/, so a clean build regenerates and nothing lands in version +// control. NativeScriptNativeApiModule copies them out of the APK on first run. +// +// One directory per variant. A single shared one is not merely untidy: two +// variants' generator tasks then declare the same output, and Gradle refuses to +// merge assets it cannot prove are up to date, failing the build with +// "uses this output of task ... without declaring an explicit dependency". +// Only release surfaces it, because a debug-only build never has a second +// variant's task registered against the same path. +def metadataAssetRootFor = { variantName -> + file("${buildDir}/generated/nativescript/assets/${variantName}") +} +def metadataOutDirFor = { variantName -> + new File(metadataAssetRootFor(variantName), "nativescript-metadata") +} + +// Registered against each variant's own source set rather than main, so that a +// variant only ever picks up the metadata generated for it. +android { + applicationVariants.all { variant -> + def assetRoot = metadataAssetRootFor(variant.name) + def sourceSet = android.sourceSets.findByName(variant.name) + if (sourceSet != null) { + sourceSet.assets.srcDirs += assetRoot + } else { + // Flavoured builds have no source set named after the full variant; + // fall back to main, where the per-variant subdirectory still keeps + // the two generators from colliding. + android.sourceSets.main.assets.srcDirs += assetRoot + } + } +} + +/** + * Every jar the variant resolves, plus android.jar. + * + * AARs are asked for as "android-classes-jar" so the artifact view unpacks + * classes.jar for us; without that an .aar arrives as an archive the generator + * cannot read. + */ +def resolveClasspathJars = { variant -> + def jars = new LinkedHashSet() + + def androidJar = Paths.get(android.sdkDirectory.absolutePath, "platforms", + android.compileSdkVersion, "android.jar").toFile() + if (androidJar.exists()) { + jars.add(androidJar) + } + + // The published name of AGP's "classes.jar unpacked from an aar" artifact. + // Spelled out rather than read off AndroidArtifacts.ArtifactType, which is + // AGP-internal and not resolvable from a script plugin. + def classesJarType = "android-classes-jar" + + try { + def view = variant.runtimeConfiguration.incoming.artifactView { config -> + config.lenient(true) + config.attributes { container -> + container.attribute( + org.gradle.api.attributes.Attribute.of("artifactType", String), + classesJarType) + } + } + view.artifacts.artifactFiles.each { jars.add(it) } + } catch (Exception e) { + logger.warn("NativeScript: could not resolve the ${variant.name} runtime classpath " + + "(${e.message}). Metadata will cover only the Android SDK.") + } + + return jars +} + +/** Directories of .class files this build produced, across every subproject. */ +def resolveCompiledClassDirs = { variant -> + def dirs = new LinkedHashSet() + def variantName = variant.name + + rootProject.subprojects.each { sub -> + ["intermediates/javac/${variantName}/classes", + "intermediates/javac/${variantName}/compile${variantName.capitalize()}JavaWithJavac/classes", + "tmp/kotlin-classes/${variantName}"].each { relative -> + def candidate = new File(sub.buildDir, relative) + if (candidate.isDirectory()) { + dirs.add(candidate) + } + } + } + + return dirs +} + +def collectClassFiles = { Collection roots -> + def files = new ArrayList() + roots.each { root -> + root.eachFileRecurse { f -> + if (f.isFile() && f.name.endsWith(".class")) { + files.add(f) + } + } + } + return files +} + +android.applicationVariants.all { variant -> + def variantName = variant.name.capitalize() + def workDir = file("${buildDir}/nativescript/${variant.name}") + def metadataOutDir = metadataOutDirFor(variant.name) + // R8 rules covering exactly the classes the filtered metadata kept. Only + // written when a filter is in play; without one the generator keeps + // everything and there is nothing to say. + def metadataKeepRules = new File(workDir, "metadata-keep-rules.pro") + + def metadataTask = tasks.register("generateNativeScriptMetadata${variantName}", JavaExec) { + group = "nativescript" + description = "Generates NativeScript metadata for the ${variant.name} classpath." + + // The generator takes its input as files in the working directory + // rather than as arguments -- a classpath can be thousands of entries + // and would overflow the command line. + workingDir workDir + mainClass = "-jar" + + // Everything the emitted metadata is a function of. Without these the + // task is up to date whenever its output directory merely exists, so a + // dependency upgrade -- or turning filtering on -- silently reuses the + // previous tree, which reads exactly like the change having no effect. + // + // Through the artifact view resolveClasspathJars uses; consuming the + // configuration directly leaves artifactType unset and AGP cannot then + // choose between an AAR's variants. Providers keep both this and the + // compiled classes lazy, so they resolve at up-to-date-check time, + // after javac has run. + inputs.files(variant.runtimeConfiguration.incoming.artifactView { config -> + config.lenient(true) + config.attributes { container -> + container.attribute( + org.gradle.api.attributes.Attribute.of("artifactType", String), + "android-classes-jar") + } + }.artifacts.artifactFiles).withPropertyName("nsClasspath") + inputs.files(provider { resolveCompiledClassDirs(variant) }) + .withPropertyName("compiledClasses") + inputs.file(metadataGeneratorJar).withPropertyName("generator") + // The seed is written by another task into this task's working + // directory, so it has to be named or the two never connect. + inputs.files(provider { + def sources = [file("${project.projectDir}/whitelist.mdg"), + file("${project.projectDir}/blacklist.mdg")] + if (nsFilterMetadata) sources.add(new File(workDir, "generated-whitelist.mdg")) + sources.findAll { it.exists() } + }).withPropertyName("filters") + // Toggling the flag off leaves no filter file behind to notice. + inputs.property("filterMetadata", nsFilterMetadata) + // Editing the declaration changes both what is kept and which module + // packages are excluded, and neither is visible in the files above. + inputs.property("metadataInclude", nsMetadataInclude) + + outputs.dir(metadataOutDir) + // Not beside the metadata: that directory's parent is an asset source + // root, and a keep-rules file dropped there ships inside the APK. + outputs.file(metadataKeepRules) + + doFirst { + if (!metadataGeneratorJar.exists()) { + throw new GradleException( + "NativeScript: ${metadataGeneratorJar} is missing. It ships with " + + "@nativescript/react-native; reinstall the package, or run " + + "scripts/build_react_native_android_tools.sh from a checkout.") + } + + workDir.mkdirs() + metadataOutDir.deleteDir() + metadataOutDir.mkdirs() + // A run with no filter writes no rules; the previous run's file + // would otherwise stay behind and keep a set that no longer matches. + metadataKeepRules.delete() + + def jars = resolveClasspathJars(variant) + def classDirs = resolveCompiledClassDirs(variant) + + new File(workDir, "mdg-java-dependencies.txt").withWriter { out -> + jars.each { if (it.name.endsWith(".jar")) out.println it.absolutePath } + classDirs.each { out.println it.absolutePath } + } + + new File(workDir, "mdg-java-out.txt").withWriter { out -> + collectClassFiles(classDirs).each { out.println it.absolutePath } + } + + new File(workDir, "mdg-output-dir.txt").withWriter { out -> + out.println metadataOutDir.absolutePath + } + + // Optional filters, same file names and format the NativeScript CLI + // uses. Absent means "everything on the classpath". + // + // whitelist.mdg has two possible sources: one derived from the + // app's bundle by generateNativeScriptMetadataSeed, and one the app + // author wrote. They are concatenated rather than one winning -- + // both are keep-lists, and the generator takes the union before + // computing its closure. A hand-written entry can therefore only + // ever add, which is the safe direction. + // + // The generated half is gated on the flag rather than on the file + // existing: the seed task leaves its output in the working + // directory, so a later build without -PnsFilterMetadata would + // otherwise go on filtering against it. + def generatedSeed = new File(workDir, "generated-whitelist.mdg") + def useGeneratedSeed = nsFilterMetadata && generatedSeed.exists() + def authoredWhitelist = file("${project.projectDir}/whitelist.mdg") + def whitelistTarget = new File(workDir, "whitelist.mdg") + + // Only in a build that is already filtering. A declaration is meant + // to widen a filtered set, not to become the whole filter: adding it + // to an unfiltered (debug) build would turn a whitelist on for the + // first time and cut that build down to the declaration's closure. + def declaredEntries = useGeneratedSeed + ? nsMetadataInclude.collect { nsToMdgEntry(it) }.findAll { it != null } + : [] + + if (useGeneratedSeed || authoredWhitelist.exists() || !declaredEntries.isEmpty()) { + def parts = [] + if (useGeneratedSeed) parts.add(generatedSeed.text) + if (authoredWhitelist.exists()) parts.add(authoredWhitelist.text) + if (!declaredEntries.isEmpty()) { + parts.add("# from nativescript.react-native.json\n" + + declaredEntries.join("\n") + "\n") + } + whitelistTarget.text = parts.join("\n") + } else { + whitelistTarget.delete() + } + + def blacklistSource = file("${project.projectDir}/blacklist.mdg") + def blacklistTarget = new File(workDir, "blacklist.mdg") + + // Autolinked React Native modules, minus the ones this app actually + // reaches from JS. + // + // Gated with the whitelist because the generator only consults a + // blacklist once a whitelist exists, and an unfiltered build should + // stay unfiltered. + // + // Two things spare a module from the exclusion, and both matter + // because the blacklist beats the whitelist inside the generator + // (`isSeed(d) && !isBlacklisted(d)` in MetadataClosure) -- once a + // package is excluded here, nothing downstream can bring it back: + // + // * the seed names it. A worker calling + // `com.ammarahmed.reactnativeworkers.WorkerTurboModules` is + // using that module as an interop class, whatever else the + // TurboModule bridge is doing with it, so describing it is the + // whole point. + // * the app declared it in nativescript.react-native.json, for + // the case where the analysis cannot see the use. + def seededPackages = [] + if (useGeneratedSeed && whitelistTarget.exists()) { + whitelistTarget.eachLine { line -> + def trimmed = line.trim() + if (trimmed.isEmpty() || trimmed.startsWith("#")) return + def colon = trimmed.indexOf(":") + if (colon > 0) seededPackages.add(trimmed.substring(0, colon)) + } + } + + def keptPackages = (nsIncludedPackages + seededPackages).unique() + def excludedModules = useGeneratedSeed + ? nsAutolinkedModulePackages(variantName) + .findAll { pkg -> !keptPackages.any { it == pkg || it.startsWith(pkg + ".") } } + : [] + + def blacklistParts = [] + if (blacklistSource.exists()) blacklistParts.add(blacklistSource.text) + if (!excludedModules.isEmpty()) { + // Two patterns per module: the package itself, and its + // subpackages. The matcher globs the package name, but "a.b:*" + // matches only "a.b" -- a module that splits itself across + // a.b.core / a.b.views (nitro does) would otherwise leak. + blacklistParts.add( + "# Autolinked React Native modules: reached over the TurboModule\n" + + "# bridge rather than the interop, so they are not described here.\n" + + "# Declare one in nativescript.react-native.json to keep it.\n" + + excludedModules.collect { it + ":*\n" + it + ".*:*" }.join("\n") + "\n") + def keptModules = nsAutolinkedModulePackages(variantName) - excludedModules + logger.lifecycle("NativeScript: excluding ${excludedModules.size()} autolinked " + + "React Native module package(s) from metadata: " + excludedModules.join(", ") + + (keptModules.isEmpty() ? "" : "; kept because the app uses them: " + + keptModules.join(", "))) + } + + if (!blacklistParts.isEmpty()) { + blacklistTarget.text = blacklistParts.join("\n") + } else { + blacklistTarget.delete() + } + + logger.lifecycle("NativeScript: generating metadata from ${jars.size()} jars and " + + "${classDirs.size()} class directories") + + def params = [metadataGeneratorJar.absolutePath] + params.add("proguardOut=" + metadataKeepRules.absolutePath) + // Only a generated seed may be grown into a closure and held to the + // soundness check; an app's own whitelist.mdg is filtered as + // authored. See Builder.applyClosureIfRequested. + if (nsFilterMetadata) { + params.add("enforceClosure=true") + } + if (project.hasProperty("nsVerboseMetadata")) { + params.add("verbose") + } + args params.toArray() + } + } + + // Alongside the app rather than in build/: these are checked in and + // referenced from tsconfig.json, the same way types/ios is on the Apple + // side. Declared out here so it can be the task's output. + def typingsDir = file("${rootProject.projectDir}/../types/android") + + def typingsTask = tasks.register("generateNativeScriptTypings${variantName}", JavaExec) { + group = "nativescript" + description = "Generates TypeScript declarations for the ${variant.name} classpath." + + workingDir workDir + mainClass = "-jar" + onlyIf { nsTypingsEnabled } + + // Content-keyed, so a dependency upgrade that keeps the same path is + // still noticed, and an untouched classpath costs nothing. + // + // Through the same artifact view resolveClasspathJars uses. Consuming + // the configuration directly leaves artifactType unset, and AGP then + // cannot choose between an AAR's variants ("cannot choose between + // android-aar-metadata, android-art-profile, android-assets, ..."). + // artifactFiles stays lazy, so nothing resolves at configuration time. + inputs.files(variant.runtimeConfiguration.incoming.artifactView { config -> + config.lenient(true) + config.attributes { container -> + container.attribute( + org.gradle.api.attributes.Attribute.of("artifactType", String), + "android-classes-jar") + } + }.artifacts.artifactFiles).withPropertyName("nsClasspath") + inputs.file(dtsGeneratorJar).withPropertyName("generator") + outputs.dir(typingsDir).withPropertyName("typings") + + doFirst { + // Created here as well as by the metadata task: the two are + // independent, and typings can be asked for on their own. + workDir.mkdirs() + + if (!dtsGeneratorJar.exists()) { + throw new GradleException( + "NativeScript: ${dtsGeneratorJar} is missing. It ships with " + + "@nativescript/react-native; reinstall the package, or run " + + "scripts/build_react_native_android_tools.sh from a checkout.") + } + + typingsDir.mkdirs() + + def params = [dtsGeneratorJar.absolutePath, "-input"] + resolveClasspathJars(variant).each { params.add(it.absolutePath) } + params.add("-output") + params.add(typingsDir.absolutePath) + + logger.lifecycle("NativeScript: writing TypeScript declarations to ${typingsDir}") + args params.toArray() + } + } + + // Pre-generated Java for named (@JavaProxy) subclasses. + // + // Only for variants that ship a bundle. In debug, Metro serves JS over HTTP, + // there is nothing on disk to read, and unnamed classes are generated on the + // device anyway. + // + // The bundle React Native itself produces is no use here: hermesc compiles + // it and moves the bytecode over the JavaScript inside a single task, so by + // the time any later task runs there is nothing readable left. So Metro is + // run a second time into a scratch directory, purely as generator input. + // That costs a bundle on release builds and nothing on debug. + // + // --dev false is what makes the second bundle usable: it has to be minified + // the same way the shipped one is, or the class names the generator reads + // will not be the names the runtime computes at load. A @JavaProxy name is + // a string literal and survives minification either way; an unnamed class + // that fails to line up simply gets generated on the device, as it does in + // debug, so a mismatch costs startup time rather than correctness. + def sbgEnabled = !variant.buildType.debuggable && + !project.hasProperty("nsSkipStaticBindings") && + sbgGeneratorJar.exists() + + if (sbgEnabled) { + def sbgWorkDir = file("${buildDir}/nativescript/${variant.name}/sbg") + def sbgBundleDir = new File(sbgWorkDir, "bundle") + def sbgJavaDir = new File(sbgWorkDir, "java") + + def bindingsTask = tasks.register("generateNativeScriptBindings${variantName}", JavaExec) { + group = "nativescript" + description = "Pre-generates Java bindings for named NativeScript subclasses (${variant.name})." + + // The generator resolves jsparser and every one of its scratch + // files relative to the working directory, so it gets its own. + workingDir sbgWorkDir + mainClass = "-jar" + + inputs.files(fileTree(reactRoot) { + include "**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx" + exclude "**/node_modules/**", "**/android/**", "**/ios/**", "**/build/**" + }) + // The app's own sources are not the whole program: a dependency's JS + // is bundled into these graphs too, and a library that calls Java is + // as much a part of the analysis as the app is. node_modules is + // excluded above because hashing it on every build is far more + // expensive than the bundling it would guard -- the lockfiles stand + // in for it, since a dependency cannot change without one of them + // changing. Without this, upgrading a library silently leaves the + // bindings and the metadata filter describing the previous one. + // + // Searched up from the app as well as in it: in a workspace the app + // is a package and the lockfile belongs to the repository root, so + // looking only beside package.json finds nothing at all -- which + // fails silently, as an input that never changes. + inputs.files(provider { + def names = ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lockb"] + def found = [] + def dir = reactRoot + // A handful of levels: enough for app-in-a-workspace, and never + // so far up that an unrelated lockfile outside the project is + // picked up. + for (int depth = 0; depth < 5 && dir != null; depth++) { + names.each { name -> + def candidate = new File(dir, name) + if (candidate.exists()) found.add(candidate) + } + if (new File(dir, ".git").exists()) break + dir = dir.parentFile + } + def appPackageJson = new File(reactRoot, "package.json") + if (appPackageJson.exists()) found.add(appPackageJson) + found + }).withPropertyName("dependencyLocks").optional() + // Which entry points exist at all. A worker added or repointed + // changes the set of graphs to analyse, and the journal is the only + // place that shows -- adding one usually adds a source file, which + // the tree above catches, but repointing an existing file does not. + inputs.files(provider { + def journal = new File(reactRoot, ".rn-workers/manifest.log") + journal.exists() ? [journal] : [] + }).withPropertyName("workerEntries").optional() + // The generator and its parser are inputs too. Without them a change + // to either leaves the task up to date against stale output, which + // reads exactly like the change having no effect. + inputs.file(sbgGeneratorJar) + inputs.files(fileTree(new File(toolsDir, "jsparser"))) + // The analysis bundler is one as well; it decides what the parser + // and the seed get to see. + inputs.files(provider { + def script = new File(nsPackageDir, "scripts/bundle-for-analysis.js") + script.exists() ? [script] : [] + }).withPropertyName("analysisBundler").optional() + outputs.dir(sbgJavaDir) + // Declared so the bundles survive an up-to-date run: the metadata + // seed reads this directory, and a task that is skipped must still + // leave its inputs in place. + outputs.dir(sbgBundleDir) + + doFirst { + sbgWorkDir.mkdirs() + sbgBundleDir.deleteDir() + sbgBundleDir.mkdirs() + sbgJavaDir.deleteDir() + sbgJavaDir.mkdirs() + + // Plain JDK calls rather than project.copy / project.exec: + // Gradle no longer allows reaching into Project services while a + // task is executing. + def jsparserSource = new File(toolsDir, "jsparser").toPath() + def jsparserTarget = new File(sbgWorkDir, "jsparser").toPath() + java.nio.file.Files.walk(jsparserSource).forEach { source -> + def target = jsparserTarget.resolve(jsparserSource.relativize(source)) + if (java.nio.file.Files.isDirectory(source)) { + java.nio.file.Files.createDirectories(target) + } else { + java.nio.file.Files.createDirectories(target.getParent()) + java.nio.file.Files.copy(source, target, + java.nio.file.StandardCopyOption.REPLACE_EXISTING) + } + } + + // jsparser reads ts_helpers.js from a sibling of the input + // directory to learn the decorator names, so the copy that ships + // in the package is staged where it expects to find it. + def tsHelpersTarget = new File(sbgWorkDir, "internal/ts_helpers.js") + tsHelpersTarget.parentFile.mkdirs() + def tsHelpersSource = new File(nsPackageDir, + "src/main/assets/nativescript/internal/ts_helpers.js") + if (!tsHelpersSource.exists()) { + throw new GradleException("NativeScript: ${tsHelpersSource} is missing from the package.") + } + tsHelpersTarget.text = tsHelpersSource.text + + // Secondary entry points, each of which is its own module graph + // and so appears in no other bundle. A worker's JS is invisible + // to everything here unless it is bundled separately: it neither + // contributes subclasses to the static binding generator nor + // Java usage to the metadata seed, and the result is a release + // build that drops exactly the classes a worker needs. + // + // Read from the journal a worker bundler leaves behind + // (.rn-workers/manifest.log, JSON lines of {id, absPath}) rather + // than from its output: that runs later in the build, and by + // then it has already compiled its bundles to Hermes bytecode, + // which no JS parser can read. + def workerJournal = new File(reactRoot, ".rn-workers/manifest.log") + def entriesById = [:] + if (workerJournal.exists()) { + def slurper = new groovy.json.JsonSlurper() + workerJournal.eachLine { line -> + def trimmed = line.trim() + if (trimmed.isEmpty()) return + try { + def parsed = slurper.parseText(trimmed) + // Later lines win: the journal is appended to on + // every transform, so it holds stale duplicates. + if (parsed?.id && parsed?.absPath) entriesById[parsed.id] = parsed.absPath + } catch (Exception ignored) { + // A malformed line is one worker, not the build. + } + } + } + def workerEntries = entriesById.findAll { id, absPath -> new File(absPath).exists() } + + // With workers to build, one Metro session builds the app entry + // and all of them together -- so the app entry is not bundled + // twice, and the copy that survives is the unminified one the + // analysis reads far better (minified input is what leaves most + // call sites unresolved). + def analysisScript = new File(nsPackageDir, "scripts/bundle-for-analysis.js") + def useAnalysisScript = !workerEntries.isEmpty() && analysisScript.exists() + + logger.lifecycle("NativeScript: bundling ${variant.name} JavaScript for binding generation") + + // --unstable-transform-profile is what keeps `class X extends Y {}` + // in the output. React Native's Babel preset downlevels classes + // into a function plus an anonymous helper call unless this is + // set (preserveClasses = isHermesV1 in the preset), and a + // downlevelled class is not something any static analysis can + // recognise as a subclass. + // + // It only affects this bundle. The one that ships is built by + // React Native's own task and is untouched -- and the runtime + // handles the downlevelled form there perfectly well. + // + // The value is "hermes-stable"; the CLI's own help text still + // says "hermes", which the preset does not match. + def runCliMainBundle = { + def bundleCmd = nsNodeCommand + [reactCliFile, "bundle", + "--platform", "android", + "--dev", "false", + "--unstable-transform-profile", "hermes-stable", + "--entry-file", nsEntryFile, + "--bundle-output", new File(sbgBundleDir, "index.android.bundle").absolutePath, + "--assets-dest", new File(sbgWorkDir, "assets").absolutePath] + + // Kept so a failure can name the node that actually ran: the + // whole class of toolchain problems here is "not the node you + // think", and the exit code alone does not show which one. + def bundleOutput = new StringBuilder() + def bundleProcess = new ProcessBuilder(bundleCmd) + .directory(reactRoot) + .redirectErrorStream(true) + .start() + bundleProcess.inputStream.eachLine { + logger.info(it) + bundleOutput.append(it).append("\n") + } + def bundleExit = bundleProcess.waitFor() + if (bundleExit != 0) { + // 126 is "found but not executable", which is what a version + // manager's shim returns when it has no version to delegate + // to; 127 is "not on PATH at all". Both mean the node lookup, + // not the bundle. + def nodeProblem = bundleExit == 126 || bundleExit == 127 + throw new GradleException( + "NativeScript: bundling for binding generation failed (exit ${bundleExit}) " + + "using node '${nsNodeCommand.join(' ')}'.\n" + + (nodeProblem ? "That exit code means the node command could not be run. " + + nsNodeHelp + "\n" : "") + + (bundleOutput.length() > 0 + ? "Bundler output:\n${bundleOutput.toString().trim()}\n" + : "The bundler produced no output; re-run with --info.\n") + + "Or pass -PnsSkipStaticBindings to build without pre-generated bindings.") + } + } + + // Skipped when the analysis script runs: that builds this same + // entry alongside the workers, in one Metro session. + if (!useAnalysisScript) { + runCliMainBundle() + } + + if (useAnalysisScript) { + { + logger.lifecycle("NativeScript: bundling the app entry and " + + "${workerEntries.size()} worker " + + "entr${workerEntries.size() == 1 ? 'y' : 'ies'} in one Metro session") + + // One session for all of them. A `react-native bundle` + // per entry would pay a fresh Node process, a fresh + // config load and a cold transform cache each time; + // loading the config once and reusing it is what React + // Native's own tooling does. + def analysisCmd = nsNodeCommand + [analysisScript.absolutePath, + "--project-root", reactRoot.absolutePath, + "--out", sbgBundleDir.absolutePath, + "--entry", nsEntryFile, + // Worker graphs swap `react-native` for + // the worker shim; without this the + // analysis would read a graph the + // device never runs. + "--resolver-option", "rnworkers=true"] + workerEntries.each { id, absPath -> analysisCmd.addAll(["--worker", "${id}=${absPath}"]) } + + def analysisOutput = new StringBuilder() + def analysisProcess = new ProcessBuilder(analysisCmd.collect { it.toString() }) + .directory(reactRoot) + .redirectErrorStream(true) + .start() + analysisProcess.inputStream.eachLine { + logger.info(it) + analysisOutput.append(it).append("\n") + } + def analysisExit = analysisProcess.waitFor() + + // Exit 3 is "this React Native has no programmatic + // bundle API"; anything else is a real failure, and + // carrying on would filter metadata against a view of + // the program that is missing these workers -- a launch + // crash in the field rather than an error here. + if (analysisExit == 3) { + logger.lifecycle("NativeScript: falling back to one bundler run per entry") + // The app entry was skipped above on the assumption + // this session would build it. + runCliMainBundle() + workerEntries.each { id, absPath -> + def safeName = id.replaceAll("[^A-Za-z0-9]", "_") + def workerOut = new File(sbgBundleDir, "worker-${safeName}.js") + def workerCmd = nsNodeCommand + [reactCliFile, "bundle", + "--platform", "android", + "--dev", "false", + "--unstable-transform-profile", "hermes-stable", + "--entry-file", absPath, + "--bundle-output", workerOut.absolutePath] + def workerOutput = new StringBuilder() + def workerProcess = new ProcessBuilder(workerCmd.collect { it.toString() }) + .directory(reactRoot) + .redirectErrorStream(true) + .start() + workerProcess.inputStream.eachLine { + logger.info(it) + workerOutput.append(it).append("\n") + } + if (workerProcess.waitFor() != 0) { + throw new GradleException( + "NativeScript: bundling worker '${id}' for analysis failed.\n" + + workerOutput.toString().trim()) + } + } + } else if (analysisExit != 0) { + throw new GradleException( + "NativeScript: bundling the app and its workers for analysis " + + "failed (exit ${analysisExit}).\n" + analysisOutput.toString().trim()) + } + } + } + + def bundles = sbgBundleDir.listFiles()?.findAll { it.isFile() } ?: [] + if (bundles.isEmpty()) { + throw new GradleException("NativeScript: the bundler produced no output in ${sbgBundleDir}") + } + logger.lifecycle("NativeScript: scanning ${bundles.size()} bundle file(s) for named subclasses") + + new File(sbgWorkDir, "sbg-input-file.txt").text = "${sbgBundleDir.absolutePath}\n" + new File(sbgWorkDir, "sbg-output-file.txt").text = "${sbgJavaDir.absolutePath}\n" + + // Only jars that exist and actually open. Two kinds do not: + // + // - sibling library modules whose classes.jar is produced later + // in the build. Registering this task as a source generator + // puts it in front of javac, which is in front of those. + // - artifacts that are not really archives (soloader ships a + // runtime jar with no class entries). + // + // Either one aborts the generator with "Error while reading JAR + // entry" and takes the whole build down, so they are dropped and + // reported. The cost of dropping one is that a class extending + // something declared inside it is not pre-generated -- it still + // works, built on the device as in debug. + def usableJars = [] + def skippedJars = [] + resolveClasspathJars(variant).each { jar -> + if (!jar.name.endsWith(".jar")) return + if (!jar.isFile()) { + skippedJars.add("${jar.name} (not built yet)") + return + } + try { + new java.util.zip.ZipFile(jar).withCloseable { zip -> + if (zip.entries().findAll { it.name.endsWith(".class") }.isEmpty()) { + skippedJars.add("${jar.name} (no classes)") + } else { + usableJars.add(jar) + } + } + } catch (Exception e) { + skippedJars.add("${jar.name} (unreadable)") + } + } + + if (!skippedJars.isEmpty()) { + logger.lifecycle("NativeScript: skipping ${skippedJars.size()} classpath " + + "entr${skippedJars.size() == 1 ? 'y' : 'ies'} for binding generation: " + + skippedJars.join(", ")) + } + + new File(sbgWorkDir, "sbg-java-dependencies.txt").withWriter { out -> + usableJars.each { out.println it.absolutePath } + } + + // jsparser's Babel packages are dependencies of + // @nativescript/react-native, so they live in the app's + // node_modules rather than beside the copied tree. + environment "NODE_PATH", new File(reactRoot, "node_modules").absolutePath + + // The generator spawns `node` for jsparser itself, so the node + // resolved above has to be on the PATH it inherits -- otherwise + // the bundle step uses ours and the parser step silently uses a + // different one (or a version manager's shim, which fails). + def resolvedNode = new File(nodeExecutable) + if (resolvedNode.isAbsolute() && resolvedNode.parentFile != null) { + environment "PATH", resolvedNode.parentFile.absolutePath + + File.pathSeparator + System.getenv("PATH") + } + + // An app that subclasses nothing has no bindings to generate, + // which is an ordinary outcome -- most React Native apps call + // native APIs without ever extending a Java class. The generator + // does not treat it as one: jsparser writes sbg-bindings.txt + // only when it finds something, and the generator then fails + // with "Couldn't find ... bindings input file". Pre-creating the + // file does not help, because the generator deletes it before + // running the parser itself. + // + // So run the parser here first -- the same script, with the node + // and NODE_PATH resolved above -- and skip the generator when it + // comes back empty. The generator would re-run it anyway, so + // this costs one parse of an already-built bundle. + def parserScript = new File(sbgWorkDir, "jsparser/js_parser.js") + def sbgBindings = new File(sbgWorkDir, "sbg-bindings.txt") + if (parserScript.exists()) { + sbgBindings.delete() + def probeBuilder = new ProcessBuilder( + (nsNodeCommand + [parserScript.absolutePath]).collect { it.toString() }) + .directory(sbgWorkDir) + .redirectErrorStream(true) + probeBuilder.environment().put("NODE_PATH", + new File(reactRoot, "node_modules").absolutePath) + def probe = probeBuilder.start() + probe.inputStream.eachLine { logger.info(" [sbg-parser] $it") } + probe.waitFor() + + if (!sbgBindings.exists() || sbgBindings.text.trim().isEmpty()) { + logger.lifecycle("NativeScript: no Java subclasses found in the bundle, " + + "so there are no static bindings to generate. Any subclassing the " + + "app does at runtime is still bound on the device, as in debug.") + throw new org.gradle.api.tasks.StopExecutionException() + } + } + + def params = [sbgGeneratorJar.absolutePath] + // The generated names must be derived the same way the runtime + // derives them; the guest runtime always uses content keys. + params.add("-content-keyed-bindings") + args params.toArray() + } + } + + // Registering the directory is what puts the generated Java in front of + // javac; it also makes javac depend on this task, which is why the + // bundling above has to be free of any dependency on compiled classes. + variant.registerJavaGeneratingTask(bindingsTask.get(), sbgJavaDir) + + // Ship metadata for what the app can actually reach, rather than for + // every class on the compile classpath. + // + // Reuses the bundle the binding generator already produced -- the whole + // reason this is release-only is that debug has no JS on disk to read, + // and a filter built from a partial view of the program is how apps + // break in the field. The seed is only a starting point: the generator + // closes over supertypes, nested classes and signature types, and fails + // the build if that closure turns out unsound. + if (nsFilterMetadata) { + def seedTask = tasks.register("generateNativeScriptMetadataSeed${variantName}") { + group = "nativescript" + description = "Derives the metadata whitelist from the ${variant.name} bundle." + + dependsOn(bindingsTask) + + inputs.dir(sbgBundleDir) + inputs.files(fileTree(new File(toolsDir, "metadata-filter"))) + outputs.file(new File(workDir, "generated-whitelist.mdg")) + + doLast { + workDir.mkdirs() + + def seedScript = new File(toolsDir, "metadata-filter/seed.js") + if (!seedScript.exists()) { + throw new GradleException( + "NativeScript: ${seedScript} is missing. It ships with " + + "@nativescript/react-native; reinstall the package, or run " + + "scripts/build_react_native_android_tools.sh from a checkout.") + } + + def cmd = nsNodeCommand + [seedScript.absolutePath, sbgBundleDir.absolutePath, + "--out", new File(workDir, "generated-whitelist.mdg").absolutePath] + + def manifest = android.sourceSets.main.manifest.srcFile + if (manifest != null && manifest.exists()) { + cmd.addAll(["--manifest", manifest.absolutePath]) + } + + // Custom views, fragments and behaviours are named in + // resource XML and inflated by the framework; the JS never + // mentions them. + android.sourceSets.main.res.srcDirs.each { dir -> + if (dir.exists()) { + cmd.addAll(["--res", dir.absolutePath]) + } + } + + def bindings = new File(sbgWorkDir, "sbg-bindings.txt") + if (bindings.exists()) { + cmd.addAll(["--sbg-bindings", bindings.absolutePath]) + } + + logger.lifecycle("NativeScript: deriving metadata filter from ${sbgBundleDir}") + + // Real Strings: interpolated paths are GStrings, which + // ProcessBuilder's List will not take. + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + process.inputStream.eachLine { logger.lifecycle(" [metadata-seed] $it") } + def seedExit = process.waitFor() + if (seedExit != 0) { + throw new GradleException( + "NativeScript: metadata seed generation failed (exit ${seedExit}) " + + "using node '${nsNodeCommand.join(' ')}'." + + ((seedExit == 126 || seedExit == 127) ? "\n" + nsNodeHelp : "")) + } + } + } + + metadataTask.configure { it.dependsOn(seedTask) } + + // Filtered metadata names a subset of the classpath, so R8 is now + // free to remove the rest -- but only if it is told which subset, + // and only if the app has minification on in the first place. This + // adds the rules; it deliberately does not turn minification on, + // which is the app's decision to make. + if (variant.buildType.minifyEnabled) { + variant.buildType.proguardFiles.add(metadataKeepRules) + tasks.matching { + it.name == "minify${variantName}WithR8" || + it.name == "minify${variantName}WithProguard" + }.configureEach { it.dependsOn(metadataTask) } + } else { + logger.lifecycle("NativeScript: metadata filtering is on but ${variant.name} " + + "has minifyEnabled=false, so the keep rules at ${metadataKeepRules} " + + "are written and not applied. The metadata shrinks either way; the " + + "dex does not.") + } + } + } + + // Metadata describes the compiled classes, so it has to run after javac and + // before the assets are merged into the APK. + // Depend on the provider directly. Reaching into javaCompileProvider.configure + // to wire these up nests one task's configuration inside another's, which + // Gradle rejects. + metadataTask.configure { it.dependsOn(variant.javaCompileProvider) } + typingsTask.configure { it.dependsOn(variant.javaCompileProvider) } + + tasks.matching { it.name == "merge${variantName}Assets" }.configureEach { + it.dependsOn(metadataTask) + if (nsTypingsEnabled) { + it.dependsOn(typingsTask) + } + } + + // The typings land next to the app (types/android, checked in and named by + // tsconfig, the way types/ios is on the Apple side), which puts them inside + // the tree React Native's bundler reads. Gradle sees one task writing into + // another's input directory and fails the build rather than risk ordering + // them wrongly -- but only when both are scheduled, which is why a + // single-variant build never hit it and `assembleRelease assembleDebug` + // does: the *debug* typings task and the *release* bundle task have no + // relationship at all. + // + // Ordering rather than a dependency: the bundler does not consume the + // typings, it merely reads a directory that contains them. Making it depend + // on them would serialise a JS bundle behind a full classpath scan for no + // reason. + // + // Only across variants. Within one variant the bundle task runs *before* + // javac (its output feeds the resource pipeline) while typings run after it, + // so ordering that pair asks for a cycle: + // + // createBundleRelease -> typingsRelease -> compileReleaseJavaWithJavac + // -> ... -> mapReleaseSourceSetPaths -> createBundleRelease + // + // and Gradle rejects it. The conflict this fixes is a cross-variant one + // anyway: the release bundle and the *debug* typings task have no + // relationship at all, which is why they only collide when both variants + // are built in a single invocation. + def ownBundleTask = "createBundle${variantName}JsAndAssets" + tasks.matching { + it.name.startsWith("createBundle") && it.name.endsWith("JsAndAssets") && + it.name != ownBundleTask + }.configureEach { + it.mustRunAfter(typingsTask) + } + + // Lint reads the asset source sets, which now include a generated directory. + // Gradle fails the build on an undeclared read of another task's output + // rather than risking a stale result, so the ordering is stated. Matched by + // name because the lint tasks vary between AGP versions and not all of them + // exist in every build. + tasks.matching { + it.name.startsWith("lint") || it.name.startsWith("generate${variantName}Lint") + }.configureEach { + it.mustRunAfter(metadataTask) + it.dependsOn(metadataTask) + } +} diff --git a/packages/react-native/android/scripts/bundle-for-analysis.js b/packages/react-native/android/scripts/bundle-for-analysis.js new file mode 100644 index 000000000..11fae01c9 --- /dev/null +++ b/packages/react-native/android/scripts/bundle-for-analysis.js @@ -0,0 +1,127 @@ +#!/usr/bin/env node +/* + * Builds every JS graph the app ships into one directory, for the static + * binding generator and the metadata seed to read. + * + * Why this exists rather than one `react-native bundle` per entry: an app's + * secondary entry points (workers) are separate module graphs that appear in no + * other bundle, so each has to be built -- but a CLI invocation per entry pays a + * fresh Node process, a fresh Metro config load and a cold transform cache every + * time. Loading the config once and reusing it across entries is what React + * Native's own tooling does, and what a worker bundler does too. + * + * Everything is resolved from the *app's* root, never from this package: the + * Metro, React Native and community-cli-plugin that build the analysis bundles + * must be the ones that build the shipping bundles, or the analysis describes a + * program the device never runs. + * + * Usage: + * bundle-for-analysis.js --project-root --out --entry + * [--worker =]… [--resolver-option k=v]… + */ + +const fs = require("fs"); +const path = require("path"); + +function reqFrom(fromDir, request) { + return require(require.resolve(request, { paths: [fromDir] })); +} + +function parseArgs(argv) { + const args = { workers: [], resolverOptions: [] }; + for (let i = 0; i < argv.length; i++) { + const key = argv[i]; + if (key === "--project-root") args.projectRoot = argv[++i]; + else if (key === "--out") args.out = argv[++i]; + else if (key === "--entry") args.entry = argv[++i]; + else if (key === "--resolver-option") args.resolverOptions.push(argv[++i]); + else if (key === "--worker") { + const value = argv[++i]; + const eq = value.indexOf("="); + if (eq > 0) args.workers.push({ id: value.slice(0, eq), file: value.slice(eq + 1) }); + } + } + return args; +} + +async function loadMetroConfig(projectRoot, platform) { + /* The same path @react-native/community-cli-plugin takes: its own loader when + * present, else Metro's. Both read the app's metro.config.js. */ + try { + const { loadConfigAsync } = reqFrom(projectRoot, "@react-native/community-cli-plugin"); + if (typeof loadConfigAsync === "function") { + return await loadConfigAsync({ root: projectRoot, platform }, { platform }); + } + } catch (_) { + /* fall through to Metro's own loader */ + } + const metro = reqFrom(projectRoot, "metro"); + return await metro.loadConfig({ cwd: projectRoot }); +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + if (!args.projectRoot || !args.out || !args.entry) { + console.error("bundle-for-analysis: --project-root, --out and --entry are required"); + process.exit(2); + } + + const { unstable_buildBundleWithConfig } = reqFrom( + args.projectRoot, + "@react-native/community-cli-plugin" + ); + if (typeof unstable_buildBundleWithConfig !== "function") { + /* Older React Native. The caller falls back to one CLI run per entry, which + * is slower but produces the same bundles. */ + console.error("bundle-for-analysis: unstable_buildBundleWithConfig is unavailable"); + process.exit(3); + } + + fs.mkdirSync(args.out, { recursive: true }); + const config = await loadMetroConfig(args.projectRoot, "android"); + + const build = async (label, entryFile, outFile, resolverOption) => + unstable_buildBundleWithConfig( + { + platform: "android", + dev: false, + /* Readable output: the analysis reads class and member names out of + * this, and a minified graph hides most of them. */ + minify: false, + entryFile, + bundleOutput: outFile, + assetsDest: path.join(args.out, "assets"), + /* Keeps `class X extends Y {}` intact. React Native's Babel preset + * downlevels classes into a helper call otherwise, and a downlevelled + * class is not something static analysis can recognise as a subclass. */ + unstableTransformProfile: "hermes-stable", + ...(resolverOption ? { resolverOption } : {}), + }, + config + ).catch((e) => { + throw new Error(`bundle-for-analysis: ${label} failed: ${e.stack || e.message}`); + }); + + await build("app entry", args.entry, path.join(args.out, "index.android.bundle"), null); + + for (const worker of args.workers) { + const safe = worker.id.replace(/[^A-Za-z0-9]/g, "_"); + await build( + `worker ${worker.id}`, + worker.file, + path.join(args.out, `worker-${safe}.js`), + /* Worker graphs resolve `react-native` to the worker shim; without the + * flag this would analyse a graph the device never runs. */ + args.resolverOptions.length > 0 ? args.resolverOptions : null + ); + } + + console.error( + `bundle-for-analysis: built ${1 + args.workers.length} bundle(s) in one Metro session` + ); +} + +main().catch((e) => { + console.error(e.message || e); + process.exit(1); +}); diff --git a/packages/react-native/android/src/main/AndroidManifest.xml b/packages/react-native/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..94cbbcfc3 --- /dev/null +++ b/packages/react-native/android/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/packages/react-native/android/src/main/assets/nativescript/internal/ts_helpers.js b/packages/react-native/android/src/main/assets/nativescript/internal/ts_helpers.js new file mode 100644 index 000000000..bc6fe53fc --- /dev/null +++ b/packages/react-native/android/src/main/assets/nativescript/internal/ts_helpers.js @@ -0,0 +1,454 @@ +(function () { + var __decorate = + (this && this.__decorate) || + function (decorators, target, key, desc) { + var c = arguments.length; + var r = + c < 3 + ? target + : desc === null + ? (desc = Object.getOwnPropertyDescriptor(target, key)) + : desc, + d; + + if ( + typeof globalThis.Reflect === "object" && + typeof globalThis.Reflect.decorate === "function" + ) { + r = globalThis.Reflect.decorate(decorators, target, key, desc); + } else { + for (var i = decorators.length - 1; i >= 0; i--) { + if ((d = decorators[i])) { + r = + (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + } + } + } + return c > 3 && r && Object.defineProperty(target, key, r), r; + }; + + // For backward compatibility. + var __native = function (thiz) { + // we are setting the __container__ property to the base class when the super method is called + // if the constructor returns the __native(this) call we will use the old implementation + // copying all the properties to the result + // otherwise if we are using the result from the super() method call we won't need such logic + // as thiz already contains the parent properties + // this way we now support both implementations in typescript generated constructors: + // 1: super(); return __native(this); + // 2: return super() || this; + // if (thiz.__container__) { + // if (__useHostObjects) { + // for (var prop in thiz) { + // if (thiz.hasOwnProperty(prop)) { + // thiz.__proto__[prop] = thiz[prop]; + // delete thiz[prop]; + // } + // } + // } + + return thiz; + // } else { + // return thiz; + // } + }; + + var __extends = function (Child, Parent) { + // Detect a native class by a brand the runtime stamps on its native extend() rather than + // sniffing Parent.extend.toString() for "[native code]": in release/bytecode builds every + // function (JS or native) stringifies to "[native code]", which misdetects a plain JS class + // with a static "extend" method as native. The brand works for source and bytecode alike. + var extendNativeClass = + !!Parent.extend && Parent.extend.__isNativeExtend__ === true; + + if (!extendNativeClass) { + __extends_ts(Child, Parent); + return; + } + if (Parent.__isPrototypeImplementationObject) { + throw new Error("Can not extend an already extended native object."); + } + + function extend(thiz) { + var child = thiz.__proto__.__child; + if (!child.__extended) { + var parent = thiz.__proto__.__parent; + child.__extended = parent.extend(child.name, child.prototype, true); + // This will deal with "i instanceof child" + child[Symbol.hasInstance] = function (instance) { + return instance instanceof this.__extended; + }; + } + return child.__extended; + } + + Parent.__activityExtend = function (parent, name, implementationObject) { + __log("__activityExtend called"); + return parent.extend(name, implementationObject); + }; + + Parent.call = function (thiz) { + var Extended = extend(thiz); + thiz.__container__ = true; + if (arguments.length > 1) { + if (typeof Extended !== "function") { + thiz = Reflect.construct( + Extended, + Array.prototype.slice.call(arguments, 1) + ); + } else { + thiz = new (Function.prototype.bind.apply( + Extended, + [null].concat(Array.prototype.slice.call(arguments, 1)) + ))(); + } + } else { + thiz = new Extended(); + } + return thiz; + }; + + Parent.apply = function (thiz, args) { + var Extended = extend(thiz); + thiz.__container__ = true; + if (args && args.length > 0) { + if (typeof Extended !== "function") { + thiz = Reflect.construct(Extended, [null].concat(args)); + } else { + thiz = new (Function.prototype.bind.apply( + Extended, + [null].concat(args) + ))(); + } + } else { + thiz = new Extended(); + } + return thiz; + }; + __extends_ns(Child, Parent); + Child.__isPrototypeImplementationObject = true; + Child.__proto__ = Parent; + Child.prototype.__parent = Parent; + Child.prototype.__child = Child; + }; + + var __extends_ts = function (child, parent) { + extendStaticFunctions(child, parent); + assignPrototypeFromParentToChild(parent, child); + }; + + var __extends_ns = function (child, parent) { + if (!parent.extend) { + assignPropertiesFromParentToChild(parent, child); + } + + assignPrototypeFromParentToChild(parent, child); + }; + + var extendStaticFunctions = + Object.setPrototypeOf || + (hasInternalProtoProperty() && + function (child, parent) { + child.__proto__ = parent; + }) || + assignPropertiesFromParentToChild; + + function hasInternalProtoProperty() { + return { __proto__: [] } instanceof Array; + } + + function assignPropertiesFromParentToChild(parent, child) { + for (var property in parent) { + if (parent.hasOwnProperty(property)) { + child[property] = parent[property]; + } + } + } + + function assignPrototypeFromParentToChild(parent, child) { + function __() { + this.constructor = child; + } + + if (parent === null) { + child.prototype = Object.create(null); + } else { + __.prototype = parent.prototype; + child.prototype = new __(); + } + } + + function JavaProxy(className) { + return function (target) { + var extended = target.extend(className, target.prototype); + extended.name = className; + return extended; + }; + } + + function Interfaces(interfacesArr) { + return function (target) { + if (interfacesArr instanceof Array) { + // attach interfaces: [] to the object + target.prototype.interfaces = interfacesArr; + } + }; + } + Object.defineProperty(globalThis, "__native", { value: __native }); + Object.defineProperty(globalThis, "__extends", { value: __extends }); + Object.defineProperty(globalThis, "__decorate", { value: __decorate }); + + + + if (!globalThis.__ns__worker) { + globalThis.JavaProxy = JavaProxy; + } + globalThis.Interfaces = Interfaces; + + if (globalThis.WeakRef && !globalThis.WeakRef.prototype.get) { + globalThis.WeakRef.prototype.get = globalThis.WeakRef.prototype.deref; + } + + // Native array access: numeric indexing and the map/forEach/toString/ + // Symbol.iterator helpers are now implemented natively (see MetadataNode's + // array prototype + the host object's indexed accessors), so the old JS + // getNativeArrayProp/setNativeArrayProp helpers are gone. + + function findInPrototypeChain(obj, prop) { + while (obj) { + if (obj.hasOwnProperty(prop)) { + return Object.getOwnPropertyDescriptor(obj, prop); + } + obj = Object.getPrototypeOf(obj); + } + return undefined; + } + + globalThis.__prepareHostObject = function (hostObject, jsThis) { + // const prototype = Object.getPrototypeOf(jsThis); + // Object.setPrototypeOf(hostObject, prototype); + Object.defineProperty(hostObject, "super", { + get: () => jsThis["super"], + }); + }; + + const EXTERNAL_PROP = "[[external]]"; + const REFERENCE_PROP_JSC = "[[jsc_reference_info]]"; + + function __createNativeProxy(object, objectId) { + const proxy = new Proxy(object, { + get: function (target, prop) { + if (prop === EXTERNAL_PROP) return this[EXTERNAL_PROP]; + if (prop === REFERENCE_PROP_JSC) return this[REFERENCE_PROP_JSC]; + // Numeric indices go straight to the native element accessor; the + // map/forEach/toString/Symbol.iterator helpers live on the array + // prototype now, so everything else just forwards to the target. + if (target.__is__javaArray && typeof prop !== "symbol" && !isNaN(prop)) { + return target.getValueAtIndex(parseInt(prop)); + } + return target[prop]; + }, + set: function (target, prop, value) { + if (prop === EXTERNAL_PROP) { + this[EXTERNAL_PROP] = value; + return true; + } + + if (prop === REFERENCE_PROP_JSC) { + this[REFERENCE_PROP_JSC] = value; + } + + if (target.__is__javaArray && !isNaN(prop)) { + target.setValueAtIndex(parseInt(prop), value); + return true; + } + + target[prop] = value; + return true; + }, + }); + return proxy; + } + globalThis.__createNativeProxy = __createNativeProxy; + + globalThis.getErrorStack = (err) => { + if (err) return err.stack; + const stack = new Error("").stack; + const lines = stack.split("\n"); + // Line 2 results in invalid stack if not replaced when doing typescript extend. + lines[2] = " at extend(native)"; + return lines.join("\n"); + }; + + if (globalThis.URL) { + const BLOB_STORE = new Map(); + URL.createObjectURL = function (object, options = null) { + try { + if (object instanceof Blob || object instanceof File) { + const id = java.util.UUID.randomUUID().toString(); + const ret = `blob:nativescript/${id}`; + BLOB_STORE.set(ret, { + blob: object, + type: object ? object.type : undefined, + ext: options ? options.ext : undefined, + }); + return ret; + } + } catch (error) { + return null; + } + return null; + }; + URL.revokeObjectURL = function (url) { + BLOB_STORE.delete(url); + }; + function InternalAccessor() {} + InternalAccessor.getData = function (url) { + return BLOB_STORE.get(url); + }; + URL.InternalAccessor = InternalAccessor; + const searchParamsDescriptor = Object.getOwnPropertyDescriptor( + URL.prototype, + "searchParams" + ); + if (!searchParamsDescriptor || searchParamsDescriptor.configurable) { + Object.defineProperty(URL.prototype, "searchParams", { + get() { + if (this._searchParams == null) { + this._searchParams = new URLSearchParams(this.search); + Object.defineProperty(this._searchParams, "_url", { + enumerable: false, + writable: false, + value: this, + }); + this._searchParams._append = this._searchParams.append; + this._searchParams.append = function (name, value) { + this._append(name, value); + this._url.search = this.toString(); + }; + this._searchParams._delete = this._searchParams.delete; + this._searchParams.delete = function (name) { + this._delete(name); + this._url.search = this.toString(); + }; + this._searchParams._set = this._searchParams.set; + this._searchParams.set = function (name, value) { + this._set(name, value); + this._url.search = this.toString(); + }; + this._searchParams._sort = this._searchParams.sort; + this._searchParams.sort = function () { + this._sort(); + this._url.search = this.toString(); + }; + } + return this._searchParams; + }, + }); + } + } + + const pendingUnhandledRejections = []; + const hasBeenNotifiedProperty = new WeakMap(); + function emitPendingUnhandledRejections() { + while (pendingUnhandledRejections.length > 0) { + var promise = pendingUnhandledRejections.shift(); + var reason = pendingUnhandledRejections.shift(); + if ( + hasBeenNotifiedProperty.get(promise) === false && + globalThis.__onUncaughtError + ) { + globalThis.__onUncaughtError(reason); + } + } + } + + function unhandledPromise(promise, reason) { + pendingUnhandledRejections.push(promise, reason); + __ns__setTimeout(() => { + emitPendingUnhandledRejections(); + }, 1); + } + + function handledPromise(promise) { + const hasBeenNotified = hasBeenNotifiedProperty.get(promise); + if (hasBeenNotified != undefined) { + hasBeenNotifiedProperty.delete(promise); + } + } + + function makeRejectionError(reason) { + const stringValue = Object.prototype.toString.call(reason); + if ( + stringValue === "[object Error]" || + reason instanceof Error || + typeof reason === "object" + ) { + reason.message = `(Unhandled promise rejection): ${reason.message}`; + + if (!reason.stack) { + reason.stack = new Error("").stack; + } + return reason; + } else { + const error = new Error(reason, { + cause: reason, + }); + error.message = `(Unhandled promise rejection): ${error.message}`; + return error; + } + } + + if (globalThis.__engine === "V8") { + // Only report errors for promise rejections that go unhandled. + globalThis.onUnhandledPromiseRejectionTracker = ( + event, + promise, + reason + ) => { + if (event === globalThis.__promiseUnhandledEvent) { + hasBeenNotifiedProperty.set(promise, false); + const error = makeRejectionError(reason); + unhandledPromise(promise, error); + } else { + handledPromise(promise); + } + }; + } else if (globalThis.__engine === "QuickJS") { + globalThis.onUnhandledPromiseRejectionTracker = ( + promise, + reason, + isHandled + ) => { + if (!isHandled) { + hasBeenNotifiedProperty.set(promise, false); + const error = makeRejectionError(reason); + // Preserve original stack trace. + if (!promise.then["[[stack]]"]) { + promise.then["[[stack]]"] = error.stack; + } else { + error.stack = promise.then["[[stack]]"]; + } + unhandledPromise(promise, error); + } else { + handledPromise(promise); + } + }; + } else if (globalThis.__engine === "JSC") { + // JSC's unhandled-rejection callback only fires for rejections that go + // unhandled (there is no "handled later" retraction event), and it is + // invoked with (promise, reason). + globalThis.onUnhandledPromiseRejectionTracker = (promise, reason) => { + hasBeenNotifiedProperty.set(promise, false); + const error = makeRejectionError(reason); + unhandledPromise(promise, error); + }; + } else if (globalThis.__engine === "Hermes") { + HermesInternal.enablePromiseRejectionTracker({ + allRejections: true, + onUnhandled: function (id, error) { + globalThis.__onUncaughtError(makeRejectionError(error)); + }, + }); + } +})(); diff --git a/packages/react-native/android/src/main/cpp/NativeScriptRnInstall.cpp b/packages/react-native/android/src/main/cpp/NativeScriptRnInstall.cpp new file mode 100644 index 000000000..f08e3c5f2 --- /dev/null +++ b/packages/react-native/android/src/main/cpp/NativeScriptRnInstall.cpp @@ -0,0 +1,84 @@ +// The guest entry point: binds the NativeScript runtime to the jsi::Runtime +// React Native owns. +// +// This is the Android counterpart of ffi/objc/hermes/NativeApiJsi.mm, and it is +// deliberately small. Everything that makes NativeScript work -- the metadata +// tree, the object manager, the JNI interop -- is the same code the standalone +// runtime compiles; all that differs is who created the runtime it installs +// into. See NativeScript/runtime/android/jsi/EngineHost.h. + +#include +#include + +#include + +#include "ArgConverter.h" +#include "NativeScriptException.h" +#include "Runtime.h" + +using namespace tns; + +extern "C" JNIEXPORT void JNICALL Java_com_tns_Runtime_attachNativeScript( + JNIEnv* env, jobject obj, jint runtimeId, jstring metadataPath, jlong hostRuntimePtr, + jboolean verboseLoggingEnabled, jint maxLogcatObjectSize, jboolean forceLog, + jstring bootstrapScript) { + try { + if (hostRuntimePtr == 0) { + throw NativeScriptException( + "No JavaScript runtime was provided. NativeScript needs the host's " + "jsi::Runtime, which React Native exposes through its " + "JavaScriptContextHolder."); + } + + auto* hostRuntime = reinterpret_cast(hostRuntimePtr); + std::string metadataDir = ArgConverter::jstringToString(metadataPath); + + Runtime* runtime = Runtime::Attach(env, obj, runtimeId, *hostRuntime, metadataDir, + verboseLoggingEnabled == JNI_TRUE, maxLogcatObjectSize, + forceLog == JNI_TRUE); + + // ts_helpers.js. The standalone runtime runs this as its first module; + // there is no module loader here, so the embedder reads it out of the + // library's assets and hands the source over. Without it there is no + // __extends, no JavaProxy and -- the one that matters most -- no + // __createNativeProxy, which every Java object handed to JS goes through. + if (bootstrapScript != nullptr) { + std::string source = ArgConverter::jstringToString(bootstrapScript); + if (!source.empty()) { + JSScope scope(runtime->GetEngineHost()); + runtime->GetEngineHost()->ExecuteScript(source, "internal/ts_helpers.js"); + } + } + } catch (NativeScriptException& e) { + e.ReThrowToJava(nullptr); + } catch (std::exception& e) { + NativeScriptException nsEx(std::string("Error: c++ exception: ") + e.what()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} + +extern "C" JNIEXPORT void JNICALL Java_com_tns_Runtime_detachNativeScript(JNIEnv*, jobject, + jint runtimeId) { + try { + Runtime::DisposeRuntime(Runtime::GetRuntime(runtimeId)); + } catch (NativeScriptException& e) { + e.ReThrowToJava(nullptr); + } catch (std::exception& e) { + NativeScriptException nsEx(std::string("Error: c++ exception: ") + e.what()); + nsEx.ReThrowToJava(nullptr); + } catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(nullptr); + } +} + +extern "C" JNIEXPORT jstring JNICALL +Java_org_nativescript_reactnative_NativeScriptNativeApiModule_nativeRuntimeBackend(JNIEnv* env, + jobject) { + // What the *engine layer* is, not which VM sits underneath: a guest build + // talks jsi and cannot tell Hermes from JSC, and does not need to. + return env->NewStringUTF("jsi"); +} diff --git a/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptLogger.java b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptLogger.java new file mode 100644 index 000000000..4e4e24555 --- /dev/null +++ b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptLogger.java @@ -0,0 +1,45 @@ +package org.nativescript.reactnative; + +import android.util.Log; + +import com.tns.Logger; + +/** + * Routes the runtime's diagnostics to logcat. + * + *

The standalone runtime reads its verbosity from the app's package.json. A + * guest has no such file, so it is passed in at construction and defaults off: + * the runtime logs every marshalled call when enabled, which is far too much + * for an app that is only using NativeScript for a few APIs. + */ +public class NativeScriptLogger implements Logger { + private static final String TAG = "TNS.Java"; + + private boolean enabled; + + public NativeScriptLogger(boolean enabled) { + this.enabled = enabled; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean isEnabled) { + this.enabled = isEnabled; + } + + @Override + public void write(String msg) { + write(TAG, msg); + } + + @Override + public void write(String tag, String msg) { + if (enabled) { + Log.d(tag, msg); + } + } +} diff --git a/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiModule.java b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiModule.java new file mode 100644 index 000000000..3b5e5508e --- /dev/null +++ b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiModule.java @@ -0,0 +1,284 @@ +package org.nativescript.reactnative; + +import android.content.Context; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.queue.MessageQueueThread; +import com.facebook.react.module.annotations.ReactModule; + +import com.tns.Logger; +import com.tns.Runtime; + +import org.nativescript.nativeapi.NativeScriptNativeApiSpec; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Installs NativeScript into React Native's JavaScript runtime. + * + *

The interesting call is {@link #install(String)}, which must run on the JS + * thread -- it is a synchronous TurboModule method, so React Native already + * guarantees that. Everything it does happens against the runtime React Native + * created; nothing here starts a JavaScript engine. + * + *

React Native creates one instance of this module per JavaScript runtime and + * invalidates it before destroying that runtime, which is where NativeScript + * lets go of it. A reloaded bundle installs into its new runtime through a new + * instance. + */ +@ReactModule(name = NativeScriptNativeApiModule.NAME) +public class NativeScriptNativeApiModule extends NativeScriptNativeApiSpec { + public static final String NAME = "NativeScriptNativeApi"; + + /** Written into the APK by nativescript.gradle. */ + private static final String ASSET_METADATA_DIR = "nativescript-metadata"; + + /** Ships with this library; see src/main/assets. */ + private static final String ASSET_TS_HELPERS = "nativescript/internal/ts_helpers.js"; + + private static final String[] METADATA_FILES = { + "treeNodeStream.dat", "treeStringsStream.dat", "treeValueStream.dat", + }; + + static { + System.loadLibrary("nativescript_rn"); + } + + private final ReactApplicationContext reactContext; + + private volatile Runtime attachedRuntime; + + public NativeScriptNativeApiModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + + @Override + public String getName() { + return NAME; + } + + private native String nativeRuntimeBackend(); + + @Override + public boolean install(String metadataPath) { + if (attachedRuntime != null) { + return true; + } + + Context context = reactContext.getApplicationContext(); + String resolvedPath = (metadataPath == null || metadataPath.isEmpty()) + ? defaultMetadataPath() + : metadataPath; + + try { + stageMetadata(context, new File(resolvedPath)); + } catch (IOException e) { + throw new RuntimeException( + "NativeScript could not stage its metadata. Check that nativescript.gradle is " + + "applied in android/app/build.gradle so the metadata is generated " + + "into the APK.", e); + } + + // The runtime reads a jsi::Runtime pointer, not a bridge. This holder is + // how React Native hands one out, and it stays valid for the life of the + // JS context -- a reload replaces the context and re-runs install(). + long runtimePtr = reactContext.getJavaScriptContextHolder().get(); + if (runtimePtr == 0) { + throw new RuntimeException( + "React Native did not expose a JavaScript runtime. NativeScript requires the " + + "JSI runtime and cannot run over the legacy bridge."); + } + + attachedRuntime = attachRuntime(context, runtimePtr, resolvedPath, + readAsset(context, ASSET_TS_HELPERS)); + return true; + } + + @Override + public void invalidate() { + Runtime runtime = attachedRuntime; + attachedRuntime = null; + if (runtime != null) { + // Detaching releases handles into the jsi::Runtime, so it has to run + // before React Native destroys that runtime and never alongside JS. + // Bridgeless has already stopped the JS thread here, so the post is + // refused and this thread is safe. The bridge can invalidate from + // another thread while JS still runs, and queues the runtime's + // destruction on the JS thread behind this. + MessageQueueThread jsThread = reactContext.getJSMessageQueueThread(); + if (jsThread == null || jsThread.isOnThread() || !jsThread.runOnQueue(runtime::detach)) { + runtime.detach(); + } + } + super.invalidate(); + } + + @Override + public boolean installWorkletRuntime(com.facebook.react.bridge.ReadableMap runtimeHolder, + String metadataPath) { + // Worklet runtimes are separate jsi::Runtimes on separate threads. Each + // would need its own attach and its own object manager, which the object + // graph does not yet model on Android; iOS reaches this through + // NativeApiJsi.mm. Reported honestly rather than silently no-oped. + return false; + } + + @Override + public boolean isInstalled() { + return attachedRuntime != null; + } + + /** + * The directory the runtime is pointed at. + * + *

Note this is the parent: MetadataBuilder appends "/metadata" + * to whatever it is handed, so the .dat files are staged one level below. + */ + @Override + public String defaultMetadataPath() { + return new File(reactContext.getApplicationContext().getFilesDir(), "nativescript") + .getAbsolutePath(); + } + + @Override + public String getRuntimeBackend() { + return nativeRuntimeBackend(); + } + + @Override + public boolean __writeTestMarker(String content) { + try { + File marker = new File(reactContext.getApplicationContext().getFilesDir(), + "nativescript-test-marker.txt"); + try (OutputStream out = new FileOutputStream(marker)) { + out.write(content.getBytes("UTF-8")); + } + return true; + } catch (IOException e) { + return false; + } + } + + private static Runtime attachRuntime(Context context, long runtimePtr, String metadataPath, + String bootstrapScript) { + File filesDir = context.getFilesDir(); + File dexDir = new File(filesDir, "nativescript-dex"); + if (!dexDir.exists() && !dexDir.mkdirs()) { + throw new RuntimeException("Could not create " + dexDir.getAbsolutePath()); + } + + Logger logger = new NativeScriptLogger(isDebuggable(context)); + + return Runtime.attachToHostRuntime( + logger, + context.getPackageName(), + context.getApplicationInfo().nativeLibraryDir, + filesDir, + context.getClassLoader(), + dexDir, + /* dexThumb */ buildStamp(context), + isDebuggable(context), + runtimePtr, + metadataPath, + bootstrapScript); + } + + /** + * Reads one of this library's own assets. + * + *

Kept in memory rather than staged to disk like the metadata: it is a + * single small script that is evaluated once, and the runtime takes it as + * source rather than as a path. + */ + private static String readAsset(Context context, String name) { + try (InputStream in = context.getAssets().open(name)) { + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + byte[] buffer = new byte[16 * 1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + return out.toString("UTF-8"); + } catch (IOException e) { + throw new RuntimeException("NativeScript could not read its own asset: " + name, e); + } + } + + /** + * Identifies this build of the app. + * + *

Used both as the dex cache key and as the metadata staging stamp, so it + * has to change whenever the app is rebuilt or upgraded -- otherwise a new + * build reads the previous one's generated proxies and metadata. + */ + private static String buildStamp(Context context) { + try { + android.content.pm.PackageInfo info = context.getPackageManager() + .getPackageInfo(context.getPackageName(), 0); + return info.versionName + "-" + info.lastUpdateTime; + } catch (android.content.pm.PackageManager.NameNotFoundException e) { + throw new RuntimeException("Could not read this app's own package info", e); + } + } + + private static boolean isDebuggable(Context context) { + return (context.getApplicationInfo().flags + & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + + /** + * Copies the generated metadata out of the APK once. + * + *

The reader takes a directory of files, and assets inside an APK are not + * files. Extraction is keyed on the build stamp so a rebuild that regenerates + * metadata does not keep serving the previous build's copy. + */ + private static void stageMetadata(Context context, File root) throws IOException { + File stamp = new File(root, ".version"); + String version = buildStamp(context); + + if (stamp.exists() && version.equals(readAll(stamp))) { + return; + } + + // MetadataBuilder::BuildMetadata appends "/metadata" to the path it is + // given, so the files go one level below the directory we report. + File target = new File(root, "metadata"); + if (!target.exists() && !target.mkdirs()) { + throw new IOException("Could not create " + target.getAbsolutePath()); + } + + for (String name : METADATA_FILES) { + try (InputStream in = context.getAssets().open(ASSET_METADATA_DIR + "/" + name); + OutputStream out = new FileOutputStream(new File(target, name))) { + byte[] buffer = new byte[64 * 1024]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } + } + + try (OutputStream out = new FileOutputStream(stamp)) { + out.write(version.getBytes("UTF-8")); + } + } + + private static String readAll(File file) throws IOException { + byte[] data = new byte[(int) file.length()]; + try (InputStream in = new java.io.FileInputStream(file)) { + int read = 0; + while (read < data.length) { + int n = in.read(data, read, data.length - read); + if (n < 0) break; + read += n; + } + } + return new String(data, "UTF-8"); + } +} diff --git a/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiPackage.java b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiPackage.java new file mode 100644 index 000000000..443c1b570 --- /dev/null +++ b/packages/react-native/android/src/main/java/org/nativescript/reactnative/NativeScriptNativeApiPackage.java @@ -0,0 +1,42 @@ +package org.nativescript.reactnative; + +import androidx.annotation.Nullable; + +import com.facebook.react.BaseReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; + +import java.util.HashMap; +import java.util.Map; + +/** Autolinking entry point for @nativescript/react-native. */ +public class NativeScriptNativeApiPackage extends BaseReactPackage { + + @Nullable + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + if (NativeScriptNativeApiModule.NAME.equals(name)) { + return new NativeScriptNativeApiModule(reactContext); + } + return null; + } + + @Override + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return () -> { + Map infos = new HashMap<>(); + infos.put( + NativeScriptNativeApiModule.NAME, + new ReactModuleInfo( + NativeScriptNativeApiModule.NAME, + NativeScriptNativeApiModule.NAME, + /* canOverrideExistingModule */ false, + /* needsEagerInit */ false, + /* isCxxModule */ false, + /* isTurboModule */ true)); + return infos; + }; + } +} diff --git a/packages/react-native/cli/configure-android.js b/packages/react-native/cli/configure-android.js new file mode 100644 index 000000000..477d398f5 --- /dev/null +++ b/packages/react-native/cli/configure-android.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const path = require('path'); + +const APPLY_LINE = + 'apply from: "../../node_modules/@nativescript/react-native/android/nativescript.gradle"'; + +const TYPINGS = ['types/android/android.d.ts', 'types/android/android-declarations.d.ts']; + +/** + * Adds the NativeScript Gradle script to the app module. + * + * Autolinking brings in the runtime itself, but metadata and typings are + * generated from the *app's* resolved classpath, so they have to be tasks in + * the app project rather than in the library. + */ +function ensureGradleScript(projectRoot) { + const buildGradle = path.join(projectRoot, 'android', 'app', 'build.gradle'); + if (!fs.existsSync(buildGradle)) { + return { changed: false, reason: 'android/app/build.gradle was not found' }; + } + + let source = fs.readFileSync(buildGradle, 'utf8'); + if (source.includes('@nativescript/react-native/android/nativescript.gradle')) { + return { changed: false, reason: 'already applied' }; + } + + // After the React plugin, whose applicationVariants the script hooks into. + const reactPlugin = /apply plugin: ["']com\.facebook\.react["']\s*\n/; + if (reactPlugin.test(source)) { + source = source.replace(reactPlugin, (match) => `${match}${APPLY_LINE}\n`); + } else { + source = `${APPLY_LINE}\n${source}`; + } + + fs.writeFileSync(buildGradle, source); + return { changed: true }; +} + +/** Points tsconfig at the generated declarations, if they are being generated. */ +function ensureTypings(projectRoot) { + const tsconfigPath = path.join(projectRoot, 'tsconfig.json'); + if (!fs.existsSync(tsconfigPath)) { + return { changed: false, reason: 'tsconfig.json was not found' }; + } + + const source = fs.readFileSync(tsconfigPath, 'utf8'); + let tsconfig; + try { + tsconfig = JSON.parse(source); + } catch (e) { + // tsconfig.json permits comments; rewriting it would lose them, and a + // hand-edited config is likelier to be deliberate than stale. + return { changed: false, reason: 'tsconfig.json is not plain JSON; add the types manually' }; + } + + const include = Array.isArray(tsconfig.include) ? tsconfig.include.slice() : []; + const missing = TYPINGS.filter((entry) => !include.includes(entry)); + if (missing.length === 0) { + return { changed: false, reason: 'already referenced' }; + } + + tsconfig.include = include.concat(missing); + fs.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`); + return { changed: true }; +} + +function configureAndroid(projectRoot) { + const gradle = ensureGradleScript(projectRoot); + const typings = ensureTypings(projectRoot); + + if (gradle.changed) { + console.log('Added the NativeScript Gradle script to android/app/build.gradle.'); + } else { + console.log(`Android Gradle script: ${gradle.reason}.`); + } + + if (typings.changed) { + console.log('Added the Android typings to tsconfig.json.'); + } else { + console.log(`Android typings: ${typings.reason}.`); + } + + return { gradle, typings }; +} + +module.exports = { + APPLY_LINE, + configureAndroid, + ensureGradleScript, + ensureTypings, +}; diff --git a/packages/react-native/cli/configure.js b/packages/react-native/cli/configure.js old mode 100644 new mode 100755 index fea36f3d0..79c4a015d --- a/packages/react-native/cli/configure.js +++ b/packages/react-native/cli/configure.js @@ -7,6 +7,7 @@ const { ensureMetadataConfig, normalizeMetadataOptions, } = require('../plugin/withNativeScriptReactNative'); +const { configureAndroid } = require('./configure-android'); function readJson(filePath) { if (!fs.existsSync(filePath)) { @@ -22,8 +23,12 @@ function projectRoot() { function printNextSteps() { console.log('NativeScript React Native configuration checked.'); console.log('Next steps:'); - console.log(' cd ios'); - console.log(' RCT_NEW_ARCH_ENABLED=1 USE_HERMES=1 pod install'); + console.log(' iOS : cd ios && RCT_NEW_ARCH_ENABLED=1 USE_HERMES=1 pod install'); + console.log(' Android : npx react-native run-android'); + console.log(''); + console.log(' Android typings are written to types/android/ on the first build'); + console.log(' (opt out with -PnsGenerateTypings=false). To generate them on their own:'); + console.log(' cd android && ./gradlew generateNativeScriptTypingsDebug'); } function configure(argv = process.argv.slice(2)) { @@ -71,6 +76,8 @@ function configure(argv = process.argv.slice(2)) { } } + configureAndroid(root); + printNextSteps(); } diff --git a/packages/react-native/cli/generate-metadata.js b/packages/react-native/cli/generate-metadata.js old mode 100644 new mode 100755 diff --git a/packages/react-native/examples/UIKitPresentation.ts b/packages/react-native/examples/UIKitPresentation.ts index a44a6b41c..60bd2e7e8 100644 --- a/packages/react-native/examples/UIKitPresentation.ts +++ b/packages/react-native/examples/UIKitPresentation.ts @@ -4,6 +4,7 @@ export function topVisibleViewController( root: UIViewController | null | undefined = UIApplication.sharedApplication.keyWindow?.rootViewController, ): UIViewController | null { + 'worklet'; let current = root ?? null; while (current?.presentedViewController) { current = current.presentedViewController; @@ -23,6 +24,7 @@ export async function presentDocumentCamera( delegate: VNDocumentCameraViewControllerDelegate, ) { await NativeScript.runOnUI(() => { + 'worklet'; if ( !NativeScript.loadFramework('VisionKit') || !NativeScript.isClassAvailable('VNDocumentCameraViewController') @@ -47,6 +49,7 @@ export async function presentDocumentCamera( export async function presentPasses(pass: PKPass) { await NativeScript.runOnUI(() => { + 'worklet'; if ( !NativeScript.loadFramework('PassKit') || !NativeScript.isClassAvailable('PKAddPassesViewController') diff --git a/packages/react-native/ios/Fabric/NativeScriptUIViewComponentView.mm b/packages/react-native/ios/Fabric/NativeScriptUIViewComponentView.mm index f890bc3b7..39e07ae9d 100644 --- a/packages/react-native/ios/Fabric/NativeScriptUIViewComponentView.mm +++ b/packages/react-native/ios/Fabric/NativeScriptUIViewComponentView.mm @@ -1,13 +1,81 @@ #import "NativeScriptUIViewComponentView.h" #import +#import #import +#import #import #import "NativeScriptUIView.h" using namespace facebook::react; +static const std::shared_ptr& NativeScriptUIViewDefaultProps() { + static const auto defaultProps = std::make_shared(); + return defaultProps; +} + +static BOOL NativeScriptFabricViewIsDescendantOfView(UIView* view, UIView* ancestor) { + UIView* current = view; + while (current != nil) { + if (current == ancestor) { + return YES; + } + current = current.superview; + } + return NO; +} + +static CGRect NativeScriptFabricEffectiveTabBarHitBounds(UITabBar* tabBar) { + CGRect bounds = tabBar.bounds; + CGSize fittingSize = [tabBar sizeThatFits:CGSizeMake(bounds.size.width, bounds.size.height)]; + CGFloat maximumHeight = MAX(fittingSize.height + 32, 96); + + if (bounds.size.height > maximumHeight) { + bounds.origin.y = CGRectGetMaxY(bounds) - maximumHeight; + bounds.size.height = maximumHeight; + } + + return CGRectInset(bounds, -24, -16); +} + +static BOOL NativeScriptFabricPointInsideTabBarHitArea(UITabBar* tabBar, UIWindow* window, + CGPoint windowPoint) { + if (tabBar == nil || tabBar.hidden || tabBar.alpha <= 0.01 || + !tabBar.userInteractionEnabled) { + return NO; + } + + CGPoint localPoint = [tabBar convertPoint:windowPoint fromView:window]; + return CGRectContainsPoint(NativeScriptFabricEffectiveTabBarHitBounds(tabBar), localPoint); +} + +static UITabBar* NativeScriptFabricVisibleTabBarAtPoint(UIView* root, UIWindow* window, + CGPoint windowPoint) { + if (root.hidden || root.alpha <= 0.01 || !root.userInteractionEnabled) { + return nil; + } + + if ([root isKindOfClass:UITabBar.class]) { + UITabBar* tabBar = static_cast(root); + if (NativeScriptFabricPointInsideTabBarHitArea(tabBar, window, windowPoint)) { + return static_cast(root); + } + } + + for (UIView* subview in [root.subviews reverseObjectEnumerator]) { + UITabBar* tabBar = NativeScriptFabricVisibleTabBarAtPoint(subview, window, windowPoint); + if (tabBar != nil) { + return tabBar; + } + } + + return nil; +} + +@interface NativeScriptUIViewComponentView () +@end + @implementation NativeScriptUIViewComponentView { NativeScriptUIView* _containerView; NSString* _debugName; @@ -15,10 +83,10 @@ @implementation NativeScriptUIViewComponentView { - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - static const auto defaultProps = std::make_shared(); - _props = defaultProps; + _props = NativeScriptUIViewDefaultProps(); _containerView = [[NativeScriptUIView alloc] initWithFrame:self.bounds]; + _containerView.hostReadyDelegate = self; _containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.contentView = _containerView; @@ -28,11 +96,30 @@ - (instancetype)initWithFrame:(CGRect)frame { } - (void)dealloc { + _containerView.hostReadyDelegate = nil; [_debugName release]; [_containerView release]; [super dealloc]; } +- (void)nativeScriptUIView:(NativeScriptUIView*)view + didHostReady:(NSDictionary*)event { + (void)view; + if (_eventEmitter == nullptr) { + return; + } + + static_cast(*_eventEmitter) + .onHostReady(NativeScriptUIViewEventEmitter::OnHostReady{ + .hostReadyId = RCTStringFromNSString(event[@"hostReadyId"] ?: @""), + .hostId = RCTStringFromNSString(event[@"hostId"] ?: @""), + .nativeViewHandle = RCTStringFromNSString(event[@"nativeViewHandle"] ?: @""), + .childrenViewHandle = RCTStringFromNSString(event[@"childrenViewHandle"] ?: @""), + .controllerHandle = RCTStringFromNSString(event[@"controllerHandle"] ?: @""), + .hasChildren = [event[@"hasChildren"] boolValue], + }); +} + - (NSString*)description { if (_debugName.length == 0) { return [super description]; @@ -49,15 +136,70 @@ - (NSString*)description { - (void)mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { [_containerView insertSubview:childComponentView atIndex:index]; + [_containerView refreshDetachedChildrenHost]; } - (void)unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { [childComponentView removeFromSuperview]; + [_containerView refreshDetachedChildrenHost]; +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + [_containerView refreshDetachedChildrenHost]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + [_containerView refreshDetachedChildrenHost]; +} + +- (void)updateLayoutMetrics:(const LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + [_containerView refreshDetachedChildrenHost]; +} + +- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { + [_containerView refreshDetachedChildrenHost]; + + UIView* hitView = [super hitTest:point withEvent:event]; + if (hitView == nil && _containerView != nil && _containerView.window != nil) { + CGPoint containerPoint = [_containerView convertPoint:point fromView:self]; + hitView = [_containerView hitTest:containerPoint withEvent:event]; + } + + if (hitView == nil || self.window == nil) { + return hitView; + } + + CGPoint windowPoint = [self convertPoint:point toView:self.window]; + UITabBar* tabBar = NativeScriptFabricVisibleTabBarAtPoint(self.window, self.window, windowPoint); + if (tabBar != nil) { + if (NativeScriptFabricViewIsDescendantOfView(tabBar, self)) { + CGPoint tabBarPoint = [tabBar convertPoint:windowPoint fromView:self.window]; + UIView* tabBarHitView = [tabBar hitTest:tabBarPoint withEvent:event]; + if (tabBarHitView != nil) { + return tabBarHitView; + } + return tabBar; + } + if (!NativeScriptFabricViewIsDescendantOfView(self, tabBar)) { + return nil; + } + } + + return hitView; } - (void)updateProps:(Props::Shared const&)props oldProps:(Props::Shared const&)oldProps { - const auto oldViewProps = std::static_pointer_cast(_props); + // Diff against oldProps, not _props: React Native leaves _props untouched on + // recycle, while prepareForRecycle clears the container, so a recycled view + // mounted with its previous handle would otherwise never attach it. + const auto oldViewProps = + oldProps ? std::static_pointer_cast(oldProps) + : NativeScriptUIViewDefaultProps(); const auto newViewProps = std::static_pointer_cast(props); const std::string oldNativeViewHandle = oldViewProps->nativeViewHandle; const std::string newNativeViewHandle = newViewProps->nativeViewHandle; @@ -65,8 +207,18 @@ - (void)updateProps:(Props::Shared const&)props oldProps:(Props::Shared const&)o const std::string newChildrenViewHandle = newViewProps->childrenViewHandle; const std::string oldControllerHandle = oldViewProps->controllerHandle; const std::string newControllerHandle = newViewProps->controllerHandle; + const auto oldDetachControllerView = oldViewProps->detachControllerView; + const auto newDetachControllerView = newViewProps->detachControllerView; const std::string oldDebugName = oldViewProps->debugName; const std::string newDebugName = newViewProps->debugName; + const std::string oldHostId = oldViewProps->hostId; + const std::string newHostId = newViewProps->hostId; + const std::string oldHostReadyId = oldViewProps->hostReadyId; + const std::string newHostReadyId = newViewProps->hostReadyId; + const auto oldUpdateRevision = oldViewProps->updateRevision; + const auto newUpdateRevision = newViewProps->updateRevision; + const auto oldMountedRevision = oldViewProps->mountedRevision; + const auto newMountedRevision = newViewProps->mountedRevision; [super updateProps:props oldProps:oldProps]; @@ -78,6 +230,10 @@ - (void)updateProps:(Props::Shared const&)props oldProps:(Props::Shared const&)o _containerView.debugName = debugName; } + if (oldDetachControllerView != newDetachControllerView) { + _containerView.detachControllerView = newDetachControllerView; + } + if (oldNativeViewHandle != newNativeViewHandle) { NSString* nativeViewHandle = newNativeViewHandle.empty() ? nil @@ -94,22 +250,48 @@ - (void)updateProps:(Props::Shared const&)props oldProps:(Props::Shared const&)o } if (oldControllerHandle != newControllerHandle) { - NSString* controllerHandle = - newControllerHandle.empty() - ? nil - : [NSString stringWithUTF8String:newControllerHandle.c_str()]; + NSString* controllerHandle = newControllerHandle.empty() + ? nil + : [NSString stringWithUTF8String:newControllerHandle.c_str()]; _containerView.controllerHandle = controllerHandle; } + + if (oldHostId != newHostId) { + NSString* hostId = newHostId.empty() ? nil : [NSString stringWithUTF8String:newHostId.c_str()]; + _containerView.hostId = hostId; + } + + if (oldHostReadyId != newHostReadyId) { + NSString* hostReadyId = newHostReadyId.empty() + ? nil + : [NSString stringWithUTF8String:newHostReadyId.c_str()]; + _containerView.hostReadyId = hostReadyId; + } + + if (oldUpdateRevision != newUpdateRevision) { + _containerView.updateRevision = newUpdateRevision; + } + + if (oldMountedRevision != newMountedRevision) { + _containerView.mountedRevision = newMountedRevision; + } + + [_containerView refreshDetachedChildrenHost]; } - (void)prepareForRecycle { [super prepareForRecycle]; [_debugName release]; _debugName = nil; + _containerView.hostId = nil; + _containerView.hostReadyId = nil; _containerView.debugName = nil; _containerView.nativeViewHandle = nil; _containerView.childrenViewHandle = nil; _containerView.controllerHandle = nil; + _containerView.detachControllerView = NO; + _containerView.updateRevision = 0; + _containerView.mountedRevision = 0; } + (ComponentDescriptorProvider)componentDescriptorProvider { diff --git a/packages/react-native/ios/NativeScriptNativeApiModule.h b/packages/react-native/ios/NativeScriptNativeApiModule.h index 540613fa2..ebe82a47f 100644 --- a/packages/react-native/ios/NativeScriptNativeApiModule.h +++ b/packages/react-native/ios/NativeScriptNativeApiModule.h @@ -15,6 +15,8 @@ class NativeScriptNativeApiModule explicit NativeScriptNativeApiModule(std::shared_ptr jsInvoker); bool install(jsi::Runtime& runtime, std::string metadataPath); + bool installWorkletRuntime(jsi::Runtime& runtime, jsi::Object runtimeHolder, + std::string metadataPath); bool isInstalled(jsi::Runtime& runtime); std::string defaultMetadataPath(jsi::Runtime& runtime); std::string getRuntimeBackend(jsi::Runtime& runtime); diff --git a/packages/react-native/ios/NativeScriptNativeApiModule.mm b/packages/react-native/ios/NativeScriptNativeApiModule.mm index 5b3797808..99722918c 100644 --- a/packages/react-native/ios/NativeScriptNativeApiModule.mm +++ b/packages/react-native/ios/NativeScriptNativeApiModule.mm @@ -3,9 +3,22 @@ #import #import +#include +#include #include +#include #include "NativeApiJsiReactNative.h" +#include "NativeScriptUIKitHost.h" + +#import +#import +#import +#import +#import +#import +#include +#include namespace { @@ -34,10 +47,9 @@ } Class providerClass = NSClassFromString(@"NativeScriptNativeApiModuleProvider"); - NSBundle* providerBundle = - providerClass != Nil ? [NSBundle bundleForClass:providerClass] : nil; - NSString* resourceBundlePath = - [providerBundle pathForResource:@"NativeScriptNativeApi" ofType:@"bundle"]; + NSBundle* providerBundle = providerClass != Nil ? [NSBundle bundleForClass:providerClass] : nil; + NSString* resourceBundlePath = [providerBundle pathForResource:@"NativeScriptNativeApi" + ofType:@"bundle"]; NSBundle* resourceBundle = resourceBundlePath != nil ? [NSBundle bundleWithPath:resourceBundlePath] : nil; @@ -55,10 +67,9 @@ void writeSmokeMarkerIfRequested(const char* stage) { return; } - NSString* path = [NSTemporaryDirectory() - stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; - NSString* content = - [NSString stringWithFormat:@"stage=%s\n", stage != nullptr ? stage : ""]; + NSString* path = + [NSTemporaryDirectory() stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; + NSString* content = [NSString stringWithFormat:@"stage=%s\n", stage != nullptr ? stage : ""]; [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; } @@ -68,52 +79,382 @@ bool writeSmokeMarkerContentIfRequested(const std::string& content) { return false; } - NSString* path = [NSTemporaryDirectory() - stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; - NSString* nativeContent = - [[NSString alloc] initWithBytes:content.data() - length:content.size() - encoding:NSUTF8StringEncoding]; + NSString* path = + [NSTemporaryDirectory() stringByAppendingPathComponent:@"NativeScriptNativeApiSmoke.marker"]; + NSString* nativeContent = [[NSString alloc] initWithBytes:content.data() + length:content.size() + encoding:NSUTF8StringEncoding]; if (nativeContent == nil) { nativeContent = @""; } - BOOL ok = [nativeContent writeToFile:path - atomically:YES - encoding:NSUTF8StringEncoding - error:nil]; + BOOL ok = [nativeContent writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil]; #if !__has_feature(objc_arc) [nativeContent release]; #endif return ok == YES; } +bool nativeApiInstalled(facebook::jsi::Runtime& runtime) { + return runtime.global().hasProperty(runtime, "__nativeScriptNativeApi"); +} + +NSString* nativeScriptHandleFromNSObject(id object) { + if (object == nil) { + return @""; + } + + return [NSString stringWithFormat:@"%p", object]; +} + +RCTImageLoader* currentReactImageLoader() { + RCTBridge* bridge = [RCTBridge currentBridge]; + if (bridge == nil) { + return nil; + } + + RCTImageLoader* imageLoader = nil; + if ([bridge respondsToSelector:@selector(imageLoader)]) { + imageLoader = bridge.imageLoader; + } + if (imageLoader == nil && + [bridge respondsToSelector:@selector(moduleForName:lazilyLoadIfNecessary:)]) { + id module = [bridge moduleForName:@"RCTImageLoader" lazilyLoadIfNecessary:YES]; + if ([module isKindOfClass:RCTImageLoader.class]) { + imageLoader = (RCTImageLoader*)module; + } + } + return imageLoader; +} + +UIImage* imageWithRenderingMode(UIImage* image, bool isTemplate) { + if (image == nil) { + return nil; + } + + return [image imageWithRenderingMode:isTemplate ? UIImageRenderingModeAlwaysTemplate + : UIImageRenderingModeAlwaysOriginal]; +} + +std::mutex& nativeScriptWorkletRuntimeMutex() { + static std::mutex mutex; + return mutex; +} + +std::weak_ptr& nativeScriptWorkletRuntime() { + static std::weak_ptr runtime; + return runtime; +} + +void setNativeScriptWorkletRuntime(std::shared_ptr runtime) { + std::lock_guard lock(nativeScriptWorkletRuntimeMutex()); + nativeScriptWorkletRuntime() = std::move(runtime); +} + +std::shared_ptr getNativeScriptWorkletRuntime() { + std::lock_guard lock(nativeScriptWorkletRuntimeMutex()); + return nativeScriptWorkletRuntime().lock(); +} + +NSString* stringProperty(facebook::jsi::Runtime& runtime, facebook::jsi::Object& object, + const char* name) { + auto value = object.getProperty(runtime, name); + if (!value.isString()) { + return nil; + } + std::string text = value.getString(runtime).utf8(runtime); + return [NSString stringWithUTF8String:text.c_str()]; +} + +id imageSourceFromJSIValue(facebook::jsi::Runtime& runtime, + const facebook::jsi::Value& value, + const std::shared_ptr& jsInvoker) { + if (value.isString()) { + std::string uri = value.asString(runtime).utf8(runtime); + return @{@"uri" : [NSString stringWithUTF8String:uri.c_str()]}; + } + + id object = + facebook::react::TurboModuleConvertUtils::convertJSIValueToObjCObject( + runtime, value, jsInvoker, YES); + return object == nil || object == [NSNull null] ? nil : object; +} + +void callImageLoadCallback( + std::weak_ptr workletRuntimeWeak, + std::shared_ptr callback, + UIImage* image, + NSString* errorMessage) { + UIImage* retainedImage = image != nil ? [image retain] : nil; + std::string imageHandle = + retainedImage != nil ? nativeScriptHandleFromNSObject(retainedImage).UTF8String : ""; + std::string errorText = errorMessage.UTF8String != nullptr ? errorMessage.UTF8String : ""; + + auto runtimeStrong = workletRuntimeWeak.lock(); + if (runtimeStrong == nullptr) { + [retainedImage release]; + return; + } + + runtimeStrong->schedule( + [callback = std::move(callback), imageHandle = std::move(imageHandle), + errorText = std::move(errorText), retainedImage](facebook::jsi::Runtime& runtime) mutable { + facebook::jsi::Value imageValue = + imageHandle.empty() + ? facebook::jsi::Value::null() + : facebook::jsi::Value( + runtime, + facebook::jsi::String::createFromUtf8(runtime, imageHandle)); + facebook::jsi::Value errorValue = + errorText.empty() + ? facebook::jsi::Value::null() + : facebook::jsi::Value( + runtime, + facebook::jsi::String::createFromUtf8(runtime, errorText)); + callback->call(runtime, imageValue, errorValue); + dispatch_async(dispatch_get_main_queue(), ^{ + [retainedImage release]; + }); + }); +} + +NSDictionary* handlesFromJSIValue(facebook::jsi::Runtime& runtime, + facebook::jsi::Value&& result) { + if (!result.isObject()) { + return nil; + } + + auto resultObject = result.asObject(runtime); + NSMutableDictionary* handles = + [NSMutableDictionary dictionaryWithCapacity:3]; + NSString* nativeViewHandle = stringProperty(runtime, resultObject, "nativeViewHandle"); + NSString* childrenViewHandle = stringProperty(runtime, resultObject, "childrenViewHandle"); + NSString* controllerHandle = stringProperty(runtime, resultObject, "controllerHandle"); + + if (nativeViewHandle.length > 0) { + handles[@"nativeViewHandle"] = nativeViewHandle; + } + if (childrenViewHandle.length > 0) { + handles[@"childrenViewHandle"] = childrenViewHandle; + } + if (controllerHandle.length > 0) { + handles[@"controllerHandle"] = controllerHandle; + } + return handles; +} + +NSDictionary* runUIKitHostFunction(NSString* hostId, NSString* phase, + const char* globalName, + const char* logAction) { + if (hostId.length == 0 || ![NSThread isMainThread]) { + return nil; + } + + auto workletRuntime = getNativeScriptWorkletRuntime(); + if (workletRuntime == nullptr) { + return nil; + } + + std::string hostIdString = hostId.UTF8String != nullptr ? hostId.UTF8String : ""; + if (hostIdString.empty()) { + return nil; + } + + std::string phaseString = phase.UTF8String != nullptr ? phase.UTF8String : ""; + + try { + return workletRuntime->runSync( + [hostIdString = std::move(hostIdString), phaseString = std::move(phaseString), + globalName](facebook::jsi::Runtime& runtime) -> NSDictionary* { + auto global = runtime.global(); + auto functionValue = global.getProperty(runtime, globalName); + if (!functionValue.isObject()) { + return nil; + } + + auto functionObject = functionValue.asObject(runtime); + if (!functionObject.isFunction(runtime)) { + return nil; + } + + auto function = functionObject.asFunction(runtime); + auto hostIdValue = facebook::jsi::String::createFromUtf8(runtime, hostIdString); + if (phaseString.empty()) { + return handlesFromJSIValue(runtime, function.call(runtime, hostIdValue)); + } + + return handlesFromJSIValue( + runtime, function.call(runtime, hostIdValue, + facebook::jsi::String::createFromUtf8(runtime, phaseString))); + }); + } catch (const std::exception& error) { + NSLog(@"NativeScript failed to %s UIKit host %@: %s", logAction, hostId, error.what()); + } catch (...) { + NSLog(@"NativeScript failed to %s UIKit host %@", logAction, hostId); + } + return nil; +} + } // namespace +NSDictionary* NativeScriptCreateUIKitHost(NSString* hostId) { + return runUIKitHostFunction(hostId, nil, "__nativeScriptCreateUIKitHostFromNative", "create"); +} + +NSDictionary* NativeScriptRunUIKitHostLifecycle(NSString* hostId, + NSString* phase) { + return runUIKitHostFunction(hostId, phase, "__nativeScriptRunUIKitHostLifecycleFromNative", + "run"); +} + namespace facebook::react { -NativeScriptNativeApiModule::NativeScriptNativeApiModule( - std::shared_ptr jsInvoker) +NativeScriptNativeApiModule::NativeScriptNativeApiModule(std::shared_ptr jsInvoker) : NativeScriptNativeApiCxxSpec(jsInvoker), jsInvoker_(std::move(jsInvoker)) {} -bool NativeScriptNativeApiModule::install(jsi::Runtime& runtime, - std::string metadataPath) { +bool NativeScriptNativeApiModule::install(jsi::Runtime& runtime, std::string metadataPath) { writeSmokeMarkerIfRequested("install:resolve-metadata"); - std::string resolvedMetadataPath = - metadataPath.empty() ? bundledMetadataPath() : metadataPath; + std::string resolvedMetadataPath = metadataPath.empty() ? bundledMetadataPath() : metadataPath; const char* metadataPathArg = resolvedMetadataPath.empty() ? nullptr : resolvedMetadataPath.c_str(); writeSmokeMarkerIfRequested("install:before-jsi"); - auto config = nativescript::MakeReactNativeNativeApiJsiConfig( - jsInvoker_, nullptr, metadataPathArg); + auto config = + nativescript::MakeReactNativeNativeApiJsiConfig( + jsInvoker_, nullptr, metadataPathArg, nullptr, "__nativeScriptNativeApi"); + config.installGlobalSymbols = false; + config.invokeCallbacksOnNativeCallerThread = false; nativescript::InstallNativeApiJSI(runtime, config); writeSmokeMarkerIfRequested("install:after-jsi"); return isInstalled(runtime); } +bool NativeScriptNativeApiModule::installWorkletRuntime(jsi::Runtime& runtime, + jsi::Object runtimeHolder, + std::string metadataPath) { + writeSmokeMarkerIfRequested("installWorkletRuntime:headers"); + if (!runtimeHolder.hasNativeState(runtime)) { + writeSmokeMarkerIfRequested("installWorkletRuntime:no-holder"); + return false; + } + + auto holder = runtimeHolder.getNativeState(runtime); + if (holder == nullptr || holder->runtime_ == nullptr) { + writeSmokeMarkerIfRequested("installWorkletRuntime:null-runtime"); + return false; + } + + setNativeScriptWorkletRuntime(holder->runtime_); + + std::string resolvedMetadataPath = metadataPath.empty() ? bundledMetadataPath() : metadataPath; + auto jsInvoker = jsInvoker_; + auto workletRuntimeRef = holder->runtime_; + return holder->runtime_->runSync( + [jsInvoker = std::move(jsInvoker), resolvedMetadataPath = std::move(resolvedMetadataPath), + workletRuntimeRef = std::move(workletRuntimeRef)]( + jsi::Runtime& workletRuntime) -> bool { + if (!nativeApiInstalled(workletRuntime)) { + std::weak_ptr workletRuntimeWeak(workletRuntimeRef); + const char* metadataPathArg = + resolvedMetadataPath.empty() ? nullptr : resolvedMetadataPath.c_str(); + auto config = + nativescript::MakeReactNativeNativeApiJsiConfig( + jsInvoker, nullptr, metadataPathArg, nullptr, "__nativeScriptNativeApi"); + config.installGlobalSymbols = true; + config.invokeCallbacksOnNativeCallerThread = true; + config.runtimeCallbackInvoker = + [workletRuntimeWeak](std::function task) mutable { + auto runtimeStrong = workletRuntimeWeak.lock(); + if (runtimeStrong == nullptr) { + return; + } + + auto taskBox = + std::make_shared>(std::move(task)); + dispatch_semaphore_t done = dispatch_semaphore_create(0); + runtimeStrong->schedule( + [taskBox = std::move(taskBox), done](jsi::Runtime&) mutable { + (*taskBox)(); + dispatch_semaphore_signal(done); + }); + dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER); + }; + nativescript::InstallNativeApiJSI(workletRuntime, config); + } + + auto refreshUIKitHostView = jsi::Function::createFromHostFunction( + workletRuntime, + jsi::PropNameID::forAscii(workletRuntime, "__nativeScriptRefreshUIKitHostView"), + 1, + [](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, + size_t count) -> jsi::Value { + if (count < 1 || !args[0].isString()) { + return false; + } + + std::string handle = args[0].asString(runtime).utf8(runtime); + NSString* nativeHandle = [NSString stringWithUTF8String:handle.c_str()]; + return NativeScriptRefreshUIKitHostView(nativeHandle) == YES; + }); + workletRuntime.global().setProperty( + workletRuntime, "__nativeScriptRefreshUIKitHostView", std::move(refreshUIKitHostView)); + + std::weak_ptr imageWorkletRuntimeWeak(workletRuntimeRef); + auto loadImage = jsi::Function::createFromHostFunction( + workletRuntime, + jsi::PropNameID::forAscii(workletRuntime, "__nativeScriptLoadReactImage"), + 3, + [jsInvoker, imageWorkletRuntimeWeak](jsi::Runtime& runtime, const jsi::Value&, + const jsi::Value* args, + size_t count) -> jsi::Value { + if (count < 3 || !args[2].isObject() || + !args[2].asObject(runtime).isFunction(runtime)) { + return false; + } + + id jsonSource = imageSourceFromJSIValue(runtime, args[0], jsInvoker); + if (jsonSource == nil) { + return false; + } + + RCTImageSource* imageSource = [RCTConvert RCTImageSource:jsonSource]; + RCTImageLoader* imageLoader = currentReactImageLoader(); + if (imageSource == nil || imageLoader == nil) { + return false; + } + + bool isTemplate = count > 1 && args[1].isBool() && args[1].getBool(); + auto callback = std::make_shared( + args[2].asObject(runtime).asFunction(runtime)); + + [imageLoader loadImageWithURLRequest:imageSource.request + size:imageSource.size + scale:imageSource.scale + clipped:YES + resizeMode:RCTResizeModeCenter + progressBlock:^(int64_t, int64_t) { + } + partialLoadBlock:^(UIImage*) { + } + completionBlock:^(NSError* error, UIImage* image) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIImage* renderedImage = + imageWithRenderingMode(image, isTemplate); + callImageLoadCallback( + imageWorkletRuntimeWeak, callback, renderedImage, + error.localizedDescription); + }); + }]; + return true; + }); + workletRuntime.global().setProperty( + workletRuntime, "__nativeScriptLoadReactImage", std::move(loadImage)); + return nativeApiInstalled(workletRuntime); + }); + } + bool NativeScriptNativeApiModule::isInstalled(jsi::Runtime& runtime) { - return runtime.global().hasProperty(runtime, "__nativeScriptNativeApi"); + return nativeApiInstalled(runtime); } std::string NativeScriptNativeApiModule::defaultMetadataPath(jsi::Runtime&) { @@ -125,8 +466,7 @@ bool writeSmokeMarkerContentIfRequested(const std::string& content) { return "hermes-jsi"; } -bool NativeScriptNativeApiModule::__writeTestMarker(jsi::Runtime&, - std::string content) { +bool NativeScriptNativeApiModule::__writeTestMarker(jsi::Runtime&, std::string content) { return writeSmokeMarkerContentIfRequested(content); } diff --git a/packages/react-native/ios/NativeScriptUIKitHost.h b/packages/react-native/ios/NativeScriptUIKitHost.h new file mode 100644 index 000000000..bec2d9dff --- /dev/null +++ b/packages/react-native/ios/NativeScriptUIKitHost.h @@ -0,0 +1,8 @@ +#import + +FOUNDATION_EXPORT NSDictionary* NativeScriptCreateUIKitHost(NSString* hostId); + +FOUNDATION_EXPORT NSDictionary* NativeScriptRunUIKitHostLifecycle( + NSString* hostId, NSString* phase); + +FOUNDATION_EXPORT BOOL NativeScriptRefreshUIKitHostView(NSString* viewHandle); diff --git a/packages/react-native/ios/NativeScriptUIView.h b/packages/react-native/ios/NativeScriptUIView.h index 2f653feb9..8293a0b60 100644 --- a/packages/react-native/ios/NativeScriptUIView.h +++ b/packages/react-native/ios/NativeScriptUIView.h @@ -1,10 +1,28 @@ #import +#import + +@class NativeScriptUIView; + +@protocol NativeScriptUIViewHostReadyDelegate +- (void)nativeScriptUIView:(NativeScriptUIView*)view + didHostReady:(NSDictionary*)event; +@end @interface NativeScriptUIView : UIView +@property(nonatomic, copy) NSString* hostId; +@property(nonatomic, copy) NSString* hostReadyId; @property(nonatomic, copy) NSString* nativeViewHandle; @property(nonatomic, copy) NSString* childrenViewHandle; @property(nonatomic, copy) NSString* controllerHandle; +@property(nonatomic, assign) BOOL detachControllerView; @property(nonatomic, copy) NSString* debugName; +@property(nonatomic, assign) NSInteger updateRevision; +@property(nonatomic, assign) NSInteger mountedRevision; +@property(nonatomic, copy) RCTDirectEventBlock onHostReady; +@property(nonatomic, assign) id hostReadyDelegate; + +- (void)layoutDetachedChildrenViewSubviewsIfNeeded; +- (BOOL)refreshDetachedChildrenHost; @end diff --git a/packages/react-native/ios/NativeScriptUIView.mm b/packages/react-native/ios/NativeScriptUIView.mm index c36e3a76e..444b1094e 100644 --- a/packages/react-native/ios/NativeScriptUIView.mm +++ b/packages/react-native/ios/NativeScriptUIView.mm @@ -1,4 +1,15 @@ #import "NativeScriptUIView.h" +#import "NativeScriptUIKitHost.h" +#import + +#if __has_include() +#import +#endif + +#if __has_include() && __has_include() +#import +#import +#endif static id NativeScriptNSObjectFromHandle(NSString* handle) { if (handle == nil || handle.length == 0) { @@ -38,6 +49,30 @@ static id NativeScriptNSObjectFromHandle(NSString* handle) { return static_cast(object); } +static NSString* NativeScriptHandleFromNSObject(id object) { + if (object == nil) { + return @""; + } + + return [NSString stringWithFormat:@"%p", object]; +} + +static BOOL NativeScriptChildrenViewHasVisibleChild(UIView* childrenView, UIView* sentinel) { + if (childrenView == nil) { + return NO; + } + + for (UIView* subview in childrenView.subviews) { + if (subview == sentinel || subview.hidden || subview.alpha <= 0.01) { + continue; + } + + return YES; + } + + return NO; +} + static UIViewController* NativeScriptNearestViewController(UIView* view) { UIResponder* responder = view; while (responder != nil) { @@ -49,25 +84,286 @@ static id NativeScriptNSObjectFromHandle(NSString* handle) { return nil; } +static BOOL NativeScriptViewIsDescendantOfView(UIView* view, UIView* ancestor) { + UIView* current = view; + while (current != nil) { + if (current == ancestor) { + return YES; + } + current = current.superview; + } + return NO; +} + +static BOOL NativeScriptViewHasGestureRecognizer(UIView* view, UIGestureRecognizer* recognizer) { + if (view == nil || recognizer == nil) { + return NO; + } + + for (UIGestureRecognizer* existingRecognizer in view.gestureRecognizers) { + if (existingRecognizer == recognizer) { + return YES; + } + } + + return NO; +} + +static UIView* NativeScriptGestureRecognizerAttachedView(id recognizer) { + if (recognizer == nil || ![recognizer isKindOfClass:UIGestureRecognizer.class]) { + return nil; + } + + return static_cast(recognizer).view; +} + +static UIGestureRecognizer* NativeScriptFindAncestorSurfaceTouchHandler(UIView* view) { +#if __has_include() + UIView* parent = view.superview; + NSUInteger depth = 0; + + while (parent != nil && depth < 32) { + for (UIGestureRecognizer* recognizer in parent.gestureRecognizers) { + if ([recognizer isKindOfClass:RCTSurfaceTouchHandler.class]) { + return recognizer; + } + } + + parent = parent.superview; + depth += 1; + } +#endif + + return nil; +} + +static BOOL NativeScriptShouldForwardControllerAppearance(UIViewController* controller) { + return controller != nil && controller.view != nil && controller.view.window != nil; +} + +static BOOL NativeScriptHostedViewContainsControllerView(UIView* hostedView, + UIViewController* controller) { + return hostedView != nil && controller != nil && controller.view != nil && + NativeScriptViewIsDescendantOfView(controller.view, hostedView); +} + +static CGRect NativeScriptEffectiveTabBarHitBounds(UITabBar* tabBar) { + CGRect bounds = tabBar.bounds; + CGSize fittingSize = [tabBar sizeThatFits:CGSizeMake(bounds.size.width, bounds.size.height)]; + CGFloat maximumHeight = MAX(fittingSize.height + 32, 96); + + if (bounds.size.height > maximumHeight) { + bounds.origin.y = CGRectGetMaxY(bounds) - maximumHeight; + bounds.size.height = maximumHeight; + } + + return CGRectInset(bounds, -24, -16); +} + +static BOOL NativeScriptPointInsideTabBarHitArea(UITabBar* tabBar, UIWindow* window, + CGPoint windowPoint) { + if (tabBar == nil || tabBar.hidden || tabBar.alpha <= 0.01 || + !tabBar.userInteractionEnabled) { + return NO; + } + + CGPoint localPoint = [tabBar convertPoint:windowPoint fromView:window]; + return CGRectContainsPoint(NativeScriptEffectiveTabBarHitBounds(tabBar), localPoint); +} + +static UITabBar* NativeScriptVisibleTabBarAtPoint(UIView* root, UIWindow* window, + CGPoint windowPoint) { + if (root.hidden || root.alpha <= 0.01 || !root.userInteractionEnabled) { + return nil; + } + + if ([root isKindOfClass:UITabBar.class]) { + UITabBar* tabBar = static_cast(root); + if (NativeScriptPointInsideTabBarHitArea(tabBar, window, windowPoint)) { + return static_cast(root); + } + } + + for (UIView* subview in [root.subviews reverseObjectEnumerator]) { + UITabBar* tabBar = NativeScriptVisibleTabBarAtPoint(subview, window, windowPoint); + if (tabBar != nil) { + return tabBar; + } + } + + return nil; +} + +static BOOL NativeScriptSubviewShouldFillParent(UIView* parent, UIView* child) { + if (parent == nil || child == nil) { + return NO; + } + + const CGRect parentBounds = parent.bounds; + const CGRect childFrame = child.frame; + if (parentBounds.size.width <= 0) { + return NO; + } + + return fabs(childFrame.origin.x) < 1 && fabs(childFrame.origin.y) < 1 && + (childFrame.size.width <= 0 || fabs(childFrame.size.width - parentBounds.size.width) < 2); +} + +static void NativeScriptLayoutHostedSubviewChain(UIView* root, NSUInteger depth) { + if (root == nil || depth > 12 || [root isKindOfClass:UIScrollView.class]) { + return; + } + + const CGRect bounds = root.bounds; + for (UIView* subview in root.subviews) { + if (!NativeScriptSubviewShouldFillParent(root, subview)) { + continue; + } + + subview.frame = bounds; + subview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [subview setNeedsLayout]; + [subview layoutIfNeeded]; + NativeScriptLayoutHostedSubviewChain(subview, depth + 1); + } +} + +@class NativeScriptUIView; + +static const void* NativeScriptDetachedChildrenOwnerKey = + &NativeScriptDetachedChildrenOwnerKey; + +static NativeScriptUIView* NativeScriptDetachedChildrenOwner(UIView* view) { + id owner = view == nil ? nil : objc_getAssociatedObject(view, NativeScriptDetachedChildrenOwnerKey); + if (owner == nil || ![owner isKindOfClass:NativeScriptUIView.class]) { + return nil; + } + + return static_cast(owner); +} + +static void NativeScriptSetDetachedChildrenOwner(UIView* view, NativeScriptUIView* owner) { + if (view == nil) { + return; + } + + objc_setAssociatedObject( + view, NativeScriptDetachedChildrenOwnerKey, owner, OBJC_ASSOCIATION_ASSIGN); +} + +@interface NativeScriptUIView () +- (void)attachDetachedChildrenTouchHandlerIfNeeded; +- (void)installDetachedChildrenTouchSentinelIfNeeded; +- (void)notifyHostReadyIfNeeded; +- (BOOL)refreshDetachedChildrenHost; +- (void)updateDetachedChildrenTouchHandlerOrigin; +@end + +@interface NativeScriptDetachedChildrenTouchSentinel : UIView +@property(nonatomic, assign) NativeScriptUIView* owner; +@end + +@implementation NativeScriptDetachedChildrenTouchSentinel + +- (void)didMoveToWindow { + [super didMoveToWindow]; + [self.owner refreshDetachedChildrenHost]; +} + +- (void)didMoveToSuperview { + [super didMoveToSuperview]; + [self.owner refreshDetachedChildrenHost]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + [self.owner refreshDetachedChildrenHost]; +} + +@end + @implementation NativeScriptUIView { UIView* _nativeView; UIView* _childrenView; UIViewController* _viewController; + id _detachedTouchHandler; + UIView* _detachedTouchHandlerView; + UIWindow* _detachedTouchHandlerWindow; + NativeScriptDetachedChildrenTouchSentinel* _detachedTouchSentinel; + NSInteger _hostMountRetryCount; + NSString* _lastHostReadyKey; } - (void)dealloc { + if (_hostId.length > 0) { + NativeScriptRunUIKitHostLifecycle(_hostId, @"dispose"); + } [self detachViewController]; + [self detachDetachedChildrenTouchHandler]; + [_detachedTouchSentinel removeFromSuperview]; + [_detachedTouchSentinel release]; [_nativeView removeFromSuperview]; [_nativeView release]; + if (NativeScriptDetachedChildrenOwner(_childrenView) == self) { + NativeScriptSetDetachedChildrenOwner(_childrenView, nil); + } [_childrenView release]; [_viewController release]; + [_detachedTouchHandler release]; + [_detachedTouchHandlerView release]; [_nativeViewHandle release]; [_childrenViewHandle release]; [_controllerHandle release]; + [_hostId release]; + [_hostReadyId release]; [_debugName release]; + [_onHostReady release]; + [_lastHostReadyKey release]; [super dealloc]; } +- (void)setHostId:(NSString*)hostId { + if ((_hostId == hostId) || [_hostId isEqualToString:hostId]) { + return; + } + + NSString* previousHostId = [_hostId copy]; + if (previousHostId.length > 0) { + NativeScriptRunUIKitHostLifecycle(previousHostId, @"dispose"); + } + [previousHostId release]; + + [_hostId release]; + _hostId = [hostId copy]; + _hostMountRetryCount = 0; + [_lastHostReadyKey release]; + _lastHostReadyKey = nil; + [self mountUIKitHostIfNeeded]; + [self notifyHostReadyIfNeeded]; +} + +- (void)setHostReadyId:(NSString*)hostReadyId { + if ((_hostReadyId == hostReadyId) || [_hostReadyId isEqualToString:hostReadyId]) { + return; + } + + [_hostReadyId release]; + _hostReadyId = [hostReadyId copy]; + [_lastHostReadyKey release]; + _lastHostReadyKey = nil; + [self notifyHostReadyIfNeeded]; +} + +- (void)setOnHostReady:(RCTDirectEventBlock)onHostReady { + if (_onHostReady == onHostReady) { + return; + } + + [_onHostReady release]; + _onHostReady = [onHostReady copy]; + [self notifyHostReadyIfNeeded]; +} + - (void)setNativeViewHandle:(NSString*)nativeViewHandle { if ((_nativeViewHandle == nativeViewHandle) || [_nativeViewHandle isEqualToString:nativeViewHandle]) { @@ -76,7 +372,15 @@ - (void)setNativeViewHandle:(NSString*)nativeViewHandle { [_nativeViewHandle release]; _nativeViewHandle = [nativeViewHandle copy]; - [self setNativeView:NativeScriptUIViewFromHandle(_nativeViewHandle)]; + UIView* nativeView = NativeScriptUIViewFromHandle(_nativeViewHandle); + if (_detachControllerView && _viewController != nil && nativeView == _viewController.view) { + nativeView = nil; + } + if (nativeView == nil && _nativeViewHandle.length == 0 && !_detachControllerView && + _viewController != nil) { + nativeView = _viewController.view; + } + [self setNativeView:nativeView]; } - (void)setChildrenViewHandle:(NSString*)childrenViewHandle { @@ -101,6 +405,28 @@ - (void)setControllerHandle:(NSString*)controllerHandle { [self setViewController:NativeScriptUIViewControllerFromHandle(_controllerHandle)]; } +- (void)setDetachControllerView:(BOOL)detachControllerView { + if (_detachControllerView == detachControllerView) { + return; + } + + if (detachControllerView) { + [self detachViewController]; + if (_viewController != nil && _nativeView == _viewController.view) { + [self setNativeView:nil]; + } + } + + _detachControllerView = detachControllerView; + + if (!_detachControllerView && _viewController != nil) { + if (_nativeViewHandle.length == 0) { + [self setNativeView:_viewController.view]; + } + [self attachViewControllerIfPossible]; + } +} + - (void)setDebugName:(NSString*)debugName { if ((_debugName == debugName) || [_debugName isEqualToString:debugName]) { return; @@ -110,6 +436,28 @@ - (void)setDebugName:(NSString*)debugName { _debugName = [debugName copy]; } +- (void)setUpdateRevision:(NSInteger)updateRevision { + if (_updateRevision == updateRevision) { + return; + } + + _updateRevision = updateRevision; + if (_updateRevision > 0) { + [self runUIKitHostLifecycle:@"update"]; + } +} + +- (void)setMountedRevision:(NSInteger)mountedRevision { + if (_mountedRevision == mountedRevision) { + return; + } + + _mountedRevision = mountedRevision; + if (_mountedRevision > 0) { + [self runUIKitHostLifecycle:@"mounted"]; + } +} + - (NSString*)description { if (_debugName.length == 0) { return [super description]; @@ -123,14 +471,132 @@ - (NSString*)description { return [description stringByAppendingFormat:@" debugName = %@", _debugName]; } +- (NSDictionary*)hostReadyEventWithHasChildren:(BOOL)hasChildren { + NSString* readyId = _hostReadyId.length > 0 ? _hostReadyId : _hostId; + if (readyId.length == 0) { + return nil; + } + + NSMutableDictionary* event = [NSMutableDictionary dictionaryWithCapacity:6]; + event[@"hostReadyId"] = readyId; + event[@"hostId"] = _hostId ?: @""; + event[@"nativeViewHandle"] = NativeScriptHandleFromNSObject(_nativeView); + event[@"childrenViewHandle"] = NativeScriptHandleFromNSObject(_childrenView); + event[@"controllerHandle"] = NativeScriptHandleFromNSObject(_viewController); + event[@"hasChildren"] = @(hasChildren); + return event; +} + +- (void)notifyHostReadyIfNeeded { + const BOOL hasChildren = + NativeScriptChildrenViewHasVisibleChild(_childrenView, _detachedTouchSentinel); + if (!hasChildren) { + return; + } + + NSDictionary* event = [self hostReadyEventWithHasChildren:hasChildren]; + if (event == nil) { + return; + } + + NSString* key = [NSString + stringWithFormat:@"%@|%@|%@|%@|%@|%@", + event[@"hostReadyId"] ?: @"", + event[@"hostId"] ?: @"", + event[@"nativeViewHandle"] ?: @"", + event[@"childrenViewHandle"] ?: @"", + event[@"controllerHandle"] ?: @"", + [event[@"hasChildren"] boolValue] ? @"1" : @"0"]; + if ([_lastHostReadyKey isEqualToString:key]) { + return; + } + + [_lastHostReadyKey release]; + _lastHostReadyKey = [key copy]; + + if (_onHostReady != nil) { + _onHostReady(event); + } + if ([_hostReadyDelegate respondsToSelector:@selector(nativeScriptUIView:didHostReady:)]) { + [_hostReadyDelegate nativeScriptUIView:self didHostReady:event]; + } +} + +- (void)applyUIKitHostHandles:(NSDictionary*)handles { + if (handles == nil) { + return; + } + + NSString* nativeViewHandle = handles[@"nativeViewHandle"]; + NSString* childrenViewHandle = handles[@"childrenViewHandle"]; + NSString* controllerHandle = handles[@"controllerHandle"]; + + if (controllerHandle.length > 0) { + self.controllerHandle = controllerHandle; + } + if (nativeViewHandle.length > 0) { + self.nativeViewHandle = nativeViewHandle; + } + if (childrenViewHandle.length > 0) { + self.childrenViewHandle = childrenViewHandle; + } + [self notifyHostReadyIfNeeded]; +} + +- (void)mountUIKitHostIfNeeded { + if (_hostId.length == 0) { + return; + } + + NSDictionary* handles = NativeScriptCreateUIKitHost(_hostId); + if (handles != nil) { + _hostMountRetryCount = 0; + [self applyUIKitHostHandles:handles]; + return; + } + + if (_hostMountRetryCount >= 8) { + return; + } + + _hostMountRetryCount += 1; + NSString* retryHostId = [_hostId copy]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (retryHostId.length > 0 && [self->_hostId isEqualToString:retryHostId]) { + [self mountUIKitHostIfNeeded]; + } + [retryHostId release]; + }); +} + +- (void)runUIKitHostLifecycle:(NSString*)phase { + if (_hostId.length == 0 || phase.length == 0) { + return; + } + + [self mountUIKitHostIfNeeded]; + [self applyUIKitHostHandles:NativeScriptRunUIKitHostLifecycle(_hostId, phase)]; +} + - (void)setChildrenView:(UIView*)childrenView { if (_childrenView == childrenView) { return; } + [self detachDetachedChildrenTouchHandler]; + [_detachedTouchSentinel removeFromSuperview]; + [_detachedTouchSentinel release]; + _detachedTouchSentinel = nil; + if (NativeScriptDetachedChildrenOwner(_childrenView) == self) { + NativeScriptSetDetachedChildrenOwner(_childrenView, nil); + } [_childrenView release]; _childrenView = [childrenView retain]; + NativeScriptSetDetachedChildrenOwner(_childrenView, self); [self moveReactSubviewsToChildrenView]; + [self installDetachedChildrenTouchSentinelIfNeeded]; + [self attachDetachedChildrenTouchHandlerIfNeeded]; + [self notifyHostReadyIfNeeded]; } - (void)setNativeView:(UIView*)nativeView { @@ -153,6 +619,7 @@ - (void)setNativeView:(UIView*)nativeView { [super insertSubview:_nativeView atIndex:0]; [self moveReactSubviewsToChildrenView]; [self setNeedsLayout]; + [self notifyHostReadyIfNeeded]; } - (void)setViewController:(UIViewController*)viewController { @@ -163,13 +630,22 @@ - (void)setViewController:(UIViewController*)viewController { [self detachViewController]; [_viewController release]; _viewController = [viewController retain]; - [self setNativeView:_viewController.view]; + if (_detachControllerView) { + if (_viewController != nil && _nativeView == _viewController.view) { + [self setNativeView:nil]; + } + return; + } + if (_nativeViewHandle.length == 0) { + [self setNativeView:_viewController.view]; + } [self attachViewControllerIfPossible]; + [self notifyHostReadyIfNeeded]; } - (void)attachViewControllerIfPossible { - if (_viewController == nil || _viewController.parentViewController != nil || - self.window == nil) { + if (_detachControllerView || _viewController == nil || + _viewController.parentViewController != nil || self.window == nil) { return; } @@ -178,17 +654,68 @@ - (void)attachViewControllerIfPossible { return; } + UIView* hostedViewToReinsert = nil; + NSUInteger hostedViewIndex = NSNotFound; + if (_nativeView.superview == self && + NativeScriptHostedViewContainsControllerView(_nativeView, _viewController)) { + hostedViewToReinsert = [_nativeView retain]; + hostedViewIndex = [self.subviews indexOfObject:hostedViewToReinsert]; + [hostedViewToReinsert removeFromSuperview]; + } + + const BOOL shouldForwardAppearance = + hostedViewToReinsert == nil && NativeScriptShouldForwardControllerAppearance(_viewController); + if (shouldForwardAppearance) { + [_viewController beginAppearanceTransition:YES animated:NO]; + } + [parent addChildViewController:_viewController]; + if (hostedViewToReinsert != nil) { + NSUInteger targetIndex = + hostedViewIndex == NSNotFound ? 0 : MIN(hostedViewIndex, self.subviews.count); + [super insertSubview:hostedViewToReinsert atIndex:targetIndex]; + } [_viewController didMoveToParentViewController:parent]; + + if (shouldForwardAppearance) { + [_viewController endAppearanceTransition]; + } + [hostedViewToReinsert release]; } - (void)detachViewController { - if (_viewController == nil || _viewController.parentViewController == nil) { + if (_detachControllerView || _viewController == nil || + _viewController.parentViewController == nil) { return; } + UIView* hostedViewToReinsert = nil; + NSUInteger hostedViewIndex = NSNotFound; + if (_nativeView.superview == self && + NativeScriptHostedViewContainsControllerView(_nativeView, _viewController)) { + hostedViewToReinsert = [_nativeView retain]; + hostedViewIndex = [self.subviews indexOfObject:hostedViewToReinsert]; + } + + const BOOL shouldForwardAppearance = + hostedViewToReinsert == nil && NativeScriptShouldForwardControllerAppearance(_viewController); + if (shouldForwardAppearance) { + [_viewController beginAppearanceTransition:NO animated:NO]; + } + [_viewController willMoveToParentViewController:nil]; + [hostedViewToReinsert removeFromSuperview]; [_viewController removeFromParentViewController]; + if (hostedViewToReinsert != nil) { + NSUInteger targetIndex = + hostedViewIndex == NSNotFound ? 0 : MIN(hostedViewIndex, self.subviews.count); + [super insertSubview:hostedViewToReinsert atIndex:targetIndex]; + } + + if (shouldForwardAppearance) { + [_viewController endAppearanceTransition]; + } + [hostedViewToReinsert release]; } - (void)moveReactSubviewsToChildrenView { @@ -204,6 +731,10 @@ - (void)moveReactSubviewsToChildrenView { [_childrenView addSubview:subview]; } [subviews release]; + [self layoutDetachedChildrenViewSubviewsIfNeeded]; + [self installDetachedChildrenTouchSentinelIfNeeded]; + [self attachDetachedChildrenTouchHandlerIfNeeded]; + [self notifyHostReadyIfNeeded]; } - (void)insertSubview:(UIView*)view atIndex:(NSInteger)index { @@ -211,19 +742,242 @@ - (void)insertSubview:(UIView*)view atIndex:(NSInteger)index { NSUInteger targetIndex = MIN(static_cast(MAX(index, 0)), _childrenView.subviews.count); [_childrenView insertSubview:view atIndex:targetIndex]; + [self layoutDetachedChildrenViewSubviewsIfNeeded]; + [self installDetachedChildrenTouchSentinelIfNeeded]; + [self attachDetachedChildrenTouchHandlerIfNeeded]; + [self notifyHostReadyIfNeeded]; return; } [super insertSubview:view atIndex:index]; + [self notifyHostReadyIfNeeded]; +} + +- (void)layoutDetachedChildrenViewSubviewsIfNeeded { + if (_childrenView == nil) { + return; + } + + const CGRect bounds = _childrenView.bounds; + for (UIView* subview in _childrenView.subviews) { + if (subview == _detachedTouchSentinel) { + subview.frame = CGRectZero; + continue; + } + + subview.frame = bounds; + subview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [subview setNeedsLayout]; + [subview layoutIfNeeded]; + NativeScriptLayoutHostedSubviewChain(subview, 0); + } +} + +- (BOOL)refreshDetachedChildrenHost { + if (_childrenView == nil) { + return NO; + } + + [self layoutDetachedChildrenViewSubviewsIfNeeded]; + [self installDetachedChildrenTouchSentinelIfNeeded]; + [self attachDetachedChildrenTouchHandlerIfNeeded]; + [self updateDetachedChildrenTouchHandlerOrigin]; + [self notifyHostReadyIfNeeded]; + + return NativeScriptChildrenViewHasVisibleChild(_childrenView, _detachedTouchSentinel); +} + +- (void)installDetachedChildrenTouchSentinelIfNeeded { + if (_childrenView == nil || _detachedTouchSentinel != nil) { + return; + } + + NativeScriptDetachedChildrenTouchSentinel* sentinel = + [[NativeScriptDetachedChildrenTouchSentinel alloc] initWithFrame:CGRectZero]; + sentinel.owner = self; + sentinel.hidden = YES; + sentinel.userInteractionEnabled = NO; + sentinel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _detachedTouchSentinel = sentinel; + [_childrenView addSubview:sentinel]; +} + +- (void)attachDetachedChildrenTouchHandlerIfNeeded { + if (_childrenView == nil) { + return; + } + + UIView* touchView = _childrenView; + touchView.userInteractionEnabled = YES; + if (NativeScriptFindAncestorSurfaceTouchHandler(touchView) != nil) { + [self detachDetachedChildrenTouchHandler]; + return; + } + + if (_detachedTouchHandler != nil) { + UIView* attachedTouchHandlerView = + NativeScriptGestureRecognizerAttachedView(_detachedTouchHandler); + if (_detachedTouchHandlerView != touchView || + (attachedTouchHandlerView != nil && attachedTouchHandlerView != touchView) || + _detachedTouchHandlerWindow != touchView.window || + !NativeScriptViewHasGestureRecognizer(touchView, _detachedTouchHandler)) { + [self detachDetachedChildrenTouchHandler]; + } else { + [self updateDetachedChildrenTouchHandlerOrigin]; + return; + } + } + + if (_detachedTouchHandler != nil) { + [self updateDetachedChildrenTouchHandlerOrigin]; + return; + } + +#if __has_include() + RCTSurfaceTouchHandler* surfaceTouchHandler = [RCTSurfaceTouchHandler new]; + [surfaceTouchHandler attachToView:touchView]; + _detachedTouchHandler = surfaceTouchHandler; + _detachedTouchHandlerView = [touchView retain]; + _detachedTouchHandlerWindow = touchView.window; + [self updateDetachedChildrenTouchHandlerOrigin]; + return; +#endif +} + +- (void)updateDetachedChildrenTouchHandlerOrigin { +#if __has_include() + if (_detachedTouchHandler == nil || _detachedTouchHandlerView == nil || + ![_detachedTouchHandler isKindOfClass:RCTSurfaceTouchHandler.class]) { + return; + } + + CGPoint origin = CGPointZero; + if (_detachedTouchHandlerView.window != nil) { + origin = [_detachedTouchHandlerView convertPoint:CGPointZero + toView:_detachedTouchHandlerView.window]; + } + + ((RCTSurfaceTouchHandler*)_detachedTouchHandler).viewOriginOffset = origin; +#endif +} + +- (void)detachDetachedChildrenTouchHandler { + if (_detachedTouchHandler == nil || _detachedTouchHandlerView == nil) { + [_detachedTouchHandler release]; + _detachedTouchHandler = nil; + [_detachedTouchHandlerView release]; + _detachedTouchHandlerView = nil; + _detachedTouchHandlerWindow = nil; + return; + } + + UIView* attachedTouchHandlerView = + NativeScriptGestureRecognizerAttachedView(_detachedTouchHandler); + UIView* detachView = + attachedTouchHandlerView != nil ? attachedTouchHandlerView : _detachedTouchHandlerView; + + if ([_detachedTouchHandler respondsToSelector:@selector(detachFromView:)]) { + if (NativeScriptViewHasGestureRecognizer(detachView, _detachedTouchHandler)) { + [_detachedTouchHandler detachFromView:detachView]; + } + } + + [_detachedTouchHandler release]; + _detachedTouchHandler = nil; + [_detachedTouchHandlerView release]; + _detachedTouchHandlerView = nil; + _detachedTouchHandlerWindow = nil; +} + +- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event { + [self refreshDetachedChildrenHost]; + + UIView* hitView = [super hitTest:point withEvent:event]; + if (hitView == nil && _childrenView != nil && _childrenView.window != nil) { + CGPoint childrenPoint = [_childrenView convertPoint:point fromView:self]; + hitView = [_childrenView hitTest:childrenPoint withEvent:event]; + } + + if (hitView == nil || self.window == nil) { + return hitView; + } + + CGPoint windowPoint = [self convertPoint:point toView:self.window]; + UITabBar* tabBar = NativeScriptVisibleTabBarAtPoint(self.window, self.window, windowPoint); + if (tabBar != nil) { + if (NativeScriptViewIsDescendantOfView(tabBar, self)) { + CGPoint tabBarPoint = [tabBar convertPoint:windowPoint fromView:self.window]; + UIView* tabBarHitView = [tabBar hitTest:tabBarPoint withEvent:event]; + if (tabBarHitView != nil) { + return tabBarHitView; + } + return tabBar; + } + if (!NativeScriptViewIsDescendantOfView(self, tabBar)) { + return nil; + } + } + + return hitView; } - (void)didMoveToWindow { [super didMoveToWindow]; + [self mountUIKitHostIfNeeded]; [self attachViewControllerIfPossible]; + [self attachDetachedChildrenTouchHandlerIfNeeded]; + [self updateDetachedChildrenTouchHandlerOrigin]; + [self notifyHostReadyIfNeeded]; } - (void)layoutSubviews { [super layoutSubviews]; _nativeView.frame = self.bounds; + [self layoutDetachedChildrenViewSubviewsIfNeeded]; + [self installDetachedChildrenTouchSentinelIfNeeded]; + [self attachDetachedChildrenTouchHandlerIfNeeded]; + [self updateDetachedChildrenTouchHandlerOrigin]; + [self notifyHostReadyIfNeeded]; } @end + +static BOOL NativeScriptRefreshUIKitHostSubviews(UIView* root, NSUInteger depth) { + if (root == nil || depth > 24) { + return NO; + } + + BOOL refreshed = NO; + if ([root isKindOfClass:NativeScriptUIView.class]) { + refreshed = [static_cast(root) refreshDetachedChildrenHost] || refreshed; + } + + NativeScriptUIView* detachedChildrenOwner = NativeScriptDetachedChildrenOwner(root); + if (detachedChildrenOwner != nil) { + refreshed = [detachedChildrenOwner refreshDetachedChildrenHost] || refreshed; + } + + if ([root isKindOfClass:NativeScriptDetachedChildrenTouchSentinel.class]) { + NativeScriptDetachedChildrenTouchSentinel* sentinel = + static_cast(root); + refreshed = [sentinel.owner refreshDetachedChildrenHost] || refreshed; + } + + for (UIView* subview in root.subviews) { + refreshed = NativeScriptRefreshUIKitHostSubviews(subview, depth + 1) || refreshed; + } + + return refreshed; +} + +BOOL NativeScriptRefreshUIKitHostView(NSString* viewHandle) { + if (![NSThread isMainThread]) { + return NO; + } + + UIView* view = NativeScriptUIViewFromHandle(viewHandle); + if (view == nil) { + return NO; + } + + return NativeScriptRefreshUIKitHostSubviews(view, 0); +} diff --git a/packages/react-native/ios/NativeScriptUIViewManager.mm b/packages/react-native/ios/NativeScriptUIViewManager.mm index dab55ce25..9de511f65 100644 --- a/packages/react-native/ios/NativeScriptUIViewManager.mm +++ b/packages/react-native/ios/NativeScriptUIViewManager.mm @@ -14,6 +14,14 @@ - (UIView*)view { } RCT_EXPORT_VIEW_PROPERTY(nativeViewHandle, NSString) +RCT_EXPORT_VIEW_PROPERTY(childrenViewHandle, NSString) +RCT_EXPORT_VIEW_PROPERTY(controllerHandle, NSString) +RCT_EXPORT_VIEW_PROPERTY(detachControllerView, BOOL) RCT_EXPORT_VIEW_PROPERTY(debugName, NSString) +RCT_EXPORT_VIEW_PROPERTY(hostId, NSString) +RCT_EXPORT_VIEW_PROPERTY(hostReadyId, NSString) +RCT_EXPORT_VIEW_PROPERTY(updateRevision, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(mountedRevision, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(onHostReady, RCTDirectEventBlock) @end diff --git a/packages/react-native/package.json b/packages/react-native/package.json index cd72e79ec..f7dddb779 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -7,11 +7,13 @@ "React Native", "TurboModule", "JSI", - "iOS" + "iOS", + "Android", + "Java" ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/napi-ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/react-native" }, "author": { @@ -34,9 +36,10 @@ "plugin", "src", "types", + "android", "ios", "metadata", - "native-api-jsi", + "native-api", "NativeScriptNativeApi.podspec", "README.md", "LICENSE" @@ -44,11 +47,15 @@ "peerDependencies": { "expo": "*", "react": "*", - "react-native": ">=0.79" + "react-native": ">=0.79", + "react-native-worklets": ">=0.8.0" }, "peerDependenciesMeta": { "expo": { "optional": true + }, + "react-native-worklets": { + "optional": true } }, "codegenConfig": { @@ -66,5 +73,11 @@ "NativeScriptNativeApi": "NativeScriptNativeApiModuleProvider" } } + }, + "dependencies": { + "@babel/parser": "^7.17.3", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0", + "split": "^1.0.1" } } diff --git a/packages/react-native/plugin/babel-plugin.js b/packages/react-native/plugin/babel-plugin.js index 8db3c1d3e..9e80f8b98 100644 --- a/packages/react-native/plugin/babel-plugin.js +++ b/packages/react-native/plugin/babel-plugin.js @@ -1,4 +1,17 @@ const PACKAGE_NAME = '@nativescript/react-native'; +const UIKIT_DEFINITION_CALLEES = new Set([ + 'defineUIKitContainer', + 'defineUIKitView', + 'defineUIViewController', +]); +const UIKIT_WORKLET_CALLBACKS = new Set([ + 'create', + 'createController', + 'childrenView', + 'dispose', + 'mounted', + 'update', +]); function isDirectiveFunction(path) { const body = path.node.body; @@ -75,6 +88,81 @@ function findNativeScriptIdentifier(programPath, t) { return null; } +function collectNativeScriptBindings(programPath, t) { + const nativeScriptIdentifiers = new Set(); + const uikitDefinitionIdentifiers = new Set(); + + for (const statement of programPath.get('body')) { + if (statement.isImportDeclaration()) { + if (!isNativeScriptSource(statement.node.source.value)) { + continue; + } + for (const specifier of statement.node.specifiers) { + if ( + t.isImportDefaultSpecifier(specifier) || + t.isImportNamespaceSpecifier(specifier) + ) { + nativeScriptIdentifiers.add(specifier.local.name); + } else if (t.isImportSpecifier(specifier)) { + const imported = specifier.imported; + const importedName = t.isIdentifier(imported) + ? imported.name + : imported.value; + if (UIKIT_DEFINITION_CALLEES.has(importedName)) { + uikitDefinitionIdentifiers.add(specifier.local.name); + } + } + } + continue; + } + + if (!statement.isVariableDeclaration()) { + continue; + } + for (const declaration of statement.node.declarations) { + const init = declaration.init; + const requiredNativeScript = + t.isCallExpression(init) && + t.isIdentifier(init.callee, {name: 'require'}) && + init.arguments.length === 1 && + t.isStringLiteral(init.arguments[0], {value: PACKAGE_NAME}); + const requiredNativeScriptDefault = + t.isMemberExpression(init) && + !init.computed && + t.isIdentifier(init.property, {name: 'default'}) && + t.isCallExpression(init.object) && + t.isIdentifier(init.object.callee, {name: 'require'}) && + init.object.arguments.length === 1 && + t.isStringLiteral(init.object.arguments[0], {value: PACKAGE_NAME}); + if (t.isIdentifier(declaration.id)) { + if (requiredNativeScript || requiredNativeScriptDefault) { + nativeScriptIdentifiers.add(declaration.id.name); + } + } else if (t.isObjectPattern(declaration.id) && requiredNativeScript) { + for (const property of declaration.id.properties) { + if (!t.isObjectProperty(property)) { + continue; + } + const key = property.key; + const value = property.value; + const keyName = t.isIdentifier(key) ? key.name : key.value; + if ( + UIKIT_DEFINITION_CALLEES.has(keyName) && + t.isIdentifier(value) + ) { + uikitDefinitionIdentifiers.add(value.name); + } + } + } + } + } + + return { + nativeScriptIdentifiers, + uikitDefinitionIdentifiers, + }; +} + function ensureNativeScriptIdentifier(programPath, state, t) { if (state.nativeScriptIdentifier) { return state.nativeScriptIdentifier; @@ -124,6 +212,86 @@ function ensureNativeScriptIdentifier(programPath, state, t) { return identifier.name; } +function isUIKitDefinitionCall(path, state, t) { + const callee = path.node.callee; + if ( + t.isIdentifier(callee) && + state.uikitDefinitionIdentifiers?.has(callee.name) + ) { + return true; + } + if ( + t.isMemberExpression(callee) && + !callee.computed && + t.isIdentifier(callee.object) && + t.isIdentifier(callee.property) && + state.nativeScriptIdentifiers?.has(callee.object.name) && + UIKIT_DEFINITION_CALLEES.has(callee.property.name) + ) { + return true; + } + return false; +} + +function propertyKeyName(property, t) { + const key = property.node.key; + if (t.isIdentifier(key)) { + return key.name; + } + if (t.isStringLiteral(key)) { + return key.value; + } + return null; +} + +function ensureWorkletDirective(functionNode, t) { + if (!functionNode.body) { + return; + } + if (!t.isBlockStatement(functionNode.body)) { + functionNode.body = t.blockStatement([ + t.returnStatement(functionNode.body), + ]); + } + const directives = functionNode.body.directives || []; + if (directives.some((directive) => directive.value?.value === 'worklet')) { + return; + } + functionNode.body.directives = [ + t.directive(t.directiveLiteral('worklet')), + ...directives, + ]; +} + +function workletizeUIKitDefinitionCallbacks(path, state, t) { + if (!isUIKitDefinitionCall(path, state, t)) { + return; + } + + const definition = path.get('arguments')[0]; + if (!definition || !definition.isObjectExpression()) { + return; + } + + for (const property of definition.get('properties')) { + if (property.isSpreadElement()) { + continue; + } + const keyName = propertyKeyName(property, t); + if (!UIKIT_WORKLET_CALLBACKS.has(keyName)) { + continue; + } + if (property.isObjectMethod()) { + ensureWorkletDirective(property.node, t); + } else if (property.isObjectProperty()) { + const value = property.get('value'); + if (value.isFunctionExpression() || value.isArrowFunctionExpression()) { + ensureWorkletDirective(value.node, t); + } + } + } +} + function isAlreadyWrapped(path, t) { const parent = path.parentPath; if (!parent || !parent.isCallExpression()) { @@ -144,6 +312,11 @@ function wrapDirectiveFunction(path, state, t) { if (!policy || isAlreadyWrapped(path, t)) { return; } + if (policy === 'ui') { + throw path.buildCodeFrameError( + 'NativeScript "use ui" callbacks are not supported in React Native. Use a Worklets "worklet" callback with NativeScript.runOnUI().', + ); + } const programPath = path.findParent((parentPath) => parentPath.isProgram()); const nativeScriptIdentifier = ensureNativeScriptIdentifier(programPath, state, t); @@ -152,7 +325,7 @@ function wrapDirectiveFunction(path, state, t) { t.callExpression( t.memberExpression( t.identifier(nativeScriptIdentifier), - t.identifier(policy === 'ui' ? 'uiInvoker' : 'jsInvoker'), + t.identifier('jsInvoker'), ), [original], ), @@ -165,8 +338,14 @@ module.exports = function nativeScriptReactNativeBabelPlugin({types: t}) { name: 'nativescript-react-native-thread-directives', visitor: { Program(path, state) { + const bindings = collectNativeScriptBindings(path, t); + state.nativeScriptIdentifiers = bindings.nativeScriptIdentifiers; + state.uikitDefinitionIdentifiers = bindings.uikitDefinitionIdentifiers; state.nativeScriptIdentifier = findNativeScriptIdentifier(path, t); }, + CallExpression(path, state) { + workletizeUIKitDefinitionCallbacks(path, state, t); + }, ArrowFunctionExpression(path, state) { wrapDirectiveFunction(path, state, t); }, diff --git a/packages/react-native/plugin/withNativeScriptReactNative.js b/packages/react-native/plugin/withNativeScriptReactNative.js index baa605207..8f3d78349 100644 --- a/packages/react-native/plugin/withNativeScriptReactNative.js +++ b/packages/react-native/plugin/withNativeScriptReactNative.js @@ -9,22 +9,38 @@ const DEFAULTS = { }; const BABEL_PLUGIN = '@nativescript/react-native/babel-plugin'; +const WORKLETS_BABEL_PLUGIN = 'react-native-worklets/plugin'; +const BABEL_PLUGINS = [BABEL_PLUGIN, WORKLETS_BABEL_PLUGIN]; +const EXPO_BABEL_PRESET = 'babel-preset-expo'; +// Without strictGlobal the worklets plugin captures every undeclared name into +// the worklet's closure on the JS thread when the worklet is created, where +// NativeScript's classes (UIView, UILabel, ...) do not exist yet. +const WORKLETS_BABEL_PLUGIN_ENTRY = `['${WORKLETS_BABEL_PLUGIN}', { strictGlobal: true }]`; +// babel-preset-expo adds its own worklets plugin, without strictGlobal. +const EXPO_BABEL_PRESET_ENTRY = `['${EXPO_BABEL_PRESET}', { worklets: false }]`; const METADATA_CONFIG_FILE = 'nativescript.react-native.json'; function readBoolean(value, fallback) { return typeof value === 'boolean' ? value : fallback; } +function stringList(value) { + return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : undefined; +} + function normalizeMetadataOptions(options = {}) { const metadata = options.metadata || {}; - return { - includePods: Array.isArray(metadata.includePods) - ? metadata.includePods.filter((value) => typeof value === 'string') - : [], - includeSystemFrameworks: Array.isArray(metadata.includeSystemFrameworks) - ? metadata.includeSystemFrameworks.filter((value) => typeof value === 'string') - : [], + const normalized = { + includePods: stringList(metadata.includePods) || [], + includeSystemFrameworks: stringList(metadata.includeSystemFrameworks) || [], }; + // Omitted unless given, so ensureMetadataConfig keeps an `android` block + // written by hand into nativescript.react-native.json. + const androidInclude = stringList(metadata.android && metadata.android.include); + if (androidInclude) { + normalized.android = {include: androidInclude}; + } + return normalized; } function normalizeOptions(options = {}) { @@ -58,11 +74,16 @@ function ensureMetadataConfig(projectRoot, metadataOptions) { } } + const existingReactNative = existing.reactNative || {}; + const existingMetadata = + existingReactNative.metadata && typeof existingReactNative.metadata === 'object' + ? existingReactNative.metadata + : {}; const merged = { ...existing, reactNative: { - ...(existing.reactNative || {}), - metadata: metadataOptions, + ...existingReactNative, + metadata: {...existingMetadata, ...metadataOptions}, }, }; const nextSource = `${JSON.stringify(merged, null, 2)}\n`; @@ -72,6 +93,19 @@ function ensureMetadataConfig(projectRoot, metadataOptions) { return merged; } +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&'); +} + +// Replaces `'name'` written as a bare array element with `entry`. Elements +// already given options (`['name', {...}]`) are left alone. +function replaceBareEntry(source, name, entry) { + const bare = new RegExp( + `([\\[,]\\s*)(['"])${escapeRegExp(name)}\\2(?=\\s*(?:,(?!\\s*\\{)|\\]))`, + ); + return source.replace(bare, (match, prefix) => `${prefix}${entry}`); +} + function ensureBabelPlugin(projectRoot) { const fs = require('fs'); const path = require('path'); @@ -88,8 +122,8 @@ function ensureBabelPlugin(projectRoot) { 'module.exports = function(api) {', ' api.cache(true);', ' return {', - " presets: ['babel-preset-expo'],", - ` plugins: ['${BABEL_PLUGIN}'],`, + ` presets: [${EXPO_BABEL_PRESET_ENTRY}],`, + ` plugins: ['${BABEL_PLUGIN}', ${WORKLETS_BABEL_PLUGIN_ENTRY}],`, ' };', '};', '', @@ -98,34 +132,50 @@ function ensureBabelPlugin(projectRoot) { return; } - let source = fs.readFileSync(babelConfigPath, 'utf8'); - if (source.includes(BABEL_PLUGIN)) { - return; + const original = fs.readFileSync(babelConfigPath, 'utf8'); + let source = original; + const missingPlugins = BABEL_PLUGINS.filter((plugin) => !source.includes(plugin)); + if (missingPlugins.length > 0) { + const pluginEntry = missingPlugins + .map((plugin) => + plugin === WORKLETS_BABEL_PLUGIN ? WORKLETS_BABEL_PLUGIN_ENTRY : `'${plugin}'`, + ) + .join(', ') + ', '; + if (/plugins\s*:\s*\[/.test(source)) { + source = source.replace(/plugins\s*:\s*\[/, (match) => `${match}${pluginEntry}`); + } else if (/return\s*\{/.test(source)) { + source = source.replace( + /return\s*\{/, + (match) => `${match}\n plugins: [${pluginEntry}],`, + ); + } else if (/module\.exports\s*=\s*\{/.test(source)) { + source = source.replace( + /module\.exports\s*=\s*\{/, + (match) => `${match}\n plugins: [${pluginEntry}],`, + ); + } else if (/export\s+default\s+\{/.test(source)) { + source = source.replace( + /export\s+default\s+\{/, + (match) => `${match}\n plugins: [${pluginEntry}],`, + ); + } else { + source += `\n// @nativescript/react-native: add ${missingPlugins.map((plugin) => `'${plugin}'`).join(' and ')} to your Babel plugins.\n`; + } } - const pluginEntry = `'${BABEL_PLUGIN}', `; - if (/plugins\s*:\s*\[/.test(source)) { - source = source.replace(/plugins\s*:\s*\[/, (match) => `${match}${pluginEntry}`); - } else if (/return\s*\{/.test(source)) { - source = source.replace( - /return\s*\{/, - (match) => `${match}\n plugins: ['${BABEL_PLUGIN}'],`, - ); - } else if (/module\.exports\s*=\s*\{/.test(source)) { - source = source.replace( - /module\.exports\s*=\s*\{/, - (match) => `${match}\n plugins: ['${BABEL_PLUGIN}'],`, - ); - } else if (/export\s+default\s+\{/.test(source)) { - source = source.replace( - /export\s+default\s+\{/, - (match) => `${match}\n plugins: ['${BABEL_PLUGIN}'],`, + source = replaceBareEntry(source, WORKLETS_BABEL_PLUGIN, WORKLETS_BABEL_PLUGIN_ENTRY); + source = replaceBareEntry(source, EXPO_BABEL_PRESET, EXPO_BABEL_PRESET_ENTRY); + + if (source.includes(WORKLETS_BABEL_PLUGIN) && !/strictGlobal\s*:\s*true/.test(source)) { + console.warn( + `@nativescript/react-native: pass { strictGlobal: true } to '${WORKLETS_BABEL_PLUGIN}' ` + + `in ${path.basename(babelConfigPath)}, or worklets that name native classes fail as their module loads.`, ); - } else { - source += `\n// @nativescript/react-native: add '${BABEL_PLUGIN}' to your Babel plugins.\n`; } - fs.writeFileSync(babelConfigPath, source); + if (source !== original) { + fs.writeFileSync(babelConfigPath, source); + } } function withNativeScriptBabelPlugin(config) { @@ -136,13 +186,11 @@ function withNativeScriptBabelPlugin(config) { return config; } - return withDangerousMod(config, [ - 'ios', - async (modConfig) => { - ensureBabelPlugin(modConfig.modRequest.projectRoot); - return modConfig; - }, - ]); + const mod = async (modConfig) => { + ensureBabelPlugin(modConfig.modRequest.projectRoot); + return modConfig; + }; + return withDangerousMod(withDangerousMod(config, ['ios', mod]), ['android', mod]); } function withNativeScriptMetadata(config, metadataOptions) { @@ -153,13 +201,42 @@ function withNativeScriptMetadata(config, metadataOptions) { return config; } - return withDangerousMod(config, [ - 'ios', - async (modConfig) => { - ensureMetadataConfig(modConfig.modRequest.projectRoot, metadataOptions); - return modConfig; - }, - ]); + const mod = async (modConfig) => { + ensureMetadataConfig(modConfig.modRequest.projectRoot, metadataOptions); + return modConfig; + }; + return withDangerousMod(withDangerousMod(config, ['ios', mod]), ['android', mod]); +} + +/** + * Applies the NativeScript Gradle script to the app module under prebuild. + * + * Autolinking already brings in the runtime; what this adds are the metadata and + * typings tasks, which read the app's own resolved classpath and so have to live + * in the app project. + */ +function withNativeScriptAndroidGradle(config) { + let withAppBuildGradle; + try { + ({withAppBuildGradle} = require('expo/config-plugins')); + } catch (error) { + return config; + } + + return withAppBuildGradle(config, (modConfig) => { + const {APPLY_LINE} = require('../cli/configure-android'); + let contents = modConfig.modResults.contents; + + if (!contents.includes('@nativescript/react-native/android/nativescript.gradle')) { + const reactPlugin = /apply plugin: ["']com\.facebook\.react["']\s*\n/; + contents = reactPlugin.test(contents) + ? contents.replace(reactPlugin, (match) => `${match}${APPLY_LINE}\n`) + : `${APPLY_LINE}\n${contents}`; + modConfig.modResults.contents = contents; + } + + return modConfig; + }); } function withNativeScriptReactNative(config, options) { @@ -182,6 +259,7 @@ function withNativeScriptReactNative(config, options) { config = withNativeScriptBabelPlugin(config); } config = withNativeScriptMetadata(config, normalized.metadata); + config = withNativeScriptAndroidGradle(config); return config; } @@ -190,6 +268,9 @@ module.exports = withNativeScriptReactNative; module.exports.default = withNativeScriptReactNative; module.exports.withNativeScriptReactNative = withNativeScriptReactNative; module.exports.ensureBabelPlugin = ensureBabelPlugin; +module.exports.BABEL_PLUGIN = BABEL_PLUGIN; +module.exports.WORKLETS_BABEL_PLUGIN = WORKLETS_BABEL_PLUGIN; module.exports.ensureMetadataConfig = ensureMetadataConfig; +module.exports.withNativeScriptAndroidGradle = withNativeScriptAndroidGradle; module.exports.normalizeMetadataOptions = normalizeMetadataOptions; module.exports.pkg = pkg; diff --git a/packages/react-native/react-native.config.js b/packages/react-native/react-native.config.js index 22b2e13d5..c43d86754 100644 --- a/packages/react-native/react-native.config.js +++ b/packages/react-native/react-native.config.js @@ -1,7 +1,12 @@ module.exports = { dependency: { platforms: { - android: null, + android: { + sourceDir: "android", + packageImportPath: + "import org.nativescript.reactnative.NativeScriptNativeApiPackage;", + packageInstance: "new NativeScriptNativeApiPackage()", + }, }, }, }; diff --git a/packages/react-native/src/NativeClass.d.ts b/packages/react-native/src/NativeClass.d.ts new file mode 100644 index 000000000..2a16bd9de --- /dev/null +++ b/packages/react-native/src/NativeClass.d.ts @@ -0,0 +1,48 @@ +/** + * `@NativeClass` — the marker every class that extends a native class carries. + * + * Implemented in plain JavaScript (NativeClass.js) so that it is identical + * under every bundler and needs no NativeScript-specific transform; these are + * the types for it. + */ + +export type NativeClassOptions = { + /** + * Fully-qualified name for the generated Java class, e.g. + * "com.example.Ticker". Taken verbatim by the runtime, which is what allows + * the static binding generator to emit the class at build time. Without it + * the class is keyed on the call site and generated on the device. + */ + name?: string; + + /** + * Java interfaces the generated class should implement, in addition to + * whatever it extends. A JavaScript class has one base, so this is the only + * way to express "implements Runnable and Callable". + */ + interfaces?: unknown[]; +}; + +type AnyClass = { new (...args: any[]): any; prototype: any }; + +/** Set on every decorated class. */ +export declare const NATIVE_CLASS: "__nsNativeClass"; + +export declare function NativeClass(target: T): T; +export declare function NativeClass( + target: T, + context: unknown, +): T; +export declare function NativeClass( + options?: NativeClassOptions, +): (target: T, context?: unknown) => T; + +/** Equivalent to `@NativeClass({ interfaces })`. */ +export declare function Interfaces( + interfaces: unknown[], +): (target: T, context?: unknown) => T; + +/** `@JavaProxy(name)` — equivalent to `@NativeClass({ name })`. */ +export declare function JavaProxy( + name: string, +): (target: T, context?: unknown) => T; diff --git a/packages/react-native/src/NativeClass.js b/packages/react-native/src/NativeClass.js new file mode 100644 index 000000000..c5a3f61ed --- /dev/null +++ b/packages/react-native/src/NativeClass.js @@ -0,0 +1,186 @@ +/** + * `@NativeClass` — the marker every class that extends a native class carries. + * + * This is a plain runtime decorator. It needs no NativeScript-specific + * transformer, reads nothing at build time, and behaves the same under Babel, + * SWC, tsc or anything else: all it ever receives is the class object the + * language hands a decorator, and all it ever does is annotate it before + * handing it back. + * + * Both decorator dialects are supported, because which one a project gets + * depends on its toolchain rather than on its code: + * + * legacy (TypeScript `experimentalDecorators`, Babel legacy) + * -> called as (target) + * standard (TC39 stage 3, Babel/SWC 2023-05, TypeScript 5) + * -> called as (value, context) + * + * and both the bare and the called form: + * + * @NativeClass + * class Foo extends java.lang.Object {} + * + * @NativeClass() + * class Foo extends java.lang.Object {} + * + * @NativeClass({ interfaces: [java.lang.Runnable, java.util.concurrent.Callable] }) + * class Foo extends java.lang.Object {} + * + * The interfaces list is the part that carries weight: a JavaScript class has + * one base, so implementing several Java interfaces cannot be expressed in the + * `extends` clause. The runtime reads `prototype.interfaces` when it generates + * the proxy class, which is exactly what this sets. + */ + +/** Set on every decorated class, so the runtime and tooling can recognise one. */ +const NATIVE_CLASS = "__nsNativeClass"; + +/** A native base carries the brand the runtime stamps on its own extend(). */ +function nativeExtendOf(base) { + const extend = base && base.extend; + return typeof extend === "function" && extend.__isNativeExtend__ === true + ? extend + : undefined; +} + +function annotate(target, options) { + if (typeof target !== "function") { + throw new TypeError( + "@NativeClass can only be applied to a class declaration.", + ); + } + + Object.defineProperty(target, NATIVE_CLASS, { + value: true, + enumerable: false, + configurable: true, + }); + + const interfaces = options.interfaces; + if (interfaces && interfaces.length > 0) { + // On the prototype: the prototype is the implementation object the runtime + // is handed when it generates the proxy, and it reads `interfaces` off it. + Object.defineProperty(target.prototype, "interfaces", { + value: interfaces, + enumerable: false, + configurable: true, + writable: true, + }); + } + + const explicitName = options.name; + if (explicitName !== undefined && explicitName.indexOf(".") < 0) { + throw new Error( + `@NativeClass name "${explicitName}" must be fully qualified, e.g. ` + + `"com.example.${explicitName}". The name is the generated Java class's ` + + `name, and the static binding generator matches on it.`, + ); + } + + const base = Object.getPrototypeOf(target); + const extend = nativeExtendOf(base); + if (!extend) { + // Not extending a native class. Nothing to wire up, and no reason to + // complain: @NativeClass on a plain class is harmless. + return target; + } + + // Generate the Java class now. + // + // This is the whole reason the decorator exists as more than a marker. A + // downlevelled class calls the runtime's __extends helper, which does this + // lazily; a real `class X extends Native {}` does not call any helper at all, + // so without this the JS side works -- overrides resolve through the + // prototype chain -- while Java sees an unmodified base class and never + // dispatches an override back into JS. + // + // Passing the prototype as the implementation object and `true` for the + // TypeScript-extend form is exactly what ts_helpers does. + // A fully-qualified name is taken verbatim by the runtime, with no call-site + // location mixed in. That is what makes it reproducible at build time: the + // static binding generator can emit a Java class under exactly this name and + // the runtime will find it instead of generating one on the device. + // + // Without a name the generated class is keyed on the call site, which under a + // bundler is a position in the bundle rather than in your source -- so nothing + // ahead of time can predict it, and the class is generated at runtime. + const extended = explicitName + ? extend.call(base, explicitName, target.prototype) + : extend.call(base, target.name, target.prototype, true); + + // Re-point the class at the generated one. `super()` resolves through the + // constructor's own prototype, so this is what makes construction go via the + // runtime -- and because it is still `target` that is being constructed, the + // class's constructor body and field initialisers run on the object the + // runtime returns, exactly as ES semantics require. + // + // Only the constructor. The instance chain is already right: the runtime uses + // the implementation object it was handed as the generated class's prototype, + // so extended.prototype is target.prototype, and re-pointing that at itself is + // a prototype cycle. + Object.setPrototypeOf(target, extended); + + return target; +} + +/** + * True when `args` is a standard-decorator invocation, i.e. (value, context) + * with a class context, rather than a legacy (target) one or an options object. + */ +function isStandardDecoratorCall(args) { + return ( + args.length >= 2 && + typeof args[0] === "function" && + args[1] != null && + typeof args[1] === "object" && + args[1].kind === "class" + ); +} + +function NativeClass(...args) { + // @NativeClass (standard decorators) + if (isStandardDecoratorCall(args)) { + return annotate(args[0], {}); + } + + // @NativeClass (legacy decorators) + if (args.length === 1 && typeof args[0] === "function") { + return annotate(args[0], {}); + } + + // @NativeClass(options) / @NativeClass() -- both dialects land here, and the + // returned decorator has to accept either shape in turn. + const options = args[0] || {}; + return function decorate(target, _context) { + return annotate(target, options); + }; +} + +/** + * Declares the Java interfaces a class implements. + * + * Equivalent to `@NativeClass({ interfaces })`, kept as a separate decorator + * because that is how NativeScript has always spelled it. + */ +function Interfaces(interfaces) { + return function decorate(target, _context) { + return annotate(target, { interfaces }); + }; +} + +/** + * Names the generated Java class. + * + * `@JavaProxy("com.example.Ticker")` is `@NativeClass({ name })`, spelled the way + * NativeScript has always spelled it. Naming a class is what lets it be + * generated at build time rather than on the device, and it is required for a + * class that has to exist before the app runs -- one referenced from + * AndroidManifest.xml, say. + */ +function JavaProxy(name) { + return function decorate(target, _context) { + return annotate(target, { name }); + }; +} + +module.exports = { NativeClass, Interfaces, JavaProxy, NATIVE_CLASS }; diff --git a/packages/react-native/src/NativeScriptNativeApi.ts b/packages/react-native/src/NativeScriptNativeApi.ts index 96c9b2688..e611d7319 100644 --- a/packages/react-native/src/NativeScriptNativeApi.ts +++ b/packages/react-native/src/NativeScriptNativeApi.ts @@ -1,12 +1,30 @@ import type {TurboModule} from 'react-native'; +import type {UnsafeObject} from 'react-native/Libraries/Types/CodegenTypes'; import {TurboModuleRegistry} from 'react-native'; export interface Spec extends TurboModule { readonly install: (metadataPath: string) => boolean; + readonly installWorkletRuntime: ( + runtimeHolder: UnsafeObject, + metadataPath: string, + ) => boolean; readonly isInstalled: () => boolean; readonly defaultMetadataPath: () => string; readonly getRuntimeBackend: () => string; readonly __writeTestMarker: (content: string) => boolean; } -export default TurboModuleRegistry.getEnforcing('NativeScriptNativeApi'); +function loadNativeApi(): Spec { + const nativeApi = TurboModuleRegistry.get('NativeScriptNativeApi'); + if (nativeApi == null) { + throw new Error( + "@nativescript/react-native: the native module 'NativeScriptNativeApi' is not in this app binary. " + + 'The package contains native code, so it cannot run in Expo Go: use a development build ' + + '(npx expo run:ios / npx expo run:android, or EAS Build). ' + + 'If the package was added after the app was built, rebuild the app.', + ); + } + return nativeApi; +} + +export default loadNativeApi(); diff --git a/packages/react-native/src/NativeScriptUIViewNativeComponent.ts b/packages/react-native/src/NativeScriptUIViewNativeComponent.ts index 32b7cc947..e6b930c63 100644 --- a/packages/react-native/src/NativeScriptUIViewNativeComponent.ts +++ b/packages/react-native/src/NativeScriptUIViewNativeComponent.ts @@ -1,11 +1,30 @@ import type {HostComponent, ViewProps} from 'react-native'; +import type { + DirectEventHandler, + Int32, +} from 'react-native/Libraries/Types/CodegenTypes'; import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +export type HostReadyEvent = { + hostReadyId: string; + hostId: string; + nativeViewHandle: string; + childrenViewHandle: string; + controllerHandle: string; + hasChildren: boolean; +}; + export interface NativeProps extends ViewProps { + hostId?: string; + hostReadyId?: string; nativeViewHandle?: string; childrenViewHandle?: string; controllerHandle?: string; + detachControllerView?: boolean; debugName?: string; + updateRevision?: Int32; + mountedRevision?: Int32; + onHostReady?: DirectEventHandler; } export default codegenNativeComponent( diff --git a/packages/react-native/src/index.d.ts b/packages/react-native/src/index.d.ts index 0437ac236..96c55f20a 100644 --- a/packages/react-native/src/index.d.ts +++ b/packages/react-native/src/index.d.ts @@ -4,8 +4,8 @@ import type { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, -} from 'react'; -import type {ViewProps} from 'react-native'; +} from "react"; +import type { ViewProps } from "react-native"; export type NativeApiHost = { runtime?: string; @@ -30,23 +30,53 @@ export type NativeApiHost = { getClass?: (name: string) => unknown; getProtocol?: (name: string) => unknown; getEnum?: (name: string) => unknown; + /** C constant names of the enum's members, which installGlobals() defines as globals. */ + getEnumGlobalNames?: (name: string) => string[]; getStruct?: (name: string) => unknown; getUnion?: (name: string) => unknown; - runOnUI?: (callback?: () => void) => Promise; [name: string]: unknown; }; export type InstallOptions = { + /** + * Install Objective-C classes/functions/constants as RN runtime globals. + * Native UI should run through worklets; React Native defaults this off so + * UIKit cannot be touched from the RN JavaScript thread by accident. + */ globals?: boolean; }; -export type UIKitSizingMode = 'fill' | 'intrinsic' | 'sizeThatFits' | 'autoLayout'; +export type NativeScriptWorklets = { + getUIRuntimeHolder: () => object; + isWorkletFunction: (value: unknown) => boolean; + runOnUIAsync: ( + callback: (...args: Args) => ReturnValue | Promise, + ...args: Args + ) => Promise; +}; + +export type UIKitSizingMode = + | "fill" + | "intrinsic" + | "sizeThatFits" + | "autoLayout"; export type UIKitLayoutOptions = { sizing?: UIKitSizingMode; - defaultSize?: {width?: number; height?: number}; - minSize?: {width?: number; height?: number}; - maxSize?: {width?: number; height?: number}; + defaultSize?: { width?: number; height?: number }; + minSize?: { width?: number; height?: number }; + maxSize?: { width?: number; height?: number }; +}; + +export type UIKitHostReadyEvent = { + nativeEvent: { + hostReadyId: string; + hostId: string; + nativeViewHandle: string; + childrenViewHandle: string; + controllerHandle: string; + hasChildren: boolean; + }; }; export type UIKitViewContext = { @@ -59,11 +89,12 @@ export type UIKitViewContext = { ? Payload : unknown, ): void; - targetAction( - control: unknown, - events: unknown, - callback: () => void, - ): void; + targetAction(control: unknown, events: unknown, callback: () => void): void; + gestureAction(gesture: unknown, callback: (gesture: unknown) => void): void; + actionTarget(callback: (sender: unknown) => void): { + target: unknown; + action: string; + }; delegate( object: unknown, protocolRef: unknown, @@ -83,12 +114,26 @@ export type UIKitViewContext = { release(value?: unknown): void; dispose(callback: () => void): void; invalidateLayout(): void; + loadImage( + source: unknown, + options: NativeScriptImageLoadOptions, + callback: NativeScriptImageLoadCallback, + ): boolean; }; -export type NativeScriptCallbackThread = 'ui' | 'js'; -export type NativeScriptInvokedCallback any> = T & { - readonly __nativeScriptCallbackThread?: NativeScriptCallbackThread; +export type NativeScriptImageLoadOptions = { + template?: boolean; }; +export type NativeScriptImageLoadCallback = ( + image: unknown | null, + error: Error | null, +) => void; +export type NativeScriptCallbackThread = "js" | "runtime"; +export type NativeScriptInvokedCallback any> = + T & { + readonly __nativeScriptCallbackThread?: NativeScriptCallbackThread; + readonly __nativeScriptWrappedCallback?: T; + }; export type NativeRetainer = { readonly size: number; retain(value: T): T; @@ -103,7 +148,7 @@ export type NativeDelegateOwner = { export type NativeProtocolReference = string | object | Function; export type CreateDelegateOptions = { name?: string; - thread?: NativeScriptCallbackThread | 'caller'; + thread?: NativeScriptCallbackThread | "caller"; retainer?: NativeRetainer; owner?: NativeDelegateOwner; assignTo?: { @@ -112,6 +157,12 @@ export type CreateDelegateOptions = { }; }; +export type UIKitDisposeResult = + | void + | { + removeHostView?: boolean; + }; + export type UIKitViewDefinition = { /** * Human-readable name for this UIKit view definition. This names the JS @@ -130,7 +181,9 @@ export type UIKitViewDefinition = { */ displayName?: string; layout?: UIKitLayoutOptions; - create: (ctx: UIKitViewContext & Readonly) => NativeView; + create: ( + ctx: UIKitViewContext & Readonly, + ) => NativeView; update?: ( view: NativeView, props: Readonly, @@ -146,7 +199,7 @@ export type UIKitViewDefinition = { view: NativeView, props: Readonly, ctx?: UIKitViewContext, - ) => void; + ) => UIKitDisposeResult; nativeProps?: ( props: Readonly, ) => Partial | undefined; @@ -155,14 +208,24 @@ export type UIKitViewDefinition = { export type UIKitViewRef = { readonly nativeView: NativeView | null; runOnUI: (callback: (view: NativeView) => T) => Promise; - measureNative: () => Promise<{width: number; height: number}>; + measureNative: () => Promise<{ width: number; height: number }>; invalidateNativeLayout: () => void; }; -export type UIKitViewComponent = - ForwardRefExoticComponent< - PropsWithoutRef & RefAttributes> - >; +export type UIKitHostViewProps = ViewProps & { + attachController?: boolean; + attachControllerView?: boolean; + attachNativeView?: boolean; + onHostReady?: (event: UIKitHostReadyEvent) => void; +}; + +export type UIKitViewComponent< + Props extends object, + NativeView = unknown, +> = ForwardRefExoticComponent< + PropsWithoutRef & + RefAttributes> +>; export type UIKitContainerResult = { rootView: RootView; @@ -175,7 +238,7 @@ export type UIKitContainerDefinition< ChildrenView = unknown, > = Omit< UIKitViewDefinition>, - 'create' | 'update' | 'mounted' | 'dispose' + "create" | "update" | "mounted" | "dispose" > & { create: ( ctx: UIKitViewContext & Readonly, @@ -195,16 +258,18 @@ export type UIKitContainerDefinition< view: UIKitContainerResult, props: Readonly, ctx?: UIKitViewContext, - ) => void; + ) => UIKitDisposeResult; }; export type UIViewControllerDefinition< Props extends object, Controller = unknown, -> = Omit, 'create'> & { +> = Omit, "create"> & { createController: ( ctx: UIKitViewContext & Readonly, ) => Controller; + hostView?: (controller: Controller) => unknown; + childrenView?: (controller: Controller) => unknown; }; export function init(metadataPath?: string, options?: InstallOptions): boolean; @@ -213,20 +278,37 @@ export function installGlobals(): boolean; export function isInstalled(): boolean; export function defaultMetadataPath(): string; export function getRuntimeBackend(): string; -export function runOnUI(callback?: () => void): Promise; +export function installWorklets( + worklets?: NativeScriptWorklets, + metadataPath?: string, +): boolean; +export function runOnUI( + callback: (...args: Args) => ReturnValue | Promise, + ...args: Args +): Promise; export function uiInvoker any>( callback: T, -): NativeScriptInvokedCallback; +): never; export function jsInvoker any>( callback: T, ): NativeScriptInvokedCallback; +export function runtimeInvoker any>( + callback: T, +): NativeScriptInvokedCallback; export function eventBridge any>( callback: T, - thread?: NativeScriptCallbackThread | 'caller', + thread?: NativeScriptCallbackThread | "caller", ): T | NativeScriptInvokedCallback; export const createEventBridge: typeof eventBridge; export function isMainThread(): boolean; export function assertUIKitThread(message?: string): void; +export function refreshUIKitHostView(view: unknown): boolean; +export function refreshUIKitHostViewHandle(viewHandle: string): boolean; +export function loadImage( + source: unknown, + options: NativeScriptImageLoadOptions, + callback: NativeScriptImageLoadCallback, +): boolean; export function warnIfNotUIKitThread(message?: string): boolean; export function createRetainer(): NativeRetainer; export function retain(value: T): T; @@ -268,6 +350,7 @@ declare const NativeScript: { defineUIKitView: typeof defineUIKitView; defineUIViewController: typeof defineUIViewController; getRuntimeBackend: typeof getRuntimeBackend; + installWorklets: typeof installWorklets; assertUIKitThread: typeof assertUIKitThread; createDelegate: typeof createDelegate; createEventBridge: typeof createEventBridge; @@ -282,7 +365,9 @@ declare const NativeScript: { loadFramework: typeof loadFramework; release: typeof release; retain: typeof retain; + refreshUIKitHostView: typeof refreshUIKitHostView; runOnUI: typeof runOnUI; + runtimeInvoker: typeof runtimeInvoker; uiInvoker: typeof uiInvoker; warnIfNotUIKitThread: typeof warnIfNotUIKitThread; }; diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 31fca7585..14baeaa06 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -2,18 +2,20 @@ import React, { forwardRef, useEffect, useImperativeHandle, + useLayoutEffect, useRef, useState, -} from 'react'; +} from "react"; import type { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes, -} from 'react'; -import type {ViewProps} from 'react-native'; -import {StyleSheet} from 'react-native'; -import NativeScriptNativeApi from './NativeScriptNativeApi'; -import NativeScriptUIViewNativeComponent from './NativeScriptUIViewNativeComponent'; +} from "react"; +import type { ViewProps } from "react-native"; +import NativeScriptNativeApi from "./NativeScriptNativeApi"; +import NativeScriptUIViewNativeComponent from "./NativeScriptUIViewNativeComponent"; + +declare const require: (id: string) => any; type NativeApiHost = { metadata?: { @@ -29,23 +31,52 @@ type NativeApiHost = { getClass?: (name: string) => unknown; getProtocol?: (name: string) => unknown; getEnum?: (name: string) => unknown; + getEnumGlobalNames?: (name: string) => string[]; getStruct?: (name: string) => unknown; getUnion?: (name: string) => unknown; - runOnUI?: (callback?: () => void) => Promise; [name: string]: unknown; }; export type InstallOptions = { + /** + * Install Objective-C classes/functions/constants as RN runtime globals. + * Native UI should run through worklets; React Native defaults this off so + * UIKit cannot be touched from the RN JavaScript thread by accident. + */ globals?: boolean; }; -export type UIKitSizingMode = 'fill' | 'intrinsic' | 'sizeThatFits' | 'autoLayout'; +export type NativeScriptWorklets = { + getUIRuntimeHolder: () => object; + isWorkletFunction: (value: unknown) => boolean; + runOnUIAsync: ( + callback: (...args: Args) => ReturnValue | Promise, + ...args: Args + ) => Promise; +}; + +export type UIKitSizingMode = + | "fill" + | "intrinsic" + | "sizeThatFits" + | "autoLayout"; export type UIKitLayoutOptions = { sizing?: UIKitSizingMode; - defaultSize?: {width?: number; height?: number}; - minSize?: {width?: number; height?: number}; - maxSize?: {width?: number; height?: number}; + defaultSize?: { width?: number; height?: number }; + minSize?: { width?: number; height?: number }; + maxSize?: { width?: number; height?: number }; +}; + +export type UIKitHostReadyEvent = { + nativeEvent: { + hostReadyId: string; + hostId: string; + nativeViewHandle: string; + childrenViewHandle: string; + controllerHandle: string; + hasChildren: boolean; + }; }; export type UIKitViewContext = { @@ -58,11 +89,12 @@ export type UIKitViewContext = { ? Payload : unknown, ): void; - targetAction( - control: unknown, - events: unknown, - callback: () => void, - ): void; + targetAction(control: unknown, events: unknown, callback: () => void): void; + gestureAction(gesture: unknown, callback: (gesture: unknown) => void): void; + actionTarget(callback: (sender: unknown) => void): { + target: unknown; + action: string; + }; delegate( object: unknown, protocolRef: unknown, @@ -82,10 +114,30 @@ export type UIKitViewContext = { release(value?: unknown): void; dispose(callback: () => void): void; invalidateLayout(): void; + loadImage( + source: unknown, + options: NativeScriptImageLoadOptions, + callback: NativeScriptImageLoadCallback, + ): boolean; }; -type UIKitCreateArgument = - UIKitViewContext & Readonly; +type UIKitCreateArgument = UIKitViewContext & + Readonly; + +export type NativeScriptImageLoadOptions = { + template?: boolean; +}; + +export type NativeScriptImageLoadCallback = ( + image: unknown | null, + error: Error | null, +) => void; + +export type UIKitDisposeResult = + | void + | { + removeHostView?: boolean; + }; export type UIKitViewDefinition = { name?: string; @@ -108,7 +160,7 @@ export type UIKitViewDefinition = { view: NativeView, props: Readonly, ctx?: UIKitViewContext, - ) => void; + ) => UIKitDisposeResult; nativeProps?: ( props: Readonly, ) => Partial | undefined; @@ -117,19 +169,26 @@ export type UIKitViewDefinition = { export type UIKitViewRef = { readonly nativeView: NativeView | null; runOnUI: (callback: (view: NativeView) => T) => Promise; - measureNative: () => Promise<{width: number; height: number}>; + measureNative: () => Promise<{ width: number; height: number }>; invalidateNativeLayout: () => void; }; -export type UIKitViewComponent = - ForwardRefExoticComponent< - PropsWithoutRef & RefAttributes> - >; +export type UIKitHostViewProps = ViewProps & { + attachController?: boolean; + attachControllerView?: boolean; + attachNativeView?: boolean; + onHostReady?: (event: UIKitHostReadyEvent) => void; +}; -export type UIKitContainerResult< - RootView = unknown, - ChildrenView = unknown, -> = { +export type UIKitViewComponent< + Props extends object, + NativeView = unknown, +> = ForwardRefExoticComponent< + PropsWithoutRef & + RefAttributes> +>; + +export type UIKitContainerResult = { rootView: RootView; childrenView: ChildrenView; }; @@ -140,7 +199,7 @@ export type UIKitContainerDefinition< ChildrenView = unknown, > = Omit< UIKitViewDefinition>, - 'create' | 'update' | 'mounted' | 'dispose' + "create" | "update" | "mounted" | "dispose" > & { create: ( ctx: UIKitCreateArgument, @@ -160,32 +219,40 @@ export type UIKitContainerDefinition< view: UIKitContainerResult, props: Readonly, ctx?: UIKitViewContext, - ) => void; + ) => UIKitDisposeResult; }; export type UIViewControllerDefinition< Props extends object, Controller = unknown, -> = Omit< - UIKitViewDefinition, - 'create' -> & { +> = Omit, "create"> & { createController: (ctx: UIKitCreateArgument) => Controller; + hostView?: (controller: Controller) => unknown; + childrenView?: (controller: Controller) => unknown; }; -const nativeApiGlobalName = '__nativeScriptNativeApi'; -const nativeApiGlobalCacheName = '__nativeScriptNativeApiGlobalCache'; -const nativeApiTypeCodeKey = '__nativeApiTypeCode'; -const nativeApiCallbackThreadKey = '__nativeScriptCallbackThread'; -const nativeApiWrappedCallbackKey = '__nativeScriptWrappedCallback'; +const nativeApiGlobalName = "__nativeScriptNativeApi"; +const nativeApiGlobalCacheName = "__nativeScriptNativeApiGlobalCache"; +const nativeApiTypeCodeKey = "__nativeApiTypeCode"; +const nativeApiCallbackThreadKey = "__nativeScriptCallbackThread"; +const nativeApiWrappedCallbackKey = "__nativeScriptWrappedCallback"; const nativeClassWrappers = new WeakMap(); -export type NativeScriptCallbackThread = 'ui' | 'js'; +export type NativeScriptCallbackThread = "js" | "runtime"; type AnyFunction = (...args: any[]) => any; export type NativeScriptInvokedCallback = T & { readonly __nativeScriptCallbackThread?: NativeScriptCallbackThread; + readonly __nativeScriptWrappedCallback?: T; }; +const nativeCallbackMetadataSkipKeys = new Set([ + "length", + "name", + "prototype", + "arguments", + "caller", +]); + export type NativeRetainer = { readonly size: number; retain(value: T): T; @@ -201,7 +268,7 @@ export type NativeDelegateOwner = { export type CreateDelegateOptions = { name?: string; - thread?: NativeScriptCallbackThread | 'caller'; + thread?: NativeScriptCallbackThread | "caller"; retainer?: NativeRetainer; owner?: NativeDelegateOwner; assignTo?: { @@ -221,7 +288,9 @@ function nativeApiHost(): NativeApiHost | undefined { function requireNativeApiHost(): NativeApiHost { const api = nativeApiHost(); if (!api) { - throw new Error('NativeScript Native API JSI host object was not installed'); + throw new Error( + "NativeScript Native API JSI host object was not installed", + ); } return api; } @@ -229,7 +298,7 @@ function requireNativeApiHost(): NativeApiHost { function nativeApiGlobalCache(): Record { const globalObject = globalThis as Record; const existing = globalObject[nativeApiGlobalCacheName]; - if (existing && typeof existing === 'object') { + if (existing && typeof existing === "object") { return existing as Record; } @@ -296,57 +365,58 @@ export function release(value?: unknown): void { } const hostViewPropNames = new Set([ - 'accessible', - 'accessibilityActions', - 'accessibilityElementsHidden', - 'accessibilityHint', - 'accessibilityIgnoresInvertColors', - 'accessibilityLabel', - 'accessibilityLanguage', - 'accessibilityLiveRegion', - 'accessibilityRole', - 'accessibilityState', - 'accessibilityValue', - 'accessibilityViewIsModal', - 'children', - 'collapsable', - 'focusable', - 'hitSlop', - 'id', - 'importantForAccessibility', - 'nativeID', - 'needsOffscreenAlphaCompositing', - 'onAccessibilityAction', - 'onAccessibilityEscape', - 'onAccessibilityTap', - 'onLayout', - 'onMagicTap', - 'onMoveShouldSetResponder', - 'onMoveShouldSetResponderCapture', - 'onResponderEnd', - 'onResponderGrant', - 'onResponderMove', - 'onResponderReject', - 'onResponderRelease', - 'onResponderStart', - 'onResponderTerminate', - 'onResponderTerminationRequest', - 'onStartShouldSetResponder', - 'onStartShouldSetResponderCapture', - 'pointerEvents', - 'removeClippedSubviews', - 'renderToHardwareTextureAndroid', - 'shouldRasterizeIOS', - 'style', - 'testID', + "accessible", + "accessibilityActions", + "accessibilityElementsHidden", + "accessibilityHint", + "accessibilityIgnoresInvertColors", + "accessibilityLabel", + "accessibilityLanguage", + "accessibilityLiveRegion", + "accessibilityRole", + "accessibilityState", + "accessibilityValue", + "accessibilityViewIsModal", + "children", + "collapsable", + "focusable", + "hitSlop", + "id", + "importantForAccessibility", + "nativeID", + "needsOffscreenAlphaCompositing", + "onAccessibilityAction", + "onAccessibilityEscape", + "onAccessibilityTap", + "onHostReady", + "onLayout", + "onMagicTap", + "onMoveShouldSetResponder", + "onMoveShouldSetResponderCapture", + "onResponderEnd", + "onResponderGrant", + "onResponderMove", + "onResponderReject", + "onResponderRelease", + "onResponderStart", + "onResponderTerminate", + "onResponderTerminationRequest", + "onStartShouldSetResponder", + "onStartShouldSetResponderCapture", + "pointerEvents", + "removeClippedSubviews", + "renderToHardwareTextureAndroid", + "shouldRasterizeIOS", + "style", + "testID", ]); function splitUIKitViewProps( - props: Props & ViewProps, + props: Props & UIKitHostViewProps, definition: UIKitViewDefinition, ): { nativeProps: ViewProps; - pluginProps: Props & ViewProps; + pluginProps: Props & UIKitHostViewProps; } { const nativeProps: Record = {}; const pluginProps: Record = {}; @@ -354,8 +424,8 @@ function splitUIKitViewProps( for (const [key, value] of Object.entries(props)) { if ( hostViewPropNames.has(key) || - key.startsWith('accessibility') || - key.startsWith('aria-') + key.startsWith("accessibility") || + key.startsWith("aria-") ) { nativeProps[key] = value; } else { @@ -367,41 +437,77 @@ function splitUIKitViewProps( return { nativeProps: nativeProps as ViewProps, - pluginProps: pluginProps as Props & ViewProps, + pluginProps: pluginProps as Props & UIKitHostViewProps, }; } function nativeHandleForUIKitView(view: unknown): string { + "worklet"; + const interop = (globalThis as Record).interop; - if (!interop || typeof interop.handleof !== 'function') { - throw new Error('NativeScript interop globals are not installed'); + if (!interop || typeof interop.handleof !== "function") { + throw new Error("NativeScript interop globals are not installed"); } const pointer = interop.handleof(view); if (!pointer) { - throw new Error('UIKit view definition returned a value without a native handle'); + throw new Error( + "UIKit view definition returned a value without a native handle", + ); } - if (typeof pointer.toHexString === 'function') { + if (typeof pointer.toHexString === "function") { const text = pointer.toHexString(); - if (typeof text === 'string' && text.length > 0) { + if (typeof text === "string" && text.length > 0) { return text; } } - if (typeof pointer.address === 'string' && pointer.address.length > 0) { + if (typeof pointer.address === "string" && pointer.address.length > 0) { return pointer.address; } - if (typeof pointer.address === 'number') { + if (typeof pointer.address === "number") { return String(pointer.address); } - if (typeof pointer.toNumber === 'function') { + if (typeof pointer.toNumber === "function") { return String(pointer.toNumber()); } - throw new Error('UIKit view native handle could not be read'); + throw new Error("UIKit view native handle could not be read"); +} + +function nativeHandleOrUndefined(value: unknown): string | undefined { + "worklet"; + + return value == null ? undefined : nativeHandleForUIKitView(value); +} + +function nativeHandleForNSObject(value: unknown): string | undefined { + "worklet"; + + if (value == null) { + return undefined; + } + const interop = (globalThis as Record).interop; + const pointer = interop?.handleof?.(value); + if (!pointer) { + return undefined; + } + if (typeof pointer.toHexString === "function") { + return pointer.toHexString(); + } + if (typeof pointer.address === "string") { + return pointer.address; + } + if (typeof pointer.address === "number") { + return String(pointer.address); + } + if (typeof pointer.toNumber === "function") { + return String(pointer.toNumber()); + } + return undefined; } function ensureNativeScriptInstalled(): void { @@ -421,7 +527,7 @@ function defineLazyNativeGlobal( if (!force && Object.prototype.hasOwnProperty.call(globalThis, name)) { const descriptor = Object.getOwnPropertyDescriptor(globalThis, name); - if (descriptor && 'value' in descriptor) { + if (descriptor && "value" in descriptor) { cacheNativeGlobal(name, descriptor.value); } return; @@ -458,7 +564,7 @@ function defineLazyNativeGlobal( } function wrapAggregateConstructor(nativeConstructor: unknown): unknown { - if (typeof nativeConstructor !== 'function') { + if (typeof nativeConstructor !== "function") { return nativeConstructor; } @@ -472,13 +578,14 @@ function wrapAggregateConstructor(nativeConstructor: unknown): unknown { configurable: true, enumerable: false, value(value: unknown) { - if (!value || typeof value !== 'object') { + if (!value || typeof value !== "object") { return false; } const actual = value as Record; return ( actual.kind === (nativeConstructor as Record).kind && - actual.name === (nativeConstructor as Record).runtimeName + actual.name === + (nativeConstructor as Record).runtimeName ); }, }); @@ -487,12 +594,12 @@ function wrapAggregateConstructor(nativeConstructor: unknown): unknown { } for (const key of [ - 'kind', - 'runtimeName', - 'metadataOffset', - 'sizeof', - 'fields', - 'equals', + "kind", + "runtimeName", + "metadataOffset", + "sizeof", + "fields", + "equals", ]) { try { Object.defineProperty(aggregate, key, { @@ -512,7 +619,7 @@ function wrapAggregateConstructor(nativeConstructor: unknown): unknown { function wrapNativeClass(nativeClass: unknown): unknown { if ( !nativeClass || - (typeof nativeClass !== 'object' && typeof nativeClass !== 'function') + (typeof nativeClass !== "object" && typeof nativeClass !== "function") ) { return nativeClass; } @@ -524,32 +631,34 @@ function wrapNativeClass(nativeClass: unknown): unknown { const constructable = function NativeScriptNativeClass(...args: unknown[]) { const cls = nativeClass as Record; - if (args.length > 0 && typeof cls.construct === 'function') { + if (args.length > 0 && typeof cls.construct === "function") { return cls.construct(...args); } - if (typeof cls.alloc !== 'function') { - throw new Error('Native class cannot be allocated'); + if (typeof cls.alloc !== "function") { + throw new Error("Native class cannot be allocated"); } const instance = cls.alloc(); - if (instance && typeof instance.init === 'function') { + if (instance && typeof instance.init === "function") { return instance.init(); } return instance; }; - Object.defineProperty(constructable, 'new', { + Object.defineProperty(constructable, "new", { configurable: true, enumerable: false, writable: false, value(...args: unknown[]) { if (args.length !== 0) { - throw new Error('new does not take arguments; use invoke for an explicit Objective-C selector.'); + throw new Error( + "new does not take arguments; use invoke for an explicit Objective-C selector.", + ); } return constructable(); }, }); - Object.defineProperty(constructable, '__nativeApiClass', { + Object.defineProperty(constructable, "__nativeApiClass", { configurable: false, enumerable: false, writable: false, @@ -563,14 +672,18 @@ function wrapNativeClass(nativeClass: unknown): unknown { configurable: true, enumerable: false, value(value: unknown) { - if (!value || typeof value !== 'object') { + if (!value || typeof value !== "object") { return false; } const cls = nativeClass as Record; try { - if (typeof (value as Record).isKindOfClass === 'function') { - return Boolean((value as Record).isKindOfClass(constructable)); + if ( + typeof (value as Record).isKindOfClass === "function" + ) { + return Boolean( + (value as Record).isKindOfClass(constructable), + ); } } catch { // Fall through to class-name equality for host objects that cannot @@ -579,7 +692,7 @@ function wrapNativeClass(nativeClass: unknown): unknown { const expectedName = cls.runtimeName ?? cls.name; const actualName = (value as Record).className; - return typeof expectedName === 'string' && actualName === expectedName; + return typeof expectedName === "string" && actualName === expectedName; }, }); } catch { @@ -594,8 +707,10 @@ function wrapNativeClass(nativeClass: unknown): unknown { if (cachedNativeFunctions.has(property)) { return cachedNativeFunctions.get(property); } - const nativeValue = (nativeClass as Record)[property]; - if (typeof nativeValue === 'function') { + const nativeValue = (nativeClass as Record)[ + property + ]; + if (typeof nativeValue === "function") { cachedNativeFunctions.set(property, nativeValue); try { Object.defineProperty(target, property, { @@ -627,7 +742,7 @@ function wrapInteropFactory( nativeFactory: unknown, properties: Record, ): unknown { - if (typeof nativeFactory !== 'function') { + if (typeof nativeFactory !== "function") { return nativeFactory; } @@ -640,10 +755,12 @@ function wrapInteropFactory( }; try { - const nativePrototype = (nativeFactory as {prototype?: unknown}).prototype; + const nativePrototype = (nativeFactory as { prototype?: unknown }) + .prototype; if ( nativePrototype && - (typeof nativePrototype === 'object' || typeof nativePrototype === 'function') + (typeof nativePrototype === "object" || + typeof nativePrototype === "function") ) { constructable.prototype = nativePrototype; } @@ -659,7 +776,7 @@ function wrapInteropFactory( value(value: unknown) { return ( Boolean(value) && - typeof value === 'object' && + typeof value === "object" && (value as Record).kind === properties.kind ); }, @@ -681,7 +798,7 @@ function wrapInteropFactory( } } - Object.defineProperty(constructable, '__nativeScriptConstructable', { + Object.defineProperty(constructable, "__nativeScriptConstructable", { configurable: false, enumerable: false, writable: false, @@ -695,7 +812,7 @@ function installInteropConstructors(): void { const interop = (globalThis as Record).interop as | Record | undefined; - if (!interop || typeof interop !== 'object') { + if (!interop || typeof interop !== "object") { return; } @@ -703,7 +820,7 @@ function installInteropConstructors(): void { const pointerType = (interop.types as Record | undefined) ?.pointer; let pointerSize: unknown = undefined; - if (typeof sizeof === 'function' && pointerType !== undefined) { + if (typeof sizeof === "function" && pointerType !== undefined) { try { pointerSize = sizeof(pointerType); } catch { @@ -712,22 +829,26 @@ function installInteropConstructors(): void { } interop.Pointer = wrapInteropFactory(interop.Pointer, { - kind: 'pointer', + kind: "pointer", sizeof: pointerSize, }); interop.Reference = wrapInteropFactory(interop.Reference, { - kind: 'reference', + kind: "reference", + sizeof: pointerSize, + }); + interop.Block = wrapInteropFactory(interop.Block, { + kind: "block", sizeof: pointerSize, }); interop.FunctionReference = wrapInteropFactory(interop.FunctionReference, { - kind: 'functionReference', + kind: "functionReference", sizeof: pointerSize, }); const types = interop.types as Record | undefined; - if (types && typeof types === 'object') { + if (types && typeof types === "object") { for (const [name, value] of Object.entries(types)) { - if (typeof value !== 'number') { + if (typeof value !== "number") { continue; } const boxed = { @@ -758,25 +879,25 @@ function defineInlineFunction(name: string, value: Function): void { } function installInlineFunctions(): void { - const makePoint = (x: number, y: number) => ({x, y}); - const makeSize = (width: number, height: number) => ({width, height}); + const makePoint = (x: number, y: number) => ({ x, y }); + const makeSize = (width: number, height: number) => ({ width, height }); const makeRect = (x: number, y: number, width: number, height: number) => ({ - origin: {x, y}, - size: {width, height}, + origin: { x, y }, + size: { width, height }, }); - defineInlineFunction('CGPointMake', makePoint); - defineInlineFunction('NSMakePoint', makePoint); - defineInlineFunction('CGSizeMake', makeSize); - defineInlineFunction('NSMakeSize', makeSize); - defineInlineFunction('CGRectMake', makeRect); - defineInlineFunction('NSMakeRect', makeRect); - defineInlineFunction('NSMakeRange', (location: number, length: number) => ({ + defineInlineFunction("CGPointMake", makePoint); + defineInlineFunction("NSMakePoint", makePoint); + defineInlineFunction("CGSizeMake", makeSize); + defineInlineFunction("NSMakeSize", makeSize); + defineInlineFunction("CGRectMake", makeRect); + defineInlineFunction("NSMakeRect", makeRect); + defineInlineFunction("NSMakeRange", (location: number, length: number) => ({ location, length, })); defineInlineFunction( - 'UIEdgeInsetsMake', + "UIEdgeInsetsMake", (top: number, left: number, bottom: number, right: number) => ({ top, left, @@ -794,7 +915,9 @@ export function installGlobals(): boolean { const classNames = api.metadata?.classNames?.() ?? []; for (const name of classNames) { - defineLazyNativeGlobal(name, (className) => wrapNativeClass(api[className])); + defineLazyNativeGlobal(name, (className) => + wrapNativeClass(api[className]), + ); } const functionNames = api.metadata?.functionNames?.() ?? []; @@ -817,21 +940,17 @@ export function installGlobals(): boolean { const enumNames = api.metadata?.enumNames?.() ?? []; for (const name of enumNames) { - const resolveEnum = (enumName: string) => api.getEnum?.(enumName) ?? api[enumName]; + const resolveEnum = (enumName: string) => + api.getEnum?.(enumName) ?? api[enumName]; defineLazyNativeGlobal(name, resolveEnum); - const enumValue = resolveEnum(name); - if (!enumValue || typeof enumValue !== 'object') { - continue; - } - for (const memberName of Object.keys(enumValue)) { - if (/^-?\d+$/.test(memberName)) { - continue; - } - defineLazyNativeGlobal( - memberName, - () => (enumValue as Record)[memberName], - ); + for (const memberName of api.getEnumGlobalNames?.(name) ?? []) { + defineLazyNativeGlobal(memberName, () => { + const enumValue = resolveEnum(name) as + | Record + | undefined; + return enumValue?.[memberName]; + }); } } @@ -840,7 +959,9 @@ export function installGlobals(): boolean { defineLazyNativeGlobal( name, (structName) => - wrapAggregateConstructor(api.getStruct?.(structName) ?? api[structName]), + wrapAggregateConstructor( + api.getStruct?.(structName) ?? api[structName], + ), true, ); } @@ -858,19 +979,20 @@ export function installGlobals(): boolean { return true; } -export function init( - metadataPath = '', - options: InstallOptions = {}, -): boolean { - const installed = NativeScriptNativeApi.isInstalled() - || NativeScriptNativeApi.install(metadataPath); +export function init(metadataPath = "", options: InstallOptions = {}): boolean { + const installed = + NativeScriptNativeApi.isInstalled() || + NativeScriptNativeApi.install(metadataPath); if (installed) { installInteropConstructors(); installInlineFunctions(); } - if (installed && options.globals !== false) { + if (installed && options.globals === true) { installGlobals(); } + if (installed) { + ensureWorkletsInstalled(metadataPath); + } return installed; } @@ -888,22 +1010,355 @@ export function getRuntimeBackend(): string { return NativeScriptNativeApi.getRuntimeBackend(); } -export function runOnUI(callback?: () => void): Promise { - const run = requireNativeApiHost().runOnUI; - if (typeof run !== 'function') { - throw new Error( - 'NativeScript Native API JSI host was installed without runOnUI', +let workletsAdapter: NativeScriptWorklets | undefined; +const workletsPackageName = "react-native-worklets"; + +function workletsSetupError(reason: string): Error { + return new Error( + `${reason}. Install ${workletsPackageName}, add ${workletsPackageName}/plugin to your Babel plugins, and run pod install so RNWorklets is linked.`, + ); +} + +function requireReactNativeWorklets(): NativeScriptWorklets { + try { + return require(workletsPackageName) as NativeScriptWorklets; + } catch (error) { + throw workletsSetupError( + `NativeScript.runOnUI requires ${workletsPackageName}`, + ); + } +} + +function validateWorkletsModule( + worklets: NativeScriptWorklets, +): NativeScriptWorklets { + if ( + worklets == null || + typeof worklets.getUIRuntimeHolder !== "function" || + typeof worklets.isWorkletFunction !== "function" || + typeof worklets.runOnUIAsync !== "function" + ) { + throw workletsSetupError( + "NativeScript.runOnUI received an incompatible Worklets module", + ); + } + return worklets; +} + +function installIdleAwareWorkletsFrameLoop(): boolean { + "worklet"; + + const globalObject = globalThis as Record; + if (globalObject.__nativeScriptIdleAwareWorkletsFrameLoop === true) { + return true; + } + + const nativeRequestAnimationFrame = + globalObject.__nativeRequestAnimationFrame; + const callMicrotasks = globalObject.__callMicrotasks; + + if ( + typeof nativeRequestAnimationFrame !== "function" || + typeof callMicrotasks !== "function" + ) { + return false; + } + + globalObject.__nativeScriptIdleAwareWorkletsFrameLoop = true; + globalObject.__nativeScriptNativeRequestAnimationFrame = + nativeRequestAnimationFrame; + + let queuedCallbacks: Array<(timestamp: number) => void> = []; + let queuedCallbacksBegin = 0; + let queuedCallbacksEnd = 0; + let flushedCallbacks = queuedCallbacks; + let flushedCallbacksBegin = 0; + let flushedCallbacksEnd = 0; + let queuedFinalizers: Array<() => void> = []; + let nativeFlushScheduled = false; + + const NSTimerClass = globalObject.NSTimer; + const NSRunLoopClass = globalObject.NSRunLoop; + if ( + NSTimerClass == null || + NSRunLoopClass == null || + NSRunLoopClass.mainRunLoop == null + ) { + throw new Error("NativeScript Worklets timers require NSTimer/NSRunLoop"); + } + + type NativeTimer = { invalidate?: () => void }; + const nativeTimers = new Map(); + let nextNativeTimerHandle = 1; + + function runtimeTimerInvoker any>( + callback: T, + ): T { + const wrapped = function nativeScriptWorkletTimerCallback( + this: unknown, + ...args: unknown[] + ) { + return callback.apply(this, args); + } as T; + Object.defineProperties(wrapped, { + __nativeScriptCallbackThread: { + configurable: false, + enumerable: false, + writable: false, + value: "runtime", + }, + __nativeScriptWrappedCallback: { + configurable: false, + enumerable: false, + writable: false, + value: callback, + }, + }); + return wrapped; + } + + function normalizeTimerDelay(delay: unknown): number { + const numericDelay = + typeof delay === "number" && Number.isFinite(delay) ? delay : 0; + return Math.max(0.001, numericDelay / 1000); + } + + function scheduleNativeTimer( + callback: (...args: unknown[]) => void, + delay: unknown, + repeats: boolean, + args: unknown[], + ): number { + if (typeof callback !== "function") { + throw new TypeError("NativeScript Worklets timer expects a callback"); + } + + const handle = nextNativeTimerHandle++; + const fireTimer = runtimeTimerInvoker((timer: NativeTimer) => { + if (!nativeTimers.has(handle)) { + return; + } + if (!repeats) { + nativeTimers.delete(handle); + } + callback(...args); + callMicrotasks(); + if (!repeats) { + timer?.invalidate?.(); + } + }); + + const interval = normalizeTimerDelay(delay); + const timer = + typeof NSTimerClass.timerWithTimeIntervalRepeatsBlock === "function" + ? NSTimerClass.timerWithTimeIntervalRepeatsBlock( + interval, + repeats, + fireTimer, + ) + : NSTimerClass.scheduledTimerWithTimeIntervalRepeatsBlock( + interval, + repeats, + fireTimer, + ); + + nativeTimers.set(handle, timer); + if (typeof NSTimerClass.timerWithTimeIntervalRepeatsBlock === "function") { + NSRunLoopClass.mainRunLoop.addTimerForMode( + timer, + "kCFRunLoopCommonModes", + ); + } + return handle; + } + + function clearNativeTimer(handle: unknown) { + if (typeof handle !== "number") { + return; + } + const timer = nativeTimers.get(handle); + nativeTimers.delete(handle); + timer?.invalidate?.(); + } + + function hasPendingFrameWork() { + return queuedCallbacks.length > 0 || queuedFinalizers.length > 0; + } + + function executeQueue(timestamp: number) { + flushedCallbacks = queuedCallbacks; + queuedCallbacks = []; + + flushedCallbacksBegin = queuedCallbacksBegin; + flushedCallbacksEnd = queuedCallbacksEnd; + queuedCallbacksBegin = queuedCallbacksEnd; + + for (const callback of flushedCallbacks) { + callback(timestamp); + } + + flushedCallbacksBegin = flushedCallbacksEnd; + callMicrotasks(); + + const finalizers = queuedFinalizers; + queuedFinalizers = []; + for (const finalizer of finalizers) { + finalizer(); + } + } + + function flushQueue(timestamp: number) { + globalObject.__frameTimestamp = timestamp; + executeQueue(timestamp); + globalObject.__frameTimestamp = undefined; + } + + function nativeFlushQueue(timestamp: number) { + nativeFlushScheduled = false; + flushQueue(timestamp); + if (hasPendingFrameWork()) { + scheduleNativeFlush(); + } + } + + function scheduleNativeFlush() { + if (nativeFlushScheduled) { + return; + } + nativeFlushScheduled = true; + nativeRequestAnimationFrame(nativeFlushQueue); + } + + globalObject.requestAnimationFrame = ( + callback: (timestamp: number) => void, + ): number => { + const handle = queuedCallbacksEnd; + queuedCallbacksEnd += 1; + queuedCallbacks.push(callback); + scheduleNativeFlush(); + return handle; + }; + + globalObject.cancelAnimationFrame = (handle: number) => { + if (handle < flushedCallbacksBegin || handle >= queuedCallbacksEnd) { + return; + } + + if (handle < flushedCallbacksEnd) { + flushedCallbacks[handle - flushedCallbacksBegin] = () => undefined; + } else { + queuedCallbacks[handle - queuedCallbacksBegin] = () => undefined; + } + }; + + globalObject.requestAnimationFrameFinalizer = (callback: () => void) => { + queuedFinalizers.push(callback); + scheduleNativeFlush(); + }; + + globalObject.setTimeout = ( + callback: (...args: unknown[]) => void, + delay?: unknown, + ...args: unknown[] + ) => scheduleNativeTimer(callback, delay, false, args); + globalObject.clearTimeout = clearNativeTimer; + globalObject.setInterval = ( + callback: (...args: unknown[]) => void, + delay?: unknown, + ...args: unknown[] + ) => scheduleNativeTimer(callback, delay, true, args); + globalObject.clearInterval = clearNativeTimer; + + globalObject.__flushAnimationFrame = (eventTimestamp: number) => { + nativeFlushScheduled = false; + flushQueue(eventTimestamp); + if (hasPendingFrameWork()) { + scheduleNativeFlush(); + } + }; + + // Stop react-native-worklets' startup frame pump. The replacements above + // schedule the native display link only when worklet callbacks are pending. + globalObject.__nativeRequestAnimationFrame = () => undefined; + + return true; +} + +function ensureWorkletsInstalled(metadataPath = ""): NativeScriptWorklets { + if (workletsAdapter) { + return workletsAdapter; + } + installWorklets(requireReactNativeWorklets(), metadataPath); + return workletsAdapter as NativeScriptWorklets; +} + +export function installWorklets( + worklets: NativeScriptWorklets = requireReactNativeWorklets(), + metadataPath = "", +): boolean { + if (!NativeScriptNativeApi.isInstalled()) { + const installed = NativeScriptNativeApi.install(metadataPath); + if (!installed) { + throw new Error( + "NativeScript Native API JSI host object was not installed", + ); + } + installInteropConstructors(); + installInlineFunctions(); + } + + const validWorklets = validateWorkletsModule(worklets); + const holder = validWorklets.getUIRuntimeHolder(); + if (holder == null || typeof holder !== "object") { + throw workletsSetupError( + "NativeScript.runOnUI could not resolve a Worklets UI runtime", + ); + } + const installRuntime = NativeScriptNativeApi.installWorkletRuntime; + if (typeof installRuntime !== "function") { + throw workletsSetupError( + "NativeScript Native API was built without RNWorklets runtime support", + ); + } + const installed = installRuntime(holder, metadataPath); + if (!installed) { + throw workletsSetupError( + "NativeScript Native API could not install into the Worklets UI runtime", + ); + } + validWorklets + .runOnUIAsync(installIdleAwareWorkletsFrameLoop) + .catch(() => undefined); + workletsAdapter = validWorklets; + return true; +} + +export function runOnUI( + callback: (...args: Args) => ReturnValue | Promise, + ...args: Args +): Promise { + if (typeof callback !== "function") { + throw new TypeError("NativeScript.runOnUI expects a Worklets callback"); + } + + ensureNativeScriptInstalled(); + const worklets = ensureWorkletsInstalled(); + if (worklets.isWorkletFunction(callback) !== true) { + throw workletsSetupError( + "NativeScript.runOnUI requires a worklet callback", ); } - return run(callback); + return worklets.runOnUIAsync(callback, ...args); } function callbackInvoker( thread: NativeScriptCallbackThread, callback: T, ): NativeScriptInvokedCallback { - if (typeof callback !== 'function') { - throw new TypeError('NativeScript callback invoker expects a function'); + "worklet"; + + if (typeof callback !== "function") { + throw new TypeError("NativeScript callback invoker expects a function"); } const existingPolicy = (callback as Record)[ @@ -920,6 +1375,27 @@ function callbackInvoker( return callback.apply(this, args); } as NativeScriptInvokedCallback; + for (const key of [ + ...Object.getOwnPropertyNames(callback), + ...Object.getOwnPropertySymbols(callback), + ]) { + if (nativeCallbackMetadataSkipKeys.has(key)) { + continue; + } + + const descriptor = Object.getOwnPropertyDescriptor(callback, key); + if (!descriptor) { + continue; + } + + try { + Object.defineProperty(wrapped, key, descriptor); + } catch { + // Metadata preservation is best-effort for runtimes with fixed function + // internals; the callback policy markers below are still applied. + } + } + Object.defineProperties(wrapped, { [nativeApiCallbackThreadKey]: { configurable: false, @@ -937,69 +1413,199 @@ function callbackInvoker( return wrapped; } -export function uiInvoker( +export function uiInvoker(_callback: T): never { + throw new Error( + 'NativeScript.uiInvoker is not supported in React Native. Use a Worklets "worklet" callback with NativeScript.runOnUI().', + ); +} + +export function jsInvoker( callback: T, ): NativeScriptInvokedCallback { - return callbackInvoker('ui', callback); + "worklet"; + + return callbackInvoker("js", callback); } -export function jsInvoker( +export function runtimeInvoker( callback: T, ): NativeScriptInvokedCallback { - return callbackInvoker('js', callback); + "worklet"; + + return callbackInvoker("runtime", callback); +} + +function nativeScriptCallbackThread( + callback: AnyFunction, +): NativeScriptCallbackThread | undefined { + "worklet"; + + const thread = (callback as Record)[ + nativeApiCallbackThreadKey + ]; + return thread === "js" || thread === "runtime" ? thread : undefined; +} + +function nativeScriptWrappedCallback(callback: AnyFunction): AnyFunction { + "worklet"; + + const wrapped = (callback as Record)[ + nativeApiWrappedCallbackKey + ]; + return typeof wrapped === "function" ? (wrapped as AnyFunction) : callback; +} + +function invokeNativeScriptCallback( + callback: AnyFunction, + args: unknown[], + isDisposed?: () => boolean, +): void { + "worklet"; + + if (nativeScriptCallbackThread(callback) !== "js") { + callback(...args); + return; + } + + const handler = nativeScriptWrappedCallback(callback); + const workletsProxy = (globalThis as Record) + .__workletsModuleProxy; + const serializer = (globalThis as Record).__serializer; + + if ( + workletsProxy && + typeof workletsProxy.scheduleOnRN === "function" && + typeof serializer === "function" + ) { + workletsProxy.scheduleOnRN(handler, serializer(args)); + return; + } + + setTimeout(() => { + if (!isDisposed?.()) { + handler(...args); + } + }, 0); } export function eventBridge( callback: T, - thread: NativeScriptCallbackThread | 'caller' = 'js', + thread: NativeScriptCallbackThread | "caller" = "js", ): T | NativeScriptInvokedCallback { - if (thread === 'ui') { - return uiInvoker(callback); - } - if (thread === 'js') { + "worklet"; + + if (thread === "js") { return jsInvoker(callback); } + if (thread === "runtime") { + return runtimeInvoker(callback); + } return callback; } export const createEventBridge = eventBridge; export function isMainThread(): boolean { + "worklet"; + const NSThread = (globalThis as Record).NSThread; return NSThread?.isMainThread === true; } export function assertUIKitThread( - message = 'UIKit native APIs must be called through NativeScript.runOnUI', + message = "UIKit native APIs must be called through NativeScript.runOnUI", ): void { + "worklet"; + if (!isMainThread()) { throw new Error(message); } } -export function warnIfNotUIKitThread( - message = 'UIKit native APIs should be mutated through NativeScript.runOnUI', -): boolean { - if (isMainThread()) { +export function refreshUIKitHostView(view: unknown): boolean { + "worklet"; + + const refresh = (globalThis as Record) + .__nativeScriptRefreshUIKitHostView; + if (typeof refresh !== "function") { return false; } - if (typeof console !== 'undefined' && typeof console.warn === 'function') { - console.warn(message); - } - return true; + + return refresh(nativeHandleForUIKitView(view)) === true; } -function systemFrameworkPath(nameOrPath: string): string { - if (!nameOrPath) { - return ''; - } - if (nameOrPath.includes('/')) { - return nameOrPath; +export function refreshUIKitHostViewHandle(viewHandle: string): boolean { + "worklet"; + + const refresh = (globalThis as Record) + .__nativeScriptRefreshUIKitHostView; + if (typeof refresh !== "function") { + return false; } - const frameworkName = nameOrPath.endsWith('.framework') - ? nameOrPath - : `${nameOrPath}.framework`; - return `/System/Library/Frameworks/${frameworkName}`; + + return refresh(viewHandle) === true; +} + +export function loadImage( + source: unknown, + options: NativeScriptImageLoadOptions = {}, + callback: NativeScriptImageLoadCallback, +): boolean { + "worklet"; + + const loadReactImage = (globalThis as Record) + .__nativeScriptLoadReactImage; + if (typeof loadReactImage !== "function" || typeof callback !== "function") { + return false; + } + + return ( + loadReactImage( + source, + options.template === true, + (handle: unknown, errorMessage: unknown) => { + "worklet"; + + const interop = (globalThis as Record).interop; + const image = + typeof handle === "string" && handle.length > 0 + ? interop?.object?.(interop.Pointer(handle)) ?? null + : null; + const error = + typeof errorMessage === "string" && errorMessage.length > 0 + ? new Error(errorMessage) + : null; + callback(image, error); + }, + ) === true + ); +} + +export function warnIfNotUIKitThread( + message = "UIKit native APIs should be mutated through NativeScript.runOnUI", +): boolean { + "worklet"; + + if (isMainThread()) { + return false; + } + if (typeof console !== "undefined" && typeof console.warn === "function") { + console.warn(message); + } + return true; +} + +function systemFrameworkPath(nameOrPath: string): string { + if (!nameOrPath) { + return ""; + } + if (nameOrPath.includes("/")) { + return nameOrPath; + } + const frameworkName = nameOrPath.endsWith(".framework") + ? nameOrPath + : `${nameOrPath}.framework`; + return `/System/Library/Frameworks/${frameworkName}`; } export function getClass(name: string): T | null { @@ -1024,20 +1630,30 @@ export function getProtocol(name: string): T | null { return protocol == null ? null : (protocol as T); } +function requireNSObject(): any { + "worklet"; + + const nsObject = (globalThis as Record).NSObject; + if (!nsObject || typeof nsObject.extend !== "function") { + throw new Error("NSObject.extend is not available"); + } + return nsObject; +} + export function isClassAvailable(name: string): boolean { const nativeClass = getClass>(name); if (!nativeClass) { return false; } - if (typeof nativeClass.available === 'boolean') { + if (typeof nativeClass.available === "boolean") { return nativeClass.available; } return true; } function frameworkBundle(nameOrPath: string): any | null { - const NSBundle = getClass('NSBundle'); - if (!NSBundle || typeof NSBundle.bundleWithPath !== 'function') { + const NSBundle = getClass("NSBundle"); + if (!NSBundle || typeof NSBundle.bundleWithPath !== "function") { return null; } const path = systemFrameworkPath(nameOrPath); @@ -1048,11 +1664,11 @@ function frameworkBundle(nameOrPath: string): any | null { } const frameworkSentinelClasses: Record = { - Foundation: 'NSObject', - UIKit: 'UIView', - QuickLook: 'QLPreviewController', - VisionKit: 'VNDocumentCameraViewController', - PassKit: 'PKPass', + Foundation: "NSObject", + UIKit: "UIView", + QuickLook: "QLPreviewController", + VisionKit: "VNDocumentCameraViewController", + PassKit: "PKPass", }; function frameworkName(nameOrPath: string): string { @@ -1060,7 +1676,7 @@ function frameworkName(nameOrPath: string): string { if (match) { return match[1]; } - return nameOrPath.replace(/\.framework$/, ''); + return nameOrPath.replace(/\.framework$/, ""); } export function isFrameworkLoaded(nameOrPath: string): boolean { @@ -1072,10 +1688,10 @@ export function isFrameworkLoaded(nameOrPath: string): boolean { if (!bundle) { return false; } - if (typeof bundle.loaded === 'boolean') { + if (typeof bundle.loaded === "boolean") { return bundle.loaded; } - if (typeof bundle.isLoaded === 'function') { + if (typeof bundle.isLoaded === "function") { return Boolean(bundle.isLoaded()); } return false; @@ -1090,7 +1706,7 @@ export function loadFramework(nameOrPath: string): boolean { } const api = requireNativeApiHost(); try { - if (typeof api.import === 'function') { + if (typeof api.import === "function") { api.import(nameOrPath); return true; } @@ -1098,7 +1714,7 @@ export function loadFramework(nameOrPath: string): boolean { // Fall through to NSBundle below so callers get a false availability result. } const bundle = frameworkBundle(nameOrPath); - if (!bundle || typeof bundle.load !== 'function') { + if (!bundle || typeof bundle.load !== "function") { return false; } try { @@ -1108,8 +1724,12 @@ export function loadFramework(nameOrPath: string): boolean { } } -function resolveProtocolReference(protocolRef: NativeProtocolReference): unknown { - if (typeof protocolRef !== 'string') { +function resolveProtocolReference( + protocolRef: NativeProtocolReference, +): unknown { + "worklet"; + + if (typeof protocolRef !== "string") { return protocolRef; } return ( @@ -1120,9 +1740,11 @@ function resolveProtocolReference(protocolRef: NativeProtocolReference): unknown function wrapDelegateMethods( methods: T, - thread: CreateDelegateOptions['thread'], + thread: CreateDelegateOptions["thread"], ): T { - if (!thread || thread === 'caller') { + "worklet"; + + if (!thread || thread === "caller") { return methods; } @@ -1132,7 +1754,7 @@ function wrapDelegateMethods( if (!descriptor) { continue; } - if ('value' in descriptor && typeof descriptor.value === 'function') { + if ("value" in descriptor && typeof descriptor.value === "function") { descriptor.value = eventBridge(descriptor.value, thread); } Object.defineProperty(wrapped, key, descriptor); @@ -1145,11 +1767,15 @@ export function createDelegate( methods: Partial, options: CreateDelegateOptions = {}, ): T { + "worklet"; + const protocolList = (Array.isArray(protocols) ? protocols : [protocols]) .map(resolveProtocolReference) .filter(Boolean); if (protocolList.length === 0) { - throw new Error('NativeScript.createDelegate requires at least one protocol'); + throw new Error( + "NativeScript.createDelegate requires at least one protocol", + ); } const DelegateClass = requireNSObject().extend( @@ -1171,7 +1797,7 @@ export function createDelegate( const assignedObject = options.assignTo?.object as | Record | undefined; - const assignedProperty = options.assignTo?.property ?? 'delegate'; + const assignedProperty = options.assignTo?.property ?? "delegate"; if (assignedObject) { assignedObject[assignedProperty] = delegate; } @@ -1203,99 +1829,448 @@ type UIKitHostInstance = { controller?: unknown; }; -type UIKitAdapterDefinition = - UIKitViewDefinition & { - resolveHostInstance?: (created: NativeView) => UIKitHostInstance; +type RegisteredUIKitHost = { + context: UIKitRuntimeContext; + dispose?: (props: Readonly) => UIKitDisposeResult; + hostInstance: UIKitHostInstance; + hasMounted?: boolean; + mounted?: (props: Readonly) => void; + nativeView: NativeView; + previousProps?: Readonly; + propsRef: { current: Readonly }; + update?: ( + props: Readonly, + previousProps: Readonly | undefined, + ) => void; +}; + +type PendingUIKitHost = { + debugName: string; + mountHost: () => RegisteredUIKitHost; + propsRef: { current: Readonly }; +}; + +type UIKitHostHandles = { + nativeViewHandle?: string; + childrenViewHandle?: string; + controllerHandle?: string; +}; + +type UIKitAdapterDefinition< + Props extends object, + NativeView, +> = UIKitViewDefinition & { + resolveHostInstance?: (created: NativeView) => UIKitHostInstance; +}; + +const uikitHostRegistryGlobalName = "__nativeScriptUIKitHostRegistry"; +const pendingUIKitHostRegistryGlobalName = + "__nativeScriptPendingUIKitHostRegistry"; +const createUIKitHostFromNativeGlobalName = + "__nativeScriptCreateUIKitHostFromNative"; +const runUIKitHostLifecycleFromNativeGlobalName = + "__nativeScriptRunUIKitHostLifecycleFromNative"; +let nextUIKitHostId = 1; + +function createUIKitHostId(debugName: string): string { + return `${debugName}:${nextUIKitHostId++}`; +} + +function uikitHostRegistry(): Map> { + "worklet"; + + const globalObject = globalThis as Record; + const existing = globalObject[uikitHostRegistryGlobalName]; + if (existing instanceof Map) { + return existing as Map>; + } + + const registry = new Map>(); + Object.defineProperty(globalThis, uikitHostRegistryGlobalName, { + configurable: true, + enumerable: false, + writable: false, + value: registry, + }); + return registry; +} + +function pendingUIKitHostRegistry(): Map< + string, + PendingUIKitHost +> { + "worklet"; + + const globalObject = globalThis as Record; + const existing = globalObject[pendingUIKitHostRegistryGlobalName]; + if (existing instanceof Map) { + return existing as Map>; + } + + const registry = new Map>(); + Object.defineProperty(globalThis, pendingUIKitHostRegistryGlobalName, { + configurable: true, + enumerable: false, + writable: false, + value: registry, + }); + return registry; +} + +function uikitHostHandles( + host: RegisteredUIKitHost, +): UIKitHostHandles { + "worklet"; + + return { + nativeViewHandle: nativeHandleOrUndefined(host.hostInstance.hostView), + childrenViewHandle: nativeHandleOrUndefined(host.hostInstance.childrenView), + controllerHandle: nativeHandleForNSObject(host.hostInstance.controller), }; +} -let targetActionClass: any; -let observerClass: any; -const targetActionCallbacks = new WeakMap void>(); -const observerCallbacks = new WeakMap< - object, - (keyPath: string, object: unknown, change: unknown) => void ->(); +function getRegisteredUIKitHost( + hostId: string, +): RegisteredUIKitHost { + "worklet"; + + const host = uikitHostRegistry().get(hostId); + if (!host) { + throw new Error(`UIKit host ${hostId} has not been created`); + } + return host as RegisteredUIKitHost; +} + +function registerUIKitHost( + hostId: string, + host: RegisteredUIKitHost, +): void { + "worklet"; + + uikitHostRegistry().set(hostId, host as RegisteredUIKitHost); +} + +function createRegisteredUIKitHostFromNative( + hostId: string, +): UIKitHostHandles | null { + "worklet"; + + const existingHost = uikitHostRegistry().get(hostId); + if (existingHost) { + return uikitHostHandles(existingHost); + } + + const pending = pendingUIKitHostRegistry().get(hostId); + if (!pending) { + return null; + } + + const host = pending.mountHost(); + registerUIKitHost(hostId, host); + return uikitHostHandles(host); +} + +function ensureRegisteredUIKitHost( + hostId: string, +): RegisteredUIKitHost | null { + "worklet"; + + const existingHost = uikitHostRegistry().get(hostId); + if (existingHost) { + return existingHost as RegisteredUIKitHost; + } + + if (createRegisteredUIKitHostFromNative(hostId) == null) { + return null; + } + + const createdHost = uikitHostRegistry().get(hostId); + return (createdHost ?? null) as RegisteredUIKitHost | null; +} + +function disposeRegisteredUIKitHost( + hostId: string, + props: Readonly, +): void { + "worklet"; + + pendingUIKitHostRegistry().delete(hostId); + const registry = uikitHostRegistry(); + const host = registry.get(hostId) as + | RegisteredUIKitHost + | undefined; + if (!host) { + return; + } + registry.delete(hostId); + host.propsRef.current = props; + const disposeResult = host.dispose?.(props); + host.context.disposeResources(); + const maybeView = host.hostInstance.hostView as + | Record + | undefined; + if ( + disposeResult?.removeHostView !== false && + typeof maybeView?.removeFromSuperview === "function" + ) { + maybeView.removeFromSuperview(); + } +} + +function syncUIKitHostPropsFromReact( + hostId: string, + props: Readonly, +): void { + "worklet"; + + const pending = pendingUIKitHostRegistry().get(hostId); + if (pending) { + pending.propsRef.current = props; + } + + const host = uikitHostRegistry().get(hostId); + if (host) { + host.propsRef.current = props; + } +} + +function runUIKitHostLifecycleFromNative( + hostId: string, + phase: string, +): UIKitHostHandles | null { + "worklet"; + + if (phase === "dispose") { + const host = uikitHostRegistry().get(hostId); + const pending = pendingUIKitHostRegistry().get(hostId); + disposeRegisteredUIKitHost( + hostId, + host?.propsRef.current ?? pending?.propsRef.current ?? {}, + ); + return null; + } + + const handles = createRegisteredUIKitHostFromNative(hostId); + if (handles == null) { + return null; + } + + const host = getRegisteredUIKitHost(hostId); + const nextProps = host.propsRef.current; + if (phase === "update") { + if (host.previousProps !== nextProps) { + host.update?.(nextProps, host.previousProps); + host.previousProps = nextProps; + } + } else if (phase === "mounted" && !host.hasMounted) { + host.hasMounted = true; + host.mounted?.(nextProps); + } + + return uikitHostHandles(host); +} + +function installUIKitNativeMountBridge(): void { + "worklet"; + + const globalObject = globalThis as Record; + if (typeof globalObject[createUIKitHostFromNativeGlobalName] !== "function") { + Object.defineProperty(globalThis, createUIKitHostFromNativeGlobalName, { + configurable: true, + enumerable: false, + writable: false, + value: createRegisteredUIKitHostFromNative, + }); + } + if ( + typeof globalObject[runUIKitHostLifecycleFromNativeGlobalName] !== + "function" + ) { + Object.defineProperty( + globalThis, + runUIKitHostLifecycleFromNativeGlobalName, + { + configurable: true, + enumerable: false, + writable: false, + value: runUIKitHostLifecycleFromNative, + }, + ); + } +} + +function ignoreUIKitLayoutInvalidation(): void { + "worklet"; +} + +const targetActionClassGlobalName = "__nativeScriptUIKitTargetActionClass"; +const observerClassGlobalName = "__nativeScriptUIKitObserverClass"; +const targetActionCallbacksGlobalName = + "__nativeScriptUIKitTargetActionCallbacks"; +const observerCallbacksGlobalName = "__nativeScriptUIKitObserverCallbacks"; function objcInteropTypes(): any { + "worklet"; + return (globalThis as Record).interop?.types; } -function requireNSObject(): any { - const nsObject = (globalThis as Record).NSObject; - if (!nsObject || typeof nsObject.extend !== 'function') { - throw new Error('NSObject.extend is not available'); +function runtimeGlobalMap(name: string): Map { + "worklet"; + + const globalObject = globalThis as Record; + const existing = globalObject[name]; + if (existing instanceof Map) { + return existing as Map; } - return nsObject; + + const map = new Map(); + Object.defineProperty(globalThis, name, { + configurable: true, + enumerable: false, + writable: false, + value: map, + }); + return map; +} + +function targetActionCallbacksForRuntime(): Map< + string, + (sender: unknown) => void +> { + "worklet"; + + return runtimeGlobalMap<(sender: unknown) => void>( + targetActionCallbacksGlobalName, + ); +} + +function observerCallbacksForRuntime(): Map< + string, + (keyPath: string, object: unknown, change: unknown) => void +> { + "worklet"; + + return runtimeGlobalMap< + (keyPath: string, object: unknown, change: unknown) => void + >(observerCallbacksGlobalName); +} + +function nativeCallbackKey(value: unknown): string { + "worklet"; + + const handleof = (globalThis as Record).interop?.handleof; + if (value != null && typeof handleof === "function") { + const handle = handleof(value); + if (handle != null) { + if (typeof handle.toHexString === "function") { + return handle.toHexString(); + } + return String(handle); + } + } + return String(value); } function getTargetActionClass(): any { - if (targetActionClass) { - return targetActionClass; + "worklet"; + + const globalObject = globalThis as Record; + const cached = globalObject[targetActionClassGlobalName]; + if (cached) { + return cached; } const types = objcInteropTypes(); const NSObject = requireNSObject(); - targetActionClass = NSObject.extend( + const targetActionClass = NSObject.extend( { nativeScriptHandleAction(sender: unknown) { - const callback = targetActionCallbacks.get(this as object); - if (typeof callback === 'function') { + const callback = targetActionCallbacksForRuntime().get( + nativeCallbackKey(this), + ); + if (typeof callback === "function") { callback(sender); } }, }, { exposedMethods: { - 'nativeScriptHandleAction:': { + "nativeScriptHandleAction:": { returns: types?.void, params: [NSObject], }, }, }, ); + Object.defineProperty(globalThis, targetActionClassGlobalName, { + configurable: true, + enumerable: false, + writable: false, + value: targetActionClass, + }); return targetActionClass; } function getObserverClass(): any { - if (observerClass) { - return observerClass; + "worklet"; + + const globalObject = globalThis as Record; + const cached = globalObject[observerClassGlobalName]; + if (cached) { + return cached; } const types = objcInteropTypes(); const NSObject = requireNSObject(); const NSString = (globalThis as Record).NSString; const NSDictionary = (globalThis as Record).NSDictionary; - const Pointer = (globalThis as Record).interop?.Pointer - ?? types?.id; + const Pointer = + (globalThis as Record).interop?.Pointer ?? types?.id; - observerClass = NSObject.extend( + const observerClass = NSObject.extend( { - 'observeValueForKeyPath:ofObject:change:context:'( + "observeValueForKeyPath:ofObject:change:context:"( keyPath: string, object: unknown, change: unknown, ) { - const callback = observerCallbacks.get(this as object); - if (typeof callback === 'function') { + const callback = observerCallbacksForRuntime().get( + nativeCallbackKey(this), + ); + if (typeof callback === "function") { callback(keyPath, object, change); } }, }, { exposedMethods: { - 'observeValueForKeyPath:ofObject:change:context:': { + "observeValueForKeyPath:ofObject:change:context:": { returns: types?.void, - params: [NSString ?? NSObject, NSObject, NSDictionary ?? NSObject, Pointer], + params: [ + NSString ?? NSObject, + NSObject, + NSDictionary ?? NSObject, + Pointer, + ], }, }, }, ); + Object.defineProperty(globalThis, observerClassGlobalName, { + configurable: true, + enumerable: false, + writable: false, + value: observerClass, + }); return observerClass; } function createUIKitContext( name: string, - propsRef: React.MutableRefObject, + propsRef: { current: Props }, invalidateLayout: () => void, ): UIKitRuntimeContext { + "worklet"; + const retained: unknown[] = []; const cleanupCallbacks: Array<() => void> = []; let disposed = false; @@ -1317,63 +2292,151 @@ function createUIKitContext( const handler = (propsRef.current as Record)[ eventName as PropertyKey ]; - if (typeof handler !== 'function') { + if (typeof handler !== "function") { return; } - setTimeout(() => { - if (!disposed) { - (handler as Function)(payload); - } - }, 0); + const workletsProxy = (globalThis as Record) + .__workletsModuleProxy; + const serializer = (globalThis as Record).__serializer; + if ( + workletsProxy && + typeof workletsProxy.scheduleOnRN === "function" && + typeof serializer === "function" + ) { + workletsProxy.scheduleOnRN(handler, serializer([payload])); + } else { + setTimeout(() => { + if (!disposed) { + (handler as Function)(payload); + } + }, 0); + } }, targetAction(control, events, callback) { - if (control == null || typeof callback !== 'function') { + if (control == null || typeof callback !== "function") { return; } const target = getTargetActionClass().alloc().init(); - targetActionCallbacks.set(target as object, uiInvoker(() => { + const targetKey = nativeCallbackKey(target); + targetActionCallbacksForRuntime().set(targetKey, () => { if (!disposed) { - callback(); + invokeNativeScriptCallback(callback, [], () => disposed); } - })); - const selector = 'nativeScriptHandleAction:'; + }); + const selector = "nativeScriptHandleAction:"; const nativeControl = control as Record; - if (typeof nativeControl.addTargetActionForControlEvents !== 'function') { - throw new Error('targetAction expects a UIControl-compatible object'); + if (typeof nativeControl.addTargetActionForControlEvents !== "function") { + throw new Error("targetAction expects a UIControl-compatible object"); } nativeControl.addTargetActionForControlEvents(target, selector, events); context.retain(target); context.dispose(() => { - if (typeof nativeControl.removeTargetActionForControlEvents === 'function') { - nativeControl.removeTargetActionForControlEvents(target, selector, events); + if ( + typeof nativeControl.removeTargetActionForControlEvents === "function" + ) { + nativeControl.removeTargetActionForControlEvents( + target, + selector, + events, + ); + } + targetActionCallbacksForRuntime().delete(targetKey); + }); + }, + gestureAction(gesture, callback) { + if (gesture == null || typeof callback !== "function") { + return; + } + const target = getTargetActionClass().alloc().init(); + const targetKey = nativeCallbackKey(target); + targetActionCallbacksForRuntime().set(targetKey, (sender) => { + if (!disposed) { + callback(sender ?? gesture); + } + }); + const selector = "nativeScriptHandleAction:"; + const nativeGesture = gesture as Record; + if (typeof nativeGesture.addTargetAction !== "function") { + throw new Error( + "gestureAction expects a UIGestureRecognizer-compatible object", + ); + } + nativeGesture.addTargetAction(target, selector); + context.retain(target); + context.dispose(() => { + if (typeof nativeGesture.removeTargetAction === "function") { + nativeGesture.removeTargetAction(target, selector); } - targetActionCallbacks.delete(target as object); + targetActionCallbacksForRuntime().delete(targetKey); }); }, + actionTarget(callback) { + if (typeof callback !== "function") { + throw new Error("actionTarget expects a callback"); + } + + const target = getTargetActionClass().alloc().init(); + const targetKey = nativeCallbackKey(target); + targetActionCallbacksForRuntime().set(targetKey, (sender) => { + if (!disposed) { + invokeNativeScriptCallback(callback, [sender], () => disposed); + } + }); + context.retain(target); + context.dispose(() => { + targetActionCallbacksForRuntime().delete(targetKey); + }); + + return { + target, + action: "nativeScriptHandleAction:", + }; + }, delegate(object, protocolRef, implementation) { + const protocolList = [protocolRef as NativeProtocolReference] + .map(resolveProtocolReference) + .filter(Boolean); + if (protocolList.length === 0) { + throw new Error("NativeScript UIKit delegate requires a protocol"); + } + const nativeObject = object as Record; - return createDelegate([protocolRef as NativeProtocolReference], implementation, { - owner: context, - assignTo: nativeObject && 'delegate' in nativeObject - ? {object: nativeObject, property: 'delegate'} - : undefined, + const assignedObject = + nativeObject && "delegate" in nativeObject ? nativeObject : undefined; + const DelegateClass = requireNSObject().extend( + wrapDelegateMethods(implementation, "caller"), + { + protocols: protocolList, + }, + ); + const delegate = DelegateClass.alloc().init() as T; + context.retain(delegate); + if (assignedObject) { + assignedObject.delegate = delegate; + } + context.dispose(() => { + if (assignedObject && assignedObject.delegate === delegate) { + assignedObject.delegate = null; + } + context.release(delegate); }); + return delegate; }, notification(name, object, callback) { const center = (globalThis as Record).NSNotificationCenter ?.defaultCenter; if (!center) { - throw new Error('NSNotificationCenter.defaultCenter is not available'); + throw new Error("NSNotificationCenter.defaultCenter is not available"); } const observer = center.addObserverForNameObjectQueueUsingBlock( name, object ?? null, null, - uiInvoker((notification: unknown) => { + (notification: unknown) => { if (!disposed) { callback(notification); } - }), + }, ); context.retain(observer); context.dispose(() => { @@ -1384,31 +2447,40 @@ function createUIKitContext( const nativeObject = object as Record; if ( object == null || - typeof nativeObject.addObserverForKeyPathOptionsContext !== 'function' + typeof nativeObject.addObserverForKeyPathOptionsContext !== "function" ) { - throw new Error('observe expects a KVO-compatible NSObject'); + throw new Error("observe expects a KVO-compatible NSObject"); } const observer = getObserverClass().alloc().init(); - observerCallbacks.set(observer as object, ( - observedKeyPath: string, - _observedObject: unknown, - change: unknown, - ) => { - if (disposed || String(observedKeyPath) !== keyPath) { - return; - } - const newKey = (globalThis as Record).NSKeyValueChangeNewKey; - const value = - change && typeof (change as Record).objectForKey === 'function' - ? (change as Record).objectForKey(newKey) - : undefined; - callback(value, change); - }); - const options = (globalThis as Record).NSKeyValueObservingOptions; + const observerKey = nativeCallbackKey(observer); + observerCallbacksForRuntime().set( + observerKey, + ( + observedKeyPath: string, + _observedObject: unknown, + change: unknown, + ) => { + if (disposed || String(observedKeyPath) !== keyPath) { + return; + } + const newKey = (globalThis as Record) + .NSKeyValueChangeNewKey; + const value = + change && + typeof (change as Record).objectForKey === + "function" + ? (change as Record).objectForKey(newKey) + : undefined; + callback(value, change); + }, + ); + const options = (globalThis as Record) + .NSKeyValueObservingOptions; const optionNew = - typeof options?.New === 'number' + typeof options?.New === "number" ? options.New - : (globalThis as Record).NSKeyValueObservingOptionNew ?? 1; + : ((globalThis as Record).NSKeyValueObservingOptionNew ?? + 1); nativeObject.addObserverForKeyPathOptionsContext( observer, keyPath, @@ -1418,11 +2490,11 @@ function createUIKitContext( context.retain(observer); context.dispose(() => { try { - if (typeof nativeObject.removeObserverForKeyPath === 'function') { + if (typeof nativeObject.removeObserverForKeyPath === "function") { nativeObject.removeObserverForKeyPath(observer, keyPath); } } finally { - observerCallbacks.delete(observer as object); + observerCallbacksForRuntime().delete(observerKey); } }); }, @@ -1430,24 +2502,26 @@ function createUIKitContext( retained.push(value); return value; }, - release(value?: unknown) { - if (arguments.length === 0) { - retained.length = 0; - return; - } + release(value?: unknown) { + if (arguments.length === 0) { + retained.length = 0; + return; + } for (let i = retained.length - 1; i >= 0; i--) { if (retained[i] === value) { retained.splice(i, 1); } } }, - dispose(callback) { - cleanupCallbacks.push(callback); - }, - invalidateLayout, - createArgument() { - return Object.assign(Object.create(context), propsRef.current); - }, + dispose(callback) { + cleanupCallbacks.push(callback); + }, + invalidateLayout, + loadImage: (source, options, callback) => + loadImage(source, options, callback), + createArgument() { + return Object.assign(Object.create(context), propsRef.current); + }, disposeResources() { if (disposed) { return; @@ -1468,16 +2542,20 @@ function createUIKitContext( } function constrainedSize( - size: {width: number; height: number}, + size: { width: number; height: number }, layout?: UIKitLayoutOptions, -): {width: number; height: number} { +): { width: number; height: number } { + "worklet"; + const defaultSize = layout?.defaultSize ?? {}; - let width = Number.isFinite(size.width) && size.width >= 0 - ? size.width - : defaultSize.width ?? 0; - let height = Number.isFinite(size.height) && size.height >= 0 - ? size.height - : defaultSize.height ?? 0; + let width = + Number.isFinite(size.width) && size.width >= 0 + ? size.width + : (defaultSize.width ?? 0); + let height = + Number.isFinite(size.height) && size.height >= 0 + ? size.height + : (defaultSize.height ?? 0); if (layout?.minSize?.width != null) { width = Math.max(width, layout.minSize.width); @@ -1491,27 +2569,52 @@ function constrainedSize( if (layout?.maxSize?.height != null) { height = Math.min(height, layout.maxSize.height); } - return {width, height}; + return { width, height }; } -function flattenedStyleSize(style: ViewProps['style']) { - const flat = StyleSheet.flatten(style) ?? {}; +function flattenedStyleSize(style: ViewProps["style"]) { + "worklet"; + + const flat: Record = {}; + const applyStyle = (value: unknown) => { + if (Array.isArray(value)) { + for (const item of value) { + applyStyle(item); + } + return; + } + if (!value || typeof value !== "object") { + return; + } + const record = value as Record; + if (typeof record.width === "number") { + flat.width = record.width; + } + if (typeof record.height === "number") { + flat.height = record.height; + } + }; + applyStyle(style); return { - width: typeof flat.width === 'number' ? flat.width : undefined, - height: typeof flat.height === 'number' ? flat.height : undefined, + width: typeof flat.width === "number" ? flat.width : undefined, + height: typeof flat.height === "number" ? flat.height : undefined, }; } function makeCGSize(width: number, height: number) { + "worklet"; + const CGSizeMake = (globalThis as Record).CGSizeMake; - if (typeof CGSizeMake === 'function') { + if (typeof CGSizeMake === "function") { return CGSizeMake(width, height); } - return {width, height}; + return { width, height }; } -function readNativeSize(size: unknown): {width: number; height: number} { - const nativeSize = size as {width?: unknown; height?: unknown}; +function readNativeSize(size: unknown): { width: number; height: number } { + "worklet"; + + const nativeSize = size as { width?: unknown; height?: unknown }; return { width: Number(nativeSize?.width ?? 0), height: Number(nativeSize?.height ?? 0), @@ -1521,33 +2624,46 @@ function readNativeSize(size: unknown): {width: number; height: number} { function measureUIKitView( view: unknown, layout: UIKitLayoutOptions | undefined, - style: ViewProps['style'], -): {width: number; height: number} { - const mode = layout?.sizing ?? 'fill'; - if (mode === 'fill') { - return constrainedSize(layout?.defaultSize ?? {width: 0, height: 0}, layout); + style: ViewProps["style"], +): { width: number; height: number } { + "worklet"; + + const mode = layout?.sizing ?? "fill"; + if (mode === "fill") { + return constrainedSize( + layout?.defaultSize ?? { width: 0, height: 0 }, + layout, + ); } const styleSize = flattenedStyleSize(style); const nativeView = view as Record; - let measured = layout?.defaultSize ?? {width: 0, height: 0}; + let measured = layout?.defaultSize ?? { width: 0, height: 0 }; - if (mode === 'intrinsic') { + if (mode === "intrinsic") { measured = readNativeSize(nativeView.intrinsicContentSize); - } else if (mode === 'sizeThatFits' && typeof nativeView.sizeThatFits === 'function') { + } else if ( + mode === "sizeThatFits" && + typeof nativeView.sizeThatFits === "function" + ) { measured = readNativeSize( nativeView.sizeThatFits( - makeCGSize(styleSize.width ?? Number.MAX_SAFE_INTEGER, styleSize.height ?? Number.MAX_SAFE_INTEGER), + makeCGSize( + styleSize.width ?? Number.MAX_SAFE_INTEGER, + styleSize.height ?? Number.MAX_SAFE_INTEGER, + ), ), ); } else if ( - mode === 'autoLayout' && - typeof nativeView.systemLayoutSizeFittingSize === 'function' + mode === "autoLayout" && + typeof nativeView.systemLayoutSizeFittingSize === "function" ) { const fittingSize = (globalThis as Record).UIView?.layoutFittingCompressedSize ?? makeCGSize(styleSize.width ?? 0, styleSize.height ?? 0); - measured = readNativeSize(nativeView.systemLayoutSizeFittingSize(fittingSize)); + measured = readNativeSize( + nativeView.systemLayoutSizeFittingSize(fittingSize), + ); } return constrainedSize( @@ -1559,83 +2675,107 @@ function measureUIKitView( ); } -function nativeHandleOrUndefined(value: unknown): string | undefined { - return value == null ? undefined : nativeHandleForUIKitView(value); -} - -function nativeHandleForNSObject(value: unknown): string | undefined { - if (value == null) { - return undefined; - } - const interop = (globalThis as Record).interop; - const pointer = interop?.handleof?.(value); - if (!pointer) { - return undefined; - } - if (typeof pointer.toHexString === 'function') { - return pointer.toHexString(); - } - if (typeof pointer.address === 'string') { - return pointer.address; - } - if (typeof pointer.address === 'number') { - return String(pointer.address); - } - if (typeof pointer.toNumber === 'function') { - return String(pointer.toNumber()); - } - return undefined; -} - function defineUIKitHost( definition: UIKitAdapterDefinition, ): UIKitViewComponent { const debugName = - definition.debugName - || definition.name - || definition.displayName - || 'NativeScriptUIKitView'; - - const Component = forwardRef, Props & ViewProps>( - function NativeScriptUIKitView(props, ref) { - const {nativeProps, pluginProps} = splitUIKitViewProps(props, definition); - const viewRef = useRef(null); - const propsRef = useRef(pluginProps); - const previousPropsRef = useRef | undefined>(); - const contextRef = useRef | null>( - null, - ); - const hostInstanceRef = useRef | null>(null); - const mountedRef = useRef(false); - const disposedRef = useRef(false); - const [nativeViewHandle, setNativeViewHandle] = useState(); - const [childrenViewHandle, setChildrenViewHandle] = useState(); - const [controllerHandle, setControllerHandle] = useState(); - const [measuredSize, setMeasuredSize] = useState< - {width: number; height: number} | undefined - >(() => definition.layout?.sizing === 'fill' + definition.debugName || + definition.name || + definition.displayName || + "NativeScriptUIKitView"; + + const Component = forwardRef< + UIKitViewRef, + Props & UIKitHostViewProps + >(function NativeScriptUIKitView(props, ref) { + const { nativeProps, pluginProps } = splitUIKitViewProps(props, definition); + const createHost = definition.create; + const updateHost = definition.update; + const mountedHost = definition.mounted; + const disposeHost = definition.dispose; + const resolveHostInstance = definition.resolveHostInstance; + const layout = definition.layout; + const layoutSizing = layout?.sizing ?? "fill"; + const hostIdRef = useRef(null); + if (hostIdRef.current == null) { + hostIdRef.current = createUIKitHostId(debugName); + } + const hostId = hostIdRef.current; + const propsRef = useRef(pluginProps); + const previousPropsRef = useRef | undefined>(); + const mountedRef = useRef(false); + const disposedRef = useRef(false); + const updateMeasuredSizeRef = useRef<() => void>(() => {}); + const [nativeHostRevision, setNativeHostRevision] = useState(0); + const attachController = props.attachController !== false; + const attachControllerView = props.attachControllerView !== false; + const attachNativeView = props.attachNativeView !== false; + const mountThroughNativeHost = attachController; + + const invalidateLayout = () => { + updateMeasuredSizeRef.current(); + }; + + const [nativeViewHandle, setNativeViewHandle] = useState< + string | undefined + >(); + const [childrenViewHandle, setChildrenViewHandle] = useState< + string | undefined + >(); + const [controllerHandle, setControllerHandle] = useState< + string | undefined + >(); + const [measuredSize, setMeasuredSize] = useState< + { width: number; height: number } | undefined + >(() => + layoutSizing === "fill" ? undefined - : definition.layout?.defaultSize + : layout?.defaultSize ? { - width: definition.layout.defaultSize.width ?? 0, - height: definition.layout.defaultSize.height ?? 0, + width: layout.defaultSize.width ?? 0, + height: layout.defaultSize.height ?? 0, } - : undefined); - const [error, setError] = useState(null); + : undefined, + ); + const [error, setError] = useState(null); - propsRef.current = pluginProps; + propsRef.current = pluginProps; - const updateMeasuredSize = () => { - const hostInstance = hostInstanceRef.current; - if (!hostInstance || definition.layout?.sizing === 'fill') { - return; - } - runOnUI(() => { - const nextSize = measureUIKitView( - hostInstance.hostView, - definition.layout, - nativeProps.style, - ); + const applyHostHandles = (handles: UIKitHostHandles | null | undefined) => { + if (handles == null) { + return; + } + + setNativeViewHandle((previous) => + previous === handles.nativeViewHandle + ? previous + : handles.nativeViewHandle, + ); + setChildrenViewHandle((previous) => + previous === handles.childrenViewHandle + ? previous + : handles.childrenViewHandle, + ); + setControllerHandle((previous) => + previous === handles.controllerHandle + ? previous + : handles.controllerHandle, + ); + }; + + const updateMeasuredSize = () => { + if (nativeViewHandle == null || layoutSizing === "fill") { + return; + } + runOnUI(() => { + const host = getRegisteredUIKitHost(hostId); + return measureUIKitView( + host.hostInstance.hostView, + layout, + nativeProps.style, + ); + }) + .then((nextSize) => { setMeasuredSize((previous) => previous && previous.width === nextSize.width && @@ -1643,193 +2783,393 @@ function defineUIKitHost( ? previous : nextSize, ); - }).catch((reason) => { - setError(reason instanceof Error ? reason : new Error(String(reason))); + }) + .catch((reason) => { + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); }); - }; + }; + updateMeasuredSizeRef.current = updateMeasuredSize; + + useImperativeHandle( + ref, + () => ({ + get nativeView() { + return null; + }, + runOnUI(callback) { + return runOnUI(() => { + const host = getRegisteredUIKitHost(hostId); + return callback(host.nativeView); + }); + }, + measureNative() { + return runOnUI(() => { + const host = getRegisteredUIKitHost(hostId); + return measureUIKitView( + host.hostInstance.hostView, + layout, + nativeProps.style, + ); + }); + }, + invalidateNativeLayout() { + updateMeasuredSize(); + }, + }), + [hostId, layout, nativeProps.style], + ); - useImperativeHandle( - ref, - () => ({ - get nativeView() { - return viewRef.current; - }, - runOnUI(callback) { - let result: unknown; - return runOnUI(() => { - if (viewRef.current == null) { - throw new Error('UIKit view has not been created yet'); - } - result = callback(viewRef.current); - }).then(() => result as never); - }, - measureNative() { - let measured: - | {width: number; height: number} - | undefined; - return runOnUI(() => { - if (viewRef.current == null) { - throw new Error('UIKit view has not been created yet'); - } - measured = measureUIKitView( - hostInstanceRef.current?.hostView ?? viewRef.current, - definition.layout, - nativeProps.style, - ); - }).then(() => { - if (measured == null) { - throw new Error('UIKit view measurement did not complete'); - } - return measured; - }); - }, - invalidateNativeLayout() { - updateMeasuredSize(); - }, - }), - [definition.layout, nativeProps.style], - ); + useLayoutEffect(() => { + disposedRef.current = false; + let cancelled = false; - useEffect(() => { - disposedRef.current = false; - let cancelled = false; + ensureNativeScriptInstalled(); - ensureNativeScriptInstalled(); + if (mountThroughNativeHost) { + const effectProps = propsRef.current; + runOnUI((currentProps) => { + installUIKitNativeMountBridge(); - runOnUI(() => { - const currentProps = propsRef.current; - const context = createUIKitContext( - debugName, - propsRef, - updateMeasuredSize, - ); - contextRef.current = context; - const created = definition.create(context.createArgument()); - const hostInstance = definition.resolveHostInstance - ? definition.resolveHostInstance(created) - : {hostView: created, lifecycleValue: created}; - const nativeView = hostInstance.lifecycleValue; - if (cancelled || disposedRef.current) { - definition.dispose?.(nativeView, currentProps, context); - context.disposeResources(); - const maybeView = hostInstance.hostView as Record; - if (typeof maybeView.removeFromSuperview === 'function') { - maybeView.removeFromSuperview(); - } - return undefined; + const existingHost = uikitHostRegistry().get(hostId); + if (existingHost) { + existingHost.propsRef.current = currentProps; + return uikitHostHandles(existingHost); } - hostInstanceRef.current = hostInstance; - viewRef.current = nativeView; - definition.update?.(nativeView, currentProps, undefined, context); - previousPropsRef.current = currentProps; - return undefined; - }) - .then(() => { - const hostInstance = hostInstanceRef.current; - if (cancelled || viewRef.current == null || hostInstance == null) { + + const registry = pendingUIKitHostRegistry(); + const pending = registry.get(hostId) as + | PendingUIKitHost + | undefined; + const pendingPropsRef = pending?.propsRef ?? { + current: currentProps, + }; + pendingPropsRef.current = currentProps; + + const mountHost = () => { + const nextProps = pendingPropsRef.current; + const context = createUIKitContext( + debugName, + pendingPropsRef, + ignoreUIKitLayoutInvalidation, + ); + const created = createHost(context.createArgument()); + const hostInstance = resolveHostInstance + ? resolveHostInstance(created) + : { hostView: created, lifecycleValue: created }; + const nativeView = hostInstance.lifecycleValue; + updateHost?.(nativeView, nextProps, undefined, context); + return { + context, + dispose(disposeProps: Readonly) { + return disposeHost?.(nativeView, disposeProps, context); + }, + mounted(mountedProps: Readonly) { + mountedHost?.(nativeView, mountedProps, context); + }, + hostInstance, + nativeView, + previousProps: nextProps, + propsRef: pendingPropsRef, + update( + updateProps: Readonly, + previousProps: Readonly | undefined, + ) { + updateHost?.(nativeView, updateProps, previousProps, context); + }, + }; + }; + + registry.set(hostId, { + debugName, + mountHost, + propsRef: pendingPropsRef, + }); + + return null; + }, effectProps) + .then((handles) => { + if (cancelled || disposedRef.current) { return; } - setNativeViewHandle(nativeHandleOrUndefined(hostInstance.hostView)); - setChildrenViewHandle(nativeHandleOrUndefined(hostInstance.childrenView)); - setControllerHandle(nativeHandleForNSObject(hostInstance.controller)); + previousPropsRef.current = propsRef.current; + applyHostHandles(handles); + setNativeHostRevision((revision) => revision + 1); updateMeasuredSize(); }) .catch((reason) => { - setError(reason instanceof Error ? reason : new Error(String(reason))); + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); }); return () => { cancelled = true; disposedRef.current = true; - const nativeView = viewRef.current; - const context = contextRef.current; - const hostInstance = hostInstanceRef.current; - viewRef.current = null; - hostInstanceRef.current = null; - contextRef.current = null; mountedRef.current = false; - if (nativeView == null) { - context?.disposeResources(); - return; - } runOnUI(() => { - definition.dispose?.(nativeView, propsRef.current, context ?? undefined); - context?.disposeResources(); - const maybeView = hostInstance?.hostView as - | Record - | undefined; - if (typeof maybeView.removeFromSuperview === 'function') { - maybeView.removeFromSuperview(); + if (!uikitHostRegistry().has(hostId)) { + pendingUIKitHostRegistry().delete(hostId); } }).catch((reason) => { - setError(reason instanceof Error ? reason : new Error(String(reason))); + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); }); }; - }, [definition]); + } + + const effectProps = propsRef.current; + runOnUI((currentProps) => { + installUIKitNativeMountBridge(); - useEffect(() => { - const nativeView = viewRef.current; - if (nativeView == null) { - return; + const existingHost = uikitHostRegistry().get(hostId); + if (existingHost) { + existingHost.propsRef.current = currentProps; + return uikitHostHandles(existingHost); } - const previousProps = previousPropsRef.current; - const currentProps = propsRef.current; - const context = contextRef.current; - previousPropsRef.current = currentProps; + const registry = pendingUIKitHostRegistry(); + const pending = registry.get(hostId) as + | PendingUIKitHost + | undefined; + const pendingPropsRef = pending?.propsRef ?? { current: currentProps }; + pendingPropsRef.current = currentProps; + + const mountHost = () => { + const nextProps = pendingPropsRef.current; + const context = createUIKitContext( + debugName, + pendingPropsRef, + ignoreUIKitLayoutInvalidation, + ); + const created = createHost(context.createArgument()); + const hostInstance = resolveHostInstance + ? resolveHostInstance(created) + : { hostView: created, lifecycleValue: created }; + const nativeView = hostInstance.lifecycleValue; + updateHost?.(nativeView, nextProps, undefined, context); + return { + context, + dispose(disposeProps: Readonly) { + return disposeHost?.(nativeView, disposeProps, context); + }, + mounted(mountedProps: Readonly) { + mountedHost?.(nativeView, mountedProps, context); + }, + hostInstance, + nativeView, + previousProps: nextProps, + propsRef: pendingPropsRef, + update( + updateProps: Readonly, + previousProps: Readonly | undefined, + ) { + updateHost?.(nativeView, updateProps, previousProps, context); + }, + }; + }; - runOnUI(() => { - definition.update?.(nativeView, currentProps, previousProps, context ?? undefined); - }).catch((reason) => { - setError(reason instanceof Error ? reason : new Error(String(reason))); + registry.set(hostId, { + debugName, + mountHost, + propsRef: pendingPropsRef, + }); + return createRegisteredUIKitHostFromNative(hostId); + }, effectProps) + .then((handles) => { + if (handles == null) { + throw new Error(`UIKit host ${hostId} was not created`); + } + if (cancelled || disposedRef.current) { + const disposeProps = propsRef.current; + runOnUI((currentProps) => { + disposeRegisteredUIKitHost(hostId, currentProps); + }, disposeProps).catch((reason) => { + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); + }); + return; + } + previousPropsRef.current = propsRef.current; + applyHostHandles(handles); + updateMeasuredSize(); + }) + .catch((reason) => { + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); }); - updateMeasuredSize(); - }, [definition, pluginProps]); - useEffect(() => { - const nativeView = viewRef.current; - if (nativeViewHandle == null || nativeView == null || mountedRef.current) { - return; - } + return () => { + cancelled = true; + disposedRef.current = true; + mountedRef.current = false; + const disposeProps = propsRef.current; + runOnUI((currentProps) => { + disposeRegisteredUIKitHost(hostId, currentProps); + }, disposeProps).catch((reason) => { + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); + }); + }; + }, [ + createHost, + debugName, + disposeHost, + hostId, + mountedHost, + mountThroughNativeHost, + resolveHostInstance, + updateHost, + ]); + + useEffect(() => { + if (nativeViewHandle == null && !mountThroughNativeHost) { + return; + } - mountedRef.current = true; - const context = contextRef.current; - runOnUI(() => { - if (!disposedRef.current) { - definition.mounted?.(nativeView, propsRef.current, context ?? undefined); + const currentProps = propsRef.current; + const previousProps = previousPropsRef.current; + previousPropsRef.current = currentProps; + + if (mountThroughNativeHost) { + runOnUI( + (nextProps, fallbackPreviousProps) => { + syncUIKitHostPropsFromReact(hostId, nextProps); + const host = ensureRegisteredUIKitHost(hostId); + if (!host) { + return null; + } + host.propsRef.current = nextProps; + updateHost?.( + host.nativeView, + nextProps, + host.previousProps ?? fallbackPreviousProps, + host.context, + ); + host.previousProps = nextProps; + return uikitHostHandles(host); + }, + currentProps, + previousProps, + ) + .then(applyHostHandles) + .catch((reason) => { + setError( + reason instanceof Error ? reason : new Error(String(reason)), + ); + }); + updateMeasuredSize(); + return; + } + + runOnUI( + (nextProps, fallbackPreviousProps) => { + const host = ensureRegisteredUIKitHost(hostId); + if (!host) { + return; } - }).catch((reason) => { - setError(reason instanceof Error ? reason : new Error(String(reason))); - }); - }, [definition, nativeViewHandle]); + host.propsRef.current = nextProps; + updateHost?.( + host.nativeView, + nextProps, + host.previousProps ?? fallbackPreviousProps, + host.context, + ); + host.previousProps = nextProps; + }, + currentProps, + previousProps, + ).catch((reason) => { + setError(reason instanceof Error ? reason : new Error(String(reason))); + }); + updateMeasuredSize(); + }, [ + hostId, + mountThroughNativeHost, + nativeViewHandle, + pluginProps, + updateHost, + ]); + + useEffect(() => { + if ( + mountedRef.current || + (nativeViewHandle == null && !mountThroughNativeHost) + ) { + return; + } - if (error) { - throw error; + if (mountThroughNativeHost) { + mountedRef.current = true; + return; } - const layoutStyle = - measuredSize && definition.layout?.sizing !== 'fill' - ? { - width: measuredSize.width, - height: measuredSize.height, + mountedRef.current = true; + const currentProps = propsRef.current; + const isDisposed = disposedRef.current; + runOnUI( + (nextProps, shouldSkipMounted) => { + if (!shouldSkipMounted) { + const host = ensureRegisteredUIKitHost(hostId); + if (!host) { + return; } - : undefined; - - return React.createElement(NativeScriptUIViewNativeComponent, { - ...nativeProps, - collapsable: false, - childrenViewHandle, - controllerHandle, - debugName, - nativeViewHandle, - style: layoutStyle - ? [nativeProps.style, layoutStyle] - : nativeProps.style, + host.propsRef.current = nextProps; + mountedHost?.(host.nativeView, nextProps, host.context); + } + }, + currentProps, + isDisposed, + ).catch((reason) => { + setError(reason instanceof Error ? reason : new Error(String(reason))); }); - }, - ); + }, [hostId, mountedHost, mountThroughNativeHost, nativeViewHandle]); + + if (error) { + throw error; + } + + const layoutStyle = + measuredSize && layoutSizing !== "fill" + ? { + width: measuredSize.width, + height: measuredSize.height, + } + : undefined; + const { children, ...nativePropsWithoutChildren } = + nativeProps as ViewProps & { children?: React.ReactNode }; + + return React.createElement(NativeScriptUIViewNativeComponent, { + ...nativePropsWithoutChildren, + collapsable: false, + children, + childrenViewHandle, + controllerHandle: attachController ? controllerHandle : undefined, + detachControllerView: + attachController && !attachControllerView ? true : undefined, + debugName, + hostReadyId: hostId, + hostId: mountThroughNativeHost ? hostId : undefined, + mountedRevision: + mountThroughNativeHost && mountedHost != null && nativeHostRevision > 0 + ? nativeHostRevision + : undefined, + nativeViewHandle: attachNativeView ? nativeViewHandle : undefined, + style: layoutStyle ? [nativeProps.style, layoutStyle] : nativeProps.style, + updateRevision: + mountThroughNativeHost && nativeHostRevision > 0 + ? nativeHostRevision + : undefined, + }); + }); - Component.displayName = definition.displayName || definition.name || debugName; + Component.displayName = + definition.displayName || definition.name || debugName; return Component; } @@ -1849,6 +3189,8 @@ export function defineUIKitContainer< return defineUIKitHost({ ...definition, resolveHostInstance(created) { + "worklet"; + return { hostView: created.rootView, lifecycleValue: created, @@ -1871,10 +3213,13 @@ export function defineUIViewController< ...definition, create: definition.createController, resolveHostInstance(controller) { + "worklet"; + const controllerRecord = controller as Record; return { - hostView: controllerRecord.view, + hostView: definition.hostView?.(controller) ?? controllerRecord.view, lifecycleValue: controller, + childrenView: definition.childrenView?.(controller), controller, }; }, @@ -1891,6 +3236,7 @@ const NativeScript = { defineUIKitView, defineUIViewController, getRuntimeBackend, + installWorklets, assertUIKitThread, createDelegate, createEventBridge, @@ -1905,9 +3251,16 @@ const NativeScript = { loadFramework, release, retain, + refreshUIKitHostView, runOnUI, + runtimeInvoker, uiInvoker, warnIfNotUIKitThread, }; export default NativeScript; + +// The marker every class extending a native class carries. A runtime decorator, +// so it works the same regardless of bundler or decorator dialect. +export { NativeClass, Interfaces, JavaProxy, NATIVE_CLASS } from "./NativeClass"; +export type { NativeClassOptions } from "./NativeClass"; diff --git a/packages/react-native/test/babel-plugin.test.js b/packages/react-native/test/babel-plugin.test.js new file mode 100644 index 000000000..e69bf4e35 --- /dev/null +++ b/packages/react-native/test/babel-plugin.test.js @@ -0,0 +1,54 @@ +const assert = require('assert'); +const babel = require('@babel/core'); +const plugin = require('../plugin/babel-plugin'); + +function transform(source) { + return babel.transformSync(source, { + ast: false, + babelrc: false, + configFile: false, + plugins: [plugin], + }).code; +} + +const source = ` +import NativeScript, { + defineUIKitContainer, + defineUIKitView, + defineUIViewController, +} from '@nativescript/react-native'; + +defineUIKitView({ + create() { + return UIView.new(); + }, + update: (view) => view.setNeedsLayout(), +}); + +NativeScript.defineUIViewController({ + createController() { + return UIViewController.new(); + }, + childrenView: (controller) => controller.view, + mounted(controller) { + controller.view.setNeedsLayout(); + }, + dispose() {}, +}); + +defineUIKitContainer({ + create() { + return {rootView: UIView.new(), childrenView: UIView.new()}; + }, +}); +`; + +const output = transform(source); +const workletDirectiveCount = (output.match(/"worklet";/g) || []).length; +assert.strictEqual(workletDirectiveCount, 7); +assert(output.includes('create() {\n "worklet";')); +assert(output.includes('update: view => {\n "worklet";')); +assert(output.includes('createController() {\n "worklet";')); +assert(output.includes('childrenView: controller => {\n "worklet";')); + +console.log('babel plugin tests passed'); diff --git a/packages/react-native/test/callback-thread-policy.test.js b/packages/react-native/test/callback-thread-policy.test.js new file mode 100644 index 000000000..8c88b9ae8 --- /dev/null +++ b/packages/react-native/test/callback-thread-policy.test.js @@ -0,0 +1,44 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const repoRoot = path.resolve(__dirname, "../../.."); +const callbackSourcePaths = [ + "packages/react-native/native-api/ffi/shared/bridge/Callbacks.mm", + "NativeScript/ffi/shared/bridge/Callbacks.mm", +]; + +for (const relativePath of callbackSourcePaths) { + const callbacksSource = fs.readFileSync( + path.join(repoRoot, relativePath), + "utf8", + ); + + const nativeCallerPolicyIndex = callbacksSource.indexOf( + "if (nativeCallerThreadCallbacks && !currentThreadIsJs)", + ); + assert.notStrictEqual( + nativeCallerPolicyIndex, + -1, + `${relativePath} should have an explicit off-JS native-caller fast path`, + ); + + const asyncZeroArgBlockIndex = callbacksSource.indexOf( + "dispatchZeroArgVoidBlockAsync()", + ); + assert( + nativeCallerPolicyIndex < asyncZeroArgBlockIndex, + `${relativePath} must not route native-caller callbacks through the JS async block fallback first`, + ); + + const legacyVoidBlockFallback = callbacksSource.indexOf( + "!currentThreadIsJs && returnsVoid && block_ &&\n jsThreadCallbackInvoker", + ); + assert.strictEqual( + legacyVoidBlockFallback, + -1, + `${relativePath} must not force void native blocks from a caller-thread runtime onto JS`, + ); +} + +console.log("callback thread policy tests passed"); diff --git a/packages/react-native/test/config-plugin.test.js b/packages/react-native/test/config-plugin.test.js index a211cbcab..b63c00626 100644 --- a/packages/react-native/test/config-plugin.test.js +++ b/packages/react-native/test/config-plugin.test.js @@ -25,6 +25,10 @@ withTempProject((projectRoot) => { (source.match(/@nativescript\/react-native\/babel-plugin/g) || []).length, 1, ); + assert.strictEqual( + (source.match(/react-native-worklets\/plugin/g) || []).length, + 1, + ); }); withTempProject((projectRoot) => { @@ -47,6 +51,112 @@ withTempProject((projectRoot) => { (source.match(/@nativescript\/react-native\/babel-plugin/g) || []).length, 1, ); + assert.strictEqual( + (source.match(/react-native-worklets\/plugin/g) || []).length, + 1, + ); +}); + +withTempProject((projectRoot) => { + fs.writeFileSync( + path.join(projectRoot, 'babel.config.js'), + [ + 'module.exports = {', + " plugins: ['@nativescript/react-native/babel-plugin'],", + '};', + '', + ].join('\n'), + ); + ensureBabelPlugin(projectRoot); + ensureBabelPlugin(projectRoot); + const source = fs.readFileSync(path.join(projectRoot, 'babel.config.js'), 'utf8'); + assert.strictEqual( + (source.match(/@nativescript\/react-native\/babel-plugin/g) || []).length, + 1, + ); + assert.strictEqual( + (source.match(/react-native-worklets\/plugin/g) || []).length, + 1, + ); +}); + +function loadBabelConfig(projectRoot) { + const configPath = path.join(projectRoot, 'babel.config.js'); + delete require.cache[require.resolve(configPath)]; + const exported = require(configPath); + return typeof exported === 'function' ? exported({cache() {}}) : exported; +} + +function findEntry(entries, name) { + return (entries || []).find( + (entry) => entry === name || (Array.isArray(entry) && entry[0] === name), + ); +} + +withTempProject((projectRoot) => { + ensureBabelPlugin(projectRoot); + const config = loadBabelConfig(projectRoot); + assert.deepStrictEqual(findEntry(config.presets, 'babel-preset-expo'), [ + 'babel-preset-expo', + {worklets: false}, + ]); + assert.deepStrictEqual(findEntry(config.plugins, 'react-native-worklets/plugin'), [ + 'react-native-worklets/plugin', + {strictGlobal: true}, + ]); +}); + +withTempProject((projectRoot) => { + fs.writeFileSync( + path.join(projectRoot, 'babel.config.js'), + [ + 'module.exports = function(api) {', + ' api.cache(true);', + ' return {', + " presets: ['babel-preset-expo'],", + " plugins: ['@nativescript/react-native/babel-plugin', 'react-native-worklets/plugin'],", + ' };', + '};', + '', + ].join('\n'), + ); + ensureBabelPlugin(projectRoot); + ensureBabelPlugin(projectRoot); + const config = loadBabelConfig(projectRoot); + assert.deepStrictEqual(findEntry(config.presets, 'babel-preset-expo'), [ + 'babel-preset-expo', + {worklets: false}, + ]); + assert.deepStrictEqual(findEntry(config.plugins, 'react-native-worklets/plugin'), [ + 'react-native-worklets/plugin', + {strictGlobal: true}, + ]); + assert.strictEqual(config.plugins.length, 2); +}); + +withTempProject((projectRoot) => { + const source = [ + 'module.exports = {', + " plugins: [['react-native-worklets/plugin', { processNestedWorklets: true }]],", + '};', + '', + ].join('\n'); + fs.writeFileSync(path.join(projectRoot, 'babel.config.js'), source); + const warnings = []; + const warn = console.warn; + console.warn = (message) => warnings.push(message); + try { + ensureBabelPlugin(projectRoot); + } finally { + console.warn = warn; + } + const config = loadBabelConfig(projectRoot); + assert.deepStrictEqual(findEntry(config.plugins, 'react-native-worklets/plugin'), [ + 'react-native-worklets/plugin', + {processNestedWorklets: true}, + ]); + assert.strictEqual(warnings.length, 1); + assert.match(warnings[0], /strictGlobal/); }); withTempProject((projectRoot) => { @@ -67,4 +177,44 @@ withTempProject((projectRoot) => { }); }); +withTempProject((projectRoot) => { + const configPath = path.join(projectRoot, 'nativescript.react-native.json'); + fs.writeFileSync( + configPath, + JSON.stringify({ + reactNative: { + metadata: { + includePods: ['StalePod'], + android: {include: ['com.example.Worker']}, + }, + }, + }), + ); + ensureMetadataConfig(projectRoot, normalizeMetadataOptions({})); + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + assert.deepStrictEqual(config.reactNative.metadata, { + includePods: [], + includeSystemFrameworks: [], + android: {include: ['com.example.Worker']}, + }); +}); + +withTempProject((projectRoot) => { + const configPath = path.join(projectRoot, 'nativescript.react-native.json'); + fs.writeFileSync( + configPath, + JSON.stringify({reactNative: {metadata: {android: {include: ['com.example.Old']}}}}), + ); + ensureMetadataConfig( + projectRoot, + normalizeMetadataOptions({ + metadata: {android: {include: ['com.example.Worker', 7, 'com.example.views.*']}}, + }), + ); + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + assert.deepStrictEqual(config.reactNative.metadata.android, { + include: ['com.example.Worker', 'com.example.views.*'], + }); +}); + console.log('config plugin tests passed'); diff --git a/packages/react-native/test/configure-android.test.js b/packages/react-native/test/configure-android.test.js new file mode 100644 index 000000000..033927f6a --- /dev/null +++ b/packages/react-native/test/configure-android.test.js @@ -0,0 +1,92 @@ +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const { + APPLY_LINE, + ensureGradleScript, + ensureTypings, +} = require('../cli/configure-android'); + +function scratchProject() { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ns-rn-android-')); + fs.mkdirSync(path.join(root, 'android', 'app'), { recursive: true }); + return root; +} + +function writeBuildGradle(root, contents) { + fs.writeFileSync(path.join(root, 'android', 'app', 'build.gradle'), contents); +} + +function readBuildGradle(root) { + return fs.readFileSync(path.join(root, 'android', 'app', 'build.gradle'), 'utf8'); +} + +// The script hooks applicationVariants, so it has to come after the plugin that +// creates them. +{ + const root = scratchProject(); + writeBuildGradle( + root, + 'apply plugin: "com.android.application"\napply plugin: "com.facebook.react"\n\nandroid {\n}\n', + ); + + const result = ensureGradleScript(root); + assert.strictEqual(result.changed, true); + + const lines = readBuildGradle(root).split('\n'); + const reactIndex = lines.findIndex((line) => line.includes('com.facebook.react')); + const applyIndex = lines.findIndex((line) => line.includes('nativescript.gradle')); + assert.ok(applyIndex > reactIndex, 'apply line must follow the React plugin'); +} + +// Running configure twice must not add it twice. +{ + const root = scratchProject(); + writeBuildGradle(root, `apply plugin: "com.facebook.react"\n${APPLY_LINE}\n`); + + const result = ensureGradleScript(root); + assert.strictEqual(result.changed, false); + const occurrences = readBuildGradle(root).split('nativescript.gradle').length - 1; + assert.strictEqual(occurrences, 1); +} + +// A project that does not apply the React plugin still gets the script. +{ + const root = scratchProject(); + writeBuildGradle(root, 'apply plugin: "com.android.application"\n'); + + assert.strictEqual(ensureGradleScript(root).changed, true); + assert.ok(readBuildGradle(root).includes('nativescript.gradle')); +} + +// Typings are added to tsconfig, once. +{ + const root = scratchProject(); + const tsconfigPath = path.join(root, 'tsconfig.json'); + fs.writeFileSync(tsconfigPath, JSON.stringify({ include: ['**/*.ts'] }, null, 2)); + + assert.strictEqual(ensureTypings(root).changed, true); + const include = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8')).include; + assert.ok(include.includes('types/android/android.d.ts')); + assert.ok(include.includes('types/android/android-declarations.d.ts')); + + assert.strictEqual(ensureTypings(root).changed, false); + const after = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8')).include; + assert.strictEqual(after.length, include.length); +} + +// A tsconfig with comments is left alone rather than rewritten and stripped. +{ + const root = scratchProject(); + const tsconfigPath = path.join(root, 'tsconfig.json'); + const original = '{\n // keep me\n "include": ["**/*.ts"]\n}\n'; + fs.writeFileSync(tsconfigPath, original); + + const result = ensureTypings(root); + assert.strictEqual(result.changed, false); + assert.strictEqual(fs.readFileSync(tsconfigPath, 'utf8'), original); +} + +console.log('configure-android tests passed'); diff --git a/packages/react-native/test/interop-object-api.test.js b/packages/react-native/test/interop-object-api.test.js new file mode 100644 index 000000000..1db1b3859 --- /dev/null +++ b/packages/react-native/test/interop-object-api.test.js @@ -0,0 +1,40 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const repoRoot = path.resolve(__dirname, "../../.."); +const packageRoot = path.resolve(__dirname, ".."); + +for (const relativePath of [ + "packages/react-native/native-api/ffi/shared/bridge/TypeConv.mm", + "NativeScript/ffi/shared/bridge/TypeConv.mm", +]) { + const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8"); + assert( + source.includes('PropNameID::forAscii(runtime, "object")'), + `${relativePath} should expose interop.object`, + ); + assert( + source.includes("nativeObjectReturnTypeForClass(object_getClass(object))") && + source.includes("convertNativeReturnValue(runtime, bridge, type, &object)"), + `${relativePath} should wrap Objective-C object pointers through the generic NativeScript object bridge`, + ); + assert( + source.includes( + "parseIntegerTextToUintptr(args[0].asString(runtime).utf8(runtime)", + ), + `${relativePath} should accept string pointer addresses without treating them as C strings`, + ); +} + +const declarations = fs.readFileSync( + path.join(packageRoot, "types/objc-node-api/index.d.ts"), + "utf8", +); +assert( + declarations.includes("function object NativeClass(Child)); +} + +// Applying it to something that is not a class is a mistake worth reporting. +{ + assert.throws(() => NativeClass()(42), /class declaration/); +} + +// A fully-qualified name goes to the runtime verbatim, with no +// isTypeScriptExtend flag -- that is the form the runtime takes as-is, and the +// only one the static binding generator can predict. +{ + const { Base, calls } = makeNativeBase(); + class Ticker extends Base {} + JavaProxy('com.example.Ticker')(Ticker); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].name, 'com.example.Ticker'); + assert.strictEqual(calls[0].isTypeScriptExtend, undefined); +} + +// @NativeClass({ name }) is the same thing. +{ + const { Base, calls } = makeNativeBase(); + class Ticker extends Base {} + NativeClass({ name: 'com.example.Ticker' })(Ticker); + assert.strictEqual(calls[0].name, 'com.example.Ticker'); +} + +// An unqualified name would collide across packages and cannot be generated +// ahead of time; say so rather than producing a class nobody can find. +{ + const { Base } = makeNativeBase(); + class Ticker extends Base {} + assert.throws( + () => JavaProxy('Ticker')(Ticker), + /fully qualified/, + ); +} + +console.log('native-class tests passed'); diff --git a/packages/react-native/test/native-null-callback-conversion.test.js b/packages/react-native/test/native-null-callback-conversion.test.js new file mode 100644 index 000000000..7550fccb0 --- /dev/null +++ b/packages/react-native/test/native-null-callback-conversion.test.js @@ -0,0 +1,52 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const repoRoot = path.resolve(__dirname, "../../.."); + +for (const relativePath of [ + "packages/react-native/native-api/ffi/shared/bridge/TypeConv.mm", + "NativeScript/ffi/shared/bridge/TypeConv.mm", +]) { + const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8"); + const blockCaseIndex = source.indexOf( + "case metagen::mdTypeBlock:\n case metagen::mdTypeFunctionPointer:", + ); + assert.notStrictEqual( + blockCaseIndex, + -1, + `${relativePath} should convert block/function-pointer arguments`, + ); + + const nilGuardIndex = source.indexOf( + "if (value.isNull() || value.isUndefined())", + blockCaseIndex, + ); + const callbackCreationIndex = source.indexOf( + "createEngineCallback(", + blockCaseIndex, + ); + + assert( + nilGuardIndex !== -1 && nilGuardIndex < callbackCreationIndex, + `${relativePath} should convert null/undefined callbacks to nullptr before callback metadata lookup`, + ); + const encodingMarkerIndex = source.indexOf("__nativeApiCallbackEncoding"); + const encodingReadIndex = source.indexOf( + "stringPropertyOrEmpty(runtime, object, kNativeApiCallbackEncodingProperty)", + blockCaseIndex, + ); + const explicitCallbackIndex = source.indexOf( + "runtime, bridge, callbackEncoding", + blockCaseIndex, + ); + assert( + encodingMarkerIndex !== -1 && + encodingReadIndex !== -1 && + explicitCallbackIndex !== -1 && + encodingReadIndex < explicitCallbackIndex, + `${relativePath} should honor explicit interop callback encodings before metadata-only callback creation`, + ); +} + +console.log("native null callback conversion tests passed"); diff --git a/packages/react-native/test/react-native-image-loader-api.test.js b/packages/react-native/test/react-native-image-loader-api.test.js new file mode 100644 index 000000000..72c89885b --- /dev/null +++ b/packages/react-native/test/react-native-image-loader-api.test.js @@ -0,0 +1,46 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +const moduleSource = fs.readFileSync( + path.join(packageRoot, "ios/NativeScriptNativeApiModule.mm"), + "utf8", +); +assert( + moduleSource.includes("__nativeScriptLoadReactImage"), + "worklet runtime should expose a generic RN image loading host function", +); +assert( + moduleSource.includes("[RCTConvert RCTImageSource:jsonSource]") && + moduleSource.includes("currentReactImageLoader()") && + moduleSource.includes("loadImageWithURLRequest:imageSource.request"), + "image loading should use React Native RCTImageLoader/RCTImageSource parity path", +); +assert( + moduleSource.includes("nativeScriptHandleFromNSObject(retainedImage)") && + moduleSource.includes("runtimeStrong->schedule("), + "loaded UIImages should be retained and delivered back through the worklet runtime", +); + +const publicApi = fs.readFileSync(path.join(packageRoot, "src/index.ts"), "utf8"); +assert( + publicApi.includes("export function loadImage(") && + publicApi.includes(".__nativeScriptLoadReactImage") && + publicApi.includes("interop?.object?.(interop.Pointer(handle))") && + publicApi.includes("loadImage: (source, options, callback)"), + "public TS API should wrap image handles into NativeScript objects and expose ctx.loadImage", +); + +const declarations = fs.readFileSync( + path.join(packageRoot, "src/index.d.ts"), + "utf8", +); +assert( + declarations.includes("NativeScriptImageLoadOptions") && + declarations.includes("loadImage("), + "type declarations should expose generic image loading", +); + +console.log("React Native image loader API tests passed"); diff --git a/packages/react-native/test/runtime-callback-policy.test.js b/packages/react-native/test/runtime-callback-policy.test.js new file mode 100644 index 000000000..2536921d0 --- /dev/null +++ b/packages/react-native/test/runtime-callback-policy.test.js @@ -0,0 +1,122 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); +const repoRoot = path.resolve(packageRoot, "../.."); + +function readPackage(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +function readRepo(relativePath) { + return fs.readFileSync(path.join(repoRoot, relativePath), "utf8"); +} + +const index = readPackage("src/index.ts"); +assert( + index.includes('export type NativeScriptCallbackThread = "js" | "runtime"'), + "public JS API should expose a generic runtime callback thread policy", +); +assert( + index.includes("export function runtimeInvoker"), + "public JS API should export runtimeInvoker", +); +assert( + !index.includes("export function objCBlock") && + !index.includes("export function objCFunctionPointer") && + !index.includes("typedObjCCallback"), + "public JS API should not expose RN-specific typed ObjC callback helpers", +); +assert( + index.includes('return callbackInvoker("runtime", callback)'), + "runtimeInvoker should mark callbacks for their owning NativeScript runtime", +); +assert( + index.includes("Object.getOwnPropertyNames(callback)") && + index.includes("Object.getOwnPropertySymbols(callback)") && + index.includes("Object.defineProperty(wrapped, key, descriptor)"), + "callback invokers should preserve native callback/block metadata on wrappers", +); +assert( + index.includes("interop.Block = wrapInteropFactory"), + "interop.Block should be made callable/constructable when the runtime exposes it", +); +assert( + index.includes('if (thread === "runtime")'), + "eventBridge should route runtime callbacks through runtimeInvoker", +); +assert( + !index.includes("afterUIKitTransition"), + "runtime should not expose a transition-specific callback API", +); + +const declarations = readPackage("src/index.d.ts"); +assert( + declarations.includes('NativeScriptCallbackThread = "js" | "runtime"'), + "public declarations should include the runtime callback policy", +); +assert( + declarations.includes("runtimeInvokerschedule"), + "runtime callbacks should schedule work onto the Worklet runtime", +); +assert( + !moduleSource.includes("__nativeScriptAfterUIKitTransition"), + "Native module should not install transition-specific host functions", +); + +for (const relativePath of [ + "packages/react-native/native-api/ffi/shared/NativeApiBackendConfig.h", + "NativeScript/ffi/shared/NativeApiBackendConfig.h", +]) { + const source = readRepo(relativePath); + assert( + source.includes("runtimeCallbackInvoker"), + `${relativePath} should expose a generic runtime callback invoker`, + ); +} + +for (const relativePath of [ + "packages/react-native/native-api/ffi/shared/bridge/Callbacks.mm", + "NativeScript/ffi/shared/bridge/Callbacks.mm", +]) { + const source = readRepo(relativePath); + assert( + source.includes("NativeApiCallbackThreadPolicy::Runtime"), + `${relativePath} should support the runtime callback policy`, + ); + assert( + source.includes('policy == "runtime"'), + `${relativePath} should parse runtime callback policy markers`, + ); + assert( + source.includes("bridge_->runtimeCallbackInvoker()"), + `${relativePath} should dispatch runtime-marked callbacks through the generic invoker`, + ); + assert( + source.includes("parseObjCCallbackEngineSignature") && + source.includes("objcSignatureEncoding"), + `${relativePath} should build callbacks from explicit ObjC encodings`, + ); +} + +console.log("runtime callback policy tests passed"); diff --git a/packages/react-native/test/runtime-member-cache.test.js b/packages/react-native/test/runtime-member-cache.test.js new file mode 100644 index 000000000..3bedd944b --- /dev/null +++ b/packages/react-native/test/runtime-member-cache.test.js @@ -0,0 +1,44 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const repoRoot = path.resolve(__dirname, "../../.."); +const hostObjectSources = [ + "NativeScript/ffi/shared/bridge/HostObjects.mm", + "packages/react-native/native-api/ffi/shared/bridge/HostObjects.mm", +]; + +for (const sourcePath of hostObjectSources) { + const hostObjects = fs.readFileSync(path.join(repoRoot, sourcePath), "utf8"); + + assert( + hostObjects.includes("NativeApiRuntimeMembersCacheKey"), + `${sourcePath}: runtime member reflection should key cache entries by class/staticness`, + ); + assert( + hostObjects.includes("NativeApiRuntimeMemberIndex"), + `${sourcePath}: runtime member reflection should build an indexed lookup`, + ); + assert( + hostObjects.includes("runtimeMembersCacheMutex"), + `${sourcePath}: runtime member reflection cache should be synchronized across runtimes`, + ); + assert( + hostObjects.includes("buildRuntimeMembersForClass"), + `${sourcePath}: runtime member reflection should separate scanning from cache lookup`, + ); + assert( + hostObjects.includes("cache.emplace(key, members)"), + `${sourcePath}: runtime member reflection should populate the cache after scanning once`, + ); + assert( + hostObjects.includes("memberNames.find(name)"), + `${sourcePath}: runtime member existence checks should use indexed name lookup`, + ); + assert( + hostObjects.includes("selectorsByNameAndCount.find(name)"), + `${sourcePath}: runtime selector resolution should use indexed selector lookup`, + ); +} + +console.log("runtime member cache tests passed"); diff --git a/packages/react-native/test/runtime-objc-property-setter.test.js b/packages/react-native/test/runtime-objc-property-setter.test.js new file mode 100644 index 000000000..4c4935659 --- /dev/null +++ b/packages/react-native/test/runtime-objc-property-setter.test.js @@ -0,0 +1,39 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const repoRoot = path.resolve(__dirname, "../../.."); + +for (const relativePath of [ + "NativeScript/ffi/shared/bridge/HostObjects.mm", + "packages/react-native/native-api/ffi/shared/bridge/HostObjects.mm", +]) { + const source = fs.readFileSync(path.join(repoRoot, relativePath), "utf8"); + + assert( + source.includes("runtimeWritablePropertySetter"), + `${relativePath}: host objects should discover writable Objective-C runtime properties`, + ); + assert( + source.includes("runtimeReadablePropertyGetter"), + `${relativePath}: host objects should discover readable Objective-C runtime properties`, + ); + assert( + source.includes("property_copyAttributeValue(prop, \"S\")"), + `${relativePath}: runtime property fallback should honor custom Objective-C setters`, + ); + assert( + source.includes("property_copyAttributeValue(prop, \"R\")"), + `${relativePath}: runtime property fallback should not assign readonly Objective-C properties`, + ); + assert( + source.includes("callObjCSelector(runtime, bridge_, object_, false,\n *setterSelectorName, nullptr, args, 1);"), + `${relativePath}: runtime property fallback should invoke the discovered native setter`, + ); + assert( + source.includes("return callObjectSelector(runtime, *selector, nullptr, nullptr, 0);"), + `${relativePath}: JS-extended instances should read discovered native properties before returning undefined`, + ); +} + +console.log("runtime Objective-C property setter tests passed"); diff --git a/packages/react-native/test/uikit-controller-appearance-api.test.js b/packages/react-native/test/uikit-controller-appearance-api.test.js new file mode 100644 index 000000000..e516862ff --- /dev/null +++ b/packages/react-native/test/uikit-controller-appearance-api.test.js @@ -0,0 +1,41 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +const hostView = read("ios/NativeScriptUIView.mm"); + +assert( + hostView.includes("NativeScriptShouldForwardControllerAppearance"), + "NativeScriptUIView should centralize visible-controller appearance fallback checks", +); +assert( + hostView.includes("NativeScriptHostedViewContainsControllerView"), + "NativeScriptUIView should detect when the hosted native view contains the controller view", +); +assert( + hostView.includes("[hostedViewToReinsert removeFromSuperview];") && + hostView.includes("[parent addChildViewController:_viewController];") && + hostView.includes("[super insertSubview:hostedViewToReinsert atIndex:targetIndex];") && + hostView.includes("[_viewController didMoveToParentViewController:parent];"), + "NativeScriptUIView should add child controllers before reinserting hosted visible views", +); +assert( + hostView.includes( + "hostedViewToReinsert == nil && NativeScriptShouldForwardControllerAppearance(_viewController)", + ), + "NativeScriptUIView should only manually forward appearance when it cannot re-order the hosted view", +); +assert( + hostView.includes("[_viewController beginAppearanceTransition:YES animated:NO];") && + hostView.includes("[_viewController beginAppearanceTransition:NO animated:NO];") && + hostView.includes("[_viewController endAppearanceTransition];"), + "NativeScriptUIView should retain manual appearance forwarding as a fallback", +); + +console.log("uikit controller appearance API tests passed"); diff --git a/packages/react-native/test/uikit-controller-host-view-api.test.js b/packages/react-native/test/uikit-controller-host-view-api.test.js new file mode 100644 index 000000000..7951dc631 --- /dev/null +++ b/packages/react-native/test/uikit-controller-host-view-api.test.js @@ -0,0 +1,37 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +const index = read("src/index.ts"); +assert( + index.includes("hostView?: (controller: Controller) => unknown"), + "defineUIViewController should expose a generic hostView resolver", +); +assert( + index.includes("hostView: definition.hostView?.(controller) ?? controllerRecord.view"), + "defineUIViewController should use the resolved host view before falling back to controller.view", +); + +const declarations = read("src/index.d.ts"); +assert( + declarations.includes("hostView?: (controller: Controller) => unknown"), + "public declarations should expose UIViewControllerDefinition.hostView", +); + +const nativeHost = read("ios/NativeScriptUIView.mm"); +assert( + nativeHost.includes("if (_nativeViewHandle.length == 0) {\n [self setNativeView:_viewController.view];"), + "NativeScriptUIView should not overwrite an explicit native host view with controller.view", +); +assert( + nativeHost.includes("[self attachViewControllerIfPossible];"), + "NativeScriptUIView should still attach the controller for lifecycle when a custom host view is used", +); + +console.log("uikit controller host-view API tests passed"); diff --git a/packages/react-native/test/uikit-gesture-action-api.test.js b/packages/react-native/test/uikit-gesture-action-api.test.js new file mode 100644 index 000000000..1d40d977b --- /dev/null +++ b/packages/react-native/test/uikit-gesture-action-api.test.js @@ -0,0 +1,75 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +const index = read("src/index.ts"); +assert( + index.includes("gestureAction("), + "UIKit context should expose a gestureAction helper", +); +assert( + index.includes("targetAction(control, events, callback)"), + "UIKit context should expose a targetAction helper", +); +assert( + index.includes("actionTarget(callback)"), + "UIKit context should expose a generic target/action helper", +); +assert( + index.includes("function invokeNativeScriptCallback("), + "UIKit native callbacks should route through a shared callback scheduler", +); +assert( + index.includes('nativeScriptCallbackThread(callback) !== "js"'), + "callback scheduler should distinguish JS-owned callbacks from runtime callbacks", +); +assert( + index.includes("workletsProxy.scheduleOnRN(handler, serializer(args))"), + "JS-owned UIKit callbacks should schedule asynchronously onto the RN runtime", +); +assert( + index.includes("invokeNativeScriptCallback(callback, [], () => disposed)"), + "targetAction should honor callback thread policy instead of calling callbacks synchronously", +); +assert( + index.includes("nativeGesture.addTargetAction(target, selector)"), + "gestureAction should attach a target/action to UIGestureRecognizer", +); +assert( + index.includes("nativeGesture.removeTargetAction(target, selector)"), + "gestureAction should remove the target/action on dispose", +); +assert( + index.includes("callback(sender ?? gesture)"), + "gestureAction should pass the recognizer sender to the callback", +); +assert( + index.includes("invokeNativeScriptCallback(callback, [sender], () => disposed)"), + "actionTarget should honor callback thread policy and pass the sender", +); +assert( + index.includes('action: "nativeScriptHandleAction:"'), + "actionTarget should return the Objective-C selector name", +); + +const declarations = read("src/index.d.ts"); +assert( + declarations.includes("gestureAction("), + "public declarations should expose gestureAction", +); +assert( + declarations.includes("callback: (gesture: unknown) => void"), + "gestureAction declarations should pass the recognizer to callbacks", +); +assert( + declarations.includes("actionTarget(callback: (sender: unknown) => void)"), + "public declarations should expose generic actionTarget", +); + +console.log("uikit gesture action API tests passed"); diff --git a/packages/react-native/test/uikit-host-dispose-api.test.js b/packages/react-native/test/uikit-host-dispose-api.test.js new file mode 100644 index 000000000..c11bc5f0e --- /dev/null +++ b/packages/react-native/test/uikit-host-dispose-api.test.js @@ -0,0 +1,39 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +const index = read("src/index.ts"); +assert( + index.includes("export type UIKitDisposeResult"), + "public source should define UIKitDisposeResult", +); +assert( + index.includes("disposeResult?.removeHostView !== false"), + "disposeRegisteredUIKitHost should honor removeHostView=false", +); +assert( + index.includes("return disposeHost?.(nativeView, disposeProps, context);"), + "host adapters should propagate dispose return values", +); + +const declarations = read("src/index.d.ts"); +assert( + declarations.includes("export type UIKitDisposeResult"), + "public declarations should expose UIKitDisposeResult", +); +assert( + declarations.includes("removeHostView?: boolean"), + "UIKitDisposeResult should expose generic host-view removal control", +); +assert( + declarations.includes(") => UIKitDisposeResult"), + "dispose declarations should return UIKitDisposeResult", +); + +console.log("uikit host dispose API tests passed"); diff --git a/packages/react-native/test/uikit-host-ready-api.test.js b/packages/react-native/test/uikit-host-ready-api.test.js new file mode 100644 index 000000000..0d31ff9b8 --- /dev/null +++ b/packages/react-native/test/uikit-host-ready-api.test.js @@ -0,0 +1,79 @@ +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const packageRoot = path.resolve(__dirname, '..'); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), 'utf8'); +} + +const nativeComponent = read('src/NativeScriptUIViewNativeComponent.ts'); +assert( + nativeComponent.includes('DirectEventHandler'), + 'NativeScriptUIViewNativeComponent should use a generated direct event type', +); +assert( + nativeComponent.includes('hostReadyId?: string'), + 'NativeScriptUIViewNativeComponent should expose a stable readiness identity prop', +); +assert( + nativeComponent.includes('onHostReady?: DirectEventHandler'), + 'NativeScriptUIViewNativeComponent should expose onHostReady', +); +assert( + nativeComponent.includes('hasChildren: boolean'), + 'onHostReady should report whether RN children are attached', +); + +const declarations = read('src/index.d.ts'); +assert( + declarations.includes('export type UIKitHostReadyEvent'), + 'public declarations should export UIKitHostReadyEvent', +); +assert( + declarations.includes('onHostReady?: (event: UIKitHostReadyEvent) => void'), + 'public host props should expose onHostReady', +); + +const index = read('src/index.ts'); +assert( + index.includes('hostReadyId: hostId'), + 'defineUIKitHost should pass a stable hostReadyId to the native host view', +); +assert( + index.includes('onHostReady'), + 'defineUIKitHost should forward onHostReady to NativeScriptUIView', +); + +const header = read('ios/NativeScriptUIView.h'); +assert( + header.includes('@property(nonatomic, copy) NSString* hostReadyId'), + 'NativeScriptUIView should store the readiness identity', +); +assert( + header.includes('onHostReady'), + 'NativeScriptUIView should expose a Paper host-ready event block', +); + +const manager = read('ios/NativeScriptUIViewManager.mm'); +assert( + manager.includes('RCT_EXPORT_VIEW_PROPERTY(hostReadyId, NSString)'), + 'Paper manager should export hostReadyId', +); +assert( + manager.includes('RCT_EXPORT_VIEW_PROPERTY(onHostReady, RCTDirectEventBlock)'), + 'Paper manager should export onHostReady', +); + +const fabricView = read('ios/Fabric/NativeScriptUIViewComponentView.mm'); +assert( + fabricView.includes('EventEmitters.h'), + 'Fabric component should import generated event emitters', +); +assert( + fabricView.includes('onHostReady('), + 'Fabric component should emit onHostReady', +); + +console.log('uikit host ready API tests passed'); diff --git a/packages/react-native/test/uikit-host-refresh-api.test.js b/packages/react-native/test/uikit-host-refresh-api.test.js new file mode 100644 index 000000000..a13014513 --- /dev/null +++ b/packages/react-native/test/uikit-host-refresh-api.test.js @@ -0,0 +1,142 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +const index = read("src/index.ts"); +assert( + index.includes("export function refreshUIKitHostView"), + "public JS API should export refreshUIKitHostView", +); +assert( + index.includes("__nativeScriptRefreshUIKitHostView"), + "refreshUIKitHostView should call the worklet-installed native refresh global", +); +assert( + index.includes("export function refreshUIKitHostViewHandle") && + index.includes("return refresh(nativeHandleForUIKitView(view)) === true;") && + index.includes("return refresh(viewHandle) === true;"), + "public JS API should refresh UIKit hosts from native handles", +); + +const declarations = read("src/index.d.ts"); +assert( + declarations.includes("refreshUIKitHostView(view: unknown): boolean"), + "public declarations should expose refreshUIKitHostView", +); +assert( + declarations.includes("refreshUIKitHostViewHandle(viewHandle: string): boolean"), + "public declarations should expose handle-based UIKit host refresh", +); + +const hostHeader = read("ios/NativeScriptUIKitHost.h"); +assert( + hostHeader.includes("NativeScriptRefreshUIKitHostView"), + "UIKit host header should export a native refresh entry point", +); + +const hostView = read("ios/NativeScriptUIView.mm"); +assert( + hostView.includes("#import "), + "NativeScriptUIView should use ObjC associations for detached children hosts", +); +assert( + hostView.includes("refreshDetachedChildrenHost"), + "NativeScriptUIView should be able to refresh detached React children", +); +assert( + hostView.includes("NativeScriptDetachedChildrenOwner") && + hostView.includes("objc_setAssociatedObject") && + hostView.includes("objc_getAssociatedObject"), + "NativeScriptUIView should associate detached children views with their owner", +); +assert( + hostView.includes("NativeScriptDetachedChildrenOwner(root)") && + hostView.includes("refreshDetachedChildrenHost"), + "refreshUIKitHostView should refresh a detached children view even if its sentinel was removed", +); +assert( + hostView.includes( + "return NativeScriptChildrenViewHasVisibleChild(_childrenView, _detachedTouchSentinel);", + ), + "refreshUIKitHostView should report whether hosted React children are ready", +); +assert( + hostView.includes("UIView* touchView = _childrenView;"), + "NativeScriptUIView should attach the RN touch handler to the stable detached children host", +); +assert( + hostView.includes("NativeScriptViewHasGestureRecognizer(touchView, _detachedTouchHandler)") && + hostView.includes("NativeScriptGestureRecognizerAttachedView(_detachedTouchHandler)") && + hostView.includes("_detachedTouchHandlerWindow != touchView.window") && + hostView.includes("[self detachDetachedChildrenTouchHandler];"), + "NativeScriptUIView should repair a stale detached RN touch handler after UIKit window transitions", +); +assert( + hostView.includes("_detachedTouchHandlerWindow = touchView.window;") && + hostView.includes("_detachedTouchHandlerWindow = nil;"), + "NativeScriptUIView should track and clear the detached touch handler window", +); +assert( + hostView.includes("touchView.userInteractionEnabled = YES;"), + "NativeScriptUIView should keep the hosted RN touch surface interactive after refreshes", +); +assert( + hostView.includes("NativeScriptFindAncestorSurfaceTouchHandler") && + hostView.includes("NativeScriptFindAncestorSurfaceTouchHandler(touchView) != nil") && + hostView.includes("[self detachDetachedChildrenTouchHandler];"), + "NativeScriptUIView should not install a duplicate detached touch handler below an ancestor RCTSurfaceTouchHandler", +); +assert( + hostView.includes("UIView* detachView =") && + hostView.includes("NativeScriptGestureRecognizerAttachedView(_detachedTouchHandler)") && + hostView.includes("NativeScriptViewHasGestureRecognizer(detachView, _detachedTouchHandler)") && + hostView.includes("[_detachedTouchHandler detachFromView:detachView];"), + "NativeScriptUIView should detach RCTSurfaceTouchHandler from its actual attached view, not a stale stored host view", +); +assert( + hostView.includes("- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event {\n [self refreshDetachedChildrenHost];"), + "NativeScriptUIView should refresh the detached RN touch host before first hit testing", +); +assert( + !hostView.includes("NativeScriptFirstReactTaggedSubview"), + "NativeScriptUIView should not attach RN touch handling to a route-dependent React descendant", +); + +const fabricHostView = read("ios/Fabric/NativeScriptUIViewComponentView.mm"); +assert( + fabricHostView.includes("[_containerView refreshDetachedChildrenHost];"), + "Fabric wrapper should refresh the detached RN touch host before first hit testing", +); +assert( + fabricHostView.includes("- (void)didMoveToWindow") && + fabricHostView.includes("[_containerView refreshDetachedChildrenHost];"), + "Fabric wrapper should refresh detached RN touch hosts when UIKit moves the wrapper between windows", +); +assert( + fabricHostView.includes("- (void)mountChildComponentView") && + !fabricHostView.includes( + "- (void)mountChildComponentView:(UIView*)childComponentView\n index:(NSInteger)index {\n [_containerView insertSubview:childComponentView atIndex:index];\n [_containerView layoutDetachedChildrenViewSubviewsIfNeeded];", + ), + "Fabric child mounts should use full host refresh instead of layout-only refresh", +); +assert( + fabricHostView.includes("- (void)updateLayoutMetrics") && + !fabricHostView.includes( + "- (void)updateLayoutMetrics:(const LayoutMetrics&)layoutMetrics\n oldLayoutMetrics:(const LayoutMetrics&)oldLayoutMetrics {\n [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];\n [_containerView layoutDetachedChildrenViewSubviewsIfNeeded];", + ), + "Fabric layout updates should refresh the touch host origin and handler, not just resize children", +); + +const moduleSource = read("ios/NativeScriptNativeApiModule.mm"); +assert( + moduleSource.includes("__nativeScriptRefreshUIKitHostView"), + "worklet runtime install should expose the refresh host function", +); + +console.log("uikit host refresh API tests passed"); diff --git a/packages/react-native/test/uikit-tabbar-hit-test.test.js b/packages/react-native/test/uikit-tabbar-hit-test.test.js new file mode 100644 index 000000000..2e47c0379 --- /dev/null +++ b/packages/react-native/test/uikit-tabbar-hit-test.test.js @@ -0,0 +1,34 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); + +function read(relativePath) { + return fs.readFileSync(path.join(packageRoot, relativePath), "utf8"); +} + +for (const relativePath of [ + "ios/NativeScriptUIView.mm", + "ios/Fabric/NativeScriptUIViewComponentView.mm", +]) { + const source = read(relativePath); + assert( + source.includes("PointInsideTabBarHitArea"), + `${relativePath} should gate tab bar passthrough on the tab bar hit area`, + ); + assert( + source.includes("EffectiveTabBarHitBounds"), + `${relativePath} should cap oversized tab bar visual bounds before hit testing`, + ); + assert( + source.includes("CGRectInset(bounds, -24, -16)"), + `${relativePath} should allow a small expanded tab bar hit target`, + ); + assert( + !source.includes("VisibleHitViewAtPoint"), + `${relativePath} should not use recursive tab bar descendants as the passthrough hit area`, + ); +} + +console.log("uikit tab bar hit-test tests passed"); diff --git a/packages/react-native/test/worklets-frame-loop.test.js b/packages/react-native/test/worklets-frame-loop.test.js new file mode 100644 index 000000000..05daecfca --- /dev/null +++ b/packages/react-native/test/worklets-frame-loop.test.js @@ -0,0 +1,61 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const packageRoot = path.resolve(__dirname, ".."); +const index = fs.readFileSync(path.join(packageRoot, "src/index.ts"), "utf8"); + +assert( + index.includes("function installIdleAwareWorkletsFrameLoop"), + "runtime should install an idle-aware Worklets frame loop", +); +assert( + index.includes("__nativeScriptIdleAwareWorkletsFrameLoop"), + "frame loop install should be idempotent inside the UI runtime", +); +assert( + index.includes("__nativeScriptNativeRequestAnimationFrame"), + "frame loop should retain the native RAF host function before overriding it", +); +assert( + index.includes("globalObject.__nativeRequestAnimationFrame = () => undefined"), + "frame loop should stop react-native-worklets' perpetual startup frame pump", +); +assert( + index.includes("scheduleNativeFlush();"), + "requestAnimationFrame should schedule native frames only when callbacks exist", +); +assert( + index.includes("NSTimerClass.timerWithTimeIntervalRepeatsBlock"), + "UI runtime timers should use native NSTimer instead of RAF polling", +); +assert( + index.includes("NSRunLoopClass.mainRunLoop.addTimerForMode"), + "native UI timers should run in common run-loop modes", +); +assert( + index.includes("function runtimeTimerInvoker"), + "native UI timers should mark callbacks for the owning Worklets runtime", +); +assert( + index.includes("Math.max(0.001, numericDelay / 1000)"), + "native UI timers should treat zero-delay JS timers as next-run-loop timers", +); +assert( + index.includes('value: "runtime"'), + "native UI timer callbacks should use the generic runtime callback policy", +); +assert( + index.includes("globalObject.setTimeout = ("), + "Worklets UI runtime setTimeout should be overridden by NativeScript", +); +assert( + index.includes("globalObject.setInterval = ("), + "Worklets UI runtime setInterval should be overridden by NativeScript", +); +assert( + index.includes(".runOnUIAsync(installIdleAwareWorkletsFrameLoop)"), + "NativeScript worklet install should patch the UI runtime frame loop", +); + +console.log("worklets frame loop tests passed"); diff --git a/packages/visionos/package.json b/packages/visionos/package.json index 4c0fc9ae9..8279ffe39 100644 --- a/packages/visionos/package.json +++ b/packages/visionos/package.json @@ -9,7 +9,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NativeScript/ios", + "url": "git+https://github.com/NativeScript/runtimes.git", "directory": "packages/visionos" }, "author": { diff --git a/platforms/.gitignore b/platforms/.gitignore new file mode 100644 index 000000000..46eaaaf1d --- /dev/null +++ b/platforms/.gitignore @@ -0,0 +1,4 @@ + +# Copied out of build-tools/typings by copyTypings. +android.d.ts +android-declarations.d.ts diff --git a/platforms/android/.gitignore b/platforms/android/.gitignore new file mode 100644 index 000000000..2fd027e79 --- /dev/null +++ b/platforms/android/.gitignore @@ -0,0 +1,22 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +dist +dist_v8 +dist_quickjs +dist_hermes +dist_jsc +dist_* +binary_cache \ No newline at end of file diff --git a/platforms/android/CHANGELOG.md b/platforms/android/CHANGELOG.md new file mode 100644 index 000000000..d8f549b9e --- /dev/null +++ b/platforms/android/CHANGELOG.md @@ -0,0 +1,1189 @@ +## [8.8.5](https://github.com/NativeScript/android/compare/v8.8.4...v8.8.5) (2024-09-30) + + +### Bug Fixes + +* prevent metadata offset overflow into array space and convert shorts to uints before addition ([9cfc349](https://github.com/NativeScript/android/commit/9cfc3493017243948b043a51f68b7c7bcab1e6b9)) + + + +## [8.8.4](https://github.com/NativeScript/android/compare/v8.8.3...v8.8.4) (2024-09-06) + + +### Bug Fixes + +* ensure same mtime for js and code cache to prevent loading old code caches ([#1822](https://github.com/NativeScript/android/issues/1822)) ([3d6e101](https://github.com/NativeScript/android/commit/3d6e10115227ad556e5bbe1764217716ab5bdac7)) + + + +## [8.8.3](https://github.com/NativeScript/android/compare/v8.8.2...v8.8.3) (2024-09-02) + + +### Bug Fixes + +* generate correct metadata when overflowing signed short values ([#1821](https://github.com/NativeScript/android/issues/1821)) ([c9fac4b](https://github.com/NativeScript/android/commit/c9fac4b19a952d4df651d3d6a8b0fa9c50f7c7db)) + + + +## [8.8.2](https://github.com/NativeScript/android/compare/v8.8.1...v8.8.2) (2024-07-22) + + +### Bug Fixes + +* config with multiple bundle ids ([#1816](https://github.com/NativeScript/android/issues/1816)) ([cdcfee2](https://github.com/NativeScript/android/commit/cdcfee266617472ac7f3ac59742b858ad093e46b)) + + + +## [8.8.1](https://github.com/NativeScript/android/compare/v8.8.0...v8.8.1) (2024-07-10) + + +### Features + +* Ada 2.9 ([#1814](https://github.com/NativeScript/android/issues/1814)) ([91accf9](https://github.com/NativeScript/android/commit/91accf9be1caf9ad2accb80bf9aca18efe4dd75a)) + + + +# [8.8.0](https://github.com/NativeScript/android/compare/v8.7.0...v8.8.0) (2024-07-09) + + +### Bug Fixes + +* correctly load ts_helpers.js in workers ([#1798](https://github.com/NativeScript/android/issues/1798)) ([31f8501](https://github.com/NativeScript/android/commit/31f8501bb902815cfed8e1cd123fe8b6de2cb757)) + + +### Features + +* Kotlin 2 + Gradle 8+ ([#1812](https://github.com/NativeScript/android/issues/1812)) ([d4b7164](https://github.com/NativeScript/android/commit/d4b716427934ebb4387a04842561d5b5d0e1fa3d)) + + + +# [8.7.0](https://github.com/NativeScript/android/compare/v8.7.0-rc.3...v8.7.0) (2024-04-08) + + + +# [8.7.0-rc.3](https://github.com/NativeScript/android/compare/v8.6.2...v8.7.0-rc.3) (2024-04-08) + + +### Bug Fixes + +* devtools namespace usage ([#1810](https://github.com/NativeScript/android/issues/1810)) ([5aaac57](https://github.com/NativeScript/android/commit/5aaac5788ff9abf1c043817e87c8e03eb61907c0)) +* dts-generator.jar path ([1120a32](https://github.com/NativeScript/android/commit/1120a3258d53f83b7b4dfe7e505234e2b0d6cd2b)) +* inspector and globals ([#1811](https://github.com/NativeScript/android/issues/1811)) ([79ebd18](https://github.com/NativeScript/android/commit/79ebd18f308cd86fa98784f14b5c3f5ac39d8c5f)) + + +### Features + +* bump ndk to r23c ([#1803](https://github.com/NativeScript/android/issues/1803)) ([3894959](https://github.com/NativeScript/android/commit/3894959e0b4fe31f61cfd9fa70d5e2b04a0f36ac)) +* devtools element/network inspectors ([#1808](https://github.com/NativeScript/android/issues/1808)) ([1470796](https://github.com/NativeScript/android/commit/1470796dc506f0d01e94fe117119dc217ff8c909)) +* migrate to faster maps and use runtime context ([#1793](https://github.com/NativeScript/android/issues/1793)) ([b248dc4](https://github.com/NativeScript/android/commit/b248dc4038d0c1a6af420447c713bc968431f97e)) +* update libzip to 1.10.1 ([#1805](https://github.com/NativeScript/android/issues/1805)) ([ee2e3e0](https://github.com/NativeScript/android/commit/ee2e3e0b87caf3cff4784f1464dd51b2923c6861)) +* use node module bindings like the iOS runtime ([#1795](https://github.com/NativeScript/android/issues/1795)) ([643958b](https://github.com/NativeScript/android/commit/643958b6a4c3698567edde3fd03052873b2644dc)) +* **WinterCG:** URL & URLSearchParams ([#1801](https://github.com/NativeScript/android/issues/1801)) ([4f3a0d7](https://github.com/NativeScript/android/commit/4f3a0d7f2de5f899779bd0fe9081390e6c4d24b2)) + + +### Reverts + +* Version.h changes ([9faa25d](https://github.com/NativeScript/android/commit/9faa25dda197d3da4f694ea59208309bb02e529c)) + + + +## [8.6.2](https://github.com/NativeScript/android/compare/v8.6.1...v8.6.2) (2023-10-10) + + + +## [8.6.1](https://github.com/NativeScript/android/compare/v8.6.0...v8.6.1) (2023-10-10) + + +### Bug Fixes + +* copy drawables ([4ff92cb](https://github.com/NativeScript/android/commit/4ff92cb32a954be4c3d32c302e301cef0a4b72a6)) + + + +# [8.6.0](https://github.com/NativeScript/android/compare/v8.5.3...v8.6.0) (2023-10-06) + + +### Bug Fixes + +* make jar files readonly prior to loading ([#1790](https://github.com/NativeScript/android/issues/1790)) ([2bcdaf0](https://github.com/NativeScript/android/commit/2bcdaf01fb850db4a982c22c2d792f9493a2a7fa)) +* only use project jar files if they are linked ([d23ca94](https://github.com/NativeScript/android/commit/d23ca94ba7c660b26224c57ba6f22085aa99f95c)) +* revert namespace change as to not break existing projects ([8b7b59d](https://github.com/NativeScript/android/commit/8b7b59d23d926b696bde3c1031cf3a842a24133d)) + + +### Features + +* improved error activity ui ([#1776](https://github.com/NativeScript/android/issues/1776)) ([ee3e354](https://github.com/NativeScript/android/commit/ee3e354f1bec89268daf93086aa6dd24898677b9)) +* upgrade client gradle version ([c778c0d](https://github.com/NativeScript/android/commit/c778c0d238c4ba44390f786ba06ab8e51ffb2c97)) + +## [8.5.4](https://github.com/NativeScript/android/compare/v8.5.3...v8.5.4) (2023-09-27) + + +### Bug Fixes + +* make jar files readonly prior to loading ([#1790](https://github.com/NativeScript/android/issues/1790)) ([14a932a](https://github.com/NativeScript/android/commit/14a932ad2d62c94f2f4e139125835da760dcdd58)) + + +## [8.5.3](https://github.com/NativeScript/android/compare/v8.5.2...v8.5.3) (2023-09-22) + + +### Bug Fixes + +* resolve __postFrameCallback crash on re-scheduling ([6a533ce](https://github.com/NativeScript/android/commit/6a533ce58b22b163d888c51f203be2ef3aa98347)) + + + +## [8.5.2](https://github.com/NativeScript/android/compare/v8.5.1...v8.5.2) (2023-08-31) + + +### Bug Fixes + +* __runOnMainThread erase iterator before it can be invalidated ([e811484](https://github.com/NativeScript/android/commit/e81148409046957fceb07b14394baa5bc055286d)) +* pull js stack trace from wrapped NativeScriptExceptions ([#1774](https://github.com/NativeScript/android/issues/1774)) ([52b7fa2](https://github.com/NativeScript/android/commit/52b7fa242cb4582701dcebf97aa0f6e0400bcb1a)) + + + +## [8.5.1](https://github.com/NativeScript/android/compare/v8.5.0...v8.5.1) (2023-07-24) + + +### Bug Fixes + +* Avoid setting the same property on an ObjectTemplate twice ([9e610c8](https://github.com/NativeScript/android/commit/9e610c8b61a721c04b0a7b8f5167f5b070c5419f)) +* Don't access `super` property on implementation object ([d8b8bc0](https://github.com/NativeScript/android/commit/d8b8bc02cf51de021855bb6a39ccd39ce1287304)) +* Don't iterate properties in GetImplementedInterfaces ([9dfae65](https://github.com/NativeScript/android/commit/9dfae6589caad31ee50a5946e7369f12505cd955)) +* intermediary fix for https://github.com/NativeScript/android/pull/1771 ([32c7abb](https://github.com/NativeScript/android/commit/32c7abb3418b20d1ac2d80c18c5e25a402937628)) +* Leave context after Runtime::PrepareV8Runtime() ([cd1d285](https://github.com/NativeScript/android/commit/cd1d2850e5c28d89339a90d783db1f1623957be7)) +* memory leak on accessing static interface methods ([88ce2d8](https://github.com/NativeScript/android/commit/88ce2d8b2c2ccf5568d1b95f0bf9df47179f658a)) +* memory leak on saving code cache ([6d416a1](https://github.com/NativeScript/android/commit/6d416a11e5b304d8ac2bfb4c48931a22e353df72)) +* Update common-runtime-tests-app ([c8db3ca](https://github.com/NativeScript/android/commit/c8db3cab6b3e3ddd41ab94adb8edac46a704cbe9)) +* update legacy android version in package.json ([#1744](https://github.com/NativeScript/android/issues/1744)) ([b4ad8e5](https://github.com/NativeScript/android/commit/b4ad8e541ff211055dc5197c7e0b350b6666771f)) +* Use Isolate::TryGetCurrent() ([afe026a](https://github.com/NativeScript/android/commit/afe026a29f3e9a1a5e533e4c66fd013f5f7f163c)) + + +* Remove weak callback from __postFrameCallback cache (#1755) ([ff1b979](https://github.com/NativeScript/android/commit/ff1b97975c7fdcd6b2acfb6153ba635d4f420eff)), closes [#1755](https://github.com/NativeScript/android/issues/1755) + + +### Features + +* add support for kotlin 1.8 ([#1765](https://github.com/NativeScript/android/issues/1765)) ([1a928e4](https://github.com/NativeScript/android/commit/1a928e47e733b1d4ba2bb83b4f541e1cac835155)) + + +### Performance Improvements + +* avoid unnecessary string copying when calling static properties of interfaces ([8b53d02](https://github.com/NativeScript/android/commit/8b53d022315d4a7fa9d617f431e0364030942d24)) + + +### BREAKING CHANGES + +* __startNDKProfiler() and __stopNDKProfiler() global +bindings no longer available. + +The NDK profiler was not functional, since nothing in the build process +defined the NDK_PROFILER_ENABLED preprocessor symbol. The start and stop +functions were already no-ops. + +* chore: Remove outdated comment + +RunMicrotasks no longer exists, it's already been replaced in the code +with PerformMicrotaskCheckpoint. + +* chore: Use unique_ptr for NewBackingStore in byte buffers + +V8 doc: https://docs.google.com/document/d/1sTc_jRL87Fu175Holm5SV0kajkseGl2r8ifGY76G35k/edit + +The V8 usage examples show unique_ptr here; it probably doesn't matter +much, but we don't need the backing store after creating the ArrayBuffer, +and I believe shared_ptr is slightly more overhead than unique_ptr. + +For convenience, replace the manual empty deleter for direct buffers with +v8::BackingStore::EmptyDeleter. + +* chore: Remove weak finalizer callback from __postFrameCallback() + +Weak finalizer callbacks are going away in V8 11.x, so we have to remove +this one. Luckily, a finalizer callback is not necessary - it's never +needed to prevent the frame callback from being collected. + +However, a weak callback is not necessary in the first place. We can just +clean up the cache entry after the callback is executed, since it is only +executed once. + +Note that the call to erase() destructs the FrameCallbackCacheEntry +instance, making `entry` a dangling pointer, so don't use it after the +erase(). There's probably a safer way to do this, although the way that +first occurred to me (pass the key to the callback instead of the entry, +and then use std::unordered_map::extract()) is not available on +robin_hood::unordered_map. + +* fix: Make sure frame callbacks are not ignored + +There was a bug where __postFrameCallback() would not always cause the +callback to be called. Without initializing `removed`, sometimes it would +have a nonzero value, so the callback would be ignored. + +* chore: Clear callback caches' persistent handles in destructor + +Clearing the persistent handles in the destructor makes it a bit easier to +deal with the cache entry's lifetime: they are cleared whenever the entry +is removed from the cache. + +We do this for both the main thread callback cache and the frame callback +cache. + +Adding a destructor makes the cache entries non-movable. But the only +place where they were moved was when inserting them into the cache anyway. +We can use C++17's try_emplace() method to insert them without having to +move them. + +* chore: Construct FrameCallbackCacheEntry with ID + +This avoids the situation of forgetting to add an ID to the cache entry. + +* chore: Improve usage of unordered_map APIs in CallbackHandlers + +This fixes a few places where we can avoid double lookups: + +- In RunOnMainThreadFdCallback, we already have a valid iterator, so no + need to look up the same key again in RemoveKey (this is the only usage + of RemoveKey, so we can remove it.) (Additionally, we probably want to + remove the cache entry before throwing a NativeScript exception.) + +- In PostFrameCallback and RemoveFrameCallback, we should not do + contains() immediately followed by find(). + +* chore: Fix runtime typo + +* chore: Ignore main thread and frame callback return values + +We don't do anything with the return value from these callbacks, so it's +OK to ignore them and not convert them to a local handle. + + + +# [8.5.0](https://github.com/NativeScript/android/compare/v8.4.0...v8.5.0) (2023-06-27) + + +### Bug Fixes + +* add semicolon after console type ([32259a9](https://github.com/NativeScript/android/commit/32259a90607bdd282d855c3701c7b7c2b203439d)) +* always log console messages and uncomment live sync ([c0f5514](https://github.com/NativeScript/android/commit/c0f5514686decc111d8b2e2f93c434141788f298)) +* Compile as C++17 ([221a9c2](https://github.com/NativeScript/android/commit/221a9c2c3747c2698238841ea56cf4d837206c20)) +* Correctly initialize context in inspector client init() ([0bc0480](https://github.com/NativeScript/android/commit/0bc0480035d01c681765a01b794b48db003be050)) +* drain microtasks after devtools message ([4834a2b](https://github.com/NativeScript/android/commit/4834a2b8e3d979323753d1da265ad1faaabbd655)) +* Implement console.log inspector with Runtime protocol ([2c4337b](https://github.com/NativeScript/android/commit/2c4337bf5b7c2c00bbcd5b34da1e8c08478fd37a)) +* memcpy array data for non-direct java bytebuffers ([#1740](https://github.com/NativeScript/android/issues/1740)) ([1c0214a](https://github.com/NativeScript/android/commit/1c0214a4785d425dc83f9612169fad7f70a28d41)) +* multi threading on MethodResolver ([bc8bc52](https://github.com/NativeScript/android/commit/bc8bc5253ff8abecf97bf98462d21fda15757a6a)) +* possible infinite loop and memory leak in metadata reader ([#1749](https://github.com/NativeScript/android/issues/1749)) ([c2c8aa8](https://github.com/NativeScript/android/commit/c2c8aa8a5f81c10aee293e14af797e5d1e3fbb4c)) +* Re-enable inspector code ([f357ce6](https://github.com/NativeScript/android/commit/f357ce6b9836be109ea5717ba0c09ee01a3f9876)) +* refactor console.log implementation a bit ([#1741](https://github.com/NativeScript/android/issues/1741)) ([d3c52cb](https://github.com/NativeScript/android/commit/d3c52cbaae8620d1ba1ed43d61da6df6f9df0f9c)) +* remove free of non-owned jni buffer ([81806b3](https://github.com/NativeScript/android/commit/81806b399f42a981a0cf54ab8a4be5712333e938)) +* Remove use of DISALLOW_COPY_AND_ASSIGN macro in inspector ([6da1a6b](https://github.com/NativeScript/android/commit/6da1a6bba0d9808568e8d6ae68fb0c71c6271ff1)) +* Remove use of V8InspectorPlatform ([1f2b202](https://github.com/NativeScript/android/commit/1f2b202263207de2045bafad5afedf786da788e1)) +* Restore DOM and Network callback handlers ([35689a7](https://github.com/NativeScript/android/commit/35689a7462ea86a760705c949dea6f4de4192cf8)) +* uncomment abifilters ([f5a5434](https://github.com/NativeScript/android/commit/f5a5434f41d764352de89133d2068b109bd3a54c)) +* update js arg conversions ([0640fce](https://github.com/NativeScript/android/commit/0640fcee3bd5429a637f3811fd7f8b4dc8ddf508)) +* use min sdk 17 ([2f2358c](https://github.com/NativeScript/android/commit/2f2358c0daf4c476110367432889740c18b49694)) + + +### Features + +* add console message type prefixes ([9a10e2b](https://github.com/NativeScript/android/commit/9a10e2b89410c6757bceba705a902060f37d97a1)) +* initial support for __inspectorSendEvent ([233b7c3](https://github.com/NativeScript/android/commit/233b7c39c2910e01adc261c4d0d3ddc1d92ef9d6)) +* native timer polyfills ([#1733](https://github.com/NativeScript/android/issues/1733)) ([3415e5c](https://github.com/NativeScript/android/commit/3415e5c515b3d73360a87f4f7aec03c44bd2c63c)) +* remove old WeakRef polyfill ([97d7465](https://github.com/NativeScript/android/commit/97d7465ca46df7240c4887fffb3167e469179a7a)) +* update all archs to use v8 10.3.22 ([a68b057](https://github.com/NativeScript/android/commit/a68b057da208338a83a620e64c0f4fecd90cff59)) +* use v8 static ([ee8a521](https://github.com/NativeScript/android/commit/ee8a5213a54d40046993ee61ac652842ccacfbe4)) + + +### Performance Improvements + +* refactor arrays marshaling ([84e7ddb](https://github.com/NativeScript/android/commit/84e7ddbe7fd503a3a5f11eed55246138068ca28f)) +* remove instantiations and cache runtime in isolate ([f23c1bb](https://github.com/NativeScript/android/commit/f23c1bb5b61c2fcaf7c6961450055bfd4fa4d385)) +* update old args converter with array marshaling optimizations and use this converter again tempporarily ([b5218c9](https://github.com/NativeScript/android/commit/b5218c9f5dce1e7c603887f4c9b65f3bd5284aa8)) +* use vector pointers for passing parsed method signatures ([aa623b7](https://github.com/NativeScript/android/commit/aa623b795ef38fb023cdef60d15641b8d8bf0338)) + + + +# [8.4.0](https://github.com/NativeScript/android/compare/v8.3.0...v8.4.0) (2022-11-30) + + +### Bug Fixes + +* don't assert for sub projects ([b5efdbc](https://github.com/NativeScript/android/commit/b5efdbc5ea11a7f5c73703ddae7485c4782bd8d7)) +* handle missing child classes when querying native classes ([#1718](https://github.com/NativeScript/android/issues/1718)) ([c238166](https://github.com/NativeScript/android/commit/c238166af3ce5205b65f2d79a23cfb963c3d60f9)) +* JvmField annotated fields ([#1726](https://github.com/NativeScript/android/issues/1726)) ([59da1cb](https://github.com/NativeScript/android/commit/59da1cbc8db888833fc24ea207b7d2f9195985b8)), closes [#1604](https://github.com/NativeScript/android/issues/1604) +* JvmName annotation & Kotlin building from App Resources ([1ba30be](https://github.com/NativeScript/android/commit/1ba30becfed7ba775412d722912a5e4db8c51e51)), closes [#1682](https://github.com/NativeScript/android/issues/1682) +* null data ([3b4a56d](https://github.com/NativeScript/android/commit/3b4a56df86f36a7738cc0089195d20239ce67273)) +* RunOnMainThreadCallback & PostFrameCallback ([#1721](https://github.com/NativeScript/android/issues/1721)) ([1ccd033](https://github.com/NativeScript/android/commit/1ccd033194d498b7614f16cc5d9cdcfb6a414f44)) +* typed array handling ([#1738](https://github.com/NativeScript/android/issues/1738)) ([00509ee](https://github.com/NativeScript/android/commit/00509eec6b929353a313530932f00757a3ccadb6)) +* typedarray & raf ([#1729](https://github.com/NativeScript/android/issues/1729)) ([796b9b1](https://github.com/NativeScript/android/commit/796b9b1fbba1e4cc81cb2098035ea38b3620a983)) + + +### Features + +* add class name to native objects ([#1723](https://github.com/NativeScript/android/issues/1723)) ([0c601ae](https://github.com/NativeScript/android/commit/0c601ae4680dcb6bea95f3292f6b0c3b9c1baeb1)) +* settings.gradle plugin ([#1731](https://github.com/NativeScript/android/issues/1731)) ([1759ecd](https://github.com/NativeScript/android/commit/1759ecd4071ccbeb60ea129af46e7f019c7523fe)) +* upgrade libzip to 1.9.2 ([#1724](https://github.com/NativeScript/android/issues/1724)) ([1dfd38f](https://github.com/NativeScript/android/commit/1dfd38ff0ea3a98b6d35f6f7c53f10ac1a788770)) + + + +# [8.3.0](https://github.com/NativeScript/android/compare/v8.2.4...v8.3.0) (2022-07-14) + + +### Bug Fixes + +* apply before-plugins before checking versions ([0df7362](https://github.com/NativeScript/android/commit/0df73625b72adb2fd654091f74e527dd61e3029b)) +* Set package version before preReleaseBuild ([12acd7f](https://github.com/NativeScript/android/commit/12acd7f3c1a265c0d8cddef8cb936825d2af7877)) + + +### Features + +* runOnMain, postFrameCallback & removeFrameCallback ([#1713](https://github.com/NativeScript/android/issues/1713)) ([bdd0313](https://github.com/NativeScript/android/commit/bdd031317c15d5e7eaa0dfeab2ef599b4eeddc4c)) + + + +## [8.2.3](https://github.com/NativeScript/android/compare/v8.2.2...v8.2.3) (2022-06-01) + + +### Bug Fixes + +* direct boot should not crash/ANR ([d2b18d7](https://github.com/NativeScript/android/commit/d2b18d7a8e23af6b27a8e2281e43bf72d715af66)) + + + +## [8.2.2](https://github.com/NativeScript/android/compare/v8.2.1...v8.2.2) (2022-03-09) + + +### Bug Fixes + +* app freezing on splash screen ([a61d3c1](https://github.com/NativeScript/android/commit/a61d3c1932ca11b67c88bc1b9a0eb97c9847ee19)) + + + +## [8.2.1](https://github.com/NativeScript/android/compare/v8.2.0...v8.2.1) (2022-03-08) + + +### Bug Fixes + +* add back gradle key in package json for the time being ([af925d5](https://github.com/NativeScript/android/commit/af925d50990820c9ca92a9a1a869518dbfd43fad)) + + + +# [8.2.0](https://github.com/NativeScript/android/compare/v7.0.1...v8.2.0) (2022-03-08) + + +### Bug Fixes + +* **chrome-devtools:** Elements tab ([e935a61](https://github.com/NativeScript/android/commit/e935a61de5043ae2febd76b34963bcaba7049791)), closes [#1641](https://github.com/NativeScript/android/issues/1641) [#1640](https://github.com/NativeScript/android/issues/1640) +* ensure buildMetadata is done before R8 ([01ad92e](https://github.com/NativeScript/android/commit/01ad92e53d6fcd6448b89004e1d49a66dd2b3254)) +* js -> buffer handling ([b49801c](https://github.com/NativeScript/android/commit/b49801cb7df3acd65ecb0a7e688afe2a25be908e)) +* load `before-plugins.gradle` in build.gradle to allow to override `androidBuildToolsVersion` and other vars ([d926dd4](https://github.com/NativeScript/android/commit/d926dd4340539ee9223ab9090ab20dd3c253af2a)) +* loop ([95ba9f5](https://github.com/NativeScript/android/commit/95ba9f5bd382cbffef2469fca632b69729c5837c)) +* material lib as debugImplementation ([c6946fe](https://github.com/NativeScript/android/commit/c6946fe6633ebb851fec35c19c2a64632628396e)) +* pending intent api31 flags ([d8781fd](https://github.com/NativeScript/android/commit/d8781fdcbffcf44d49d2df1055421eeecf8316ea)) +* update to jsparser ([ea1517c](https://github.com/NativeScript/android/commit/ea1517cbcd3b62037440c42fdcedf54eb2bee035)) +* **workers:** invalidate cached isolates on dispose ([#1704](https://github.com/NativeScript/android/issues/1704)) ([67e9daf](https://github.com/NativeScript/android/commit/67e9daf709722b608ef1fb6110d5b708f1819c41)) + + +### Features + +* commonGradleProperties ([77585ad](https://github.com/NativeScript/android/commit/77585ad58cceba74d8df69e5838b9f80dc947b49)) +* expose `PerformMicrotaskCheckpoint` ([4b58570](https://github.com/NativeScript/android/commit/4b58570fa3c43fc0a0ab73790030a414412f7c19)) +* log used androidX lib versions ([#1651](https://github.com/NativeScript/android/issues/1651)) ([528c46f](https://github.com/NativeScript/android/commit/528c46f99dd00859ba38c2671b7a5372d34f6a63)) +* make console.time use 3 decimals ([7d9f90b](https://github.com/NativeScript/android/commit/7d9f90bd6c45c9f1fd94904e6b17dd028fff1a11)) +* support passing typedArrays as nio buffers ([38230e5](https://github.com/NativeScript/android/commit/38230e5b282cf5ad04d3d655cf0671bc7e576a19)) +* working gradle7 ([501f884](https://github.com/NativeScript/android/commit/501f884120d6a884a4e2bb6d4d60312aa7efacff)) + + + +7.0.1 +== + +- [Gradle not respecting kotlinVersion (#1642)](https://github.com/NativeScript/android-runtime/pull/1642) +- [--debug-brk crashing android runtime (#1641)](https://github.com/NativeScript/android-runtime/pull/1641) + + +7.0.0 +== + +## Bug Fixes + +- [appPath & appResourcePath now passed in (#1635)](https://github.com/NativeScript/android-runtime/pull/1635) +- [Kotlin extension overwriting metadata & metadata duplication (#1633)](https://github.com/NativeScript/android-runtime/pull/1633) +- [Update gradle & kotlin dependancies (#1629)](https://github.com/NativeScript/android-runtime/pull/1629) + + +6.5.3 +== +- [Java 8 compatibility when build with Java 9 (#1625)](https://github.com/NativeScript/android-runtime/pull/1625) +- [metadata crash on startup with Local Notification plugin (#1621)](https://github.com/NativeScript/android-runtime/pull/1621) + + +6.5.1 +== + +## Bug Fixes + +- [Cleanup of warnings / possible not checked nullpointers (#1610)](https://github.com/NativeScript/android-runtime/pull/1610) + +- [[metadata] not rebuilding on json api usage change (#1589)](https://github.com/NativeScript/android-runtime/issues/1589) + + +6.5.0 +== + +### No changes + +6.4.1 +== + +## What's New + +- [[FR] Customize metadata generation to avoid adding unnecessary unused JS wrappers (#1485)](https://github.com/NativeScript/android-runtime/issues/1485) +- [Provide verbose metadata filtering output (#1583)](https://github.com/NativeScript/android-runtime/issues/1583) +- [Upgrade v8 to 8.0.426.16 (#1579)](https://github.com/NativeScript/android-runtime/issues/1579) +- [Upgrade android gradle plugin to the latest 3.5.3 version (#1564)](https://github.com/NativeScript/android-runtime/issues/1564) + +## Bug Fixes + +- [Using Android KTX is causing NativeScript app to crash (#1571)](https://github.com/NativeScript/android-runtime/issues/1571) +- [SBG generating conflicting class definitions (#1569)](https://github.com/NativeScript/android-runtime/issues/1569) +- [WebAssembly won't initialize without the debugger (#1558)](https://github.com/NativeScript/android-runtime/issues/1558) +- [Clean up allocations from finalized object links (#1566)](https://github.com/NativeScript/android-runtime/pull/1566) +- [Fix Kotlin Object issue (#1562)](https://github.com/NativeScript/android-runtime/pull/1562) + +6.3.1 +== + +## Bug Fixes + +- [Kotlin enum values not visible at runtime (#1560)](https://github.com/NativeScript/android-runtime/issues/1560) +- [Code cache breaks HMR (#1554)](https://github.com/NativeScript/android-runtime/issues/1554) +- [Worker memory leak in android (#1550)](https://github.com/NativeScript/android-runtime/issues/1550) + +6.3.0 +== + +## What's New + +- [Upgrade v8 to 7.8.279.19 (#1526)](https://github.com/NativeScript/android-runtime/issues/1526) +- [Restrict Kotlin internal modifier from metadata (#1551)](https://github.com/NativeScript/android-runtime/pull/1551) +- [Restrict Kotlin properties with non-public type (#1552)](https://github.com/NativeScript/android-runtime/issues/1552) + +## Bug Fixes + +- [Android 9: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR) in libNativeScript.so (#1413)](https://github.com/NativeScript/android-runtime/issues/1413) + +6.2.0 +== + +## What's New + - [Add Kotlin extension functions support (#1515)](https://github.com/NativeScript/android-runtime/issues/1515) + - [update dts generator to the latest version (#1506)](https://github.com/NativeScript/android-runtime/pull/1506) + - [Unify exceptions information in try/catch and __onUncaughtError (#1445)](https://github.com/NativeScript/android-runtime/issues/1445) + - [Interop between JS Objects and JSONObjects (#1500)](https://github.com/NativeScript/android-runtime/issues/1500) + - [Collect build analytics (#1501)](https://github.com/NativeScript/android-runtime/issues/1501) + - [expose the NDK revision used to build the V8 and the Runtime (#1498)](https://github.com/NativeScript/android-runtime/pull/1498) + - [Upgrade android gradle plugin to the latest 3.5.1 version (#1502)](https://github.com/NativeScript/android-runtime/issues/1502) + - [support snapshot libs out of the box (#1496)](https://github.com/NativeScript/android-runtime/pull/1496) + - [Upgrade v8 to 7.7.299.11 (#1478)](https://github.com/NativeScript/android-runtime/issues/1478) + +## Bug Fixes + - [Console.log in worker makes chrome debugging crash (#1511)](https://github.com/NativeScript/android-runtime/issues/1511) + - [fix searching for merge assets folder on windows (#1503)](https://github.com/NativeScript/android-runtime/pull/1503) + - [Background job with WorkManager (#1488)](https://github.com/NativeScript/android-runtime/issues/1488) + - [Deprecated API used in the ErrorActivity is crashing the app (when latest support library is used) (#1494)](https://github.com/NativeScript/android-runtime/issues/1494) + +6.1.2 +== + +## Bug Fixes + +- [UI freezes with tns-android 6.1.0 (Android only) (#1479)](https://github.com/NativeScript/android-runtime/issues/1479) + +6.1.1 +== + +## Bug Fixes + +- [When using kotlin sometimes the metadata is not existing in the result apk (#1476)](https://github.com/NativeScript/android-runtime/issues/1476) + + +6.1.0 +== + +## What's New + - [Runtime Binding Generator depends on deprecated APIs #1441)](https://github.com/NativeScript/android-runtime/issues/1441) + - [The runtime depends on accessing a hidden value field (#1458)](https://github.com/NativeScript/android-runtime/issues/1458) + - [Upgrade v8 to 7.6.303.28 (#1439)](https://github.com/NativeScript/android-runtime/issues/1439) + - [Unify JS stack trace when exception is thrown #1443](https://github.com/NativeScript/android-runtime/issues/1443) + - [Update androidSdk, targetSdk and build tools to 29 (#1452)](https://github.com/NativeScript/android-runtime/issues/1452) + - [Upgrade android gradle plugin to the latest 3.5.0 version (#1456)](https://github.com/NativeScript/android-runtime/issues/1456) + - [Add initial Kotlin support (#1459)](https://github.com/NativeScript/android-runtime/issues/1459) + - [Add support for user defined gradle.properties (#1463)](https://github.com/NativeScript/android-runtime/issues/1463) + + +## Bug Fixes + + - [SIGSEGV in libNativeScript.so on callback from java with console.log when displaying an object. #1366](https://github.com/NativeScript/android-runtime/issues/1366) + +6.0.2 +== + +## What's New + + - [Include x86_64 architecture](https://github.com/NativeScript/android-runtime/issues/1419) + +6.0.1 +== + +## Bug Fixes + +- [Arabic and Kurdish characters show as gibberish in console.log() (#1302)](https://github.com/NativeScript/android-runtime/issues/1302) +- [IntentService extending fails at runtime when service is started (#1426)](https://github.com/NativeScript/android-runtime/issues/1426) +- [SBG may fail when parsing big JS files (#1430)](https://github.com/NativeScript/android-runtime/issues/1430) +- [Upgrade android gradle plugin to the latest 3.4.2 version (#1425)](https://github.com/NativeScript/android-runtime/issues/1425) + + +6.0.0 +== + +## Breaking Changes + +- Exception information in onDiscarderError and onUnhandledError is changed so that `message` contains the exception message and `stackTrace` contains only the stackTrace. In the previous implementation `stackTrace` contained some additional details (including the exception message) and the `message` was something like: + + ``` + The application crashed because of an uncaught exception. You can look at "stackTrace" or "nativeException" for more detailed information about the exception. + ``` + +- [The built-in `JSON.stringify` method is used for cross workers communication](https://github.com/NativeScript/android-runtime/issues/1408). Circular object references are no longer supported and attempting to send such object will throw an exception. + + +## What's New + +- [Use the built-in JSON.stringify for cross workers communication (#1411)](https://github.com/NativeScript/android-runtime/pull/1411) +- [Enable AndroidX and Jetifier(#1370)](https://github.com/NativeScript/android-runtime/issues/1370) +- [Upgrade v8 to 7.5.288.22(#1387)](https://github.com/NativeScript/android-runtime/issues/1387) +- [Upgrade android gradle plugin to the latest 3.4.1 version(#1390)](https://github.com/NativeScript/android-runtime/issues/1390) +- [Remove printStackTrace method calls from the source code(#1359)](https://github.com/NativeScript/android-runtime/issues/1359) + +## Bug Fixes + +- [Improve package.json parsing in SBG (#1407)](https://github.com/NativeScript/android-runtime/pull/1407) +- [Improve error message in SBG class parsing (#1401)](https://github.com/NativeScript/android-runtime/pull/1401) +- [java.lang.NullPointerException in Metadata generator(#13795)](https://github.com/NativeScript/android-runtime/issues/1379) +- [Buffer() is deprecated(#1392)](https://github.com/NativeScript/android-runtime/pull/1392) +- [Warnings when building android(#1396)](https://github.com/NativeScript/android-runtime/issues/1396) +- [No JS stack on discardedError and unhandledError(#1354)](https://github.com/NativeScript/android-runtime/issues/1354) + + +5.4.0 +== + +## What's New + +- [Upgrade v8 to 7.4.288.25(#1356)](https://github.com/NativeScript/android-runtime/issues/1356) +- [Upgrade the android gradle plugin to the latest 3.4.0 version(#1360)](https://github.com/NativeScript/android-runtime/issues/1360) +- [Enable V8 symbols using from application's package.json file(#1368)](https://github.com/NativeScript/android-runtime/issues/1368) + +## Bug Fixes + +- [HashMaps improvements - fix for "Attempt to use cleared object reference"(#1345)](https://github.com/NativeScript/android-runtime/issues/1345) +- [Unable to start Service when the application process is killed by the OS(#1347)](https://github.com/NativeScript/android-runtime/pull/1347) +- [gradlew not compatible with sh(#1349)](https://github.com/NativeScript/android-runtime/issues/1349) +- [Memory leak in global.postMessage(#1358)](https://github.com/NativeScript/android-runtime/issues/1358) +- [memory leak java <-> javascript when java returns [] array(#1363)](https://github.com/NativeScript/android-runtime/issues/1363) +- [Bug when reifying some generic classes in the SBG(#1372)](https://github.com/NativeScript/android-runtime/issues/1372) +- [Extending an Android service fails if `onCreate` hasn't been overridden()](https://github.com/NativeScript/android-runtime/issues/1373) + +5.3.1 +== +## Bug Fixes + - [5.3 build failing (Android) (#1329)](https://github.com/NativeScript/android-runtime/issues/1329) + - [fix(build): Correct dependencies of `cleanupAllJars` gradle task (#1338)](https://github.com/NativeScript/android-runtime/pull/1338) + +5.3.0 +== + +## What's New + - [Upgrade v8 to 7.3.492.25(#1301)](https://github.com/NativeScript/android-runtime/issues/1301) + - [Upgrade the android gradle plugin to the latest 3.3.2 version(#1304)](https://github.com/NativeScript/android-runtime/issues/1304) + - [Fail SBG when there's no sbg-bindings.txt file generated(#1286)](https://github.com/NativeScript/android-runtime/issues/1286) + - [Enable arm64-v8 in app.gradle(#1284)](https://github.com/NativeScript/android-runtime/issues/1284) + - [Support external buildscript configurations(#1279)](https://github.com/NativeScript/android-runtime/issues/1279) + - [Refactor SBG to support generics and proper handling of overridable methods(#1322)](https://github.com/NativeScript/android-runtime/issues/1322) + +## Bug Fixes + - [The minSdk version should not be declared in the android manifest file(#1316)](https://github.com/NativeScript/android-runtime/issues/1316) + - ["Unable to resolve dependency" error when runtime is not build (#1309)](https://github.com/NativeScript/android-runtime/issues/1309) + - [App crashes after tns debug android --debug-brk and trying to debug with "Step into/over"(#892)](https://github.com/NativeScript/android-runtime/issues/892) + - [Unable to call plugin's native code if application has been build before adding the plugin(#1293)](https://github.com/NativeScript/android-runtime/issues/1293) + - [Android build fails when tns-core-modules is updated(#1257)](https://github.com/NativeScript/android-runtime/issues/1257) + - [Generate better code(#689)](https://github.com/NativeScript/android-runtime/issues/689) + +5.2.1 +== + +## Bug Fixes + - [Breakpoint stop to hit when you have two open tabs and close one(#1247)](https://github.com/NativeScript/android-runtime/issues/1247) + +5.2.0 +== + +## What's New + - [Upgrade v8 to 7.1.302.32(#1237)](https://github.com/NativeScript/android-runtime/issues/1237) + - [Add OnDiscardedError handler(#1245)](https://github.com/NativeScript/android-runtime/issues/1245) + - [Upgrade the android gradle plugin to the latest 3.3.1 version(#1251)](https://github.com/NativeScript/android-runtime/issues/1251) + - [Add android X support(#1226)](https://github.com/NativeScript/android-runtime/issues/1226) + - [Provide a JS helper function on the global object to release the native object wrapped by a JS instance(#1254)](https://github.com/NativeScript/android-runtime/issues/1254) + +## Bug Fixes + + - [ClassNotFound exception when calling nested static class with correct argument(#1195)](https://github.com/NativeScript/android-runtime/issues/1195) + - [If you refresh or close the chrome dev tools window an error will be log in the console (#1202)](https://github.com/NativeScript/android-runtime/issues/1202) + - [Debug on Android fails when stopped on breakpoint and change in .xml/.css/.html is applied(#1243)](https://github.com/NativeScript/android-runtime/issues/1243) + - [Upgrade V8 to v7 to fix unstable sort() method(#1176)](https://github.com/NativeScript/android-runtime/issues/1176) + - [CodeCache option is broken since Android Runtime 4.1.0(#1235)](https://github.com/NativeScript/android-runtime/issues/1235) + - [Snapshots with ABI splits do not work since Android Runtime 4.1.0(#1234)](https://github.com/NativeScript/android-runtime/issues/1234) + +5.1.0 +== + +## What's New + - [Add a setting to wrap calls to CallJSMethod in try catch(#1223)](https://github.com/NativeScript/android-runtime/issues/1223) + - [Support for abstract interface with static methods(#1157)](https://github.com/NativeScript/android-runtime/issues/1157) + - [Use gradle plugin 3.2.1 instead of 3.2.0(#1209)](https://github.com/NativeScript/android-runtime/pull/1209) + - [Add a concrete exception when the runtime cannot be found(#1201)](https://github.com/NativeScript/android-runtime/pull/1201) + +## Bug Fixes + + - [__extends not working as expected for non native inheritance(#1181)](https://github.com/NativeScript/android-runtime/issues/1181) + +5.0.0 +== + +## What's New + - [Upgrade v8 to 6.9.427.23(#1168)](https://github.com/NativeScript/android-runtime/issues/1168) + - [Added support for before-plugins.gradle file applied before plugin(#1183)](https://github.com/NativeScript/android-runtime/pull/1185) + - [Make JSParser in SBG fail the build when failing(#1152)](https://github.com/NativeScript/android-runtime/issues/1152) + - [Generate interface names list in SBG in parallel(#1132)](https://github.com/NativeScript/android-runtime/issues/1132) + - [Upgrade android gradle plugin to 3.2.0(#1147)](https://github.com/NativeScript/android-runtime/issues/1147) + +## Bug Fixes + + - [Static Binding Generator fails if class has static properties that are used within the class(#1160)](https://github.com/NativeScript/android-runtime/issues/1160) + - [Fixing NoClassDefFoundError when using older API(#1164)](https://github.com/NativeScript/android-runtime/pull/1164) + +4.2.0 +== + +## What's New + - [Upgrade v8 to 6.7.288.46(#1130)](https://github.com/NativeScript/android-runtime/issues/1130) + - [Static binding generator now uses bundled npm packages(#1096)](https://github.com/NativeScript/android-runtime/issues/1096) + - [Add gradle dependencies versions in package.json(#1102)](https://github.com/NativeScript/android-runtime/issues/1102) + - [Introduce a setting for auto catching exceptions when calling JS method native(#1119)](https://github.com/NativeScript/android-runtime/issues/1119) + - [Make livesync work entirely through named sockets(#932)](https://github.com/NativeScript/android-runtime/issues/932) + +## Bug Fixes + + - [Unable to increase minSdk version by modifying AndroidManifest.xml(#1104)](https://github.com/NativeScript/android-runtime/issues/1104) + - [UTF8 symbols in inspector protocol are not properly encoded(#1116)](https://github.com/NativeScript/android-runtime/issues/1116) + - [ChromeDevTools: If you close the socket the app will crash on the device/emulator(#1122)](https://github.com/NativeScript/android-runtime/issues/1122) + - [App tries to write in /data/local/tmp(#828)](https://github.com/NativeScript/android-runtime/issues/828) + - [Rewrite livesync implementation(#929)](https://github.com/NativeScript/android-runtime/pull/929) + +4.1.3 +== + +## Bug Fixes + + - Use google repository as primary gradle repository + +4.1.2 +== + +## Bug Fixes + + - [Webview Crash With Android 8.1.0 / Chromium 67.0.3396.68 (#1075)](https://github.com/NativeScript/android-runtime/issues/1075) + +4.1.1 +== + +## What's New + + - [Add support for java.nio.HeapByteBuffer to ArrayBuffer conversion(#1060)](https://github.com/NativeScript/android-runtime/issues/1060) + - [Upgrade Gradle version (wrapper and plugin) to latest(#1054)](https://github.com/NativeScript/android-runtime/issues/1054) + - [Accessing Native Packages starting with 'in'(#1046)](https://github.com/NativeScript/android-runtime/issues/1046) + - [tns-android 4.0.1: undefined objects are dumped with console.log making the log unreadable(#1026)](https://github.com/NativeScript/android-runtime/issues/1026) + - [Support new gradle dependency configuration(#993)](https://github.com/NativeScript/android-runtime/issues/993) + - [new Date() does not work as expected after time zone change(#961)](https://github.com/NativeScript/android-runtime/issues/961) + - [Update V8 to latest stable version(#808)](https://github.com/NativeScript/android-runtime/issues/808) + - [Checking if java class implements java interface(#739)](https://github.com/NativeScript/android-runtime/issues/739) + - [How to disable console.logs in release builds(#1024)](https://github.com/NativeScript/android-runtime/issues/1024) + +## Bug Fixes + + - [Calling java method with incorrect params crashes with a JNI exception (#844)](https://github.com/NativeScript/android-runtime/issues/844) + - [Chrome DevTools: App crashes after adding new view element(#1051)](https://github.com/NativeScript/android-runtime/issues/1051) + - [Extraneous classes generated by SBG are not cleaned on rebuild(#904)](https://github.com/NativeScript/android-runtime/issues/904) + - [Source files have different roots, while using debug command(896)](https://github.com/NativeScript/android-runtime/issues/896) + +4.0.1 +== + +## Bug Fixes + + - [App crash on startup on Android 4.4](https://github.com/NativeScript/android-runtime/issues/999) + +4.0.0 +== + +## Breaking Changes + + - [Rewrite the build script routine to no longer use flavors as the primary mechanism to apply nativescript plugin Android configurations (#890)](https://github.com/NativeScript/android-runtime/issues/890) - **If you see the `All flavors must now belong to a named flavor dimension.` build error, ensure that you are using the latest CLI. Plugins will be built implicitly by the CLI, version 4.0.0-rc or newer.** + - [Application package outputs are now located at `platforms/android/app/build/outputs///app-.apk` (#938)](https://github.com/NativeScript/android-runtime/issues/938) + +## What's New + + - [Respect `.jar` and `.aar` libraries from `App_Resources/Android/libs` (#899)](https://github.com/NativeScript/android-runtime/issues/899) + - [Add user-defined Android project files - `.java`, resources, assets in `App_Resources/Android` (#700)](https://github.com/NativeScript/android-runtime/issues/700) - **Enabled after updating the App_Resources/Android subdirectory structure. Use `tns resources update android` with CLI 4.0.0-rc or newer.** + - [Console API improvements - file name, line, column support in Chrome DevTools; Objects are expanded to JSON representations (#894) (#884)](https://github.com/NativeScript/android-runtime/pull/894) - **Replaces the console API in the `tns-core-modules`.** + - [Update Gradle to 4.1 and Android plugin for Gradle to 3.0.1 (#938)](https://github.com/NativeScript/android-runtime/issues/938) + +## Bug Fixes + + - [fix: static binding generator creating wrong files, when two extended classes have the same name (#692)](https://github.com/NativeScript/android-runtime/issues/692) + +3.4.2 +== + +## Bug Fixes + + - [fix include gradle flavor generation for plugins with incomplete include.gradle scripts (#937)](https://github.com/NativeScript/android-runtime/pull/937) + +3.4.1 +== + +## Bug Fixes + + - [Want help to access webview document height and cookies (#5243)](https://github.com/NativeScript/NativeScript/issues/5243) + - [Question about plugin using native lib NS 3.4 (#5254)](https://github.com/NativeScript/NativeScript/issues/5254) + +3.4.0 +== + +## What's New + + - [Android Studio Integration (#876)](https://github.com/NativeScript/android-runtime/issues/876) + +## Bug Fixes + + - [Faulty Java class name when extending a class inside a file containing dots (#761)](https://github.com/NativeScript/android-runtime/issues/761) + +3.3.1 +== + +## Bug Fixes + + - [app.gradle applies before other plugin gradle scripts (#878)](https://github.com/NativeScript/android-runtime/issues/878) + +3.3.0 +== + +## Bug Fixes + + - [Provide better error message while parsing js files (#833)](https://github.com/NativeScript/android-runtime/issues/833) + - [Improve JavaScript Metadata generation (#832)](https://github.com/NativeScript/android-runtime/issues/832) + - [Improve Error handling incorrectly implementing Java interface (#836)](https://github.com/NativeScript/android-runtime/issues/836) + +3.2.0 +== + +## Bug Fixes + + - [Static binding generator fails when using Webpack + Workers (#778)](https://github.com/NativeScript/android-runtime/issues/778) + +3.1.1 +== + +## Bug Fixes + - [APKS with ABI split crash on start up (#785)](https://github.com/NativeScript/android-runtime/issues/785) + +3.1.0 +== + +## What's New + - [Chrome DevTools Elements Tab Support (#746)](https://github.com/NativeScript/android-runtime/issues/746) + +3.0.1 +== + +## Bug Fixes + + - [--debug-brk flag not working (#2741)](https://github.com/NativeScript/nativescript-cli/issues/2741) + - [Clean app between different versions of application package. Ensure Android 6's AutoBackup feature doesn't restore files for NS apps](https://github.com/NativeScript/android-runtime/pull/771#issue-232247925) + +3.0.0 +== + +## What's New + + - [Chrome DevTools Network Domain (#715)](https://github.com/NativeScript/android-runtime/issues/715) + - [Chrome DevTools Scope Tab (#713)](https://github.com/NativeScript/android-runtime/issues/713) + - [Enabling java source code or direct dex generation #663)](https://github.com/NativeScript/android-runtime/issues/663) + - [Improve Gradle incremental build (#562)](https://github.com/NativeScript/android-runtime/issues/562) + + +## Bug Fixes + + - [Javascript array not marshalling to Java long[] properly (#696)](https://github.com/NativeScript/android-runtime/issues/696) + +2.5.0-RC +== + +## What's New + + - [Error when running on real android device (#628)](https://github.com/NativeScript/android-runtime/issues/628) + - [Updating v8 to 5.4/5.5 #631)](https://github.com/NativeScript/android-runtime/issues/631) + +## Bug Fixes + + - [Can't use npm packages ending with ".js" (#666)](https://github.com/NativeScript/android-runtime/issues/666) + - [Static binding generator crash build-time: clazz is null causes app to crash (#665)](https://github.com/NativeScript/android-runtime/issues/665) + - [Decorators aren't respected when extending classes with TypeScript 2.1.4+ (#651)](https://github.com/NativeScript/android-runtime/issues/651) + - [Samples SDK app crashes (#632)](https://github.com/NativeScript/android-runtime/issues/632) + - [Missing stack trace on worker errors (#629)](https://github.com/NativeScript/android-runtime/issues/629) + - [Classes using fields from compileSdk > Platform Sdk on device cause crash when extended in TS (#626)](https://github.com/NativeScript/android-runtime/issues/626) + - [A failure building in debug and release in series (#649)](https://github.com/NativeScript/android-runtime/issues/649) + +2.4.0 +== + +## What's New + + - [Update the V8 JavaScript Engine to 5.2.361 (97% ES6 support)](http://v8project.blogspot.bg/2016/06/release-52.html) + - [[Experimental] Generate typings for android.jar and android support libs (--androidTypings) (#605)](https://github.com/NativeScript/android-runtime/pull/605) + - [[Experimental] Multithreading support enabled with Web Workers API (#532)](https://github.com/NativeScript/android-runtime/issues/532) + - [Enable enableProguardInReleaseBuilds in build.gradle (#567)](https://github.com/NativeScript/android-runtime/issues/567) + - [Optimized default apk size (#529)](https://github.com/NativeScript/android-runtime/issues/529) + - [Improved debug Error Activity (#293)](https://github.com/NativeScript/android-runtime/issues/293) + +## Bug Fixes + + - [Build for armv7 and x86 only by default (#614)](https://github.com/NativeScript/android-runtime/issues/614) + - [Make javascript parsing during build incremental (#572)](https://github.com/NativeScript/android-runtime/issues/572) + - [App won't launch on Android 22 device (#592)](https://github.com/NativeScript/android-runtime/issues/592) + - [Metadata isn't created for generated dex files (#552)](https://github.com/NativeScript/android-runtime/issues/552) + - [App doesn't load with the new custom Application\Activity support (#546)](https://github.com/NativeScript/android-runtime/issues/546) + +## Performance + + - [Enable enableProguardInReleaseBuilds in build.gradle (#567)](https://github.com/NativeScript/android-runtime/issues/567) + - [Optimize default apk size (#529)](https://github.com/NativeScript/android-runtime/issues/529) + +2.3.0 +== + +## What's New + + - [Extend is not working as previous versions in all cases. (#514)](https://github.com/NativeScript/android-runtime/issues/514) + - [JS: Binding: Run-time error occured in file: undefined at line: undefined and column: undefined (#443)](https://github.com/NativeScript/android-runtime/issues/443) + +## Bug Fixes + + - [Crash when invoking a second (different) signature of an overloaded method (meta generator cache?) (#555)](https://github.com/NativeScript/android-runtime/issues/555) + - [App crashes when set undefined to reference type field (#306)](https://github.com/NativeScript/android-runtime/issues/306) + +2.2.0 +== + +## What's New + + - [Classes can implement multiple interfaces (#501)](https://github.com/NativeScript/android-runtime/pull/501) + +## Performance + + - [Update gradle wrapper version to 2.10, and gradle plugin to 2.1.2 (#516)](https://github.com/NativeScript/android-runtime/pull/516) + +2.1 +== + +## What's New + + - [Implement custom gradle clean (#459)](https://github.com/NativeScript/android-runtime/issues/459) + +## Bug Fixes + + - [App crash (#476)](https://github.com/NativeScript/android-runtime/issues/476) + - [The static binding generator should clean redundant files (#467)](https://github.com/NativeScript/android-runtime/issues/467) + - [Android builds fail on nativescript 2.0 (#460)](https://github.com/NativeScript/android-runtime/issues/460) + - [Need to add a Gradle Android.defaultConfig (#454)](https://github.com/NativeScript/android-runtime/issues/454) + +## Performance + + - [Initial builds of ng2 apps are slow (#436)](https://github.com/NativeScript/android-runtime/issues/436) + +2.0.0 +== + +## What's New + + - [Android N early developer preview (#378)](https://github.com/NativeScript/android-runtime/issues/378) + - [[Proposal] Static binding generator specification. (#363)](https://github.com/NativeScript/android-runtime/issues/363) + - [Android Runtime Support for older Android versions (#357)](https://github.com/NativeScript/android-runtime/issues/357) + - [Data Marshalling: Support for typed arrays (#65)](https://github.com/NativeScript/android-runtime/issues/65) + - [Support Android Widgets](https://github.com/NativeScript/android-runtime/issues/69) + - [Add support for caching already compiled JS code](https://github.com/NativeScript/android-runtime/issues/257) + - [Additional Intents Crashes app](https://github.com/NativeScript/android-runtime/issues/218) + - [Enable Multidex support](https://github.com/NativeScript/android-runtime/issues/344) + +## Bug Fixes + + - [Wrong object lifecycle management (#382)](https://github.com/NativeScript/android-runtime/issues/382) + - [CLI can easily fail and blow project up on windows when you have multiple plugins. (#369)](https://github.com/NativeScript/android-runtime/issues/369) + - [Provide method implementations for partially implemented interfaces. (#259)](https://github.com/NativeScript/android-runtime/issues/259) + - [Generate metadata for protected interfaces (#236)](https://github.com/NativeScript/android-runtime/issues/236) + - [Cannot resolve method/constructor signatures when null is passed (#90)](https://github.com/NativeScript/android-runtime/issues/90) + - [App seems to load up with a white screen on run, but works in debug mode #397](https://github.com/NativeScript/android-runtime/issues/397) + +## Performance + + - [Wrong object lifecycle management (#382)](https://github.com/NativeScript/android-runtime/issues/382) + +1.7.1 +== + +## Bug Fixes + + - [Fix application initialization](https://github.com/NativeScript/android-runtime/issues/396) + - [Fix error activity](https://github.com/NativeScript/android-runtime/issues/395) + +1.7.0 +== + +## What's New + - [Extendind Application and Activity classes](https://github.com/NativeScript/android-runtime/issues/226) + - Gradle script improvements + +## Bug Fixes + + - [App crashes with "NativeScriptApplication already initialized"](https://github.com/NativeScript/android-runtime/issues/362) + - [Upgrade to Gradle 1.5.0](https://github.com/NativeScript/android-runtime/issues/375) + + +1.6.0 +== + +## What's New + + - [Build common test infrastructure for Android and iOS](https://github.com/NativeScript/android-runtime/issues/68) + - [New syntax for Java arrays](https://github.com/NativeScript/android-runtime/issues/70) + - [Improved debugger](https://github.com/NativeScript/android-runtime/issues/112) + - [Log in the debugger console](https://github.com/NativeScript/android-runtime/issues/145) + - [Update documentation](https://github.com/NativeScript/android-runtime/issues/290) + - [Provide support for ARMv8a](https://github.com/NativeScript/android-runtime/issues/297) + - [Imroved exception handling](https://github.com/NativeScript/android-runtime/issues/300) + - Gradle script improvements + +## Bug Fixes + + - [Fix app crash during debugging (#270)](https://github.com/NativeScript/android-runtime/issues/270) + - [Fix app hang during array marshalling](https://github.com/NativeScript/android-runtime/issues/320) + - [Fix incorrect module resolution](https://github.com/NativeScript/android-runtime/issues/334) + - [Fix app crash during debugging (#338)](https://github.com/NativeScript/android-runtime/issues/338) + +1.5.1 +== + +## What's New + + - [Enable requiring of JSON files (like in Node) (#217)](https://github.com/NativeScript/android-runtime/issues/217) + - [Revisit the "assert" routine in the JNI part (#221)](https://github.com/NativeScript/android-runtime/issues/221) + - [Android CallStack (#228)](https://github.com/NativeScript/android-runtime/issues/228) + - [error handling introducing c++ exceptions (#277)](https://github.com/NativeScript/android-runtime/pull/277) + - [Simplify require errors (#287)](https://github.com/NativeScript/android-runtime/issues/287) + - [Experimental: Support native modules (#291)](https://github.com/NativeScript/android-runtime/issues/291) + - Gradle script improvements + +## Bug Fixes + + - [Print meaningful error when metadata generator fails to reflect a class (#245)](https://github.com/NativeScript/android-runtime/issues/245) + +1.5.0 +== + +## What's New + + - Improved LiveSync + - [Improved error handling](https://github.com/NativeScript/android-runtime/issues/221) + - Use Gradle Wrapper + - Use V8 code cache (experimental) + + ## Bug Fixes + + - Proper handling of HTTP 401 status code + - [Generate metadata for protected interfaces](https://github.com/NativeScript/android-runtime/issues/236) + - [Fix loading module with NULL char in it](https://github.com/NativeScript/android-runtime/issues/271) + +1.4.0 +== + +## Bug Fixes + + - [Generated metadata is not updated after initial build until after 'gradle clean' is called (#227)](https://github.com/NativeScript/android-runtime/issues/227) + - [Incorrect behavior when getting or setting java fields from javascript (#219)](https://github.com/NativeScript/android-runtime/issues/219) + - [Better handling of package.json main configuration (#190)](https://github.com/NativeScript/android-runtime/issues/190) + - [Calling non existen ctor crashesh the runtime (#180)](https://github.com/NativeScript/android-runtime/issues/180) + - [ClassCastException when tries to convert numeric return value of overridden methods (#139)](https://github.com/NativeScript/android-runtime/issues/139) + +1.3.0 +== + +## What's New + + - [Expose public API for NativeScript Companion App for deleting old *.dex files (#187)](https://github.com/NativeScript/android-runtime/issues/187) + - [Add support for AppBuilder LiveSync (#186)](https://github.com/NativeScript/android-runtime/issues/186) + - [Create a new template project for Gradle build (#182)](https://github.com/NativeScript/android-runtime/issues/182) + - [Support ~ path syntax in require (#177)](https://github.com/NativeScript/android-runtime/issues/177) + - [Enable using the Google Design library (and alike) with "library add" command (#140)](https://github.com/NativeScript/android-runtime/issues/140) + - [Implement support for CLI live sync feature (#137)](https://github.com/NativeScript/android-runtime/issues/137) + - [Ahead-of-time generation of binding proxies (#103)](https://github.com/NativeScript/android-runtime/issues/103) + - [Use pool of arrays for marshalling (#33)](https://github.com/NativeScript/android-runtime/issues/33) + +## Bug Fixes + + - [Run after LiveSync starts the last synced app on the device/emulator (#214)](https://github.com/NativeScript/android-runtime/issues/214) + - [Cannot load module with relative path on Android 6 (#206)](https://github.com/NativeScript/android-runtime/issues/206) + - [App crashes when call overloaded method of a base class (#203)](https://github.com/NativeScript/android-runtime/issues/203) + - [Fix file is external to application error on Android M (#185)](https://github.com/NativeScript/android-runtime/issues/185) + - [App crash during GC (#184)](https://github.com/NativeScript/android-runtime/issues/184) + - [JNI reference leaks when passing JavaScript arrays (#167)](https://github.com/NativeScript/android-runtime/issues/167) + - [ArrayBuffer broken (#164)](https://github.com/NativeScript/android-runtime/issues/164) + - [ClassCastException when tries to convert numeric return value of overridden methods (#139)](https://github.com/NativeScript/android-runtime/issues/139) + - [Allow debugger reconnects (#136)](https://github.com/NativeScript/android-runtime/issues/136) + - [__onUncaughtError is not called. (#108)](https://github.com/NativeScript/android-runtime/issues/108) + +## Performance + + - [Cache parsed method signature (#181)](https://github.com/NativeScript/android-runtime/issues/181) + - [Use pool of arrays for marshalling (#33)](https://github.com/NativeScript/android-runtime/issues/33) + +# Android Runtime Changelog + +1.2.1 +== + +## What's New + + - Updated android widgets library + - [Allow verbose logging system property to enable debug messages early in engine bootstrap](https://github.com/NativeScript/android-runtime/issues/111) + +## Bug Fixes + + - [Additional null checks in V8 to handle certain possible garbage collection issues](https://github.com/NativeScript/android-runtime/issues/111) + - Fix sync support in runtime for specific (Samsung) Android devices where run-as is not working + - [Fix JNI memory leak](https://github.com/NativeScript/android-runtime/issues/167) + - Fix copy of correct android.jar referenced in project properties + + +1.2.0 +== + +## What's New + + - [Support http cookies in build-in http client (#159)](https://github.com/NativeScript/android-runtime/issues/159) + - [Implement support for CLI live sync feature (#137)](https://github.com/NativeScript/android-runtime/issues/137) + - [Rethink 0 day support for new android versions (#86)](https://github.com/NativeScript/android-runtime/issues/86) + +## Bug Fixes + + - [Fix ErrorActivity not displayed on uncaught exceptions (#158)](https://github.com/NativeScript/android-runtime/issues/158) + - [Fix JNI memory leak when resolving classes in metadata reader (#157)](https://github.com/NativeScript/android-runtime/issues/157) + - [Application crash with JNI ERROR (app bug): local reference table overflow (max=512) (#149)](https://github.com/NativeScript/android-runtime/issues/149) + - [Cannot set float field (#148)](https://github.com/NativeScript/android-runtime/issues/148) + - [IndexedPropertySetter not called (#127)](https://github.com/NativeScript/android-runtime/issues/127) + - [Fix GetDbgPort intent (#117)](https://github.com/NativeScript/android-runtime/issues/117) + - [Recreating an Activity with fragments on same process crashes the runtime (#96)](https://github.com/NativeScript/android-runtime/issues/96) + - [Grunt tasks fail on Windows (#61)](https://github.com/NativeScript/android-runtime/issues/61) + +## Performance + + - [Optimize Strings marshaling between Java and V8 (#160)](https://github.com/NativeScript/android-runtime/issues/160) + - [Improve required module loading (#156)](https://github.com/NativeScript/android-runtime/issues/156) + - [Improve JNI String marshalling (#126)](https://github.com/NativeScript/android-runtime/issues/126) + - [Cache folder-as-module resolved path (#121)](https://github.com/NativeScript/android-runtime/issues/121) + + +## 1.1.0 (2015, June 10) + +### New + +* Implemented [#58](https://github.com/NativeScript/android-runtime/issues/60) to remove the 3-seconds initial timeout for Debug builds. +* Implemented [#118](https://github.com/NativeScript/android-runtime/issues/118) to replace the MultiDex library with DexClassLoader. +* Started [#103](https://github.com/NativeScript/android-runtime/issues/103) AOT proxy generation to improve startup time and to enable new scenarios like BroadcastReceivers, BackgroundServices and arbitrary Activity types declared in the manifest. + +### Fixed + +* [#63](https://github.com/NativeScript/android-runtime/issues/63). An issue which prevented users to extend overloaded methods. +* [#64](https://github.com/NativeScript/android-runtime/issues/64). A JNI Crash when calling JS method with char. +* [#113](https://github.com/NativeScript/android-runtime/issues/113). Fixes the extend routine for an Activity. +* [#114](https://github.com/NativeScript/android-runtime/issues/114). Removes the redundant setNativeScriptOverrides method. + +## 1.0.0 (2015, April 29) + +### New + +* Updated the V8 version to 4.1.0.27. +* Re-implemented debugger support (no more spontaneous dead locks). + +### Fixed + +* An issue with the error reporting routine. + +### Breaking Changes + +* Renamed global functions: + * `__log` (was `Log`) + * `__debugbreak` (was `waitForDebugger`) + * `__enableVerboseLogging` (was `enableVerboseLogging`) + * `__disableVerboseLogging` (was `disableVerboseLogging`) + * `__exit` (was `fail`) + +## 0.10.0 (2015, April 17) + +### New + +* Added Dynamic Generator for binding proxies. This boosts the initial loading time, especially on Android 5.0+ devices. +* Added several optimization techniques, which further optimize the loading time and the overall performance. +* Improved the error reporting mechanism for Debug builds. +* Added support for package.json and index.js for bootstrapping an application. + +### Breaking Changes + + * Removed the simulated property-like support for Android types. E.g. the `android.content.Intent.getAction()` previously was accessible like `android.content.Intent.Action`. This is no longer valid as it contradicts with the Android APIs. + * Changed the way `extend` constructs work +```javascript +// WRONG +var handler = new android.os.Handler.extend({...})(); + +// CORRECT +var handlerType = android.os.Handler.extend({...}); +var handler = new handlerType(); +``` + * The directory structure in the `assets` folder has changed. The `tns_modules` directory is now within the `assets/app` one. To migrate older CLI projects to the new structure simply move the content of the inner app folder one level up: + +####Previous structure: +``` +|--app +|--|--app +|--|--|--bootstrap.js +|--|--|--myFile.js +|--|--tns_modules +``` + +####New structure: +``` +|--app +|--|--bootstrap.js +|--|--myFile.js +|--|--tns_modules +``` diff --git a/platforms/android/CODE_OF_CONDUCT.md b/platforms/android/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..e190410a8 --- /dev/null +++ b/platforms/android/CODE_OF_CONDUCT.md @@ -0,0 +1,81 @@ +# NativeScript Community Code of Conduct + +Our community members come from all walks of life and are all at different stages of their personal and professional journeys. To support everyone, we've prepared a short code of conduct. Our mission is best served in an environment that is friendly, safe, and accepting; free from intimidation or harassment. + +Towards this end, certain behaviors and practices will not be tolerated. + +## tl;dr + +- Be respectful. +- We're here to help. +- Abusive behavior is never tolerated. +- Violations of this code may result in swift and permanent expulsion from the NativeScript community channels. + +## Contact + +- support@nativescript.org + +## Scope + +We expect all members of the NativeScript community, including administrators, users, facilitators, and vendors to abide by this Code of Conduct at all times in our community venues, online and in person, and in one-on-one communications pertaining to NativeScript affairs. + +This policy covers the usage of the NativeScript Slack community, as well as the NativeScript support forums, NativeScript GitHub repositories, the NativeScript website, and any NativeScript-related events. This Code of Conduct is in addition to, and does not in any way nullify or invalidate, any other terms or conditions related to use of NativeScript. + +The definitions of various subjective terms such as "discriminatory", "hateful", or "confusing" will be decided at the sole discretion of the NativeScript administrators. + +## Friendly, Harassment-Free Space + +We are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics. + +We ask that you please respect that people have differences of opinion regarding technical choices, and acknowledge that every design or implementation choice carries a trade-off and numerous costs. There is seldom a single right answer. A difference of technology preferences is never a license to be rude. + +Any spamming, trolling, flaming, baiting, or other attention-stealing behaviour is not welcome, and will not be tolerated. + +Harassing other users of NativeScript is never tolerated, whether via public or private media. + +Avoid using offensive or harassing package names, nicknames, or other identifiers that might detract from a friendly, safe, and welcoming environment for all. + +Harassment includes, but is not limited to: harmful or prejudicial verbal or written comments related to gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics; inappropriate use of nudity, sexual images, and/or sexually explicit language in public spaces; threats of physical or non-physical harm; deliberate intimidation, stalking or following; harassing photography or recording; sustained disruption of talks or other events; inappropriate physical contact; and unwelcome sexual attention. + +## Acceptable Content + +The NativeScript administrators reserve the right to make judgement calls about what is and isn't appropriate in published content. These are guidelines to help you be successful in our community. + +Content must contain something applicable to the previously stated goals of the NativeScript community. "Spamming", that is, publishing any form of content that is not applicable, is not allowed. + +Content must not contain illegal or infringing content. You should only publish content to NativeScript properties if you have the right to do so. This includes complying with all software license agreements or other intellectual property restrictions. For example, redistributing an MIT-licensed module with the copyright notice removed, would not be allowed. You will be responsible for any violation of laws or others’ intellectual property rights. + +Content must not be malware. For example, content (code, video, pictures, words, etc.) which is designed to maliciously exploit or damage computer systems, is not allowed. + +Content name, description, and other visible metadata must not include abusive, inappropriate, or harassing content. + +## Reporting Violations of this Code of Conduct + +If you believe someone is harassing you or has otherwise violated this Code of Conduct, please contact the administrators and send us an abuse report. If this is the initial report of a problem, please include as much detail as possible. It is easiest for us to address issues when we have more context. + +## Consequences + +All content published to the NativeScript community channels is hosted at the sole discretion of the NativeScript administrators. + +Unacceptable behavior from any community member, including sponsors, employees, customers, or others with decision-making authority, will not be tolerated. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. + +If a community member engages in unacceptable behavior, the NativeScript administrators may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event or service). + +## Addressing Grievances + +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the administrators. We will do our best to ensure that your grievance is handled appropriately. + +In general, we will choose the course of action that we judge as being most in the interest of fostering a safe and friendly community. + +## Contact Info +Please contact Dan Wilson @DanWilson if you need to report a problem or address a grievance related to an abuse report. + +You are also encouraged to contact us if you are curious about something that might be "on the line" between appropriate and inappropriate content. We are happy to provide guidance to help you be a successful part of our community. + +## Credit and License + +This Code of Conduct borrows heavily from the WADE Code of Conduct, which is derived from the NodeBots Code of Conduct, which in turn borrows from the npm Code of Conduct, which was derived from the Stumptown Syndicate Citizen's Code of Conduct, and the Rust Project Code of Conduct. + +This document may be reused under a Creative Commons Attribution-ShareAlike License. \ No newline at end of file diff --git a/platforms/android/CONTRIBUTING.md b/platforms/android/CONTRIBUTING.md new file mode 100644 index 000000000..4d5801b1d --- /dev/null +++ b/platforms/android/CONTRIBUTING.md @@ -0,0 +1,139 @@ +# Contributing to NativeScript + +## Before You Start + +Anyone wishing to contribute to the NativeScript project MUST read & sign the [NativeScript Contribution License Agreement](http://www.nativescript.org/cla). The NativeScript team cannot accept pull requests from users who have not signed the CLA first. + +## Introduction + +These guidelines are here to facilitate your contribution and streamline the process of getting changes merged into this project and released. Any contributions you can make will help tremendously, even if only in the form of an issue report. Following these guidelines will help to streamline the pull request and change submission process. + +## Reporting Bugs + +1. Always update to the most recent master release; the bug may already be resolved. +2. Search for similar issues in the issues list for this repo; it may already be an identified problem. +3. If this is a bug or problem that is clear, simple, and is unlikely to require any discussion -- it is OK to open an issue on GitHub with a reproduction of the bug including workflows and screenshots. If possible, submit a Pull Request with a failing test, entire application or module. If you'd rather take matters into your own hands, fix the bug yourself (jump down to the "Code Fixes and Enhancements" section). + +## Requesting New Features + +1. Use Github Issues to submit feature requests. +2. First search for a similar request and extend it if applicable. This way it would be easier for the community to track the features. +2. When a brand new feature requested, try to give as many details on your need as possible. We prefer that you explain a need than explain a technical solution for it. That might trigger a nice conversation on finding the best and broadest technical solution to a specific need. + +## Asking for Help + +-------------The NativeScript team does *not* provide guaranteed formal support, except to those customers who have purchased a [commercial license for AppBuilder](http://www.telerik.com/platform) (Professional, Enterprise, etc.) or a support-only package from Telerik.com. Please do not create support requests for this project in the issues list for this repo, as these will be immediately closed and you'll be directed to post your question on a community forum. + +## Code Fixes and Enhancements + +### 1. Log an Issue + +Before doing anything else, we ask that you file an issue in the Issues list for this project. First, be sure to check the list to ensure that your issue hasn't already been logged. If you're free and clear, file an issue and provide a detailed description of the bug or feature you're interested in. If you're also planning to work on the issue you're creating, let us know so that we can help and provide feedback. To help us investigate your issue and respond in a timely manner, you can provide is with the following details: +- **Overview of the issue**: Provide a short description of the visible symptoms. If applicable, include error messages, screen shots, and stack traces. +- **Motivation for or use case**: Let us know how this particular issue affects your work. +- **Telerik NativeScript version(s)**: List the current version and build number of the CLI interface, the runtime and the modules. You can get these by checking the package.json files of the respective package. Let us know if you have not observed this behavior in earlier versions and if you consider it a regression. +- **System configuration**: Provide us with relevant system configuration information such as operating system, network connection, proxy usage, etc. Let us know if you have been able to reproduce the issue on multiple setups. +- **Steps to reproduce**: If applicable, submit a step-by-step walkthrough of how to reproduce the issue. +- **Related issues**: If you discover a similar issue in our archive, give us a heads up - it might help us identify the culprit. +- **Suggest a fix**: You are welcome to suggest a bug fix or pinpoint the line of code or the commit that you believe has introduced the issue. + +### 2. Fork and Branch + +#### Fork Us, Then Create A Topic Branch For Your Work + +The work you are doing for your pull request should not be done in the master branch of your forked repository. Create a topic branch for your work. This allows you to isolate the work you are doing from other changes that may be happening. + +Github is a smart system, too. If you submit a pull request from a topic branch and we ask you to fix something, pushing a change to your topic branch will automatically update the pull request. + +#### Isolate Your Changes For The Pull Request + +See the previous item on creating a topic branch. + +If you don't use a topic branch, we may ask you to re-do your pull request on a topic branch. If your pull request contains commits or other changes that are not related to the pull request, we will ask you to re-do your pull request. + +#### Branch from "master" + +The "master" branch of the NativeScript repository is for continuous contribution. Always create a branch for your work from the "master" branch. This will facilitate easier pull request management. + +#### Contribute to the Code Base +Before you submit a Pull Request, consider the following guidelines. + +Search GitHub for an open or closed Pull Request that relates to your submission. +Clone the repository. +```bash + git clone git@github.com:NativeScript/android-runtime.git +``` +Make your changes in a new git branch. We use the Gitflow branching model so you will have to branch from our master branch. +```bash + git checkout -b my-fix-branch master +``` +Create your patch and include appropriate test cases. +Build your changes locally. +```bash + grunt +``` +Commit your changes and create a descriptive commit message (the commit message is used to generate release notes). +```bash + git commit -a +``` +Push your branch to GitHub. +```bash + git push origin my-fix-branch +``` +In GitHub, send a Pull Request to NativeScript:android-runtime:master. +If we suggest changes, you can modify your branch, rebase, and force a new push to your GitHub repository to update the Pull Request. +```bash + git rebase master -i + git push -f +``` +That's it! Thank you for your contribution! + +When the patch is reviewed and merged, you can safely delete your branch and pull the changes from the main (upstream) repository. + +Delete the remote branch on GitHub. +```bash + git push origin --delete my-fix-branch +``` +Check out the master branch. +```bash + git checkout master -f +``` +Delete the local branch. +```bash + git branch -D my-fix-branch +``` +Update your master branch with the latest upstream version. +```bash + git pull --ff upstream master +``` + +#### Squash your commits + +When you've completed your work on a topic branch, we prefer that you squash your work down into a single commit to make the merge process easier. For information on squashing via an interactive rebase, see [the rebase documentation on GitHub](https://help.github.com/articles/interactive-rebase) + +### 3. Submit a Pull Request + +See [Github's documentation for pull requests](https://help.github.com/articles/using-pull-requests). + +Pull requests are the preferred way to contribute to NativeScript. Any time you can send us a pull request with the changes that you want, we will have an easier time seeing what you are trying to do. But a pull request in itself is not usually sufficient. There needs to be some context and purpose with it, and it should be done against specific branch. + +### Provide A Meaningful Description + +Similar to reporting an issue, it is very important to provide a meaningful description with your pull requests that alter any code. A good format for these descriptions will include four things: + +1. Why: The problem you are facing (in as much detail as is necessary to describe the problem to someone who doesn't know anything about the system you're building) + +2. What: A summary of the proposed solution + +3. How: A description of how this solution solves the problem, in more detail than item #2 + +4. Any additional discussion on possible problems this might introduce, questions that you have related to the changes, etc. + +Without at least the first 2 items in this list, we won't have any clue why you're changing the code. The first thing we'll ask, then, is that you add that information. + +## Code Style + +We are currently using Eclipse to write code. We are using predefined code formating rules for C++ and Java: + +* [C++](./CodingStyle.Cpp.xml) +* [Java](./CodingStyle.Java.xml) diff --git a/platforms/android/CodingStyle.Cpp.xml b/platforms/android/CodingStyle.Cpp.xml new file mode 100644 index 000000000..fa55f9824 --- /dev/null +++ b/platforms/android/CodingStyle.Cpp.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platforms/android/CodingStyle.Java.xml b/platforms/android/CodingStyle.Java.xml new file mode 100644 index 000000000..6e0862dbe --- /dev/null +++ b/platforms/android/CodingStyle.Java.xml @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platforms/android/LICENSE b/platforms/android/LICENSE new file mode 100755 index 000000000..4c831d8df --- /dev/null +++ b/platforms/android/LICENSE @@ -0,0 +1,19 @@ +Copyright OpenJS Foundation and other contributors, https://openjsf.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/platforms/android/MIGRATION.md b/platforms/android/MIGRATION.md new file mode 100644 index 000000000..9aa3b4e5f --- /dev/null +++ b/platforms/android/MIGRATION.md @@ -0,0 +1,73 @@ +# V8 → NAPI runtime migration ledger + +Tracks the port of substantive commits from the old V8-based runtime +(`nativescript/android-runtime`, branch `master`) into this NAPI-based runtime, +starting at `2bab8f5` (`fix(URL): allow undefined 2nd args`, #1826) through the +old runtime's HEAD (129 commits total). + +Plan: `~/.claude/plans/we-want-to-migrate-modular-prism.md` + +**Status:** the full range `2bab8f5..HEAD` has been triaged. ~60 `build(deps)`/CI/`chore`/`release` +commits are out of scope (fork tooling). Of the substantive commits: **23 ported/partial** +(all native/Java build-verified), several **already-present**, and the rest **deferred** with +rationale (each deferred row says why + what's needed to complete it). Deferred clusters: +**ESM** (`052cb21` + its dependents `7782720f`/`288491f`/`5ceb3d4`/`92c2654`/`45ed1f6`), +**timers** (`bfd7650`), **URLSearchParams spec** (`89893ae`), **workers→C++** (`a84d3c7`), +**inspector** (`55da2da`/`4b5ab0a`), **NDK r27d** (`0387a8d`). The **@CriticalNative** pair +(`085bc4f`+`3c956cf`) and **DexFactory** pair (`c9d41e6`+`fce8e29`) are now **ported** (compile-verified; +they carry device/version/release-only runtime risk — validate on real devices). Verification here is +compile/link + config-eval; on-device behavior is noted per-row where relevant. + +**Disposition:** `ported` · `already-present` · `partial` · `skipped` · `deferred` + +| # | old hash | subject | disposition | new commit | notes | +|---|----------|---------|-------------|------------|-------| +| 1 | 2bab8f5 | fix(URL): allow undefined 2nd args (#1826) | ported | ef20f1d | `URL::New` (modules/url/URL.cpp): gate base-URL branch on `argv[1]` not being undefined/null via `napi_util::is_of_type`; ported expanded `testURLImpl.js` verbatim (JS is engine-agnostic). Verified: native recompile (V8-13, all ABIs) exit 0. | +| 2 | 94ddb15 | fix: `exit(0)` causes ANR due to destroyed mutex (#1820) | ported | b46586b | Old fix was in `MetadataNode::BuildMetadata`; in napi runtime that error path moved to `MetadataBuilder.cpp:55` (identical direct-boot/locked-screen comment). Changed `exit(0)` → `_Exit(0)`. Verified: native recompile exit 0. | +| — | f290ed2 | perf: optimizations around generating JS classes from Metadata (#1824) | already-present | — | Large metadata-subsystem perf refactor (13 files). Confirmed by user: already implemented in the napi runtime's rewritten metadata code. Skipped. | +| 3 | a983931 | fix: gradle error when compileSdk or targetSdk is provided (#1825) | ported | 6f01665 | `test-app/app/build.gradle`: add `as int` cast to provided `compileSdk`/`targetSdk` in `computeCompileSdkVersion`/`computeTargetSdkVersion` (props arrive as String from `-P`). Verified: `:app:help -PcompileSdk=35 -PtargetSdk=35` config eval exit 0. | +| — | e293636 | fix: inner type not set when companion object defined as function (#1831) | already-present | — | New `MetadataNode::SetInnerTypes` (metadata/MetadataNode.cpp:1726-1733) already has the `napi_has_own_property` + `if(!hasOwnProperty)` guard before defining the inner-type accessor — napi port of the same fix. Skipped. | +| 5 | b31fc5f + 3633aed + 83f611b + 3513ce7 + 45fb275 | Ada v3 + URLPattern (#1830), Ada 3.1.1/3.1.3/3.2.7/3.3.0 (#1832/#1835/#1884) | ported | this commit | **Consolidated the whole Ada v3 chain** (Ada is a vendored single-file lib, so intermediate bumps collapse to the final): replaced `modules/url/ada/ada.{h,cpp}` 2.9.0→3.3.0 (verified no API breaks in URL/URLSearchParams). **Ported URLPattern** V8→napi: new `modules/url/URLPattern.{cpp,h}` with a `napi_regex_provider` (RegExp via global ctor + `.exec`, move-only `NapiRegex` ref wrapper, thread-local env for `create_instance`); wired `URLPattern::Init` into `NSRuntimeModules`. Registered spec-correct `hasRegExpGroups` (old C++ had typo `hasRegexpGroups`). Ported `testURLPattern.js` + registered in mainpage.js. Skipped the commit's gradle/CMake/Runtime-V8 bits (superseded / engine-specific). Verified: native build all ABIs exit 0. | +| 6 | bec401c | feat: NDK 27 and Support for Java 21 (#1819) | partial | this commit | **Ported (Java-21 source compat):** `NanoWSD.java` byte casts (`header |= (byte)…`); `NativeScriptAbstractMap` `SimpleEntry`/`SimpleImmutableEntry` gain `` bounds; generator `build.gradle` ×2 `'17'`→`JavaVersion.VERSION_17`; add `compileOptions VERSION_17` to runtime module. **Already-present:** `-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON` (runtime/build.gradle:173), armeabi/x86 android_support block already removed. **Skipped (superseded/N-A to fork):** CI workflows, `gradlew`/wrapper (fork on gradle 8.14.3 > 8.9), root `package.json` version_info, dts-generator submodule bump. Verified: `:runtime:compileDebugJavaWithJavac` under **JDK 21** exit 0. | +| — | c9a2a86 | feat: update Gradle 8.14.3 and Android build tools 8.12.1 (#1834) | already-present | — | Fork already on gradle wrapper 8.14.3 and `NS_DEFAULT_ANDROID_BUILD_TOOLS_VERSION=8.12.1`. Skipped. | +| — | e98367c | feat: performance api (#1838) | already-present | — | `runtime/performance/Performance.h` registers `global.performance`/`performance.now()` (installed at Runtime.cpp:313). Skipped. | +| 7 | 87f7f9c | feat: update libzip to 1.11.4 (#1845) | ported | this commit | Vendored binary dep. Replaced `cpp/zip/include/{zip.h,zipconf.h}` and the 4 prebuilt `libs/common//libzip.a` with upstream 1.11.4 (fork's zipconf was mislabeled "0.11"). Sole consumer `AssetExtractor.cpp` uses only stable API (zip_open/fread/stat_index/…). Verified: native compile+link all ABIs exit 0. **Caveat:** binary swap verified to link only — on-device asset extraction should be smoke-tested. | +| — | 0387a8d | feat: update NDK to latest LTS (r27d) (#1846) | deferred | — | Portable change is `defaultNdkVersion` 27.1.12297006 → **27.3.13750724** in runtime/build.gradle. Deferred: r27d is NOT installed in this env (only 27.0–27.2), and AGP's `ndkVersion defaultNdkVersion` would fail the build (blocking verification of later commits). **Action for user:** `sdkmanager "ndk;27.3.13750724"`, then bump `defaultNdkVersion`. CI/package.json NDK bumps skipped (fork-specific/superseded). | +| 8 | 78589fd | feat: kotlin 2.2.x support (#1848) | ported | this commit | `gradle.properties`: `ns_default_kotlin_version` + `ns_default_kotlinx_metadata_jvm_version` 2.0.0 → 2.2.20. `KotlinClassDescriptor.kt`: kotlinx-metadata 2.2.x renamed the API — `metaClass.enumEntries` → `metaClass.kmEnumEntries.map { it.name }` (`getEnumEntriesAsFields` already takes `Collection`). Verified: metadata-generator `compileKotlin` under 2.2.20 exit 0 + `:app:buildMetadata` runs the generator successfully. | +| 9 | f033061 | feat: queueMicrotask support (#1868) | ported | this commit | Registered global `queueMicrotask` in `Runtime::Init` (after Performance). The V8 original used `isolate->EnqueueMicrotask`; napi has no equivalent, so implemented engine-agnostically via `Promise.resolve().then(cb)` — preserves microtask ordering with promises (`['qm1','p','qm2']` case) and runs before timers. Validates the arg is a function (TypeError otherwise). Ported `testQueueMicrotask.js` + mainpage.js. Verified: native compile exit 0. **Caveat:** ordering/timing asserts not run on-device here. | +| — | 052cb21 | feat: ES modules (ESM) support (#1836) | deferred | — | **Large (26 files).** Analyzed in depth. Upstream ESM is **100% V8-native** (`v8::Module`, `ScriptCompiler::CompileModule`, `InstantiateModule`, `Evaluate`, `GetModuleNamespace`, isolate `SetHostInitializeImportMetaObjectCallback` / `SetHostImportModuleDynamicallyCallback`; ~650-line ModuleInternalCallbacks.cpp). **No napi ESM API exists**, so options are: (a) V8-only native via the napi↔v8 bridge (works only on V8 engine); (b) **engine-agnostic load-time ESM→CJS transpilation** (recommended if pursued: es-module-lexer + source rewrite + import.meta/dynamic-import polyfills through existing CommonJS path; live-binding snapshot caveat). Portable non-native parts (all engines): `Module.java` `.mjs` resolution (.js→.mjs order, index.mjs), `AppConfig.logScriptLoading` + `Runtime.getLogScriptLoadingEnabled`, SBG `generateUniqueFileIdentifier` (MD5, collision-safe for .mjs) + `isJsFile` .mjs, jsparser `sourceType:'module'` for .mjs. **Deferred by user pending design decision.** Related `7782720f` (http esm realms + HMR) also depends on this. | +| 10 | 5cb66ee | fix: prevent crash when jweak points to null (#1881) | ported | this commit | Engine-agnostic JNI/cache fix, applied to the same files under new paths. `jni/JEnv.h`: add `isSameObject`. `jni/LRUCache.h`: add optional `cacheValidCallback` (checked on lookup → evict stale entries via new `evictKey`) + member. `objectmanager/ObjectManager`: add `ValidateWeakGlobalRefCallback` (`!isSameObject(obj, NULL)`) and pass it to the weak-ref `m_cache`. Skipped upstream's cosmetic robin_hood/emplace swap (new LRUCache uses `std::unordered_map`). Verified: native compile all ABIs exit 0. | +| — | 7782720f | feat: http loaded es module realms + HMR DX enrichments (#1883) | deferred | — | Builds directly on the deferred ESM feature (modifies the V8 `ModuleInternalCallbacks`, adds HMRSupport + DevFlags on top of ESM realms, `testESModules.mjs`). Deferred together with [[052cb21]] ESM. | +| — | 45fb275 | feat: Ada 3.3.0 (#1884) | already-present | — | Covered by the consolidated Ada v3 upgrade in commit `8109ff9` (fork now on Ada 3.3.0). | +| 11 | c05e283 | fix: improve reThrowToJava exception handling and runtime retrieval (#1886) | ported | this commit | Made `ObjectManager::GetClassName` (both overloads) + its `JAVA_LANG_CLASS`/`GET_NAME_METHOD_ID` JNI ids **static** (added out-of-class defs), so `NativeScriptException` can resolve a Java class name during rethrow **without** `Runtime::GetRuntime(env)->GetObjectManager()` (avoids a throw-within-rethrow when the env/runtime is unavailable). Updated the 3 class-name-only sites in NativeScriptException; left `GetJavaExceptionFromEnv` (needs the instance). Skipped upstream's clang-format churn of ObjectManager. Runtime-retrieval robustness (`.at`→`.find`) already present in napi `GetRuntime`. Verified: native compile+link all ABIs exit 0. | +| 12 | 3ecd707 | fix: proguard builds (#1887) | ported | this commit | Comment out `buildMetadata.finalizedBy(currentTask)` for `minify*WithR8` (app/build.gradle:1181); add `consumerProguardFiles 'consumer-rules.pro'` to runtime defaultConfig; new `runtime/consumer-rules.pro` (keep `com.tns.*`, `com.tns.gen.**`, `com.tns.internal.**` + RuntimeVisibleAnnotations). Verified: gradle config eval exit 0. **Caveat:** full effect only observable in a release R8/minify build. | +| — | 288491f | fix: http realm cache key with query params (#1896) | deferred | — | Touches only `HMRSupport.cpp` + `ModuleInternalCallbacks.cpp` — both part of the deferred ESM / http-module-realms work ([[052cb21]], [[7782720f]]). Deferred with them. | +| 13 | 3e61cef | fix: URLSearchParams.forEach() crash and spec compliance (#1895) | partial | this commit | New `ForEach` already had spec arg order `(value, key, searchParams)`, thisArg, and stop-on-throw. Only remaining bug: iterated with `get_keys()`+`get(key)` (returns first value for duplicate keys) → switched to `get_entries()` structured-binding loop (`?a=1&a=2` now yields both). Ported the 4 forEach unit tests. Verified: native compile all ABIs exit 0. | +| — | 5ceb3d4 | feat: remote module security (#1899) | deferred | — | Secures the http-loaded module realms; C++ lands in `DevFlags.cpp/h` + `HMRSupport.cpp` + `ModuleInternalCallbacks.cpp` — none of which exist in the fork (they come from the deferred [[7782720f]] / [[052cb21]]). Java `AppConfig`/`Runtime` additions only make sense with that feature present. Deferred with ESM. | +| 14 | e924542 | feat: improved error logging for NativeScript exceptions (#1908) | partial | this commit | **Ported:** the engine-agnostic `std::set_terminate(LogAndAbortUncaught)` handler in `Runtime::Init(JavaVM)` — logs an uncaught native exception (message via new `NativeScriptException::what()`) before `_Exit`, instead of a bare abort. **Skipped:** the 726-line V8-based `NativeScriptException.cpp/h` logging rewrite — the fork's napi NativeScriptException already builds messages/stacks its own way (`GetErrorMessage`/`GetErrorStackTrace`/`GetFullMessage`/`PrintErrorMessage`) and the old logic is V8-stack-specific; not a clean map. Verified: native compile all ABIs exit 0. | +| 15 | 1fd144f | fix: multithreadedJS should use concurrent java maps (#1920) | ported | this commit | Broadened `Runtime.java` instance-map fields (`strongInstances`, `weakInstances`, `strongJavaObjectToID`, `weakJavaObjectToID`, `loadedJavaScriptExtends`) from concrete types to the `Map<>` interface (only Map methods are used; `NativeScriptHashMap` implements Map), and in the `Runtime(config, dynamicConfig)` ctor swap them to `ConcurrentHashMap`/`Collections.synchronizedMap(...)` when `getEnableMultithreadedJavascript()`. Ported `ConcurrentAccessTest.java` + `testConcurrentAccess.js` + mainpage require. Verified: `:runtime:` + `:app:compileDebugJavaWithJavac` exit 0. **Caveat:** concurrency behavior is device-only. | +| 22 | 085bc4f + 3c956cf | feat: @CriticalNative/@FastNative on safe methods (#1921) + optimized native method registration for Android 8-11 (#1942) | ported | this commit | Combined end-state (`3c956cf` supersedes `085bc4f`). **Java** (`Runtime.java`): `SUPPORTS_OPTIMIZED_NATIVE = SDK_INT>=26`; each hot native now has an annotated variant + a `*Legacy` variant + a dispatcher — `@CriticalNative generateNewObjectIdCritical`/`getCurrentRuntimeIdCritical`/`getPointerSizeCritical` (static, no-arg/primitive) and `@FastNative notifyGcFast`/`setManualInstrumentationModeFast`. **C++** (`com_tns_Runtime.cpp`): `RegisterOptimizedNatives` binds the critical/fast impls via `RegisterNatives` in `JNI_OnLoad` (dynamic lookup of annotated methods is broken on API 26-30); `*Legacy` externs auto-bind for older/ fallback. `generateNewObjectId` made static-safe via `GenerateNewObjectId(nullptr,nullptr)` (ignores env/obj); `getCurrentRuntimeId` uses `Runtime::Current()` (no JNI). notifyGc keeps the fork's `(int,int[])` → `"(I[I)Z"`. Verified: native+Java compile all ABIs exit 0. **Caveat:** ART calling-convention correctness is device-only — validate on Android 8-11 + 12+ and a release build. | +| 16 | df4e81b | fix: select correct runtime when calling from different threads (#1906) | ported | this commit | **Headline fix:** in `Runtime.callJSMethod(int runtimeId, …)`, re-select via `getObjectRuntime(javaObject)` not only when the id misses but also when the found runtime doesn't own the object (`runtime.getJavaObjectID(javaObject) == null`) — fixes a worker invoking a method on an object created in another runtime. **Diagnostic:** wrapped `GetJavaObjectByID` in `ObjectManager::GetJavaObjectByJsObject` with a clearer error (id + original message via the `what()` added in [[c05e283]]/#1908 port). Adapted to the fork's runtimeId-based dispatch; skipped upstream's NSE `ToString()`/`GetErrorMessage()` (fork already has its own message infra + `what()`). Verified: native+Java compile exit 0. | +| 17 | 54185ff | [jsparser]: disable sourcemap (#1796) | ported | this commit | `jsparser/webpack.config.js`: `devtool: false`. | +| 18 | e963d6c | fix: jsparser report webpack failure (#1797) | ported | this commit | `jsparser/js_parser.js`: `traverseFiles` throws when no files found (empty = broken webpack build) instead of silently succeeding. | +| 19 | dd2984b | fix(jsparser): skip non-Identifier keys in `.extend({})` (#1950) | ported | this commit | `jsparser/visitors/es5-visitors.js`: added `_getIdentifierKeyName` helper; both `.extend` property loops now skip spreads/computed/non-Identifier keys instead of crashing (e.g. bundled Zod `.extend({ ...other })`). Verified: jsparser `npm run build` (webpack) compiles clean (build/ is gitignored, rebuilt on demand). | +| 20 | 49da71b | fix: circular dependencies when using proguard (#1910) | ported | this commit | Comment out the `buildMetadata` task's `dependsOn compile*ArtProfile` and `dependsOn optimize*Resources` (app/build.gradle) — they created a circular task dependency under proguard/R8. Verified: `:app:help` config eval exit 0. **Caveat:** full effect only in a release R8 build. | +| 21 | 2b6cb03 | fix: ensure dex cache directory exists before proxy generation (#1938) | ported | this commit | `RuntimeHelper.java`: `mkdirs()` the `code_cache/secondary-dexes` dir and fall back to `appDir/secondary-dexes` when it's missing/unwritable. `ProxyGenerator.java` (runtime-binding-generator): create the parent dir before `createNewFile()` — prevents ENOENT crashes on newer Android. Verified: app + generators compile exit 0. | +| 23 | c9d41e6 + fce8e29 | fix(DexFactory): inject DEX into parent class loader (#1951) + register with a single class loader (#1968) | ported | this commit | Combined end-state (`fce8e29` supersedes `c9d41e6`). Enables `Class.forName()` to find runtime-generated proxies (e.g. FragmentFactory). `DexFactory`: new `injectIntoParentClassLoader` ctor flag; `resolveClass` adds the jar into the app's own `BaseDexClassLoader` via `injectDexIntoClassLoader` (API 24+ `addDexPath`; 23/<23 `appendDexElements` using `DexPathList.makePathElements`/`makeDexElements`) so the `DexFile` has a single owner (avoids ART "registered with multiple class loaders" on release builds), falling back to an isolated `DexClassLoader` on failure. `Runtime.java` passes `isMainThread` (workerId==0). Ported `testClassForNameDiscovery.js`. Verified: Java compile exit 0 (reflection has a try/catch fallback if hidden-API access is blocked). **Caveat:** hidden-API reflection is Android-version-sensitive — validate on real devices across versions **and a release/non-debuggable build**. | +| 24 | bfd7650 | fix(timers): order timers with the Java MessageQueue instead of ALooper fds | ported | this commit | Replaced the fork's ALooper-fd/pipe/watcher-thread/mutex/condvar timer delivery with a per-runtime `com.tns.TimerHandler` (new) bound to the runtime thread's Looper: each timer enqueues one anonymous due-token via `sendMessageAtTime`, and native `FireTimer()` (called from `handleMessage`) fires the earliest-due entry of `sortedTimers_` (now `vector` by exact sub-ms dueTime). `postTimer` posts due-now at `(long)now` (FIFO tie with `postDelayed(0)`) and future at `ceil(dueTime)`. Removed threadLoop/PumpTimerLoopCallback/fd/thread/mutex/condvar; added `extern "C" Java_com_tns_TimerHandler_nativeFireTimer` (symbol-bound). Also fixed the pre-existing callback-`napi_ref` leak by centralizing ref deletion in `TimerTask::Unschedule`. Ported `testNativeTimers.js`. Verified: native+Java compile all ABIs exit 0. **Caveat:** ordering semantics are device-only — run the regression tests on a device. | +| 25 | 89893ae | fix: URLSearchParams construction and iteration spec compliance (#1970) | ported | this commit | Rewrote the fork's napi `URLSearchParams` to the WebIDL spec: **constructor** now accepts string / record / sequence-of-pairs / primitive (via `napi_typeof` + `@@iterator` detection, `napi_get_all_property_names` for records, an ES-iterator driver for sequences with `IteratorClose` on abrupt completion, `napi_coerce_to_string` USVString coercion). **keys/values/entries** now return live spec iterators (a JS `{next}` object over ada `operator[]`, duplicate-key correct, `Symbol.iterator`→self, `Symbol.toStringTag`="URLSearchParams Iterator", freed via `napi_add_finalizer`); `prototype[@@iterator]===entries`. Added optional-value `delete`/`has`; `get` returns `null` (not undefined) on miss; brand checks ("Illegal invocation") + `ValueToString` coercion on every method. Ported the full 522-line spec test. Verified: native compile all ABIs exit 0. **Caveat:** spec behavior validated by the ported WPT-style tests **on-device**. | +| — | 92c2654 | fix: anchor relative dynamic imports at the file:// referrer's directory (#1976) | deferred | — | ESM-only: touches `ModuleInternalCallbacks.cpp` (deferred ESM file) + `.mjs` tests. Deferred with [[052cb21]]. | +| — | 45ed1f6 | fix: normalize "." and ".." in resolved module paths to dedupe modules (#1977) | deferred | — | ESM-only: `ModuleInternalCallbacks.cpp` + `.mjs` tests. Deferred with [[052cb21]]. | +| 26 | a84d3c7 | feat(workers): move worker threading and messaging to C++ (#1972) | partial | this commit | **Ported the engine-agnostic core (Path B):** moved worker threading/messaging from Java `HandlerThread` into C++ — new `runtime/workers/{WorkerWrapper,ConcurrentQueue,LooperTasks,WorkerMessage}`. Parent→worker via a `ConcurrentQueue` (eventfd+ALooper) inbox; worker→parent via the parent Runtime's `LooperTasks`. `WorkerWrapper` owns the worker `std::thread` (JVM attach → Java `initWorkerRuntime`/`runWorkerLoop`/`detachWorkerRuntime` → C++ inbox pump), nested-worker cascade, registry. Removed the Java `WorkerThread`/`WorkerThreadHandler`/`MainThreadHandler` worker branches, `initWorker`, the 6 worker native decls + JNI entrypoints, and `MessageType.java`/`JavaScriptErrorMessage.java`. **Perf:** removes 2 JNI hops + Java `Message`/`Handler` alloc + dispatch per postMessage. **Scoped out (V8-only, no napi surface):** `v8::ValueSerializer` structured clone and **SharedArrayBuffer** → payloads stay JSON on all engines (same semantics as before); `terminate()` is **cooperative** (no `v8::TerminateExecution` — a busy-loop worker isn't preempted). `napi_env`↔`WorkerWrapper` via a registry (no isolate data slot). Verified: native+Java compile all ABIs exit 0. **Caveat:** worker threading/lifecycle correctness is device-only — exercise the worker test suite on a device. | +| 27 | 55da2da + 4b5ab0a | feat(inspector): serve source maps to DevTools (#1969) + attach DevTools to Web Worker isolates (#1973) | ported | this commit | Ported both V8-inspector features onto the fork's napi+v8 hybrid inspector (`runtime/inspector/JsV8InspectorClient` reaches `env->isolate`). **#1969:** `Network.loadNetworkResource`/`IO.read`/`IO.close` disk-served source maps, `nsruntime://` sourceMapURL rewrite (+`disableSourceMapURLRewrite`), socket-thread message fast-path, resource-stream cleanup on disconnect. **#1973:** new `WorkerInspectorClient.{h,cpp}` (per-worker V8 inspector Target/flat-session CDP) + Target-domain worker-session routing + console history; hooked into the **new napi `WorkerWrapper`** — `CreateInspector(napi_env)`/`DestroyInspector()` in `BackgroundLooper` around `RunWorker`, `NotifyTerminating()` on cooperative terminate, `consoleLogCallback` via `WorkerWrapper::FromEnv`. Vendored `third_party/json.hpp`. All inspector code gated `#if defined(__V8__) && defined(APPLICATION_IN_DEBUG)` — **verified multi-engine-safe** (QuickJS build green). Verified: V8-13 debug + QuickJS native+Java compile exit 0. **Caveat:** actual DevTools attach / worker breakpoints / source-map fetch / pause-interrupt are device+DevTools-only — run a live debug session before shipping. | +| 4 | 3423e6f | feat: support 16 KB page sizes, gradle 8.5 (#1818) | partial | ca93e66 | **Ported:** 16 KB `-Wl,-z,max-page-size=16384` link option for arm64-v8a/x86_64 in `runtime/CMakeLists.txt` (verified present in ninja LINK_FLAGS); bumped `NS_DEFAULT_COMPILE_SDK_VERSION`/`NS_DEFAULT_BUILD_TOOLS_VERSION` 34→35 (both installed). **Skipped:** gradle-wrapper 8.4→8.7 and AGP 8.3.2→8.5.0 (new already newer: gradle 8.14.3 / AGP 8.12.1); STL `c++_shared`→`c++_static` (new deliberately uses `c++_shared` for multi-engine libc++ — would break engine `.so` setup). Verified: native reconfigure+relink exit 0. | + +## Verified duplication-check seeds (from planning; confirm at implementation time) + +**MISSING → implement:** `f033061` queueMicrotask · Ada v3.x chain (`b31fc5f`/`3633aed`/`3513ce7`/`83f611b`/`45fb275`) — new bundles Ada 2.9.0, old 3.3.0. + +**PARTIAL → port delta only:** `052cb21` ESM (deferred) · URLSearchParams spec follow-ups (`3e61cef`/`89893ae`/`288491f`). + +**ALREADY-PRESENT → skip w/ evidence:** `e98367c` performance api (Performance.h) · `e293636` companion-object inner-type (MetadataNode::SetInnerTypes hasOwnProperty guard) · `1fd144f`/`df4e81b` multithreaded runtime selection (Runtime.java ConcurrentHashMap + dual-path getCurrentRuntimeId). + +**Build deltas (new behind old):** compileSdk/targetSdk 34→35, buildTools 34→35, Kotlin 2.0.0→2.2.20, NDK default 27.1→27.3. AGP/Gradle 8.12.1, JDK 17, minSdk 21 already match. Apply only per-commit version/flag deltas; preserve multi-engine gradle/CMake machinery. diff --git a/platforms/android/README.md b/platforms/android/README.md new file mode 100644 index 000000000..a8a7797be --- /dev/null +++ b/platforms/android/README.md @@ -0,0 +1,137 @@ +

+ NativeScript Logo +

+ +

Node-API Android Runtime for NativeScript

+ +
+ +![NPM Version (with dist tag)](https://img.shields.io/npm/v/%40nativescript%2Fandroid/napi-v8?style=for-the-badge&logo=nativescript) +![NPM Version (with dist tag)](https://img.shields.io/npm/v/%40nativescript%2Fandroid/napi-quickjs?style=for-the-badge&logo=nativescript) +![NPM Version (with dist tag)](https://img.shields.io/npm/v/%40nativescript%2Fandroid/napi-hermes?style=for-the-badge&logo=nativescript) +![NPM Version (with dist tag)](https://img.shields.io/npm/v/%40nativescript%2Fandroid/napi-jsc?style=for-the-badge&logo=nativescript) + +
+ +[NativeScript](https://www.nativescript.org/) is an open-source framework for building truly native mobile applications using JavaScript. This repository contains the source code for the Node-API based Android Runtime used by NativeScript. + +The Android Runtime is a key component of the NativeScript framework. It is responsible for executing JavaScript code on Android devices. The runtime is built on top of [Node-API](https://nodejs.org/api/n-api.html) and provides a way to interact with the Android platform APIs from JavaScript. + +The new runtime is based on the Node-API and is designed to be more stable, faster, and easier to maintain. It also supports multiple JavaScript engines, including [V8](https://v8.dev/), [QuickJS](https://github.com/quickjs-ng/quickjs/), [Hermes](https://github.com/facebook/hermes), and [JavaScriptCore](https://docs.webkit.org/Deep%20Dive/JSC/JavaScriptCore.html). + + + +- [Main Projects](#main-projects) +- [Helper Projects](#helper-projects) +- [Build Prerequisites](#build-prerequisites) +- [How to build](#how-to-build) +- [How to run tests](#how-to-run-tests) +- [Misc](#misc) +- [Get Help](#get-help) + + + +## Project structure + +The repo is structured in the following projects (ordered by dependencies): + +- [**android-metadata-generator**](android-metadata-generator) - generates metadata necessary for the Android Runtime. +- [**android-binding-generator**](test-app/runtime-binding-generator) - enables Java & Android types to be dynamically created at runtime. Needed by the `extend` routine. +- [**android-runtime**](test-app/runtime) - contains the core logic behind the NativeScript's Android Runtime. This project contains native C++ code and needs the Android NDK to build properly. +- [**android-runtime-testapp**](test-app/app) - this is a vanilla Android Application, which contains the tests for the runtime project. +- [**napi-implementations**](test-app/runtime/src/main//cpp/napi/) - contains the implementation of the Node-API for each supported JS engine. + +## Helper Projects + +- [**android-static-binding-generator**](android-static-binding-generator) - build tool that generates bindings based on the user's javascript code. +- [**project-template**](build-artifacts/project-template-gradle) - this is an empty placeholder Android Application project, used by the [NativeScript CLI](https://github.com/NativeScript/nativescript-cli) when building an Android project. + +### Build Prerequisites + +Following are the minimal prerequisites to build the runtime package. + +- Install the latest [Android Studio](https://developer.android.com/studio/index.html). +- From the SDK Manager (Android Studio -> Tools -> Android -> SDK Manager) install the following components: + + - Android API Level 23, 24, 25, 26, 27 + - Android NDK + - Android Support Repository + - Download Build Tools + - CMake + - LLDB + +## How to Build + +Clone the repo: + +```Shell +git clone https://github.com/NativeScript/napi-android.git +``` + +Set the following environment variables: + +- `JAVA_HOME` such that `$JAVA_HOME/bin/java` points to your Java executable +- `ANDROID_HOME` pointing to where you have installed the Android SDK +- `ANDROID_NDK_HOME` pointing to the version of the Android NDK needed for this version of NativeScript + +Run the following commands **from the repository root** -- the Android build +entry points live in `scripts/` alongside the Apple ones: + +``` +git submodule update --init --force vendor/quickjs/source_ng +npm run setup-android +``` + +The first command fetches the vendored QuickJS-NG source at its pinned commit -- +a deliberate manual step, so nothing moves you off the pin behind your back (see +`vendor/quickjs/patches/README.md`). `npm run setup-android` then applies the +NativeScript patches to it and installs the jsparser build-tool dependencies. + +``` +npm run build-android +``` + +The command will let you interactively choose the JS engine you want to build +with, i.e. V8, QuickJS-NG, Hermes, JSC, PrimJS or Static Hermes. Pass the engine +directly to skip the prompt: + +``` +npm run build-android -- --engine=QUICKJS_NG +``` + +- The build process includes building of the runtime package (both optimized and with unstripped v8 symbol table), as well as all supplementary tools used for the android builds: metadata-generator, binding-generator, metadata-generator, static-binding-generator +- Output goes to the repo-root `dist/` and `build/` folders, next to the Apple build output: + - staged package: `dist/android__/` -- e.g. `dist/android_quickjs_napi/`, `dist/android_v8_13_jsi/` + - tarball: `build/npm-tarballs/nativescript-android__-.tgz` +- `` is the runtime tree the AAR was compiled from, `napi` (default) or `jsi` (`--binding=jsi`). They are two complete parallel runtimes, so each variant gets its own folder. + +## How to Run Tests + +- Go to subfolder test-app after you built the runtime. +- Start an emulator or connect a device. + +- Run command + +```Shell +gradlew runtests +``` + +## Working with the Runtime in Android Studio + +- Open the test-app folder in Android Studio. It represents a valid Android project and you are able to build and run a test application working with the Runtime from the source. + +Note: You might need to run the Android Studio from the command line in order to preserve the environment variables. This is in case you get errors like "missing npm" if starting the studio the usual way. + +You can change the JS engine used by the runtime by setting the `jsEngine` property in the [`build.gradle`](test-app/runtime/build.gradle) file in the root of the project. The possible values are `QUICKJS`, `HERMES`, `JSC` or `V8`. + +## Contribute + +We love PRs! Check out the [contributing guidelines](CONTRIBUTING.md). If you want to contribute, but you are not sure where to start - look for [issues labeled `help wanted`](https://github.com/NativeScript/napi-android/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). + +## Get Help + +Please, use [github issues](https://github.com/NativeScript/napi-android/issues) strictly for [reporting bugs](CONTRIBUTING.md#reporting-bugs) or [requesting features](CONTRIBUTING.md#requesting-new-features). For general questions and support, check out [Stack Overflow](https://stackoverflow.com/questions/tagged/nativescript) or ask our experts in [NativeScript community on Discord](https://nativescript.org/discord). + +## License + +This project is licensed under the Apache License Version 2.0. See the [LICENSE file](LICENSE) for more info. diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/AndroidManifest.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..087db9a59 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/java/com/tns/NativeScriptApplication.java b/platforms/android/build-artifacts/project-template-gradle/app/src/main/java/com/tns/NativeScriptApplication.java new file mode 100644 index 000000000..83353eb7a --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/java/com/tns/NativeScriptApplication.java @@ -0,0 +1,39 @@ +package com.tns; + +import android.app.Application; +import android.os.Build; +import androidx.multidex.MultiDex; + +public class NativeScriptApplication extends Application { + + private static NativeScriptApplication thiz; + + public NativeScriptApplication() { + thiz = this; + } + + public void onCreate() { + ManualInstrumentation.Frame frame = ManualInstrumentation.start("NativeScriptApplication.onCreate"); + try { + super.onCreate(); + com.tns.Runtime runtime = RuntimeHelper.initRuntime(this); + if (runtime != null) { + runtime.run(); + } + } finally { + frame.close(); + } + } + + public void attachBaseContext(android.content.Context base) { + super.attachBaseContext(base); + if (Build.VERSION.SDK_INT < 21) { + // As the new gradle plugin automatically uses multidex if necessary we need to call this for older android API versions + MultiDex.install(this); + } + } + + public static Application getInstance() { + return thiz; + } +} diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values-v21/colors.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values-v21/colors.xml new file mode 100644 index 000000000..1fbdcf647 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values-v21/colors.xml @@ -0,0 +1,4 @@ + + + #65adf1 + \ No newline at end of file diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values-v21/styles.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values-v21/styles.xml new file mode 100644 index 000000000..da2272ea5 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/colors.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..87f11778d --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #F5F5F5 + #757575 + #65adf1 + \ No newline at end of file diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/errorstyles.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/errorstyles.xml new file mode 100644 index 000000000..f16d76814 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/errorstyles.xml @@ -0,0 +1,7 @@ + + + + diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/strings.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..4abc791d6 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + __NAME__ + __TITLE_ACTIVITY__ + \ No newline at end of file diff --git a/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/styles.xml b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..75a1d7910 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platforms/android/build-artifacts/project-template-gradle/settings.gradle b/platforms/android/build-artifacts/project-template-gradle/settings.gradle new file mode 100644 index 000000000..e1226cf41 --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/settings.gradle @@ -0,0 +1,78 @@ +rootProject.name = "__PROJECT_NAME__" +include ':app'//, ':runtime', ':runtime-binding-generator' + +//project(':runtime').projectDir = new File("${System.env.ANDROID_RUNTIME_HOME}/test-app/runtime") +//project(':runtime-binding-generator').projectDir = new File("${System.env.ANDROID_RUNTIME_HOME}/test-app/runtime-binding-generator") + +file("google-services.json").renameTo(file("./app/google-services.json")) + +import org.gradle.internal.logging.text.StyledTextOutputFactory + +import java.nio.file.Paths + +import org.gradle.internal.logging.text.StyledTextOutputFactory +import groovy.json.JsonSlurper +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +def USER_PROJECT_ROOT = "$rootDir/../../" +def outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") +def ext = { + appResourcesPath = getProperty("appResourcesPath") + appPath = getProperty("appPath") +} + +def getAppPath = { -> + def relativePathToApp = "app" + def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + def nsConfig + + if (nsConfigFile.exists()) { + nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + } + + if (ext.appPath) { + // when appPath is passed through -PappPath=/path/to/app + // the path could be relative or absolute - either case will work + relativePathToApp = ext.appPath + } else if (nsConfig != null && nsConfig.appPath != null) { + relativePathToApp = nsConfig.appPath + } + + return Paths.get(USER_PROJECT_ROOT).resolve(relativePathToApp).toAbsolutePath() + } + + +def getAppResourcesPath = { -> + def relativePathToAppResources + def absolutePathToAppResources + def nsConfigFile = file("$USER_PROJECT_ROOT/nsconfig.json") + def nsConfig + + if (nsConfigFile.exists()) { + nsConfig = new JsonSlurper().parseText(nsConfigFile.getText("UTF-8")) + } + if (ext.appResourcesPath) { + // when appResourcesPath is passed through -PappResourcesPath=/path/to/App_Resources + // the path could be relative or absolute - either case will work + relativePathToAppResources = ext.appResourcesPath + absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + } else if (nsConfig != null && nsConfig.appResourcesPath != null) { + relativePathToAppResources = nsConfig.appResourcesPath + absolutePathToAppResources = Paths.get(USER_PROJECT_ROOT).resolve(relativePathToAppResources).toAbsolutePath() + } else { + absolutePathToAppResources = "${getAppPath()}/App_Resources" + } + return absolutePathToAppResources +} + +def applySettingsGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToSettingsGradle = "$appResourcesPath/Android/settings.gradle" + def settingsGradle = file(pathToSettingsGradle) + if (settingsGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${settingsGradle}" + apply from: pathToSettingsGradle + } +} + +applySettingsGradleConfiguration() diff --git a/platforms/android/build-artifacts/project-template-gradle/settings.json b/platforms/android/build-artifacts/project-template-gradle/settings.json new file mode 100644 index 000000000..ced41772c --- /dev/null +++ b/platforms/android/build-artifacts/project-template-gradle/settings.json @@ -0,0 +1,5 @@ +{ + "v8Version": "8.3.110.9", + "ndkRevision": "21.1.6352462", + "mksnapshotParams": "--profile_deserialization --turbo_instruction_scheduling --target_os=android --no-native-code-counters" +} diff --git a/platforms/android/build-artifacts/scripts/release-notes.js b/platforms/android/build-artifacts/scripts/release-notes.js new file mode 100644 index 000000000..1a01a09f4 --- /dev/null +++ b/platforms/android/build-artifacts/scripts/release-notes.js @@ -0,0 +1,269 @@ +// run this using "node release-notes.js", this will prepend to the start of the "../../CHANGELOG.MD" + +var https = require('https'); +var fs = require('fs'); + +var readline = require('readline'); + +var milestoneRequest = { + hostname: 'api.github.com', + path: '/repos/NativeScript/android-runtime/milestones', + method: 'GET', + headers: { + "User-Agent": "AutoRN", + "Authorization": undefined + } +}; + +var issuesRequest = { + hostname: 'api.github.com', + path: undefined, + method: 'GET', + headers: { + "User-Agent": "AutoRN", + "Authorization": undefined + } +}; + +var username, password; +var milestones = {}; + +getCredentials(); + +function getCredentials() { + + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.on('SIGINT', function() { + // Ctrl-C + console.log("Canceled."); + process.exit(1); + }); + + rl.question("Username: ", function(usr) { + rl.close(); + username = usr; + getPassword(function(pass) { + password = pass; + listMilestones(); + }); + }) +}; + +function listMilestones() { + + console.log("Request open milestones."); + milestoneRequest.headers.Authorization = "Basic " + Buffer.from(username + ":" + password).toString("base64"); + + var req = https.request(milestoneRequest, gitHubResponse(function (data) { + + if (!(data instanceof Array)) { + console.log("Error. Expected to get array with the issues for the milestone!\n"); + process.exit(1); + } else { + console.log("Open Milestones:"); + data.forEach(function (milestone) { + console.log(" - '" + milestone.title + "'"); + milestones[milestone.title] = milestone; + }); + console.log(); + } + + selectMilestone(); + + })); + req.end(); + + req.on('error', function (e) { + console.error(e); + }); +} + +function selectMilestone() { + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.on('SIGINT', function() { + // Ctrl-C + console.log("Canceled."); + process.exit(1); + }); + + rl.question("Milestone: ", function(milestone) { + rl.close(); + var selectedMilestone = milestones[milestone]; + if (selectedMilestone) { + createReleaseNotes(selectedMilestone); + } else { + console.log("Milestone error!"); + } + }); +} + +function createReleaseNotes(milestone) { + + console.log("Request closed issues in the milestone."); + issuesRequest.headers.Authorization = "Basic " + Buffer.from(username + ":" + password).toString("base64"); + issuesRequest.path = '/repos/NativeScript/android-runtime/issues?milestone=' + milestone.number + '&state=closed'; + + var req = https.request(issuesRequest, gitHubResponse(function (data) { + + if (!(data instanceof Array)) { + console.log("Error. Expected to get array with the milestones!\n"); + process.exit(1); + } else { + var issues = data; + + console.log("Received " + issues.length + " entries."); + + var bugs = issues.filter(function (i) { + return i.labels.filter(function (l) { + return l.name == "bug"; + }).length > 0; + }); + var features = issues.filter(function (i) { + return i.labels.filter(function (l) { + return l.name == "feature"; + }).length > 0; + }); + + var performance = issues.filter(function (i) { + return i.labels.filter(function (l) { + return l.name == "T:Performance"; + }).length > 0; + }); + + console.log(" - " + bugs.length + " fixed (e.g. T:Bug)"); + console.log(" - " + features.length + " new (e.g. T:Feature"); + console.log(" - " + performance.length + " performance (e.g. T:Performance"); + + var md = ""; + + md += milestone.title + "\r\n==\r\n\r\n"; + + printSection("What's New", features); + printSection("Bug Fixes", bugs); + printSection("Performance", performance); + + function printSection(title, issues) { + if (issues.length > 0) { + md += "## " + title + "\r\n\r\n"; + issues.forEach(printIssue); + md += "\r\n"; + } + } + + function printIssue(i) { + md += " - [" + i.title + " (#" + i.number + ")](" + i.html_url + ")\r\n"; + } + + var currentLog = fs.readFileSync("../../CHANGELOG.md"); + var newLog = md + currentLog; + fs.writeFileSync("../../CHANGELOG.md", newLog); + } + + })); + req.end(); + + req.on('error', function (e) { + console.error(e); + }); +} + +function gitHubResponse(f) { + var msg = ""; + + return function(res) { + + console.log(); + + var nlTerminate = false; + if (res.headers['x-ratelimit-limit']) { + console.log("Rate limit: " + res.headers['x-ratelimit-limit']); + nlTerminate = true; + } + if (res.headers['x-ratelimit-remaining']) { + console.log("Rate remaining: " + res.headers['x-ratelimit-remaining']); + nlTerminate = true; + } + if (res.headers['x-ratelimit-reset']) { + console.log("Rate will reset: " + new Date(res.headers['x-ratelimit-reset'] * 1000)); + nlTerminate = true; + } + + if (nlTerminate) { + console.log(); + } + + res.on('data', function (d) { + msg += d; + }); + res.on('end', function (d) { + + var data = JSON.parse(msg); + if (data.message) { + console.log("Message:\n" + data.message + "\n"); + } + if (data.documentation_url) { + console.log("Documentation:\n" + data.documentation_url + "\n"); + } + + f(data); + }); + } +} + +function getPassword(callback) { + process.stdout.write("Password: "); + + var stdin = process.stdin; + stdin.resume(); + stdin.setRawMode(true); + stdin.resume(); + stdin.setEncoding('utf8'); + + var password = ''; + stdin.on('data', onStdinData); + + function onStdinData(ch) { + ch = ch + ""; + + switch (ch) { + case "\n": + case "\r": + case "\u0004": + // They've finished typing their password + process.stdout.write('\n'); + stdin.setRawMode(false); + stdin.pause(); + callback(password); + stdin.removeListener('data', onStdinData); + break; + case "\u0003": + // Ctrl-C + console.log("Canceled."); + process.exit(1); + break; + case "\u007F": + // Backspace + if (password.length > 0) { + password = password.substring(0, password.length - 1); + process.stdout.write('*\033[<1>D'); + } else { + process.stdout.write(' \033[<1>D'); + } + break; + default: + // More passsword characters + process.stdout.write('*\033[<1>D'); + password += ch; + break; + } + } +} + diff --git a/platforms/android/build.dev.sh b/platforms/android/build.dev.sh new file mode 100755 index 000000000..46e29bf0c --- /dev/null +++ b/platforms/android/build.dev.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# smaller version of build.sh that sets the commit hash to the current git commit hash and uses the package.json version + +echo "Ensure adb is in PATH" +export PATH="$ANDROID_HOME/platform-tools:$PATH" +adb version + +echo "Update submodule" +git submodule update --init + +echo "Cleanup old build and test artefacts" +rm -rf consoleLog.txt +rm -rf test-app/dist/*.xml + +./gradlew cleanRuntime + +./gradlew -PgitCommitVersion=$(git rev-parse HEAD) +cp dist/nativescript-android-*.tgz dist/nativescript-android.tgz + diff --git a/platforms/android/build.gradle b/platforms/android/build.gradle new file mode 100644 index 000000000..d2eab1953 --- /dev/null +++ b/platforms/android/build.gradle @@ -0,0 +1,556 @@ +/* +* Usage: +* gradlew - Builds the NativeScript Android App Package using an application project template. + gradlew -PgitCommitVersion - sets the commit version of the build + gradlew -PpreReleaseVersion - sets the pre-release version of the build (as per semver spec "-alpha" value results in 1.0.0-alpha) + gradlew -PnoCCache - set this flag if you don't want CCache to be used in CMake build + +*/ + +defaultTasks 'createPackage' + +import groovy.json.JsonSlurper + +import groovy.json.JsonBuilder +import groovy.json.JsonOutput + +def onlyX86 = project.hasProperty("onlyX86") +def useCCache = !project.hasProperty("noCCache") +def hasNdkVersion = project.hasProperty("ndkVersion") + +def hasHostObjects = project.hasProperty("useHostObjects") + +if (hasNdkVersion) { + println "Using NDK version " + ndkVersion +} + +def hasEngine = project.hasProperty("engine"); + +def isWinOs = System.properties['os.name'].toLowerCase().contains('windows') +def pVersion = "no package version was provided by build.gradle build" +def arVersion = "no commit sha was provided by build.gradle build" +def generateRegularRuntimePackage = !project.hasProperty("skipUnoptimized") + +def jsEngine = engine ?: 'V8-10' + +// Which runtime tree the AAR was compiled from -- see test-app/runtime/build.gradle. +// It is part of the output name because the two are different runtimes, not two +// flavours of one, and a build of each has to be able to sit on disk at once. +def bindingLayer = project.findProperty("bindingLayer") ?: "napi" + +// QUICKJS_NG is the only quickjs lane left (QUICKJS is an alias for it), so it +// carries the plain name; everything else is just lowercased with '-' -> '_'. +def engineSlug = jsEngine.toLowerCase().replace('-', '_') +if (engineSlug == "quickjs_ng") { + engineSlug = "quickjs" +} + +// Build outputs live in the repo-root dist/ and build/ folders, next to the +// Apple ones, rather than in a platforms/android/dist_ of their own. +def REPO_ROOT = file("$rootDir/../..").canonicalPath +def PACKAGE_VARIANT = "android_${engineSlug}_${bindingLayer}" +def DIST_PATH = "$REPO_ROOT/dist/$PACKAGE_VARIANT" +def TARBALL_PATH = "$REPO_ROOT/build/npm-tarballs" +def TEST_APP_PATH = "$rootDir/test-app" +def BUILD_TOOLS_PATH = "$TEST_APP_PATH/build-tools" +def DIST_FRAMEWORK_PATH = "$DIST_PATH/framework" +def BYTECODE_TOOLS_PATH = "$rootDir/tools/bytecode-compiler" + +// Maps an ns_engine value to its bytecode-compiler bin/. Engines with no +// bytecode compiler (V8*, JSC) are absent, so no binaries are shipped for them. +def bytecodeEngineDir = [ + "HERMES" : "hermes", + "QUICKJS" : "quickjs", + "QUICKJS_NG": "quickjs-ng", + "PRIMJS" : "primjs", +].get(jsEngine) + +task checkEnvironmentVariables { + if ("$System.env.JAVA_HOME" == "" || "$System.env.JAVA_HOME" == "null") { + throw new GradleException("Set JAVA_HOME to point to the correct Jdk location\n") + } + + if ("$System.env.ANDROID_HOME" == "" || "$System.env.ANDROID_HOME" == "null") { + throw new GradleException("Set ANDROID_HOME to point to the correct Android SDK location\n") + } + + if ("$System.env.ANDROID_NDK_HOME" == "" || "$System.env.ANDROID_NDK_HOME" == "null" && !project.hasProperty("ndkVersion")) { + logger.warn("Warning: The ANDROID_NDK_HOME is not set nor the ndkVersion project property. The build might fail if a ndk cannot be found.\n") + } + + + if ("$System.env.GIT_COMMIT" == "null" && !project.hasProperty("gitCommitVersion")) { + logger.warn("Warning: The GIT_COMMIT is not set. This NativeScript Android Runtime will not be tagged with the git commit it is build from\n") + } + + if (project.hasProperty("metadataGen") && !file("../android-metadata-generator/dist/tns-android-metadata-generator-0.0.1.tgz").exists()) { + throw new GradleException("android-metadata-generator build output not found and no metadataGen option specified. Build android-metadata-generator first.\n") + } +} + +task cleanDistDir(type: Delete) { + delete DIST_PATH +} + +task createDistDir { + dependsOn 'cleanDistDir' + + doLast { + def distF = new File(DIST_PATH) + distF.mkdirs() + } +} + +def getCCacheVersion = { -> + try { + def ccacheVersionOutput = new ByteArrayOutputStream() + exec { + commandLine "ccache", "--version" + standardOutput = ccacheVersionOutput + } + + return ccacheVersionOutput.toString().trim() + } catch (all) { + return 'CCache not found!' + } +} + +task getPackageVersion { + doLast { + String content = new File("$rootDir/package.json").getText("UTF-8") + def jsonSlurper = new JsonSlurper() + def packageJsonMap = jsonSlurper.parseText(content) + + pVersion = packageJsonMap.version + + println "Using runtime version from package.json '${pVersion}'" + + if (project.hasProperty("packageVersion") && packageVersion != "") { + pVersion += "-" + packageVersion + + println "Using packageVersion property '${pVersion}'" + } + + + if (project.hasProperty("preReleaseVersion")) { + pVersion += "-" + preReleaseVersion + + println "Adding preReleaseVersion property '${pVersion}' to package version" + } + + if(onlyX86) { + pVersion += "-onlyX86" + } + + println "The package version is '${pVersion}'" + } +} + +task getCommitVersion { + doLast { + if (project.hasProperty("gitCommitVersion")) { + println "Using commit version property " + gitCommitVersion + arVersion = gitCommitVersion + } else if ("$System.env.GIT_COMMIT" != "null") { + println "Using commit version environment variable " + "$System.env.GIT_COMMIT" + String content = "$System.env.GIT_COMMIT" + arVersion = content.trim() + } + } +} + +task generateDtsgJar(type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + if (isWinOs) { + commandLine "cmd", "/c", "gradlew", ":dts-generator:jar", "--warning-mode", "all" + } else { + commandLine "./gradlew", ":dts-generator:jar", "--warning-mode", "all" + } + } +} + +task jsParserNPMInstall(type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH/build-tools/jsparser" + if (isWinOs) { + commandLine "cmd", "/c", "npm", "install" + } else { + commandLine "npm", "install" + } + } +} + +task generateSbgJar(type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + if (isWinOs) { + commandLine "cmd", "/c", "gradlew", ":static-binding-generator:jar", "--warning-mode", "all" + } else { + commandLine "./gradlew", ":static-binding-generator:jar", "--warning-mode", "all" + } + } +} + +task generateMdgJar(type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + if (isWinOs) { + commandLine "cmd", "/c", "gradlew", ":android-metadata-generator:jar", "--warning-mode", "all" + } else { + commandLine "./gradlew", ":android-metadata-generator:jar", "--warning-mode", "all" + } + } +} + +task cleanRuntime (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + if (isWinOs) { + commandLine "cmd", "/c", "gradlew", ":runtime:clean", "--warning-mode", "all" + } else { + commandLine "./gradlew", ":runtime:clean", "--warning-mode", "all" + } + } +} + +def getAssembleReleaseBuildArguments = { -> + def arguments = [] + if (isWinOs) { + arguments += ["cmd", "/c", "gradlew"] + } else { + arguments.add("./gradlew") + } + arguments += [":runtime:assembleRelease", "-PpackageVersion=${pVersion}", "-PgitCommitVersion=${arVersion}"] + if (onlyX86) { + arguments.add("-PonlyX86") + } + if (useCCache) { + arguments.add("-PuseCCache") + } + if (hasNdkVersion) { + arguments.add("-PndkVersion="+ndkVersion) + } + + if (hasEngine) { + arguments.add("-Pengine=${engine}") + } + + if (hasHostObjects) { + arguments.add("-PuseHostObjects") + } + + arguments += ["--warning-mode", "all"] + + return arguments +} + +task generateOptimizedRuntimeAar (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + def arguments = getAssembleReleaseBuildArguments() + arguments.add("-Poptimized") + commandLine arguments + } +} + +task generateOptimizedWithInspectorRuntimeAar (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + def arguments = getAssembleReleaseBuildArguments() + arguments.add("-PoptimizedWithInspector") + commandLine arguments + } +} + +task generateRuntimeAar (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + def arguments = getAssembleReleaseBuildArguments() + commandLine arguments + } +} + +task buildJsParser (type: Exec) { + workingDir "$BUILD_TOOLS_PATH/jsparser" + doFirst { + if (isWinOs) { + commandLine "cmd", "/c", "npm", "run", "build" + } else { + commandLine "npm", "run", "build" + } + } +} + +task copyFilesToProjectTemeplate { + doLast { + copy { + from "$TEST_APP_PATH/app/src/debug" + into "$DIST_FRAMEWORK_PATH/app/src/debug" + } + copy { + from "$TEST_APP_PATH/app/src/main/assets/internal" + into "$DIST_FRAMEWORK_PATH/app/src/main/assets/internal" + } + copy { + from "$TEST_APP_PATH/app/src/main/java/com/tns/" + include "*.java" + exclude "NativeScriptApplication.java" + exclude "NativeScriptActivity.java" + into "$DIST_FRAMEWORK_PATH/app/src/main/java/com/tns" + } + copy { + from "$TEST_APP_PATH/app/src/main/java/com/tns/internal" + into "$DIST_FRAMEWORK_PATH/app/src/main/java/com/tns/internal" + } + copy { + from "$BUILD_TOOLS_PATH/static-binding-generator/build/libs/static-binding-generator.jar" + into "$DIST_FRAMEWORK_PATH/build-tools" + } + copy { + from "$BUILD_TOOLS_PATH/android-dts-generator/dts-generator/build/libs/dts-generator.jar" + into "$DIST_FRAMEWORK_PATH/build-tools" + } + copy { + from "$BUILD_TOOLS_PATH/jsparser/build/js_parser.js" + into "$DIST_FRAMEWORK_PATH/build-tools/jsparser" + } + // Metadata filtering runs from the generated project template. Ship + // the scripts with the template, but not their development/runtime + // dependencies: seed.js invokes the sibling harvest.js and resolves + // Babel from the consuming app's node_modules. + copy { + from "$BUILD_TOOLS_PATH/metadata-filter" + include "*.js" + into "$DIST_FRAMEWORK_PATH/build-tools/metadata-filter" + } + copy { + from "$BUILD_TOOLS_PATH/android-metadata-generator/build/libs/android-metadata-generator.jar" + into "$DIST_FRAMEWORK_PATH/build-tools" + } + // Ship the engine-generic bytecode compiler (driver + adapters). The + // per-host compiler binaries under bin/ are shipped only for the engine + // this framework is built for, so a V8 template doesn't carry hermesc. + copy { + from "$BYTECODE_TOOLS_PATH" + into "$DIST_FRAMEWORK_PATH/build-tools/bytecode-compiler" + exclude "bin/**" + } + if (bytecodeEngineDir != null) { + copy { + from "$BYTECODE_TOOLS_PATH/bin/${bytecodeEngineDir}" + into "$DIST_FRAMEWORK_PATH/build-tools/bytecode-compiler/bin/${bytecodeEngineDir}" + } + } + copy { + from "$TEST_APP_PATH/runtime/build/outputs/aar/runtime-regular-release.aar" + into "$DIST_FRAMEWORK_PATH/app/libs/runtime-libs" + rename "runtime-regular-release.aar", "nativescript-regular.aar" + } + copy { + from "$TEST_APP_PATH/runtime/build/outputs/aar/runtime-optimized-release.aar" + into "$DIST_FRAMEWORK_PATH/app/libs/runtime-libs" + rename "runtime-optimized-release.aar", "nativescript-optimized.aar" + } + copy { + from "$TEST_APP_PATH/runtime/build/outputs/aar/runtime-optimized-with-inspector-release.aar" + into "$DIST_FRAMEWORK_PATH/app/libs/runtime-libs" + rename "runtime-optimized-with-inspector-release.aar", "nativescript-optimized-with-inspector.aar" + } + copy { + from "$TEST_APP_PATH/app/build.gradle" + into "$DIST_FRAMEWORK_PATH/app" + } + copy { + from "$TEST_APP_PATH/build.gradle" + into "$DIST_FRAMEWORK_PATH" + } + + ant.propertyfile(file: "$TEST_APP_PATH/gradle.properties") { + entry(key: "ns_engine", value: engine) + } + copy { + from "$TEST_APP_PATH/gradle.properties" + into "$DIST_FRAMEWORK_PATH" + } + + copy { + from "$TEST_APP_PATH/gradle-helpers/paths.gradle" + into "$DIST_FRAMEWORK_PATH/gradle-helpers" + } + copy { + from "$TEST_APP_PATH/gradle-helpers/user_properties_reader.gradle" + into "$DIST_FRAMEWORK_PATH/gradle-helpers" + } + copy { + from "$TEST_APP_PATH/app/gradle-helpers/CustomExecutionLogger.gradle" + into "$DIST_FRAMEWORK_PATH/app/gradle-helpers" + } + copy { + from "$TEST_APP_PATH/app/gradle-helpers/AnalyticsCollector.gradle" + into "$DIST_FRAMEWORK_PATH/app/gradle-helpers" + } + copy { + from "$TEST_APP_PATH/app/gradle-helpers/BuildToolTask.gradle" + into "$DIST_FRAMEWORK_PATH/app/gradle-helpers" + } + copy { + from "$TEST_APP_PATH/gradle" + into "$DIST_FRAMEWORK_PATH/gradle" + } + copy { + from "$TEST_APP_PATH/gradlew" + into "$DIST_FRAMEWORK_PATH" + } + copy { + from "$TEST_APP_PATH/gradlew.bat" + into "$DIST_FRAMEWORK_PATH" + } + } +} + +task copyProjectTemplate(type: Copy) { + from "$rootDir/build-artifacts/project-template-gradle" + into "$DIST_FRAMEWORK_PATH" +} + +task copyPackageJson(type: Copy) { + from "$rootDir/package.json" + into "$DIST_PATH" +} + +task setPackageVersionInPackageJsonFile { + doLast { + def inputFile = new File("$DIST_PATH/package.json") + def json = new JsonSlurper().parseText(inputFile.text) + json.version = pVersion + def jb = new JsonBuilder(json) + inputFile.text = JsonOutput.prettyPrint(jb.toString()) + } +} + +task copyReadme(type: Copy) { + from "README.md" + into "$DIST_PATH" +} + +task createNpmPackage(type: Exec) { + doFirst { + workingDir "$DIST_PATH" + new File(TARBALL_PATH).mkdirs() + + if (isWinOs) { + commandLine "cmd", "/c", "npm", "pack", "--pack-destination", TARBALL_PATH + } else { + commandLine "npm", "pack", "--pack-destination", TARBALL_PATH + } + } + + // npm names the tarball after the package (nativescript-android-), + // which is identical for every engine and runtime tree. Rename it to the + // variant so parallel builds don't overwrite each other in the shared + // tarball folder. + doLast { + def version = new JsonSlurper().parseText(new File("$DIST_PATH/package.json").text).version + def packed = new File("$TARBALL_PATH/nativescript-android-${version}.tgz") + def named = new File("$TARBALL_PATH/nativescript-${PACKAGE_VARIANT}-${version}.tgz") + if (packed.exists()) { + named.delete() + if (!packed.renameTo(named)) { + throw new GradleException("Failed to rename ${packed} to ${named}") + } + } + println "Tarball: ${named}" + } +} + +generateSbgJar.dependsOn(generateDtsgJar) +generateSbgJar.dependsOn(jsParserNPMInstall) +generateMdgJar.dependsOn(generateSbgJar) +buildJsParser.dependsOn(jsParserNPMInstall) +createDistDir.dependsOn(generateMdgJar) + +getPackageVersion.dependsOn(createDistDir) +getCommitVersion.dependsOn(getPackageVersion) +generateOptimizedRuntimeAar.dependsOn(getCommitVersion) + +generateOptimizedWithInspectorRuntimeAar.dependsOn(generateOptimizedRuntimeAar) + +if (generateRegularRuntimePackage) { + generateRuntimeAar.dependsOn(generateOptimizedWithInspectorRuntimeAar) + buildJsParser.dependsOn(generateRuntimeAar) +} else { + buildJsParser.dependsOn(generateOptimizedWithInspectorRuntimeAar) +} + +copyFilesToProjectTemeplate.dependsOn(buildJsParser) +copyProjectTemplate.dependsOn(copyFilesToProjectTemeplate) +copyPackageJson.dependsOn(copyProjectTemplate) +setPackageVersionInPackageJsonFile.dependsOn(copyPackageJson) +copyReadme.dependsOn(setPackageVersionInPackageJsonFile) +createNpmPackage.dependsOn(copyReadme) + +task createPackage { + println "CCache version: " + getCCacheVersion() + + description "Builds the NativeScript Android cleanBuildArtefactsApp Package using an application project template." + dependsOn createNpmPackage + println "Creating NativeScript Android Package" +} + +task runAstTests (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH/build-tools/jsparser/tests" + if (isWinOs) { + commandLine "cmd", "/c", "npm", "test" + } else { + commandLine "npm", "test" + } + } +} + +task runSbgTests (type: Exec, dependsOn: 'runAstTests') { + doFirst { + workingDir "$TEST_APP_PATH" + if (isWinOs) { + commandLine "cmd", "/c", "gradlew", ":static-binding-generator:test", "--warning-mode", "all" + } else { + commandLine "./gradlew", ":static-binding-generator:test", "--warning-mode", "all" + } + } +} + +def getRunTestsBuildArguments = { taskName -> + def arguments = [] + if (isWinOs) { + arguments += ["cmd", "/c", "gradlew"] + } else { + arguments.add("./gradlew") + } + arguments += ["-b", "runtests.gradle", taskName] + if (onlyX86) { + arguments.add("-PonlyX86") + } + if (useCCache) { + arguments.add("-PuseCCache") + } + arguments += ["--warning-mode", "all"] + return arguments +} + +task runTests (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + commandLine getRunTestsBuildArguments("runtests") + } +} + +task runTestsAndVerifyResults (type: Exec) { + doFirst { + workingDir "$TEST_APP_PATH" + commandLine getRunTestsBuildArguments("runtestsAndVerifyResults") + } +} + +runSbgTests.dependsOn(generateMdgJar) +runTests.dependsOn(runSbgTests) diff --git a/platforms/android/build.sh b/platforms/android/build.sh new file mode 100755 index 000000000..2f42b49c9 --- /dev/null +++ b/platforms/android/build.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +############################################################################################### +# Android Runtime build script for CI. +# This file is used by the CI only and it's not meant for regular development. +############################################################################################### + +echo "Ensure adb is in PATH" +export PATH="$ANDROID_HOME/platform-tools:$PATH" +adb version + +echo "Update submodule" +git submodule update --init + +echo "Cleanup old build and test artefacts" +rm -rf consoleLog.txt +rm -rf test-app/dist/*.xml + +echo "Stopping running emulators if any" +for KILLPID in `ps ax | grep 'emulator' | grep -v 'grep' | awk ' { print $1;}'`; do kill -9 $KILLPID; done +for KILLPID in `ps ax | grep 'qemu' | grep -v 'grep' | awk ' { print $1;}'`; do kill -9 $KILLPID; done +for KILLPID in `ps ax | grep 'adb' | grep -v 'grep' | awk ' { print $1;}'`; do kill -9 $KILLPID; done + +./gradlew cleanRuntime +if [ "$1" != 'unit_tests_only' ]; then + echo "Building Android Runtime with paramerter packageVersion: $ANDROID_PACKAGE_VERSION and commit: $GIT_COMMIT" + if [ "$NO_CCACHE" == 'true' ]; then + ./gradlew -PpackageVersion=$ANDROID_PACKAGE_VERSION -PgitCommitVersion=$GIT_COMMIT -PnoCCache + else + ./gradlew -PpackageVersion=$ANDROID_PACKAGE_VERSION -PgitCommitVersion=$GIT_COMMIT + fi + + cp dist/nativescript-android-*.tgz dist/nativescript-android.tgz +else + echo "Building Android Runtime for x86 unit tests with paramerter packageVersion: $ANDROID_PACKAGE_VERSION and commit: $GIT_COMMIT" + if [ "$NO_CCACHE" == 'true' ]; then + ./gradlew -PpackageVersion=$ANDROID_PACKAGE_VERSION -PgitCommitVersion=$GIT_COMMIT -PskipUnoptimized -PonlyX86 -PnoCCache + else + ./gradlew -PpackageVersion=$ANDROID_PACKAGE_VERSION -PgitCommitVersion=$GIT_COMMIT -PskipUnoptimized -PonlyX86 + fi + cp dist/nativescript-android-*.tgz dist/nativescript-android.tgz +fi + +if [ "$2" != '' ]; then + listOfEmulators=$2 +else + listOfEmulators="Emulator-Api28-Google Emulator-Api23-Default Emulator-Api19-Default" +fi + +# Run static binding generator unit tests +./gradlew runSbgTests + +for emulator in $listOfEmulators; do + echo "Start emulator $emulator" + $ANDROID_HOME/emulator/emulator -avd ${emulator} -verbose -wipe-data -gpu on& + find ~/.android/avd/${emulator}.avd -type f -name 'config.ini' -exec cat {} + + + echo "Run Android Runtime unit tests for $emulator" + $ANDROID_HOME/platform-tools/adb wait-for-device + $ANDROID_HOME/platform-tools/adb -s emulator-5554 logcat -c + $ANDROID_HOME/platform-tools/adb -s emulator-5554 logcat > consoleLog.txt& + $ANDROID_HOME/platform-tools/adb -s emulator-5554 logcat > consoleLog$emulator.txt& + + if [ "$1" != 'unit_tests_only' ]; then + ./gradlew runtests + else + ./gradlew runtests -PonlyX86 + fi + + echo "Rename unit test result" + ( + cd ./test-app/dist + mv android_unit_test_results.xml $emulator.xml + ) + + echo "Stopping running emulators" + for KILLPID in `ps ax | grep 'emulator' | grep -v 'grep' | awk ' { print $1;}'`; do kill -9 $KILLPID; done + for KILLPID in `ps ax | grep 'qemu' | grep -v 'grep' | awk ' { print $1;}'`; do kill -9 $KILLPID; done + for KILLPID in `ps ax | grep 'adb' | grep -v 'grep' | awk ' { print $1;}'`; do kill -9 $KILLPID; done +done + +echo $cwd +cd $cwd + diff --git a/platforms/android/debug.keystore b/platforms/android/debug.keystore new file mode 100644 index 000000000..364e105ed Binary files /dev/null and b/platforms/android/debug.keystore differ diff --git a/platforms/android/docs/bytecode.md b/platforms/android/docs/bytecode.md new file mode 100644 index 000000000..cf11ecb93 --- /dev/null +++ b/platforms/android/docs/bytecode.md @@ -0,0 +1,182 @@ +# Compile-time bytecode + +For **release** builds, NativeScript Android can ship pre-compiled engine +bytecode instead of plain JavaScript. Parsing + compiling JS is a large part of +cold-start cost; shipping bytecode moves that work to build time and gives a much +faster time-to-interactive (TTI). + +The mechanism is **engine-generic**. The only engine-specific piece is running +that engine's compiler binary, which is encapsulated in a small adapter under +[`tools/bytecode-compiler/lib`](../tools/bytecode-compiler/lib). + +| Engine | Bytecode | Compiler CLI | Status | +| ------- | -------- | ------------ | ------ | +| Hermes | ✅ HBC | `hermesc` (upstream) | wired end-to-end | +| QuickJS-NG | ✅ | `nsbc-quickjs-ng` shim (`native/qjs-compile.c`) | wired end-to-end; also serves the `QUICKJS` alias | +| PrimJS | ✅ | `nsbc-primjs` shim (`native/primjs-compile.c`) | wired end-to-end | +| V8 | ⚠️ | runtime code-cache only (no AOT format) | see [v8-code-cache.md](./v8-code-cache.md) | +| JSC | ⚠️ | runtime code-cache only (no AOT format) | `js_run_bytecode_file` falls back to source | + +All three AOT engines are enabled (`ready: true`) and have a runtime read side. +Their compiler CLIs are built by the +[bytecode-compilers workflow](../../../.github/workflows/bytecode-compilers.yml); +an engine with no binary for the build host simply ships source. V8 and JSC have +no compile-time format at all — both cache code at runtime instead +(`js_cache_script` / `js_run_cached_script`), so their `js_run_bytecode_file` +returns `napi_cannot_run_js` by design, not because it is unimplemented. + +There is no bellard QuickJS lane: that tree was dropped, and the runtime's +`QUICKJS` is an alias for `QUICKJS_NG`, which the `quickjs-ng` adapter answers to +under both names. + +### Bytecode container (non-Hermes engines) + +`hermesc` emits a self-identifying HBC blob. QuickJS's `qjsc` emits a C array, not +a loadable blob, so the QuickJS-family shims (`native/`) wrap the engine's +`JS_WriteObject` output in a small container the runtime detects: + +``` +[8-byte magic][4-byte format version, little-endian][JS_WriteObject payload] +``` + +Magic is per engine (`NSBCNGS\0`, `NSBCPJS\0`) and must match the adapter's +`magic` and the runtime detection. The runtime read side strips the 12-byte +header and hands the payload to `JS_ReadObject` + `JS_EvalFunction` (PrimJS: the +`LEPUS_*` equivalents). + +## How it works + +1. A normal app compiles its JS bundle and copies it into `assets/app` before the + Android build starts. +2. During a **release** build, after Gradle merges the assets, the + `compileBytecode` task runs the generic driver over the merged `app/` folder. + The driver replaces every `.js` file with bytecode for the active engine. + **The files keep their `.js` names**, so module resolution is unaffected. +3. At load time the runtime reads the file header. If it sees the engine's + bytecode magic (for Hermes, `0x1F1903C103BC1FC6`) it runs the bytecode + directly; otherwise it falls back to compiling the source. Detection is + per-file and authoritative — there is no separate flag that can get out of + sync. + +The C++ loader calls a single engine hook for both the `require` path +([ModuleInternal.cpp](../../../NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp)) +and the raw-script path +([Runtime::RunScript](../../../NativeScript/runtime/android/napi/Runtime.cpp)): + +```c +// napi/common/jsr_common.h +napi_status js_run_bytecode_file(napi_env env, const char *file, napi_value *result); +``` + +The runtime has two trees — `runtime/android/napi` (the default) and +`runtime/android/jsi` — and each carries its own loader. The napi side goes +through the hook above; the jsi side detects the container in +[EngineHost::ExecuteBytecodeFile](../../../NativeScript/runtime/android/jsi/EngineHost.cpp). +A change to one is not a change to the other. + +### Module wrapping + +The runtime wraps every `require`d module in a function before executing it: + +```js +(function(module, exports, require, __filename, __dirname){ /* module source */ +}) +``` + +Running a module's bytecode must yield that same wrapper function, so the build +step wraps the source with the **identical** prologue/epilogue before compiling +(the completion value of the compiled program is the wrapper function — verified +by disassembly: the Hermes global function ends with `CreateClosure; Ret`). This +wrapping is engine-independent, so it lives in the generic driver. + +Files the runtime runs **unwrapped** as raw scripts (via `Runtime::RunScript`, +e.g. `internal/ts_helpers.js`) are compiled as-is. The build's `--raw` list keeps +these in sync (default: `internal/ts_helpers.js`). + +The prologue/epilogue live in several places that must stay byte-for-byte +identical (`MODULE_PROLOGUE` / `MODULE_EPILOGUE` in each): + +- `NativeScript/runtime/android/napi/modules/module/ModuleInternal.cpp` +- `NativeScript/runtime/android/jsi/modules/module/ModuleInternal.cpp` +- `tools/bytecode-compiler/compile-bytecode.js` + +(The Apple runtime carries its own copy under `runtime/apple/`.) + +## The compiler tooling + +The compiler lives in [`tools/bytecode-compiler`](../tools/bytecode-compiler) and +is copied into the framework template under `build-tools/bytecode-compiler` when +the runtime is packaged (see `copyFilesToProjectTemeplate` in the root +`build.gradle`). Only the active engine's host binaries are shipped, so a V8 +template doesn't carry `hermesc`. + +Compiler binaries are host-specific, laid out as +`bin///` where `` is `-` +(`darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64`, `win32-x64`). A host +with no binary is simply absent — PrimJS, for instance, ships only the three Unix +hosts its GN toolchain can build. The driver detects the current host +automatically and, if there is no compiler for it, leaves the app as source. + +## Enabling / disabling + +Bytecode is **on by default** for release builds when the active engine has a +ready compiler for the build host. To disable it (ship plain JS): + +``` +./gradlew assembleRelease -Prelease -PnsBytecodeDisabled +``` + +Gradle properties (all optional): + +| Property | Effect | +| ------------------------------ | ------------------------------------------------- | +| `-PnsBytecodeDisabled` | Disable bytecode for this release build | +| `-PnsBytecodeSourceMaps` | Emit `.map` next to each file when supported | +| `-PbytecodeToolsDir=` | Override the bytecode-compiler tools folder | +| `-PbytecodeCompilerBinary=

` | Force a specific compiler binary | +| `-PnodePath=` | Override the `node` executable | + +The compile step never touches the app source — it only rewrites the merged-assets +build intermediate that gets packaged into the APK, and it is idempotent (files +already in bytecode form are skipped), so incremental builds are safe. + +## Source maps + +With `-PnsBytecodeSourceMaps`, engines whose compiler supports it (Hermes) emit a +source map next to each file (`.map`). Because the module wrapper is a +single-line prologue with no newlines, line numbers are preserved (only a +column offset on line 1). + +## Version compatibility + +A compiler and the runtime's engine library must share the same bytecode version, +so both must come from the same engine checkout. The workflow inputs are pinned to +exact commits for this reason — see the header of +[bytecode-compilers.yml](../../../.github/workflows/bytecode-compilers.yml). + +- **Hermes** — `BYTECODE_VERSION`, currently 99. The pinned `hermes_ref` must match + `UPSTREAM_COMMIT` in build-hermes, which produced the prebuilt both platforms link. +- **QuickJS-NG** — `BC_VERSION`, currently 21 (`vendor/quickjs/source_ng/quickjs.c`, + upstream v0.11.0). The compiler stamps its own value and `JS_ReadObject` accepts + only its own, so a compiler built from a different ref produces blobs that load + nowhere. + +A mismatch is caught at load: the magic/version check fails and the file is +rejected rather than misinterpreted. Note the failure mode is a **fallback**, not a +crash — the runtime falls back to source, so the app still works and the only +visible symptom is that the TTI win quietly disappears. + +## Adding an engine + +1. Add `tools/bytecode-compiler/lib/.js` implementing the adapter contract + (documented in `lib/index.js`) and register it in `lib/index.js`. +2. Drop the host compiler binaries under `tools/bytecode-compiler/bin///`. +3. Implement runtime execution in `napi//jsr.cpp` — `js_run_bytecode_file` + must detect that engine's bytecode magic and run it, setting `*result` to the + completion value (the module wrapper function for modules). Returning + `napi_cannot_run_js` means "fall back to source", which is what V8 and JSC do. + If the engine is also wired into the jsi tree, add the matching detection to + `runtime/android/jsi/EngineHost.cpp`. +4. Flip the adapter's `ready` flag to `true` **only** once both the compiler and + the runtime support exist — otherwise the build would ship bytecode the runtime + can't execute. diff --git a/platforms/android/docs/extending-inspector.md b/platforms/android/docs/extending-inspector.md new file mode 100644 index 000000000..ecfa8a1fa --- /dev/null +++ b/platforms/android/docs/extending-inspector.md @@ -0,0 +1,77 @@ +# How to extend the V8 inspector + +## Overview + +> Note: Check out the blog post [Chrome DevTools Integration from a Technical Perspective](https://www.nativescript.org/blog/chrome-devtools-integration) for an overview of how the v8 inspector in NativeScript works. + +The default inspector of the V8 project comes with only a [handful of implemented inspector domains](https://chromedevtools.github.io/devtools-protocol/v8). This is more than enough to be able to debug JavaScript, but what if you want to take it a step further and enable inspection of any arbitrary files of your application, need to spoof the network requests, or inspect the visual elements? + +## Getting additional protocol domain definitions + +This step involves fetching and building the V8 projectory, explanation for which can be found at [Building V8 with GN](https://github.com/v8/v8/wiki/Building-with-GN). + +> Note: When passing arguments to gn to have a project generate, make sure to include the inspector sources. For an overview of all available gn arguments run `gn args project-dir --list` + +1. Modify `v8/include/js_protocol.pdl` to only contain the domains that will be used in the runtime project. The right way to go about this is to copy-paste the `js_protocol` from the runtime project, and add on top of it. For a complete list of all protocol domains supported by the Chrome browser refer to [browser_protocol.json](https://chromium.googlesource.com/chromium/src/+log/master/third_party/WebKit/Source/core/inspector/browser_protocol.json) in the Chromium project. Example browser_protocol.json file can be found [here](https://chromedevtools.github.io/devtools-protocol/tot/). +2. Modify `v8/src/inspector/inspector_protocol_config.json` and include the names for the additional protocol domain definitions that would need to be generated. +```json +{ + "protocol": { + ... + "options": [ + { + "domain": "Schema", + "exported": ["Domain"] + }, + ... + { + "domain": "Console" + }, + { + "domain": "Log" + }, + { + "domain": "Overlay" + } + ] + }, + + "exported": { + ... + }, + + "lib": { + ... + } +} +``` +3. Figure out which parts of the big `js_inspector.json` protocol you will want to keep for the inspector implementation. It is likely that a lot of it will be browser-specific, so you don't need to spend extra time doing stub implementations for methods and events the application will not be making notifications for. + +4. Run the ninja build. Upon completion, the inspector protocol files would be at `outgn/$ARCH-release/gen/src/inspector/protocol`. + +5. Copy-paste all `.cpp` and `.h` files into `NativeScript/napi/v8/v8_inspector/src/inspector/protocol`. + +6. Create a new C++ class extending the desired Domain (e.g. DOM). Name it according to the convention already established by the V8 team - `v8--agent-impl.h/cpp` - under `NativeScript/napi/v8/v8_inspector/src/inspector`. Implement the Backend::'s methods in the `.cpp` file. + +7. To implement event handlers for a domain, add the runtime-side callback handlers under `NativeScript/runtime/android/inspector` and register them in `NativeScript/runtime/android/inspector/JsV8InspectorClient.cpp`. + +8. Register the newly created agent implementations in `NativeScript/napi/v8/v8_inspector/src/inspector/v8-inspector-session-impl.h/cc` - `V8InspectorSessionImpl` class: + +8.1. Changes in v8-inspector-session-impl.`h` + - `#include "src/inspector/protocol/.h` + - add forward class declaration of the agent implementation - `class V8AgentImpl;` + - create a private `std::unique_ptrAgentImpl> m_Agent;` field + - create a public getter method `Agent()` + +8.2. Changes in v8-inspector-session-impl.`cc` + - `#include "src/inspector/protocol/v8--agent-impl.h` + - register protocol domain command prefix in `canDispatchMethod()` + - in the `V8InspectorSessionImpl` constructor initialize the new agent implementation with `nullptr` + - create a unique pointer wrapper for your new agent impl instance, and call the static domain dispatcher's `wire` method + - make sure to call `agent.disable` in `V8InspectorSessionImpl`'s destructor + - register the newly implemented domain as a supported one inside `V8InspectorSessionImpl::supportedDomainsImpl()` + - `#include "NSV8DebuggerAgentImpl.h"` + - replace **V8DebuggerAgentImpl** with **NSV8DebuggerAgentImpl** + +9. Don't forget to add the new classes to `platforms/android/test-app/runtime/CMakeLists.txt`. +11. Test whether the new domain agent is registered by opening Chrome DevTools. In order to debug the Chrome DevTools frontend, you could open the Chrome DevTools inside an Android Chrome DevTools session - Ctrl(Cmd) + Shift + I. diff --git a/platforms/android/docs/v8-code-cache.md b/platforms/android/docs/v8-code-cache.md new file mode 100644 index 000000000..92e013ba4 --- /dev/null +++ b/platforms/android/docs/v8-code-cache.md @@ -0,0 +1,61 @@ +# V8 code cache + +V8 has no ahead-of-time bytecode format like Hermes, but it can **serialize the +code it compiles at runtime** and consume it on the next launch to skip parsing +and compilation. This is wired into the V8 runtime and works by default — no +configuration required. + +Unlike the [compile-time bytecode](./bytecode.md) feature (Hermes et al.), this is +a *runtime* cache: the first launch compiles as usual and writes the cache; every +later launch loads it. + +## How it works + +All script execution funnels through `js_execute_script` +([napi/v8/jsr.cpp](../test-app/runtime/src/main/cpp/napi/v8/jsr.cpp)), which now: + +1. **Consumes** an existing cache via `js_run_cached_script`. If a `.cache` + sits next to the module and is current, V8 compiles from it (`kConsumeCodeCache`) + and runs — no parse. +2. On a **miss**, compiles the source **once** (`CompileRunAndCache`), serializes + a code cache from that same `UnboundScript`, then runs it. The cache is written + next to the module for next time. + + > `napi_run_script_source` only returns the completion value, not the compiled + > `UnboundScript` that `CreateCodeCache` needs — so the cold path compiles + > directly (rather than delegating and then re-compiling) to avoid parsing the + > source twice. + +The cache lives at `.cache` in the app's writable files dir (next to the +extracted JS). Only real `file://` modules are cached; synthetic sources such as +`` are skipped. + +## Correctness + +- **Staleness.** The `.cache` is stamped with its source file's mtime. If the + source changes (app update, LiveSync), the mtime no longer matches and the + cache is ignored, then rewritten. V8 additionally embeds a source hash and + version tag in the cache and rejects it on any mismatch (e.g. a runtime/V8 + upgrade), recompiling from source — so a stale or incompatible cache can never + run the wrong code. Rejected caches are refreshed automatically. +- **Exceptions.** `js_run_cached_script` distinguishes a *cache miss* (returns + `napi_cannot_run_js`, caller falls back to source) from *the module actually + running and throwing* (propagates the exception; the caller must not execute it + again). This prevents double execution of module side effects. +- **Atomic writes.** The cache is written to `.cache.tmp` and renamed into + place (mtime stamped before the rename), so a crash or a worker loading the same + module never sees a half-written cache. + +## Cost + +The win is on the **second and later** launches. The first launch (or any launch +after a change) compiles the source once — the same as before this feature — plus +a small serialize-and-write step to produce the cache. There is no double compile. +Real apps are bundled into a handful of files, so the extra work is negligible and +buys faster subsequent starts. + +## Notes + +- This is orthogonal to compile-time bytecode. V8 uses this runtime cache; Hermes + (and, later, QuickJS/PrimJS) use precompiled bytecode instead. +- `js_run_bytecode_file` remains a no-op for V8 (there is no AOT bytecode format). diff --git a/platforms/android/gradle/wrapper/gradle-wrapper.jar b/platforms/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..249e5832f Binary files /dev/null and b/platforms/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/platforms/android/gradle/wrapper/gradle-wrapper.properties b/platforms/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..3ae1e2f12 --- /dev/null +++ b/platforms/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/platforms/android/gradlew b/platforms/android/gradlew new file mode 100755 index 000000000..a69d9cb6c --- /dev/null +++ b/platforms/android/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/platforms/android/gradlew.bat b/platforms/android/gradlew.bat new file mode 100644 index 000000000..f127cfd49 --- /dev/null +++ b/platforms/android/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/platforms/android/package.json b/platforms/android/package.json new file mode 100644 index 000000000..3fadb47d8 --- /dev/null +++ b/platforms/android/package.json @@ -0,0 +1,37 @@ +{ + "name": "@nativescript/android", + "description": "NativeScript for Android using Node-API", + "version": "9.0.0", + "repository": { + "type": "git", + "url": "git+https://github.com/NativeScript/runtimes.git", + "directory": "platforms/android" + }, + "files": [ + "**/*" + ], + "version_info": { + "gradle": "8.14.3", + "gradleAndroid": "8.12.1", + "ndk": "r27", + "ndkApiLevel": "21", + "minSdk": "21", + "compileSdk": "34", + "buildTools": "34.0.0", + "kotlin": "2.0.0" + }, + "// this gradle key is here for backwards compatibility - we'll phase it out slowly...": "", + "gradle": { + "version": "8.14.3", + "android": "8.12.1" + }, + "scripts": { + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", + "version": "npm run changelog && git add CHANGELOG.md" + }, + "devDependencies": { + "conventional-changelog-cli": "^2.1.1", + "dayjs": "^1.11.7", + "semver": "^7.5.0" + } +} diff --git a/platforms/android/test-app/.gitignore b/platforms/android/test-app/.gitignore new file mode 100644 index 000000000..db8355b66 --- /dev/null +++ b/platforms/android/test-app/.gitignore @@ -0,0 +1,37 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild +app/app.iml + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/dictionaries +.idea/libraries + + +treeNodeStream.dat +treeStringsStream.dat +treeValueStream.dat +NativeScriptActivity.java +NativeScriptApplication.java +**/com/tns/gen +# Generated by build-tools/metadata-filter/seed.js from the app's JS bundle. +# A build artifact, not config: regenerate it rather than committing it, so a +# checkout builds unfiltered metadata unless filtering is asked for. +/whitelist.mdg +/build-tools/whitelist.mdg +/.metadata-harvest.json + +# Generated by generateTypescriptDefinitions (on by default, cached on the classpath). +/build-tools/typings/ +/build-tools/out/ +/build-tools/generateTypescriptDefinitions.log diff --git a/platforms/android/test-app/additional_gradle.properties b/platforms/android/test-app/additional_gradle.properties new file mode 100644 index 000000000..af92e00cc --- /dev/null +++ b/platforms/android/test-app/additional_gradle.properties @@ -0,0 +1,15 @@ +# To debug Gradle scripts, you must enable this behaviour with a flag (seen some rows below) +# Start debugging by initiating a remote Java debug through your IDE on port 5005 +# Beware that Gradle spawns daemons to execute secondary builds. This could be a problem when debugging. +# You could stop them with "gradle --stop" and run a given task from the console with "gradle some_task_name --no-daemon" +# or with "org.gradle.daemon=false" set inside your "~/.gradle/gradle.properties" file. +# Uncomment the following line in order to enable Gradle debugging. +# org.gradle.debug=true + +useKotlin=true +gatherAnalyticsData=true + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/platforms/android/test-app/analytics/build-statistics.json b/platforms/android/test-app/analytics/build-statistics.json new file mode 100644 index 000000000..a972bc513 --- /dev/null +++ b/platforms/android/test-app/analytics/build-statistics.json @@ -0,0 +1,6 @@ +{ + "kotlinUsage": { + "hasUseKotlinPropertyInApp": true, + "hasKotlinRuntimeClasses": false + } +} \ No newline at end of file diff --git a/platforms/android/test-app/app/.gitignore b/platforms/android/test-app/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/platforms/android/test-app/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/platforms/android/test-app/app/build.gradle b/platforms/android/test-app/app/build.gradle new file mode 100644 index 000000000..aee8db93a --- /dev/null +++ b/platforms/android/test-app/app/build.gradle @@ -0,0 +1,1861 @@ +/* +* Script builds apk in release or debug mode +* To run: +* gradle assembleRelease -Prelease (release mode) +* gradle assembleDebug (debug mode -> default) +* Options: +* -Prelease //this flag will run build in release mode +* -PksPath=[path_to_keystore_file] +* -PksPassword=[password_for_keystore_file] +* -Palias=[alias_to_use_from_keystore_file] +* -Ppassword=[password_for_alias] +* +* -PtargetSdk=[target_sdk] +* -PbuildToolsVersion=[build_tools_version] +* -PcompileSdk=[compile_sdk_version] +* -PandroidXLegacy=[androidx_legacy_version] +* -PandroidXAppCompat=[androidx_appcompat_version] +* -PandroidXMaterial=[androidx_material_version] +* -PappPath=[app_path] +* -PappResourcesPath=[app_resources_path] +*/ + +import groovy.json.JsonSlurper +import groovy.json.JsonOutput +import groovy.xml.XmlSlurper +import org.apache.commons.io.FileUtils + +import javax.inject.Inject +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.security.MessageDigest +import java.util.jar.JarEntry +import java.util.jar.JarFile + +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +apply plugin: "com.android.application" +apply from: "gradle-helpers/BuildToolTask.gradle" +apply from: "gradle-helpers/CustomExecutionLogger.gradle" +apply from: "gradle-helpers/AnalyticsCollector.gradle" +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' + +def onlyX86 = project.hasProperty("onlyX86") +if (onlyX86) { + outLogger.withStyle(Style.Info).println "OnlyX86 build triggered." +} + +// See runtime/build.gradle: arm64-only local builds, opt-in. +def onlyArm64 = project.hasProperty("onlyArm64") +if (onlyArm64) { + outLogger.withStyle(Style.Info).println "OnlyArm64 build triggered." +} + +//common +def BUILD_TOOLS_PATH = "$rootDir/build-tools" +def PASSED_TYPINGS_PATH = System.getenv("TNS_TYPESCRIPT_DECLARATIONS_PATH") +def TYPINGS_PATH = "$BUILD_TOOLS_PATH/typings" +if (PASSED_TYPINGS_PATH != null) { + TYPINGS_PATH = PASSED_TYPINGS_PATH +} + +def PACKAGE_JSON = "package.json" + +//static binding generator +def SBG_JAVA_DEPENDENCIES = "sbg-java-dependencies.txt" +def SBG_INPUT_FILE = "sbg-input-file.txt" +def SBG_OUTPUT_FILE = "sbg-output-file.txt" +def SBG_JS_PARSED_FILES = "sbg-js-parsed-files.txt" +def SBG_BINDINGS_NAME = "sbg-bindings.txt" +def SBG_INTERFACE_NAMES = "sbg-interface-names.txt" +def INPUT_JS_DIR = "$projectDir/src/main/assets/app" +def OUTPUT_JAVA_DIR = "$projectDir/src/main/java" +def APP_DIR = "$projectDir/src/main/assets/app" + +//metadata generator +def MDG_OUTPUT_DIR = "mdg-output-dir.txt" +def MDG_JAVA_DEPENDENCIES = "mdg-java-dependencies.txt" +def METADATA_OUT_PATH = "$projectDir/src/main/assets/metadata" +def METADATA_KEEP_RULES = layout.buildDirectory.file("nativescript/metadata-keep-rules.pro").get().asFile.absolutePath +def METADATA_JAVA_OUT = "mdg-java-out.txt" + +// paths to jar libraries +def pluginsJarLibraries = new LinkedList() +def allJarLibraries = new LinkedList() + +def computeCompileSdkVersion = { -> + project.hasProperty("compileSdk") ? compileSdk as int : NS_DEFAULT_COMPILE_SDK_VERSION as int +} +def computeTargetSdkVersion = { -> + project.hasProperty("targetSdk") ? targetSdk as int : NS_DEFAULT_COMPILE_SDK_VERSION as int +} +def computeMinSdkVersion = { -> + project.hasProperty("minSdk") ? minSdk : NS_DEFAULT_MIN_SDK_VERSION as int +} +def computeBuildToolsVersion = { -> + project.hasProperty("buildToolsVersion") ? buildToolsVersion : NS_DEFAULT_BUILD_TOOLS_VERSION as String +} + +def enableAnalytics = (project.hasProperty("gatherAnalyticsData") && project.gatherAnalyticsData == "true") +def enableVerboseMDG = project.gradle.startParameter.logLevel.name() == 'DEBUG' +def analyticsFilePath = "$rootDir/analytics/build-statistics.json" +def analyticsCollector = project.ext.AnalyticsCollector.withOutputPath(analyticsFilePath) +if (enableAnalytics) { + analyticsCollector.markUseKotlinPropertyInApp(true) + analyticsCollector.writeAnalyticsFile() +} + +project.ext.selectedBuildType = "release" + +buildscript { + def applyBuildScriptConfigurations = { -> + def absolutePathToAppResources = getAppResourcesPath() + def pathToBuildScriptGradle = "$absolutePathToAppResources/Android/buildscript.gradle" + def buildScriptGradle = file(pathToBuildScriptGradle) + if (buildScriptGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from ${buildScriptGradle}" + apply from: pathToBuildScriptGradle, to: buildscript + } + + nativescriptDependencies.each { dep -> + def pathToPluginBuildScriptGradle = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/buildscript.gradle" + def pluginBuildScriptGradle = file(pathToPluginBuildScriptGradle) + if (pluginBuildScriptGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined buildscript from dependency ${pluginBuildScriptGradle}" + apply from: pathToPluginBuildScriptGradle, to: buildscript + } + } + } + applyBuildScriptConfigurations() +} +//////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// CONFIGURATIONS /////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +def applyBeforePluginGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToBeforePluginGradle = "$appResourcesPath/Android/before-plugins.gradle" + def beforePluginGradle = file(pathToBeforePluginGradle) + if (beforePluginGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${beforePluginGradle}" + apply from: pathToBeforePluginGradle + } +} + +def applyAppGradleConfiguration = { -> + def appResourcesPath = getAppResourcesPath() + def pathToAppGradle = "$appResourcesPath/Android/app.gradle" + def appGradle = file(pathToAppGradle) + if (appGradle.exists()) { + outLogger.withStyle(Style.SuccessHeader).println "\t + applying user-defined configuration from ${appGradle}" + apply from: pathToAppGradle + } else { + outLogger.withStyle(Style.Info).println "\t + couldn't load user-defined configuration from ${appGradle}. File doesn't exist." + } +} + +def applyPluginGradleConfigurations = { -> + nativescriptDependencies.each { dep -> + def includeGradlePath = "$rootDir/${dep.directory}/$PLATFORMS_ANDROID/include.gradle" + if (file(includeGradlePath).exists()) { + apply from: includeGradlePath + } + } +} + +def getAppIdentifier = { packageJsonMap -> + def appIdentifier = "" + if (packageJsonMap && packageJsonMap.nativescript) { + appIdentifier = packageJsonMap.nativescript.id + if (!(appIdentifier instanceof String)) { + appIdentifier = appIdentifier.android + } + } + + return appIdentifier +} + +def setAppIdentifier = { -> + outLogger.withStyle(Style.SuccessHeader).println "\t + setting applicationId" + File packageJsonFile = new File("$USER_PROJECT_ROOT/$PACKAGE_JSON") + + if (packageJsonFile.exists()) { + def content = packageJsonFile.getText("UTF-8") + def jsonSlurper = new JsonSlurper() + def packageJsonMap = jsonSlurper.parseText(content) + def appIdentifier = getAppIdentifier(packageJsonMap) + + if (appIdentifier) { + project.ext.nsApplicationIdentifier = appIdentifier + android.defaultConfig.applicationId = appIdentifier + android.namespace = appIdentifier + } + } +} + +def computeNamespace = { -> + def appPackageJsonFile = file("${APP_DIR}/$PACKAGE_JSON") + + if (appPackageJsonFile.exists()) { + def content = appPackageJsonFile.getText("UTF-8") + + def jsonSlurper = new JsonSlurper() + def packageJsonMap = jsonSlurper.parseText(content) + + def appIdentifier = "" + + if (packageJsonMap) { + if (packageJsonMap.android && packageJsonMap.android.id) { + appIdentifier = packageJsonMap.android.id + } else if (packageJsonMap.id) { + appIdentifier = packageJsonMap.id + } + } + + if (appIdentifier) { + return appIdentifier + } + } + return "com.tns.testapplication" +} + +android { + + namespace computeNamespace() + + applyBeforePluginGradleConfiguration() + + kotlinOptions { + jvmTarget = '17' + } + + compileSdk computeCompileSdkVersion() + buildToolsVersion = computeBuildToolsVersion() + ndkVersion = project.findProperty("ndkVersion") ?: "29.0.14206865" + + defaultConfig { + def manifest = new XmlSlurper().parse(file(android.sourceSets.main.manifest.srcFile)) + def minSdkVer = manifest."uses-sdk"."@android:minSdkVersion".text() ?: computeMinSdkVersion() + minSdkVersion minSdkVer + targetSdkVersion computeTargetSdkVersion() + ndk { + if (onlyX86) { + // The updated JSC only ships 64-bit libraries, so fall back to + // x86_64 when a single-ABI (emulator) build is requested. + abiFilters 'x86' + } else if (onlyArm64) { + abiFilters 'arm64-v8a' + } else { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + sourceSets.main { + jniLibs.srcDirs = ["$projectDir/libs/jni", "$projectDir/snapshot-build/build/ndk-build/libs", "$projectDir/src/main/jniLibs"] + } + + packagingOptions { + // The Hermes engine build pulls in fbjni, which ships its own + // libc++_shared.so alongside the one the runtime module already + // packages. The runtime module resolves this for its own merge, but the + // app merges the runtime's jniLibs with fbjni's, so it needs the same + // rule or :app:mergeDebugNativeLibs fails with "2 files found with path + // lib//libc++_shared.so". Both copies are the NDK's, so either is + // fine. libfbjni.so collides the same way: the runtime module packages a + // copy from its jniLibs and the fbjni prefab supplies another. + pickFirst "**/libc++_shared.so" + pickFirst "**/libfbjni.so" + } + + signingConfigs { + release { + if (project.hasProperty("release")) { + if (project.hasProperty("ksPath") && + project.hasProperty("ksPassword") && + project.hasProperty("alias") && + project.hasProperty("password")) { + + storeFile file(ksPath) + storePassword ksPassword + keyAlias alias + keyPassword password + } + } + } + } + + buildTypes { + release { + if (project.hasProperty("release")) { + if (project.hasProperty("ksPath") && + project.hasProperty("ksPassword") && + project.hasProperty("alias") && + project.hasProperty("password")) { + signingConfig signingConfigs.release + } + } else { + signingConfig signingConfigs.debug + } + + // Shrink the dex with the same set the metadata was filtered to. + // Only together with -PnsFilterMetadata: without the generated keep + // rules R8 cannot see that JS reaches these classes at all, and + // would strip everything the app depends on. + if (project.hasProperty("nsFilterMetadata")) { + minifyEnabled true + proguardFiles getDefaultProguardFile("proguard-android.txt"), + METADATA_KEEP_RULES + } + } + } + + setAppIdentifier() + applyPluginGradleConfigurations() + applyAppGradleConfiguration() + + def initializeMergedAssetsOutputPath = { -> + android.applicationVariants.configureEach { variant -> + if (variant.buildType.name == project.selectedBuildType) { + def task + if (variant.metaClass.respondsTo(variant, "getMergeAssetsProvider")) { + def provider = variant.getMergeAssetsProvider() + task = provider.get() + } else { + // fallback for older android gradle plugin versions + task = variant.getMergeAssets() + } + for (File file : task.getOutputs().getFiles()) { + if (!file.getPath().contains("${File.separator}incremental${File.separator}")) { + project.ext.mergedAssetsOutputPath = file.getPath() + break + } + } + } + } + } + + initializeMergedAssetsOutputPath() +} + +def externalRuntimeExists = !findProject(':runtime').is(null) +def pluginDependencies + +repositories { + // used for local *.AAR files + pluginDependencies = nativescriptDependencies.collect { + "$rootDir/${it.directory}/$PLATFORMS_ANDROID" + } + + // some plugins may have their android dependencies in a /libs subdirectory + pluginDependencies.addAll(nativescriptDependencies.collect { + "$rootDir/${it.directory}/$PLATFORMS_ANDROID/libs" + }) + + if (!externalRuntimeExists) { + pluginDependencies.add("libs/runtime-libs") + } + + def appResourcesPath = getAppResourcesPath() + def localAppResourcesLibraries = "$appResourcesPath/Android/libs" + + pluginDependencies.add(localAppResourcesLibraries) + + if (pluginDependencies.size() > 0) { + flatDir { + dirs pluginDependencies + } + } + + mavenCentral() +} + +dependencies { + // println "\t ~ [DEBUG][app] build.gradle - ns_default_androidx_appcompat_version = ${ns_default_androidx_appcompat_version}..." + + def androidXAppCompatVersion = "${ns_default_androidx_appcompat_version}" + if (project.hasProperty("androidXAppCompat")) { + androidXAppCompatVersion = androidXAppCompat + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.appcompat:appcompat:$androidXAppCompatVersion" + } + + def androidXMaterialVersion = "${ns_default_androidx_material_version}" + if (project.hasProperty("androidXMaterial")) { + androidXMaterialVersion = androidXMaterial + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library com.google.android.material:material:$androidXMaterialVersion" + } + + def androidXExifInterfaceVersion = "${ns_default_androidx_exifinterface_version}" + if (project.hasProperty("androidXExifInterface")) { + androidXExifInterfaceVersion = androidXExifInterface + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion" + } + + def androidXViewPagerVersion = "${ns_default_androidx_viewpager_version}" + if (project.hasProperty("androidXViewPager")) { + androidXViewPagerVersion = androidXViewPager + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.viewpager2:viewpager2:$androidXViewPagerVersion" + } + + def androidXFragmentVersion = "${ns_default_androidx_fragment_version}" + if (project.hasProperty("androidXFragment")) { + androidXFragmentVersion = androidXFragment + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.fragment:fragment:$androidXFragmentVersion" + } + + def androidXTransitionVersion = "${ns_default_androidx_transition_version}" + if (project.hasProperty("androidXTransition")) { + androidXTransitionVersion = androidXTransition + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.transition:transition:$androidXTransitionVersion" + } + + def androidXMultidexVersion = "${ns_default_androidx_multidex_version}" + if (project.hasProperty("androidXMultidex")) { + androidXMultidexVersion = androidXMultidex + outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.multidex:multidex:$androidXMultidexVersion" + } + + implementation "androidx.multidex:multidex:$androidXMultidexVersion" + implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion" + debugImplementation "com.google.android.material:material:$androidXMaterialVersion" + implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion" + implementation "androidx.viewpager2:viewpager2:$androidXViewPagerVersion" + //noinspection KtxExtensionAvailable + implementation "androidx.fragment:fragment:$androidXFragmentVersion" + implementation "androidx.transition:transition:$androidXTransitionVersion" + + def useV8Symbols = false + + def appPackageJsonFile = file("${getAppPath()}/$PACKAGE_JSON") + if (appPackageJsonFile.exists()) { + def appPackageJson = new JsonSlurper().parseText(appPackageJsonFile.text) + useV8Symbols = appPackageJson.android && appPackageJson.android.useV8Symbols + } + + if (!useV8Symbols) { + // check whether any of the dependencies require v8 symbols + useV8Symbols = nativescriptDependencies.any { + def packageJsonFile = file("$rootDir/${it.directory}/$PACKAGE_JSON") + def packageJson = new JsonSlurper().parseText(packageJsonFile.text) + return packageJson.nativescript && packageJson.nativescript.useV8Symbols + } + } + + if (!externalRuntimeExists) { + def runtime = "nativescript-optimized-with-inspector" + + if (project.gradle.startParameter.taskNames.any { it.toLowerCase().contains('release') }) { + runtime = "nativescript-optimized" + } + + if (useV8Symbols) { + runtime = "nativescript-regular" + } + + outLogger.withStyle(Style.SuccessHeader).println "\t + adding nativescript runtime package dependency: $runtime" + project.dependencies.add("implementation", [name: runtime, ext: "aar"]) + } else { + implementation project(':runtime') + } + +} + +//////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// CONFIGURATION PHASE ////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +task 'addDependenciesFromNativeScriptPlugins' { + nativescriptDependencies.each { dep -> + def aarFiles = fileTree(dir: file("$rootDir/${dep.directory}/$PLATFORMS_ANDROID"), include: ["**/*.aar"]) + aarFiles.each { aarFile -> + def length = aarFile.name.length() - 4 + def fileName = aarFile.name[0.. + def jarFileAbsolutePath = jarFile.getAbsolutePath() + outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath" + pluginsJarLibraries.add(jarFile.getAbsolutePath()) + } + + project.dependencies.add("implementation", jarFiles) + } +} + +task 'addDependenciesFromAppResourcesLibraries' { + def appResourcesPath = getAppResourcesPath() + def appResourcesLibraries = file("$appResourcesPath/Android/libs") + if (appResourcesLibraries.exists()) { + def aarFiles = fileTree(dir: appResourcesLibraries, include: ["**/*.aar"]) + aarFiles.each { aarFile -> + def length = aarFile.name.length() - 4 + def fileName = aarFile.name[0.. + def jarFileAbsolutePath = jarFile.getAbsolutePath() + outLogger.withStyle(Style.SuccessHeader).println "\t + adding jar plugin dependency: $jarFileAbsolutePath" + pluginsJarLibraries.add(jarFile.getAbsolutePath()) + } + + project.dependencies.add("implementation", jarFiles) + } +} + +if (failOnCompilationWarningsEnabled()) { + tasks.withType(JavaCompile).configureEach { + options.compilerArgs << '-Xlint:all' << "-Werror" + options.deprecation = true + } +} + + +//////////////////////////////////////////////////////////////////////////////////// +///////////////////////////// EXECUTION PHASE ///////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +// Compiles the app's own Java so the static binding generator can see it. +// +// Without this the generator detects an `extend` of an app class perfectly -- +// 48 rows for com.tns.tests.Button1 alone -- and then generates nothing for it, +// because it cannot load the base class to read its methods. Only classes from +// android.jar and the AAR dependencies got bindings; everything the app itself +// declared fell through to being dexed on the device at run time. +// +// It cannot simply depend on the normal javac task: that one consumes the +// generator's output, so the dependency would be circular. This compiles the +// same sources *minus* the generated bindings into a scratch directory nothing +// else reads, which breaks the cycle. +// +// Best-effort by design. A source that fails here (most often a Java class +// referring to a Kotlin one, which is not compiled at this point) simply is not +// on the generator's classpath, which is exactly today's behaviour for it. The +// real javac still compiles everything and still fails the build if it cannot. +task compileAppClassesForSbg { + dependsOn "collectAllJars" + + def outDir = layout.buildDirectory.dir("nativescript/sbg-app-classes").get().asFile + def sourceRoot = file(OUTPUT_JAVA_DIR) + + inputs.files(fileTree(sourceRoot) { include "**/*.java"; exclude "com/tns/gen/**" }) + outputs.dir(outDir) + + doLast { + def sources = [] + sourceRoot.eachFileRecurse { f -> + if (f.isFile() && f.name.endsWith(".java") + && !f.absolutePath.contains("${File.separator}com${File.separator}tns${File.separator}gen${File.separator}")) { + sources.add(f.absolutePath) + } + } + + if (sources.isEmpty()) { + return + } + + outDir.deleteDir() + outDir.mkdirs() + + // The classpath collectAllJars just wrote, plus whatever the runtime + // module has already produced -- com.tns.* is what the app's own + // classes extend. + def cp = [] + def depsFile = file("$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES") + if (depsFile.exists()) { + depsFile.eachLine { line -> if (line.trim()) cp.add(line.trim()) } + } + def runtimeProject = findProject(':runtime') + if (runtimeProject != null) { + runtimeProject.fileTree(runtimeProject.buildDir) { + include "intermediates/javac/**/classes" + include "tmp/kotlin-classes/**" + }.visit { d -> if (d.directory) cp.add(d.file.absolutePath) } + } + + def argsFile = new File(outDir.parentFile, "sbg-javac-args.txt") + argsFile.text = sources.collect { "\"${it.replace('\\', '/')}\"" }.join("\n") + + def cmd = ["javac", "-proc:none", "-nowarn", "-g", + "-source", "17", "-target", "17", + "-d", outDir.absolutePath] + if (!cp.isEmpty()) { + cmd.addAll(["-cp", cp.join(File.pathSeparator)]) + } + cmd.add("@${argsFile.absolutePath}".toString()) + + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + def errors = 0 + process.inputStream.eachLine { line -> if (line.contains("error:")) errors++ } + process.waitFor() + + def produced = 0 + if (outDir.exists()) { + outDir.eachFileRecurse { f -> if (f.isFile() && f.name.endsWith(".class")) produced++ } + } + outLogger.withStyle(Style.Info).println( + "\t ~ [sbg] compiled ${produced} app class file(s) for binding generation" + + (errors > 0 ? " (${errors} source(s) skipped -- see above)" : "")) + } +} + +// Name generated bindings after what they contain rather than after the extend() +// call site. Off by default; an app opts in with -PuseContentBindings, or with +// +// project.ext.useContentBindings = true +// +// in App_Resources/Android/app.gradle or before-plugins.gradle. The value drives +// both halves of the scheme from one place: the static binding generator (which +// writes the names) and the runtime (which looks them up, via the app config +// written by writeRuntimeAppConfig). -Pcontent-keyed-bindings is the older +// spelling, kept because it is what a runtime build passes to :runtime. +def nsContentKeyedBindings = ["useContentBindings", "content-keyed-bindings"].any { name -> + project.hasProperty(name) && + (project.property(name).toString().isEmpty() + || Boolean.parseBoolean(project.property(name).toString())) +} + +task runSbg(type: BuildToolTask) { + dependsOn "collectAllJars" + dependsOn "compileAppClassesForSbg" + def rootPath = "" + if (!findProject(':static-binding-generator').is(null)) { + rootPath = Paths.get(project(':static-binding-generator').projectDir.path, "build/libs").toString() + dependsOn ':static-binding-generator:jar' + } + + outputs.dir("$OUTPUT_JAVA_DIR/com/tns/gen") + inputs.dir(INPUT_JS_DIR) + inputs.dir(extractedDependenciesDir) + + workingDir "$BUILD_TOOLS_PATH" + mainClass = "-jar" + + def paramz = new ArrayList() + paramz.add(Paths.get(rootPath, "static-binding-generator.jar")) + + if (failOnCompilationWarningsEnabled()) { + paramz.add("-show-deprecation-warnings") + } + + if (ns_engine == "PRIMJS") { + paramz.add("-line-column-primjs") + } + + // The generated names and the names the runtime looks up are two halves of + // one scheme, and half of it is a silent LookedUpClassNotFound. The other + // half is no longer a runtime rebuild: writeRuntimeAppConfig hands the same + // value to the runtime through the app's package.json. + if (nsContentKeyedBindings) { + paramz.add("-content-keyed-bindings") + } + + setOutputs outLogger + + args paramz + + doFirst { + new File("$OUTPUT_JAVA_DIR/com/tns/gen").deleteDir() + } +} + +def failOnCompilationWarningsEnabled() { + return project.hasProperty("failOnCompilationWarnings") && (failOnCompilationWarnings || failOnCompilationWarnings.toBoolean()) +} + +def explodeAar(File compileDependency, File outputDir) { + logger.info("explodeAar: Extracting ${compileDependency.path} -> ${outputDir.path}") + + if (compileDependency.name.endsWith(".aar")) { + JarFile jar = new JarFile(compileDependency) + Enumeration enumEntries = jar.entries() + while (enumEntries.hasMoreElements()) { + JarEntry file = (JarEntry) enumEntries.nextElement() + if (file.isDirectory()) { + continue + } + if (file.name.endsWith(".jar")) { + def targetFile = new File(outputDir, file.name) + InputStream inputStream = jar.getInputStream(file) + new File(targetFile.parent).mkdirs() + Files.copy(inputStream, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } + jar.close() + } else if (compileDependency.name.endsWith(".jar")) { + copy { + from compileDependency.absolutePath + into outputDir + } + } +} + +static def md5(String string) { + MessageDigest digest = MessageDigest.getInstance("MD5") + digest.update(string.bytes) + return new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0') +} + +class WorkerTask extends DefaultTask { + @Inject + WorkerExecutor getWorkerExecutor() { + throw new UnsupportedOperationException() + } +} + +class EmptyRunnable implements Runnable { + void run() { + } +} + +def getMergedAssetsOutputPath() { + if (!project.hasProperty("mergedAssetsOutputPath")) { + // mergedAssetsOutputPath not found fallback to the default value for android gradle plugin 3.5.1 + project.ext.mergedAssetsOutputPath = "$projectDir/build/intermediates/merged_assets/" + project.selectedBuildType + "/out" + } + return project.ext.mergedAssetsOutputPath +} + +// Discover all jars and dynamically create tasks for the extraction of each of them +project.ext.allJars = [] +allprojects { + afterEvaluate { project -> + def buildType = project.selectedBuildType + def jars = [] + def artifactType = Attribute.of('artifactType', String) + android.applicationVariants.configureEach { variant -> + if (variant.buildType.name == buildType) { + variant.getCompileClasspath(null).each { fileDependency -> + processJar(fileDependency, jars) + } + } + } + } +} + +def processJar(File jar, jars) { + if (!jars.contains(jar)) { + jars.add(jar) + def destDir = md5(jar.path) + def outputDir = new File(Paths.get(extractedDependenciesDir, destDir).normalize().toString()) + + def taskName = "extract_${jar.name}_to_${destDir}" + logger.debug("Creating dynamic task ${taskName}") + + // Add discovered jars as dependencies of cleanupAllJars. + // This is crucial for cloud builds because they are different + // on each incremental build (as each time the gradle user home + // directory is a randomly generated string) + cleanupAllJars.inputs.files jar + + task "${taskName}"(type: WorkerTask) { + dependsOn cleanupAllJars + extractAllJars.dependsOn it + + // This dependency seems redundant but probably due to some Gradle issue with workers, + // without it `runSbg` sporadically starts before all extraction tasks have finished and + // fails due to missing JARs + runSbg.dependsOn it + + inputs.files jar + outputs.dir outputDir + + doLast { + // Runing in parallel no longer seems to bring any benefit. + // It mattered only when we were extracting JARs from AARs. + // To try it simply remove the following comments. + // workerExecutor.submit(EmptyRunnable.class) { + explodeAar(jar, outputDir) + // } + } + } + project.ext.allJars.add([file: jar, outputDir: outputDir]) + } +} + +task 'cleanupAllJars' { + // We depend on the list of libs directories that might contain aar or jar files + // and on the list of all discovered jars + inputs.files(pluginDependencies) + + outputs.files cleanupAllJarsTimestamp + + doLast { + def allDests = project.ext.allJars*.outputDir*.name + def dir = new File(extractedDependenciesDir) + if (dir.exists()) { + dir.eachDir { + // An old directory which is no longer a dependency (e.g. orphaned by a deleted plugin) + if (!allDests.contains(it.name)) { + logger.info("Task cleanupAllJars: Deleting orphaned ${it.path}") + FileUtils.deleteDirectory(it) + } + } + } + new File(cleanupAllJarsTimestamp).write "" + } +} + + +// Placeholder task which depends on all dynamically generated extraction tasks +task 'extractAllJars' { + dependsOn cleanupAllJars + outputs.files extractAllJarsTimestamp + + doLast { + new File(cleanupAllJarsTimestamp).write "" + } +} + +task 'collectAllJars' { + dependsOn extractAllJars + description "gathers all paths to jar dependencies before building metadata with them" + + def sdkPath = android.sdkDirectory.getAbsolutePath() + def androidJar = sdkPath + "/platforms/" + android.compileSdkVersion + "/android.jar" + + doFirst { + def allJarPaths = new LinkedList() + allJarPaths.add(androidJar) + allJarPaths.addAll(pluginsJarLibraries) + def ft = fileTree(dir: extractedDependenciesDir, include: "**/*.jar") + ft.each { currentJarFile -> + allJarPaths.add(currentJarFile.getAbsolutePath()) + } + + new File("$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES").withWriter { out -> + allJarPaths.each { out.println it } + // The app's own classes, compiled by compileAppClassesForSbg. A + // directory rather than a jar; the generator reads both. + out.println layout.buildDirectory.dir("nativescript/sbg-app-classes").get().asFile.absolutePath + } + new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriter { out -> + allJarPaths.each { + if (it.endsWith(".jar")) { + out.println it + } + } + } + + new File("$BUILD_TOOLS_PATH/$SBG_INPUT_FILE").withWriter { out -> + out.println INPUT_JS_DIR + } + new File("$BUILD_TOOLS_PATH/$SBG_OUTPUT_FILE").withWriter { out -> + out.println OUTPUT_JAVA_DIR + } + + allJarLibraries.addAll(allJarPaths) + } +} + +// Derives whitelist.mdg from the app's own JS, so metadata carries only what +// the app can reach. Opt-in: without -PnsFilterMetadata the seed is removed and +// the generator emits everything, which is the behaviour every existing build +// already has. +// +// The seed is only a starting point -- the generator closes over supertypes, +// nested classes and signature types before dropping anything, and fails the +// build if that closure turns out to be unsound. +def nsFilterMetadata = project.hasProperty("nsFilterMetadata") + +// Build-owned, and deliberately NOT $rootDir/whitelist.mdg: that path belongs to +// the app. An app may ship its own hand-written whitelist.mdg / blacklist.mdg +// and filter without -PnsFilterMetadata at all, exactly as it could before the +// generated seed existed -- writing the seed there overwrote that file, and +// turning filtering off deleted it. +def nsGeneratedSeed = file("$projectDir/build/nativescript/generated-whitelist.mdg") + +task generateMetadataSeed { + def seedScript = "$BUILD_TOOLS_PATH/metadata-filter/seed.js" + def generatedSeed = nsGeneratedSeed + + inputs.property("enabled", nsFilterMetadata) + inputs.dir(APP_DIR) + inputs.files(fileTree("$projectDir/src/main/res")) + inputs.files(fileTree("$BUILD_TOOLS_PATH/metadata-filter") { include "*.js" }) + outputs.file(generatedSeed) + + doLast { + if (!nsFilterMetadata) { + // Leaving a stale seed behind would keep filtering on for a build + // that did not ask for it -- the confusing direction to fail. Only + // the generated file is removed; an app's own whitelist.mdg is + // untouched and still filters. + if (generatedSeed.exists()) { + generatedSeed.delete() + outLogger.withStyle(Style.Info).println "\t ~ metadata filtering off; removed the generated seed" + } + return + } + + generatedSeed.parentFile.mkdirs() + + def cmd = ["node", seedScript, APP_DIR, + "--manifest", "$projectDir/src/main/AndroidManifest.xml", + "--out", generatedSeed.absolutePath] + + // Custom views, fragments and behaviours are named in resource XML and + // instantiated by the framework; nothing in the JS mentions them. + def resDir = file("$projectDir/src/main/res") + if (resDir.exists()) { + cmd.addAll(["--res", resDir.absolutePath]) + } + + def bindings = file("$BUILD_TOOLS_PATH/$SBG_BINDINGS_NAME") + if (bindings.exists()) { + cmd.addAll(["--sbg-bindings", bindings.absolutePath]) + } + + // This app ships files that are invalid on purpose, to test how the + // loader reports them. Anywhere else an unparseable file disables + // filtering for the whole build. + cmd.addAll(["--allow-unparseable", "shared/Require/SyntaxErrorInModule", + "--allow-unparseable", "shared/Workers/WorkerInvalidSyntax"]) + + // ProcessBuilder needs real Strings; interpolated paths are GStrings. + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + process.inputStream.eachLine { outLogger.withStyle(Style.Info).println "\t ~ [metadata-seed] $it" } + if (process.waitFor() != 0) { + throw new GradleException("NativeScript: metadata seed generation failed.") + } + } +} + +// Device-free guard against the one part of the seed that is written by hand. +// Wired into `check` so it runs in CI: the runtime naming a new class from +// native code is otherwise invisible until an app built with filtering on hits +// the code path that needs it. +task checkMetadataRuntimeKeepList { + group = "verification" + description = "Verifies RUNTIME_KEEP covers every class the runtime resolves by name." + + def runtimeSources = ["$rootDir/../../../NativeScript/ffi/jni/jsi", + "$rootDir/../../../NativeScript/runtime/android/jsi"] + def script = "$BUILD_TOOLS_PATH/metadata-filter/check-runtime-keeplist.js" + + inputs.files(fileTree("$BUILD_TOOLS_PATH/metadata-filter") { include "*.js" }) + runtimeSources.each { dir -> + if (file(dir).exists()) { + inputs.files(fileTree(dir) { include "**/*.cpp", "**/*.h" }) + } + } + outputs.upToDateWhen { false } + + doLast { + def cmd = ["node", script] + runtimeSources.findAll { file(it).exists() } + def process = new ProcessBuilder(cmd.collect { it.toString() }) + .redirectErrorStream(true).start() + process.inputStream.eachLine { outLogger.withStyle(Style.Info).println "\t ~ [keeplist] $it" } + if (process.waitFor() != 0) { + throw new GradleException( + "NativeScript: the metadata runtime keep-list is out of date. " + + "Add the classes listed above to RUNTIME_KEEP in " + + "build-tools/metadata-filter/seed.js.") + } + } +} + +tasks.matching { it.name == "check" }.configureEach { it.dependsOn(checkMetadataRuntimeKeepList) } + +task copyMetadataFilters { + dependsOn generateMetadataSeed + + // Declared so the task re-runs when a filter file appears or disappears. + // Without inputs it has none to compare, stays up to date on the strength + // of its outputs alone, and leaves the previous build's filter in place -- + // which silently keeps filtering on for a build that turned it off. + inputs.files("$rootDir/whitelist.mdg", "$rootDir/blacklist.mdg", nsGeneratedSeed) + outputs.files("$BUILD_TOOLS_PATH/whitelist.mdg", "$BUILD_TOOLS_PATH/blacklist.mdg") + + // Copies at execution time, not configuration time: the seed does not exist + // until generateMetadataSeed has run. Files.copy rather than project.copy, + // which Gradle 9 no longer allows during execution. + doLast { + // The whitelist MDG reads is the app's own file, the generated seed, or + // both concatenated. Either alone is a complete filter: an app with a + // hand-written whitelist and no -PnsFilterMetadata filters by its list + // (and MDG leaves it alone -- see enforceClosure in buildMetadata), + // while a build with the flag and no app file filters by the seed. + def whitelistParts = [] + def appWhitelist = file("$rootDir/whitelist.mdg") + if (appWhitelist.exists()) { + whitelistParts.add(appWhitelist.text) + } + if (nsFilterMetadata && nsGeneratedSeed.exists()) { + whitelistParts.add(nsGeneratedSeed.text) + } + + def whitelistTarget = file("$BUILD_TOOLS_PATH/whitelist.mdg") + if (whitelistParts.isEmpty()) { + whitelistTarget.delete() + } else { + whitelistTarget.text = whitelistParts.join("\n") + } + + def blacklistSource = file("$rootDir/blacklist.mdg") + def blacklistTarget = file("$BUILD_TOOLS_PATH/blacklist.mdg") + if (blacklistSource.exists()) { + Files.copy(blacklistSource.toPath(), blacklistTarget.toPath(), + StandardCopyOption.REPLACE_EXISTING) + } else { + blacklistTarget.delete() + } + } +} + +task 'copyMetadata' { + doLast { + copy { + from "$projectDir/src/main/assets/metadata" + into getMergedAssetsOutputPath() + "/metadata" + } + } +} + +def listf(String directoryName, ArrayList store) { + def directory = new File(directoryName) + + def resultList = new ArrayList() + + def fList = directory.listFiles() + resultList.addAll(Arrays.asList(fList)) + for (File file : fList) { + if (file.isFile()) { + store.add(file) + } else if (file.isDirectory()) { + resultList.addAll(listf(file.getAbsolutePath(), store)) + } + } + return resultList +} + +task buildMetadata(type: BuildToolTask) { + def rootPath = "" + if (!findProject(':android-metadata-generator').is(null)) { + rootPath = Paths.get(project(':android-metadata-generator').projectDir.path, "build/libs").toString() + dependsOn ':android-metadata-generator:jar' + } + + + android.applicationVariants.all { variant -> + def buildTypeName = variant.buildType.name.capitalize() + def mergeShadersTaskName = "merge${buildTypeName}Shaders" + def mergeShadersTask = tasks.findByName(mergeShadersTaskName) + + if (mergeShadersTask) { + dependsOn mergeShadersTask + } + + def compileJavaWithJavacTaskName = "compile${buildTypeName}JavaWithJavac" + def compileJavaWithJavacTask = tasks.findByName(compileJavaWithJavacTaskName) + + + if (compileJavaWithJavacTask) { + dependsOn compileJavaWithJavacTask + } + + def compileKotlinTaskName = "compile${buildTypeName}Kotlin" + def compileKotlinTask = tasks.findByName(compileKotlinTaskName) + + + if (compileKotlinTask) { + dependsOn compileKotlinTask + } + + + def mergeDexTaskName = "mergeDex${buildTypeName}" + def mergeDexTask = tasks.findByName(mergeDexTaskName) + + if (mergeDexTask) { + dependsOn mergeDexTask + } + + def checkDuplicateClassesTaskName = "check${buildTypeName}DuplicateClasses" + def checkDuplicateClassesTask = tasks.findByName(checkDuplicateClassesTaskName) + + if (checkDuplicateClassesTask) { + dependsOn checkDuplicateClassesTask + } + + def generateBuildConfigTaskName = "generate${buildTypeName}BuildConfig" + def generateBuildConfigTask = tasks.findByName(generateBuildConfigTaskName) + + if (generateBuildConfigTask) { + dependsOn generateBuildConfigTask + } + + def dexBuilderTaskName = "dexBuilder${buildTypeName}" + def dexBuilderTask = tasks.findByName(dexBuilderTaskName) + + if (dexBuilderTask) { + dependsOn dexBuilderTask + } + + + def mergeExtDexTaskName = "mergeExtDex${buildTypeName}" + def mergeExtDexTask = tasks.findByName(mergeExtDexTaskName) + + if (mergeExtDexTask) { + dependsOn mergeExtDexTask + } + + def mergeLibDexTaskName = "mergeLibDex${buildTypeName}" + def mergeLibDexTask = tasks.findByName(mergeLibDexTaskName) + + if (mergeLibDexTask) { + dependsOn mergeLibDexTask + } + + def mergeProjectDexTaskName = "mergeProjectDex${buildTypeName}" + def mergeProjectDexTask = tasks.findByName(mergeProjectDexTaskName) + + if (mergeProjectDexTask) { + dependsOn mergeProjectDexTask + } + + def syncLibJarsTaskName = "sync${buildTypeName}LibJars" + def syncLibJarsTask = tasks.findByName(syncLibJarsTaskName) + + if (syncLibJarsTask) { + dependsOn syncLibJarsTask + } + + def mergeJavaResourceTaskName = "merge${buildTypeName}JavaResource" + def mergeJavaResourceTask = tasks.findByName(mergeJavaResourceTaskName) + + if (mergeJavaResourceTask) { + dependsOn mergeJavaResourceTask + } + + def mergeJniLibFoldersTaskName = "merge${buildTypeName}JniLibFolders" + def mergeJniLibFoldersTask = tasks.findByName(mergeJniLibFoldersTaskName) + + if (mergeJniLibFoldersTask) { + dependsOn mergeJniLibFoldersTask + } + + def mergeNativeLibsTaskName = "merge${buildTypeName}NativeLibs" + def mergeNativeLibsTask = tasks.findByName(mergeNativeLibsTaskName) + + if (mergeNativeLibsTask) { + dependsOn mergeNativeLibsTask + } + + def stripDebugSymbolsTaskName = "strip${buildTypeName}DebugSymbols" + def stripDebugSymbolsTask = tasks.findByName(stripDebugSymbolsTaskName) + + if (stripDebugSymbolsTask) { + dependsOn stripDebugSymbolsTask + } + + def validateSigningTaskName = "validateSigning${buildTypeName}" + def validateSigningTask = tasks.findByName(validateSigningTaskName) + + if (validateSigningTask) { + dependsOn validateSigningTask + } + + + def extractProguardFilesTaskName = "extractProguardFiles" + def extractProguardFilesTask = tasks.findByName(extractProguardFilesTaskName) + + if (extractProguardFilesTask) { + dependsOn extractProguardFilesTask + } + + + // def compileArtProfileTaskName = "compile${buildTypeName}ArtProfile" + // def compileArtProfileTask = tasks.findByName(compileArtProfileTaskName) + + // if (compileArtProfileTask) { + // dependsOn compileArtProfileTask + // } + + + def extractNativeSymbolTablesTaskName = "extract${buildTypeName}NativeSymbolTables" + def extractNativeSymbolTablesTask = tasks.findByName(extractNativeSymbolTablesTaskName) + + if (extractNativeSymbolTablesTask) { + dependsOn extractNativeSymbolTablesTask + } + + + // def optimizeResourcesTaskName = "optimize${buildTypeName}Resources" + // def optimizeResourcesTask = tasks.findByName(optimizeResourcesTaskName) + + // if (optimizeResourcesTask) { + // dependsOn optimizeResourcesTask + // } + + def bundleResourcesTaskName = "bundle${buildTypeName}Resources" + def bundleResourcesTask = tasks.findByName(bundleResourcesTaskName) + + if (bundleResourcesTask) { + dependsOn bundleResourcesTask + } + + } + + dependsOn copyMetadataFilters + + // As some external gradle plugins can reorder the execution order of the tasks it may happen that buildMetadata is executed after merge{Debug/Release}Assets + // in that case the metadata won't be included in the result apk and it will crash, so to avoid this we are adding the copyMetadata task which will manually copy + // the metadata files in the merge assets folder and they will be added to the result apk + + // The next line is added to avoid adding another copyData implementation from the firebase plugin - https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/3943bb9147f43c41599e801d026378eba93d3f3a/publish/scripts/installer.js#L1105 + //buildMetadata.finalizedBy(copyMetadata) + finalizedBy copyMetadata + + description "builds metadata with provided jar dependencies" + + inputs.files("$MDG_JAVA_DEPENDENCIES") + + // make MDG aware of whitelist.mdg and blacklist.mdg files + // inputs.files(project.fileTree(dir: "$rootDir", include: "**/*.mdg")) + // use explicit inputs as the above makes the whole build-tools directory an input! + inputs.files("$BUILD_TOOLS_PATH/whitelist.mdg", "$BUILD_TOOLS_PATH/blacklist.mdg") + + // The generator itself decides what the metadata contains, so a change to + // it has to invalidate the output. Without this the task stays up-to-date + // across a generator rebuild and silently ships the previous run's tree. + inputs.files(Paths.get(rootPath, "android-metadata-generator.jar").toString()) + + // R8 keep rules derived from the same retained set as the metadata. Build + // output, not an asset -- shipping it would put 100KB+ of build input in + // the APK. + outputs.file(METADATA_KEEP_RULES) + + def classesDir = layout.buildDirectory.dir("intermediates/javac").get().asFile + if (classesDir.exists()) { + inputs.dir(classesDir) + } + + def kotlinClassesDir = layout.buildDirectory.dir("tmp/kotlin-classes").get().asFile + if (kotlinClassesDir.exists()) { + inputs.dir(kotlinClassesDir) + } + + outputs.files("$METADATA_OUT_PATH/treeNodeStream.dat", "$METADATA_OUT_PATH/treeStringsStream.dat", "$METADATA_OUT_PATH/treeValueStream.dat") + + workingDir "$BUILD_TOOLS_PATH" + mainClass = "-jar" + + doFirst { + // get compiled classes to pass to metadata generator + // these need to be called after the classes have compiled + new File(getMergedAssetsOutputPath() + "/metadata").deleteDir() + + def classesSubDirs = [] + def kotlinClassesSubDirs = [] + def selectedBuildType = project.ext.selectedBuildType + + rootProject.subprojects { + + def projectClassesDir = it.layout.buildDirectory.dir("intermediates/javac").get().asFile + def projectKotlinClassesDir = it.layout.buildDirectory.dir("tmp/kotlin-classes").get().asFile + + if (projectClassesDir.exists()) { + def projectClassesSubDirs = projectClassesDir.listFiles() + for (File subDir : projectClassesSubDirs) { + if (!classesSubDirs.contains(subDir)) { + classesSubDirs.add(subDir) + } + } + } + + if (projectKotlinClassesDir.exists()) { + def projectKotlinClassesSubDirs = projectKotlinClassesDir.listFiles() + for (File subDir : projectKotlinClassesSubDirs) { + if (!kotlinClassesSubDirs.contains(subDir)) { + kotlinClassesSubDirs.add(subDir) + } + } + } + } + + def generatedClasses = new LinkedList() + for (File subDir : classesSubDirs) { + if (subDir.getName() == selectedBuildType) { + generatedClasses.add(subDir.getAbsolutePath()) + } + } + + for (File subDir : kotlinClassesSubDirs) { + if (subDir.getName() == selectedBuildType) { + generatedClasses.add(subDir.getAbsolutePath()) + } + } + + def store = new ArrayList() + for (String dir : generatedClasses) { + listf(dir, store) + } + + + new File("$BUILD_TOOLS_PATH/$METADATA_JAVA_OUT").withWriter { out -> + store.each { + out.println it.absolutePath + } + } + + + new File("$BUILD_TOOLS_PATH/$MDG_OUTPUT_DIR").withWriter { out -> + out.println "$METADATA_OUT_PATH" + } + + new File("$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES").withWriterAppend { out -> + generatedClasses.each { out.println it } + } + + setOutputs outLogger + + def paramz = new ArrayList() + paramz.add(Paths.get(rootPath, "android-metadata-generator.jar")) + + if (enableAnalytics) { + paramz.add("analyticsFilePath=$analyticsFilePath") + } + + if (enableVerboseMDG) { + paramz.add("verbose") + } + + paramz.add("proguardOut=$METADATA_KEEP_RULES") + + // Grow the whitelist into a closure, and fail the build if that closure + // is unsound, only when the seed is the one we generated. An app's own + // whitelist.mdg is filtered as authored -- see the comment on + // generateMetadataSeed and Builder.applyClosureIfRequested. + if (nsFilterMetadata) { + paramz.add("enforceClosure=true") + } + + args paramz.toArray() + } +} + +task generateTypescriptDefinitions(type: BuildToolTask) { + // allJarLibraries is filled by collectAllJars at execution time; without + // this the generator was invoked with an empty -input and failed. The task + // has been off by default, so nothing exercised it. + dependsOn "collectAllJars" + + def dtsGeneratorJar = "dts-generator.jar" + if (!findProject(':dts-generator').is(null)) { + dependsOn ':dts-generator:jar' + dtsGeneratorJar = Paths.get(project(':dts-generator').projectDir.path, + "build/libs/dts-generator.jar").toString() + } + + def paramz = new ArrayList() + def includeDirs = ["com.android.support", "/platforms/" + android.compileSdkVersion] + + workingDir "$BUILD_TOOLS_PATH" + mainClass = "-jar" + + // Declared so Gradle can skip the task outright when no jar has changed, + // which is what makes generating typings by default affordable: the common + // build does no work at all, and a classpath change costs one full run. + // + // Keyed on the jars' *contents*, not the list of paths -- a dependency + // upgrade keeps the same path and would otherwise go unnoticed. + inputs.dir(extractedDependenciesDir).withPropertyName("dependencyJars") + inputs.file(android.sdkDirectory.getAbsolutePath() + "/platforms/" + + android.compileSdkVersion + "/android.jar").withPropertyName("androidJar") + if (!findProject(':dts-generator').is(null)) { + // By path, not by task: the other project is not configured yet, so + // tasks.named("jar") would fail during evaluation. + inputs.files(fileTree("${project(':dts-generator').projectDir}/build/libs") { + include "*.jar" + }).withPropertyName("generator") + } + outputs.dir(TYPINGS_PATH).withPropertyName("typings") + + doFirst { + delete "$TYPINGS_PATH" + + paramz.add(dtsGeneratorJar) + paramz.add("-input") + + for (String jarPath : allJarLibraries) { + // don't generate typings for runtime jars and classes + if (shouldIncludeDirForTypings(jarPath, includeDirs)) { + paramz.add(jarPath) + } + } + + paramz.add("-output") + paramz.add("$TYPINGS_PATH") + + new File("$TYPINGS_PATH").mkdirs() + + logger.info("Task generateTypescriptDefinitions: Call dts-generator.jar with arguments: " + paramz.toString().replaceAll(',', '')) + outLogger.withStyle(Style.SuccessHeader).println "Task generateTypescriptDefinitions: Call dts-generator.jar with arguments: " + paramz.toString().replaceAll(',', '') + + setOutputs outLogger + + args paramz.toArray() + } +} + +// Off by default: the dts-generator walks the whole compile classpath, which is +// slow, and most builds never look at the output. TypeScript users who need the +// typings to call anything native opt in per build with -PgenerateTypings +// (bare, or =true); -PgenerateTypings=false is still an explicit no. +generateTypescriptDefinitions.onlyIf { + project.hasProperty("generateTypings") && + (project.generateTypings.toString().isEmpty() + || Boolean.parseBoolean(project.generateTypings.toString())) +} + +collectAllJars.finalizedBy(generateTypescriptDefinitions) + +static def shouldIncludeDirForTypings(path, includeDirs) { + for (String p : includeDirs) { + if (path.indexOf(p) > -1) { + return true + } + } + + return false +} + +task 'copyTypings' { + doLast { + outLogger.withStyle(Style.Info).println "Copied generated typings to application root level. Make sure to import android.d.ts in reference.d.ts" + + copy { + from "$TYPINGS_PATH" + into "$USER_PROJECT_ROOT" + } + } +} + +copyTypings.onlyIf { generateTypescriptDefinitions.didWork } +generateTypescriptDefinitions.finalizedBy(copyTypings) + +task 'validateAppIdMatch' { + doLast { + def lineSeparator = System.getProperty("line.separator") + + if (project.hasProperty("nsApplicationIdentifier") && !project.hasProperty("release")) { + if (project.nsApplicationIdentifier != android.defaultConfig.applicationId && android.namespace != appIdentifier) { + def errorMessage = "${lineSeparator}WARNING: The Application identifier is different from the one inside \"package.json\" file.$lineSeparator" + + "NativeScript CLI might not work properly.$lineSeparator" + + "Remove applicationId from app.gradle and update the \"nativescript.id\" in package.json.$lineSeparator" + + "Actual: ${android.defaultConfig.applicationId}$lineSeparator" + + "Expected(from \"package.json\"): ${project.nsApplicationIdentifier}$lineSeparator" + + logger.error(errorMessage) + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////// +////////////////////////// COMPILE-TIME BYTECODE /////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// +// +// For RELEASE builds, replace the plain-JS app bundle in the merged assets with +// pre-compiled bytecode for the active JS engine (Hermes today; QuickJS/QuickJS-NG/ +// PrimJS are wired but gated off until their compilers + runtime support land). +// The files keep their .js names, so module resolution is unaffected; the runtime +// detects the engine's bytecode magic at load time (see js_run_bytecode_file / +// ModuleInternal) and runs it directly instead of parsing + compiling source, +// which greatly improves TTI. +// +// The engine-generic driver decides whether the selected engine has a usable +// compiler for the build host, so this task simply runs it for every release +// build and lets it no-op when there's nothing to do. It only touches the +// merged-assets build intermediate, never the app source. +// +// Disable for a release build with: -PnsBytecodeDisabled +// Force-enable for a debug build with: -PnsBytecodeForce +// Enable source maps with: -PnsBytecodeSourceMaps +// +// Overridable paths: +// -PbytecodeToolsDir=

— the bytecode-compiler tools folder +// -PbytecodeCompilerBinary= — force a specific compiler binary +// -PnodePath= — the node executable + +def bytecodeDisabled = project.hasProperty("nsBytecodeDisabled") || project.hasProperty("bytecodeDisabled") +// Force bytecode compilation even for debug builds. Bytecode is a release-only +// feature by default; this opt-in flag makes debug builds compile to bytecode +// too, which is handy for testing/debugging the bytecode path itself. +def bytecodeForced = project.hasProperty("nsBytecodeForce") || project.hasProperty("bytecodeForce") +// A release build is not reliably signalled by -Prelease (the CLI runs +// assembleRelease/bundleRelease without it), so detect it from the requested +// task names — the same heuristic the rest of this file uses. +def isReleaseBuild = project.hasProperty("release") || + project.gradle.startParameter.taskNames.any { it.toLowerCase().contains("release") } +project.ext.bytecodeEnabled = (isReleaseBuild || bytecodeForced) && !bytecodeDisabled + +// Announce the bytecode status once at configuration time so it's obvious in the +// build log whether ahead-of-time bytecode will be produced. +if (project.ext.bytecodeEnabled) { + def buildKind = isReleaseBuild ? "release build" : "debug build (forced via -PnsBytecodeForce)" + outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] ENABLED for ${buildKind} (engine: ${ns_engine})" + outLogger.withStyle(Style.Info).println "\t ~ app JS will be compiled to bytecode after asset merge (if ${ns_engine} has a compiler for this host)." + outLogger.withStyle(Style.Info).println "\t ~ disable with -PnsBytecodeDisabled" +} else { + def bytecodeReason = bytecodeDisabled ? "disabled via -PnsBytecodeDisabled" + : "debug build (release-only feature; force with -PnsBytecodeForce)" + outLogger.withStyle(Style.Info).println "\t ~ [bytecode] DISABLED — ${bytecodeReason}; shipping plain JS. isReleaseBuid: ${isReleaseBuild};" +} + +// The compiler tooling ships in the framework template under build-tools; fall +// back to the in-repo location when building the runtime itself (test-app). +def resolveBytecodeToolsDir = { -> + if (project.hasProperty("bytecodeToolsDir")) return bytecodeToolsDir as String + def candidates = [ + "$rootDir/build-tools/bytecode-compiler" as String, + "$rootDir/../tools/bytecode-compiler" as String, + ] + return candidates.find { new File(it).exists() } ?: candidates[0] +} +def resolveNodePath = { -> + project.hasProperty("nodePath") ? nodePath as String : "node" +} + +task compileBytecode { + // No inputs/outputs declared on purpose: the task is cheap and idempotent + // (it skips files that are already bytecode) and must re-run whenever the + // merged assets are refreshed. + onlyIf { project.ext.bytecodeEnabled } + doLast { + def assetsDir = getMergedAssetsOutputPath() + def appDir = assetsDir + "/app" + if (!new File(appDir).exists()) { + outLogger.withStyle(Style.Info).println "\t ~ [bytecode] no merged app assets at ${appDir}, skipping" + return + } + def toolsDir = resolveBytecodeToolsDir() + def script = "$toolsDir/compile-bytecode.js" + def node = resolveNodePath() + def sourceMaps = project.hasProperty("nsBytecodeSourceMaps") + // Resilient by default: a file that fails to compile is left as plain JS + // (the runtime loads source directly) and the build continues. Opt into + // fail-the-build behaviour with -PnsBytecodeStrict. + def strict = project.hasProperty("nsBytecodeStrict") + // QuickJS bytecode drops each function's source text, so + // Function.prototype.toString() returns "[native code]" for every + // function. Code that sniffs that string to tell a native class from a + // JS one then misreads every class as native -- @nativescript/core's + // Style constructor does exactly this. -PnsBytecodeKeepSource keeps the + // text, at roughly 4x the blob size. + def keepSource = project.hasProperty("nsBytecodeKeepSource") + + outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] compiling app JS → ${ns_engine} bytecode" + outLogger.withStyle(Style.Info).println "\t ~ engine: ${ns_engine}" + outLogger.withStyle(Style.Info).println "\t ~ app assets: ${assetsDir} (app/, internal/)" + outLogger.withStyle(Style.Info).println "\t ~ driver: ${script}" + if (project.hasProperty("bytecodeCompilerBinary")) { + outLogger.withStyle(Style.Info).println "\t ~ compiler: ${bytecodeCompilerBinary}" + } + outLogger.withStyle(Style.Info).println "\t ~ source maps: ${sourceMaps ? "on" : "off"}" + outLogger.withStyle(Style.Info).println "\t ~ on error: ${strict ? "fail the build (strict)" : "skip file, keep source"}" + + def cmd = [node, script, "--app", assetsDir, "--engine", ns_engine as String] + if (project.hasProperty("bytecodeCompilerBinary")) { + cmd += ["--compiler", bytecodeCompilerBinary as String] + } + if (keepSource) { + cmd += ["--keep-source"] + } + if (sourceMaps) { + cmd += ["--source-maps"] + } + if (!strict) { + cmd += ["--keep-going"] + } + // The driver prints its own "[bytecode] ... compiled N file(s)" summary to + // stdout, which surfaces in the build log below. + exec { + commandLine cmd + } + outLogger.withStyle(Style.SuccessHeader).println "\t + [bytecode] done." + } +} + +//////////////////////////////////////////////////////////////////////////////////// +////////////////////////////// OPTIONAL TASKS ////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +//////// custom clean /////////// +task cleanSbg(type: Delete) { + delete "$BUILD_TOOLS_PATH/$SBG_JS_PARSED_FILES", + "$BUILD_TOOLS_PATH/$SBG_JAVA_DEPENDENCIES", + "$BUILD_TOOLS_PATH/$SBG_INTERFACE_NAMES", + "$BUILD_TOOLS_PATH/$SBG_BINDINGS_NAME", + "$BUILD_TOOLS_PATH/$SBG_INPUT_FILE", + "$BUILD_TOOLS_PATH/$SBG_OUTPUT_FILE", + "$BUILD_TOOLS_PATH/$METADATA_JAVA_OUT", + "$OUTPUT_JAVA_DIR/com/tns/gen" +} + +task cleanMdg(type: Delete) { + delete "$BUILD_TOOLS_PATH/$MDG_OUTPUT_DIR", + "$BUILD_TOOLS_PATH/whitelist.mdg", + "$BUILD_TOOLS_PATH/blacklist.mdg", + "$BUILD_TOOLS_PATH/$MDG_JAVA_DEPENDENCIES", + "$METADATA_OUT_PATH" +} + +cleanSbg.dependsOn(cleanMdg) +clean.dependsOn(cleanSbg) + + +//dependsOn { +// pattern { +// include "merge*.Shaders" // Matches tasks starting with "merge" and ending with "Shaders" +// } +//} + + +tasks.configureEach({ DefaultTask currentTask -> + // println "\t ~ [DEBUG][app] build.gradle - currentTask = ${currentTask.name} ..." + + if (currentTask =~ /compile.+JavaWithJavac/) { + currentTask.dependsOn(runSbg) + } + + if (currentTask =~ /mergeDex.+/) { + currentTask.dependsOn(runSbg) + } + + if (currentTask =~ /compile.+Kotlin.+/) { + currentTask.dependsOn(runSbg) + } + + if (currentTask =~ /merge.*Assets/) { + currentTask.dependsOn(buildMetadata) + // Hand the runtime what it cannot infer, by patching the app's + // package.json where the assets were merged -- the app's own source file + // is never touched, and AppConfig already reads this one at startup. + // + // Driven off this task's own outputs rather than + // getMergedAssetsOutputPath(), which follows the pinned + // project.ext.selectedBuildType and so names the release assets even in + // a debug build. + currentTask.doLast { + if (!nsContentKeyedBindings) return + currentTask.outputs.files.files.each { File out -> + if (out.path.contains("${File.separator}incremental${File.separator}")) return + def packageJson = new File(out, "app/package.json") + if (!packageJson.exists()) return + + def json = new JsonSlurper().parseText(packageJson.text) + def androidConfig = json.android ?: [:] + // The static binding generator wrote the class names during this + // same build; the runtime has to look them up the same way. + androidConfig.contentKeyedBindings = true + json.android = androidConfig + packageJson.text = JsonOutput.prettyPrint(JsonOutput.toJson(json)) + + outLogger.withStyle(Style.SuccessHeader).println \ + "\t + [bindings] content-keyed bindings on (generator + runtime)" + } + } + // Compile the freshly-merged app JS to engine bytecode right after the + // assets are merged (the onlyIf guard makes this a no-op unless it's a + // release build with bytecode enabled). Packaging is made to wait for it below. + currentTask.finalizedBy(compileBytecode) + compileBytecode.mustRunAfter(currentTask) + } + + // The asset pipeline is: merge*Assets -> compress*Assets -> package*. The + // compress task reads the merged assets and zips each file into a `.js.jar` + // that is what actually lands in the APK/App Bundle. It only depends on the + // merge task, so without this it can run BEFORE compileBytecode and end up + // compressing plain source — the compiled bytecode in the merged assets then + // never reaches the package. Force compression to wait for compileBytecode so + // the order is merge -> compileBytecode -> compress -> package. (No cycle: + // compileBytecode only mustRunAfter the merge task, never the compress task.) + // Debug's compress*Assets task is included so a force-enabled (-PnsBytecodeForce) + // debug build compresses the compiled bytecode rather than plain source. The + // task is a no-op via compileBytecode's onlyIf guard when bytecode is off. + if (currentTask.name == "compressReleaseAssets" || currentTask.name == "compressDebugAssets") { + currentTask.dependsOn(compileBytecode) + } + + // Ensure the final APK/App Bundle packaging picks up the compiled bytecode. + // Match the packaging tasks by exact name — a broad /package.*Release/ also + // matches packageReleaseResources, which is upstream of buildMetadata -> + // mergeReleaseAssets -> compileBytecode and creates a circular dependency. + // Debug packaging is wired too so a force-enabled debug build ships bytecode; + // compileBytecode's onlyIf keeps it a no-op for ordinary debug builds. + if (currentTask.name in ["packageRelease", "packageReleaseBundle", "packageDebug", "packageDebugBundle"]) { + currentTask.dependsOn(compileBytecode) + } + + // R8 consumes metadata-keep-rules.pro, which buildMetadata writes. Without + // this ordering R8 either reads the previous build's rules or fails on a + // missing file, and either way strips classes the app reaches from JS. + if (currentTask =~ /minify.*WithR8/) { + currentTask.dependsOn(buildMetadata) + } + if (currentTask =~ /assemble.*Debug/ || currentTask =~ /assemble.*Release/) { + currentTask.finalizedBy("validateAppIdMatch") + } + + if (currentTask =~ /process.+Resources/) { + cleanupAllJars.dependsOn(currentTask) + } + +// if (currentTask.name == "extractProguardFiles") { +// currentTask.finalizedBy(buildMetadata) +// } +// + if (currentTask =~ /generate.+LintVitalReportModel/) { + currentTask.dependsOn(buildMetadata) + } + + if (currentTask =~ /lintVitalAnalyze.+/) { + currentTask.dependsOn(buildMetadata) + } +// +// if (currentTask =~ /merge.+GlobalSynthetics/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /optimize.+Resources/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /buildCMake.*/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /configureCMake.*/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /validateSigning.*/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /generate.*LintReportModel/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /generate.*AndroidTestResValues/) { +// // buildMetadata.dependsOn(currentTask) +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /generate.*AndroidTestLintModel/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /generate.*UnitTestLintModel/) { +// buildMetadata.mustRunAfter(currentTask) +// } +// +// if (currentTask =~ /generate.*UnitTestLintModel/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// +// if (currentTask =~ /lintAnalyze.*UnitTest/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /process.*JavaRes/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /strip.*DebugSymbols/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /merge.*JavaResource/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /lintAnalyze.*/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /lintAnalyze.*AndroidTest/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /bundle.*Resources/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /compile.*ArtProfile/) { +// currentTask.mustRunAfter(buildMetadata) +// } +// +// if (currentTask =~ /check.*DuplicateClasses/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /check.*AarMetadata/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /create.*CompatibleScreenManifests/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /process.*Manifest/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /generate.*ResValues/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /merge.*Resources/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /package.*Resources/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /process.*Resources/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /desugar.*Dependencies/) { +// currentTask.finalizedBy(buildMetadata) +// } +// +// if (currentTask =~ /merge.*JniLibFolders/) { +// currentTask.finalizedBy(buildMetadata) +// } + +}) + +rootProject.subprojects.forEach { + it.tasks.configureEach({ DefaultTask currentTask -> + if (currentTask =~ /.+bundleLibCompileToJar.*/) { + currentTask.finalizedBy(cleanupAllJars) + } + + if (currentTask =~ /bundleLibRuntimeToDir.*/) { + currentTask.finalizedBy(buildMetadata) + } + + if (currentTask =~ /compile.*LibraryResources/) { + currentTask.finalizedBy(buildMetadata) + } + }) +} diff --git a/platforms/android/test-app/app/gradle-helpers/AnalyticsCollector.gradle b/platforms/android/test-app/app/gradle-helpers/AnalyticsCollector.gradle new file mode 100644 index 000000000..72fa363a7 --- /dev/null +++ b/platforms/android/test-app/app/gradle-helpers/AnalyticsCollector.gradle @@ -0,0 +1,48 @@ +import groovy.json.JsonBuilder + +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.Path + +class AnalyticsCollector{ + + private final String analyticsFilePath + private boolean hasUseKotlinPropertyInApp = false + private boolean hasKotlinRuntimeClasses = false + + private AnalyticsCollector(String analyticsFilePath){ + this.analyticsFilePath = analyticsFilePath + } + + static AnalyticsCollector withOutputPath(String analyticsFilePath){ + return new AnalyticsCollector(analyticsFilePath) + } + + void markUseKotlinPropertyInApp(boolean useKotlin) { + hasUseKotlinPropertyInApp = useKotlin + } + + void writeAnalyticsFile() { + def jsonBuilder = new JsonBuilder() + def kotlinUsageData = new Object() + kotlinUsageData.metaClass.hasUseKotlinPropertyInApp = hasUseKotlinPropertyInApp + kotlinUsageData.metaClass.hasKotlinRuntimeClasses = hasKotlinRuntimeClasses + jsonBuilder(kotlinUsage: kotlinUsageData) + def prettyJson = jsonBuilder.toPrettyString() + + + + Path statisticsFilePath = Paths.get(analyticsFilePath) + + if (Files.notExists(statisticsFilePath)) { + Files.createDirectories(statisticsFilePath.getParent()) + Files.createFile(statisticsFilePath) + } + + Files.write(statisticsFilePath, prettyJson.getBytes(StandardCharsets.UTF_8)) + + } +} + +ext.AnalyticsCollector = AnalyticsCollector diff --git a/platforms/android/test-app/app/gradle-helpers/BuildToolTask.gradle b/platforms/android/test-app/app/gradle-helpers/BuildToolTask.gradle new file mode 100644 index 000000000..7b6052f4c --- /dev/null +++ b/platforms/android/test-app/app/gradle-helpers/BuildToolTask.gradle @@ -0,0 +1,50 @@ +import static org.gradle.internal.logging.text.StyledTextOutput.Style + +class BuildToolTask extends JavaExec { + void setOutputs(def logger) { + def logFile = new File("$workingDir/${name}.log") + if(logFile.exists()) { + logFile.delete() + } + standardOutput new FileOutputStream(logFile) + errorOutput new FailureOutputStream(logger, logFile) + } +} + +class FailureOutputStream extends OutputStream { + private logger + private File logFile + private currentLine = "" + private firstWrite = true + FailureOutputStream(inLogger, inLogFile) { + logger = inLogger + logFile = inLogFile + } + + @Override + void write(int i) throws IOException { + if(firstWrite) { + println "" + firstWrite = false + } + currentLine += String.valueOf((char) i) + } + + @Override + void flush() { + if(currentLine?.trim()) { + logger.withStyle(Style.Failure).println currentLine.trim() + currentLine = "" + } + } + + @Override + void close() { + if(!firstWrite && logFile.exists()) { + logger.withStyle(Style.Info).println "Detailed log here: ${logFile.getAbsolutePath()}\n" + } + super.close() + } +} + +ext.BuildToolTask = BuildToolTask \ No newline at end of file diff --git a/platforms/android/test-app/app/gradle-helpers/CustomExecutionLogger.gradle b/platforms/android/test-app/app/gradle-helpers/CustomExecutionLogger.gradle new file mode 100644 index 000000000..ec8ea6c42 --- /dev/null +++ b/platforms/android/test-app/app/gradle-helpers/CustomExecutionLogger.gradle @@ -0,0 +1,52 @@ +import org.gradle.internal.logging.text.StyledTextOutputFactory + +import static org.gradle.internal.logging.text.StyledTextOutput.Style +def outLogger = services.get(StyledTextOutputFactory).create("colouredOutputLogger") + +class CustomExecutionLogger extends BuildAdapter implements TaskExecutionListener { + private logger + private failedTask + + CustomExecutionLogger(passedLogger) { + logger = passedLogger + } + + void buildStarted(Gradle gradle) { + failedTask = null + } + + void beforeExecute(Task task) { + } + + void afterExecute(Task task, TaskState state) { + def failure = state.getFailure() + if(failure) { + failedTask = task + } + } + + void buildFinished(BuildResult result) { + def failure = result.getFailure() + if(failure) { + if(failedTask && (failedTask.getClass().getName().contains("BuildToolTask"))) { + // the error from this task is already logged + return + } + + println "" + logger.withStyle(Style.FailureHeader).println failure.getMessage() + + def causeException = failure.getCause() + while (causeException != null) { + failure = causeException + causeException = failure.getCause() + } + if(failure != causeException) { + logger.withStyle(Style.Failure).println failure.getMessage() + } + println "" + } + } +} + +gradle.useLogger(new CustomExecutionLogger(outLogger)) \ No newline at end of file diff --git a/platforms/android/test-app/app/proguard-rules.pro b/platforms/android/test-app/app/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/platforms/android/test-app/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/platforms/android/test-app/app/src/debug/java/com/tns/ErrorReport.java b/platforms/android/test-app/app/src/debug/java/com/tns/ErrorReport.java new file mode 100644 index 000000000..d2b5cf2d6 --- /dev/null +++ b/platforms/android/test-app/app/src/debug/java/com/tns/ErrorReport.java @@ -0,0 +1,512 @@ +package com.tns; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.text.SimpleDateFormat; +import java.util.Date; + +import android.Manifest; +import android.app.Activity; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import com.google.android.material.tabs.TabLayout; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.method.LinkMovementMethod; +import android.text.method.ScrollingMovementMethod; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +class ErrorReport implements TabLayout.OnTabSelectedListener { + public static final String ERROR_FILE_NAME = "hasError"; + private static AppCompatActivity activity; + + private TabLayout tabLayout; + private ViewPager viewPager; + private Context context; + + private static String exceptionMsg; + private static String logcatMsg; + + private static boolean checkingForPermissions = false; + + private final static String EXTRA_NATIVESCRIPT_ERROR_REPORT = "NativeScriptErrorMessage"; + private final static String EXTRA_ERROR_REPORT_MSG = "msg"; + private final static String EXTRA_PID = "pID"; + private final static int EXTRA_ERROR_REPORT_VALUE = 1; + + private static final int REQUEST_EXTERNAL_STORAGE = 1; + private static String[] PERMISSIONS_STORAGE = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + // Will prevent error activity from killing process if permission request dialog pops up + public static boolean isCheckingForPermissions() { + return checkingForPermissions; + } + + public static void resetCheckingForPermissions() { + checkingForPermissions = false; + } + + // The following will not compile if uncommented with compileSdk lower than 23 + public static void verifyStoragePermissions(Activity activity) { + // Check if we have write permission + final int version = Build.VERSION.SDK_INT; + if (version >= 23) { + try { + // Necessary to work around compile errors with compileSdk 22 and lower + Method checkSelfPermissionMethod; + try { + checkSelfPermissionMethod = ActivityCompat.class.getMethod("checkSelfPermission", Context.class, String.class); + } catch (NoSuchMethodException e) { + // method wasn't found, so there is no need to handle permissions explicitly + if (Util.isDebuggableApp(activity)) { + e.printStackTrace(); + } + return; + } + + int permission = (int) checkSelfPermissionMethod.invoke(null, activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + Method requestPermissionsMethod = ActivityCompat.class.getMethod("requestPermissions", Activity.class, PERMISSIONS_STORAGE.getClass(), int.class); + + checkingForPermissions = true; + + requestPermissionsMethod.invoke(null, activity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); + } + } catch (Exception e) { + Toast.makeText(activity, "Couldn't resolve permissions", Toast.LENGTH_LONG).show(); + if (Util.isDebuggableApp(activity)) { + e.printStackTrace(); + } + return; + } + } + } + + public ErrorReport(AppCompatActivity activity) { + ErrorReport.activity = activity; + this.context = activity.getApplicationContext(); + } + + static boolean startActivity(final Context context, String errorMessage) { + final Intent intent = getIntent(context); + if (intent == null) { + return false; // (if in release mode) don't do anything + } + + intent.putExtra(EXTRA_ERROR_REPORT_MSG, errorMessage); + + String PID = Integer.toString(android.os.Process.myPid()); + intent.putExtra(EXTRA_PID, PID); + + createErrorFile(context); + + try { + startPendingErrorActivity(context, intent); + } catch (CanceledException e) { + Log.d("ErrorReport", "Couldn't send pending intent! Exception: " + e.getMessage()); + } + + killProcess(context); + + return true; + } + + static void killProcess(Context context) { + // finish current activity and all below it first + if (context instanceof Activity) { + ((Activity) context).finishAffinity(); + } + + // kill process + android.os.Process.killProcess(android.os.Process.myPid()); + } + + static void startPendingErrorActivity(Context context, Intent intent) throws CanceledException { + int flags = PendingIntent.FLAG_CANCEL_CURRENT; + if (Build.VERSION.SDK_INT >= 31) { + flags = PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE; + } + + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, flags); + + pendingIntent.send(context, 0, intent); + } + + static String getErrorMessage(Throwable ex) { + String content; + PrintStream ps = null; + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ps = new PrintStream(baos); + ex.printStackTrace(ps); + + try { + content = baos.toString("UTF-8"); + } catch (UnsupportedEncodingException e) { + content = e.getMessage(); + } + } finally { + if (ps != null) { + ps.close(); + } + } + + return content; + } + + /* + * Gets the process Id of the running app and filters all + * output that doesn't belong to that process + * */ + public static String getLogcat(String pId) { + String content; + + try { + String logcatCommand = "logcat -d"; + Process process = java.lang.Runtime.getRuntime().exec(logcatCommand); + + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(process.getInputStream())); + + StringBuilder log = new StringBuilder(); + String line = ""; + String lineSeparator = System.getProperty("line.separator"); + while ((line = bufferedReader.readLine()) != null) { + if (line.contains(pId)) { + log.append(line); + log.append(lineSeparator); + } + } + + content = log.toString(); + } catch (IOException e) { + content = "Failed to read logcat"; + Log.e("TNS.Android", content); + } + + return content; + } + + static Intent getIntent(Context context) { + Class errorActivityClass; + + if (Util.isDebuggableApp(context)) { + errorActivityClass = ErrorReportActivity.class; + } else { + return null; + } + + Intent intent = new Intent(context, errorActivityClass); + + intent.putExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, EXTRA_ERROR_REPORT_VALUE); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + + return intent; + } + + static boolean hasIntent(Intent intent) { + int value = intent.getIntExtra(EXTRA_NATIVESCRIPT_ERROR_REPORT, 0); + + return value == EXTRA_ERROR_REPORT_VALUE; + } + + void buildUI() { + Intent intent = activity.getIntent(); + + exceptionMsg = intent.getStringExtra(EXTRA_ERROR_REPORT_MSG); + + String processId = intent.getStringExtra(EXTRA_PID); + logcatMsg = getLogcat(processId); + + int errActivityId = this.context.getResources().getIdentifier("error_activity", "layout", this.context.getPackageName()); + + activity.setContentView(errActivityId); + + int toolBarId = this.context.getResources().getIdentifier("toolbar", "id", this.context.getPackageName()); + + Toolbar toolbar = (Toolbar) activity.findViewById(toolBarId); + activity.setSupportActionBar(toolbar); + + final int tabLayoutId = this.context.getResources().getIdentifier("tabLayout", "id", this.context.getPackageName()); + + tabLayout = (TabLayout) activity.findViewById(tabLayoutId); + tabLayout.addTab(tabLayout.newTab().setText("Exception")); + tabLayout.addTab(tabLayout.newTab().setText("Logcat")); + tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); + int pagerId = this.context.getResources().getIdentifier("pager", "id", this.context.getPackageName()); + + viewPager = (ViewPager) activity.findViewById(pagerId); + + Pager adapter = new Pager(activity.getSupportFragmentManager(), tabLayout.getTabCount()); + + viewPager.setAdapter(adapter); + viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + tabLayout.getTabAt(position).select(); + viewPager.setCurrentItem(position); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + + this.addOnTabSelectedListener(tabLayout); + } + + private void addOnTabSelectedListener(TabLayout tabLayout) { + tabLayout.addOnTabSelectedListener(this); + } + private static void createErrorFile(final Context context) { + try { + File errFile = new File(context.getFilesDir(), ERROR_FILE_NAME); + errFile.createNewFile(); + } catch (IOException e) { + Log.d("ErrorReport", e.getMessage()); + } + } + + @Override + public void onTabSelected(TabLayout.Tab tab) { + viewPager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(TabLayout.Tab tab) { + + } + + @Override + public void onTabReselected(TabLayout.Tab tab) { + viewPager.setCurrentItem(tab.getPosition()); + } + + private class Pager extends FragmentStatePagerAdapter { + + int tabCount; + + @SuppressWarnings("deprecation") + public Pager(FragmentManager fm, int tabCount) { + super(fm); + this.tabCount = tabCount; + } + + @Override + public Fragment getItem(int position) { + switch (position) { + case 0: + return new ExceptionTab(); + case 1: + return new LogcatTab(); + default: + return null; + } + } + + @Override + public int getCount() { + return tabCount; + } + } + + + public static class ExceptionTab extends Fragment { + + public SpannableStringBuilder getStyledStacktrace(String trace) { + if (trace == null) return null; + String[] traceLines = trace.trim().split("\n"); + SpannableStringBuilder builder = new SpannableStringBuilder(); + boolean firstLine = true; + for (String line: traceLines) { + if (firstLine) { + firstLine = false; + } else { + builder.append("\n"); + builder.append("\n"); + } + + String[] nameAndPath = line.trim().split("\\("); + SpannableString nameSpan = new SpannableString(nameAndPath[0]); + nameSpan.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, nameAndPath[0].length(), 0); + builder.append(nameSpan); + + builder.append(" "); + if (nameAndPath.length > 1) { + SpannableString pathSpan = new SpannableString("(" + nameAndPath[1]); + pathSpan.setSpan(new AbsoluteSizeSpan(13, true),0, nameAndPath[1].length() + 1, 0); + pathSpan.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + Log.d("JS", line.trim()); + } + }, 0,nameAndPath[1].length() + 1, 0); + pathSpan.setSpan(new ForegroundColorSpan(Color.GRAY),0, nameAndPath[1].length() + 1, 0); + + builder.append(pathSpan); + } + } + return builder; + } + + public static void restartApp(Context context) { + PackageManager packageManager = context.getPackageManager(); + Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName()); + ComponentName componentName = intent.getComponent(); + Intent mainIntent = Intent.makeRestartActivityTask(componentName); + context.startActivity(mainIntent); + java.lang.Runtime.getRuntime().exit(0); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + int exceptionTabId = container.getContext().getResources().getIdentifier("exception_tab", "layout", container.getContext().getPackageName()); + View view = inflater.inflate(exceptionTabId, container, false); + + int errorExceptionViewId = activity.getResources().getIdentifier("errorException", "id", activity.getPackageName()); + TextView errorExceptionView = (TextView) activity.findViewById(errorExceptionViewId); + errorExceptionView.setMovementMethod(new ScrollingMovementMethod()); + + int errorStackTraceViewId = container.getContext().getResources().getIdentifier("errorStacktrace", "id", container.getContext().getPackageName()); + TextView errorStackTraceView = (TextView) view.findViewById(errorStackTraceViewId); + + String[] exceptionParts = exceptionMsg.split("StackTrace:"); + String error = exceptionParts[0]; + String trace = ""; + + if (exceptionParts.length > 1) { + for (int i=0;i < exceptionParts.length;i++) { + if (i == 0) continue; + trace += exceptionParts[i]; + } + } + + errorExceptionView.setText(error.trim()); + + errorStackTraceView.setText(trace != null ? getStyledStacktrace(trace) : "", TextView.BufferType.SPANNABLE); + errorStackTraceView.setMovementMethod(new ScrollingMovementMethod()); + errorStackTraceView.setMovementMethod(LinkMovementMethod.getInstance()); + errorStackTraceView.setEnabled(true); + + int btnCopyExceptionId = container.getContext().getResources().getIdentifier("btnCopyException", "id", container.getContext().getPackageName()); + Button copyToClipboard = (Button) view.findViewById(btnCopyExceptionId); + + int btnRestartAppId = container.getContext().getResources().getIdentifier("btnRestartApp", "id", container.getContext().getPackageName()); + Button restartApp = (Button) view.findViewById(btnRestartAppId); + restartApp.setOnClickListener(v -> { + restartApp(getContext().getApplicationContext()); + }); + copyToClipboard.setOnClickListener(v -> { + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("nsError", exceptionMsg); + clipboard.setPrimaryClip(clip); + }); + + return view; + } + } + + public static class LogcatTab extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + int logcatTabId = container.getContext().getResources().getIdentifier("logcat_tab", "layout", container.getContext().getPackageName()); + View view = inflater.inflate(logcatTabId, container, false); + + int textViewId = container.getContext().getResources().getIdentifier("logcatMsg", "id", container.getContext().getPackageName()); + TextView txtlogcatMsg = (TextView) view.findViewById(textViewId); + txtlogcatMsg.setText(logcatMsg); + + txtlogcatMsg.setMovementMethod(new ScrollingMovementMethod()); + + final String logName = "Log-" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + + int btnCopyLogcatId = container.getContext().getResources().getIdentifier("btnCopyLogcat", "id", container.getContext().getPackageName()); + Button copyToClipboard = (Button) view.findViewById(btnCopyLogcatId); + copyToClipboard.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + verifyStoragePermissions(activity); + + if (!isCheckingForPermissions()) { + try { + File dir = new File(Environment.getExternalStorageDirectory().getPath() + "/logcat-reports/"); + dir.mkdirs(); + + File logcatReportFile = new File(dir, logName); + FileOutputStream stream = new FileOutputStream(logcatReportFile); + OutputStreamWriter writer = new OutputStreamWriter(stream, "UTF-8"); + writer.write(logcatMsg); + writer.close(); + + String logPath = dir.getPath() + "/" + logName; + + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("logPath", logPath); + clipboard.setPrimaryClip(clip); + + Toast.makeText(activity, "Path copied to clipboard: " + logPath, Toast.LENGTH_LONG).show(); + } catch (Exception e) { + String err = "Could not write logcat report to sdcard. Make sure you have allowed access to external storage!"; + Toast.makeText(activity, err, Toast.LENGTH_LONG).show(); + if (Util.isDebuggableApp(container.getContext())) { + e.printStackTrace(); + } + } + } + } + }); + + return view; + } + } +} diff --git a/platforms/android/test-app/app/src/debug/java/com/tns/ErrorReportActivity.java b/platforms/android/test-app/app/src/debug/java/com/tns/ErrorReportActivity.java new file mode 100644 index 000000000..88a46658b --- /dev/null +++ b/platforms/android/test-app/app/src/debug/java/com/tns/ErrorReportActivity.java @@ -0,0 +1,52 @@ +package com.tns; + +import android.app.Application; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import android.widget.Toast; + +import java.lang.reflect.Method; + +import static com.tns.ErrorReport.isCheckingForPermissions; +import static com.tns.ErrorReport.resetCheckingForPermissions; + +public class ErrorReportActivity extends AppCompatActivity { + public void onCreate(Bundle savedInstanceState) { + setTheme(androidx.appcompat.R.style.Theme_AppCompat_NoActionBar); + + super.onCreate(savedInstanceState); + Application app = this.getApplication(); + Logger logger = new LogcatLogger(app); + + RuntimeHelper.initLiveSync(null, logger, app); + + new ErrorReport(this).buildUI(); + } + + @Override + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + + if (!isCheckingForPermissions()) { + ErrorReport.killProcess(this); + } + } + + // @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + try { + Method onRequestPermissionsResultMethod = AppCompatActivity.class.getMethod("onRequestPermissionsResult", int.class, permissions.getClass(), grantResults.getClass()); + onRequestPermissionsResultMethod.invoke(new AppCompatActivity() /* never do this */, requestCode, permissions, grantResults); + + resetCheckingForPermissions(); + } catch (Exception e) { + if (Util.isDebuggableApp(this)) { + e.printStackTrace(); + } + Toast.makeText(this, "Couldn't resolve permissions", Toast.LENGTH_LONG).show(); + resetCheckingForPermissions(); + } + } +} diff --git a/platforms/android/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java b/platforms/android/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java new file mode 100644 index 000000000..5778025fa --- /dev/null +++ b/platforms/android/test-app/app/src/debug/java/com/tns/NativeScriptSyncService.java @@ -0,0 +1,346 @@ +package com.tns; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.util.Log; + +public class NativeScriptSyncService { + private static String SYNC_ROOT_SOURCE_DIR = "/data/local/tmp/"; + private static final String SYNC_SOURCE_DIR = "/sync/"; + private static final String FULL_SYNC_SOURCE_DIR = "/fullsync/"; + private static final String REMOVED_SYNC_SOURCE_DIR = "/removedsync/"; + + private final Runtime runtime; + private static Logger logger; + private final Context context; + + private final String syncPath; + private final String fullSyncPath; + private final String removedSyncPath; + private final File fullSyncDir; + private final File syncDir; + private final File removedSyncDir; + + private LocalServerSocketThread localServerThread; + private Thread localServerJavaThread; + + public NativeScriptSyncService(Runtime runtime, Logger logger, Context context) { + this.runtime = runtime; + NativeScriptSyncService.logger = logger; + this.context = context; + + syncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + SYNC_SOURCE_DIR; + fullSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + FULL_SYNC_SOURCE_DIR; + removedSyncPath = SYNC_ROOT_SOURCE_DIR + context.getPackageName() + REMOVED_SYNC_SOURCE_DIR; + fullSyncDir = new File(fullSyncPath); + syncDir = new File(syncPath); + removedSyncDir = new File(removedSyncPath); + } + + public void sync() { + if (logger != null && logger.isEnabled()) { + logger.write("Sync is enabled:"); + logger.write("Sync path : " + syncPath); + logger.write("Full sync path : " + fullSyncPath); + logger.write("Removed files sync path: " + removedSyncPath); + } + + if (fullSyncDir.exists()) { + executeFullSync(context, fullSyncDir); + return; + } + + if (syncDir.exists()) { + executePartialSync(context, syncDir); + } + + if (removedSyncDir.exists()) { + executeRemovedSync(context, removedSyncDir); + } + } + + private class LocalServerSocketThread implements Runnable { + private volatile boolean running; + private final String name; + + private ListenerWorker commThread; + private LocalServerSocket serverSocket; + + public LocalServerSocketThread(String name) { + this.name = name; + this.running = false; + } + + public void stop() { + this.running = false; + try { + serverSocket.close(); + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + + public void run() { + running = true; + try { + serverSocket = new LocalServerSocket(this.name); + while (running) { + LocalSocket socket = serverSocket.accept(); + commThread = new ListenerWorker(socket); + new Thread(commThread).start(); + } + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + } + + private class ListenerWorker implements Runnable { + private final DataInputStream input; + private Closeable socket; + private OutputStream output; + + public ListenerWorker(LocalSocket socket) throws IOException { + this.socket = socket; + input = new DataInputStream(socket.getInputStream()); + output = socket.getOutputStream(); + } + + public void run() { + try { + int length = input.readInt(); + input.readFully(new byte[length]); // ignore the payload + executePartialSync(context, syncDir); + executeRemovedSync(context, removedSyncDir); + + runtime.runScript(new File(NativeScriptSyncService.this.context.getFilesDir(), "internal/livesync.js")); + try { + output.write(1); + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + socket.close(); + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + } + + public void startServer() { + localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync"); + localServerJavaThread = new Thread(localServerThread); + localServerJavaThread.start(); + } + + public static boolean isSyncEnabled(Context context) { + int flags; + boolean shouldExecuteSync = false; + try { + flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags; + shouldExecuteSync = ((flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); + } catch (NameNotFoundException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + return false; + } + + return shouldExecuteSync; + } + + final FileFilter deletingFilesFilter = new FileFilter() { + @Override + public boolean accept(File pathname) { + if (pathname.isDirectory()) { + return true; + } + + boolean success = pathname.delete(); + if (!success) { + logger.write("Syncing: file not deleted: " + pathname.getAbsolutePath().toString()); + } + return false; + } + }; + + private void deleteDir(File directory) { + File[] subDirectories = directory.listFiles(deletingFilesFilter); + if (subDirectories != null) { + for (int i = 0; i < subDirectories.length; i++) { + File subDir = subDirectories[i]; + deleteDir(subDir); + } + } + + boolean success = directory.delete(); + if (!success && directory.exists()) { + logger.write("Syncing: directory not deleted: " + directory.getAbsolutePath().toString()); + } + } + + private void moveFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) { + File[] files = sourceDir.listFiles(); + + if (files != null) { + if (logger.isEnabled()) { + logger.write("Syncing total number of fiiles: " + files.length); + } + + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + if (logger.isEnabled()) { + logger.write("Syncing: " + file.getAbsolutePath().toString()); + } + + String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); + File targetFileDir = new File(targetFilePath); + + File targetParent = targetFileDir.getParentFile(); + if (targetParent != null) { + targetParent.mkdirs(); + } + + boolean success = copyFile(file.getAbsolutePath(), targetFilePath); + if (!success) { + logger.write("Sync failed: " + file.getAbsolutePath().toString()); + } + } else { + moveFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + } + } + } else { + if (logger.isEnabled()) { + logger.write("Can't move files. Source is empty."); + } + } + } + + // this removes only the app directory from the device to preserve + // any existing files in /files directory on the device + private void executeFullSync(Context context, final File sourceDir) { + String appPath = context.getFilesDir().getAbsolutePath() + "/app"; + final File appDir = new File(appPath); + + if (appDir.exists()) { + deleteDir(appDir); + moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + } + } + + private void executePartialSync(Context context, File sourceDir) { + String appPath = context.getFilesDir().getAbsolutePath() + "/app"; + final File appDir = new File(appPath); + + if (!appDir.exists()) { + Log.e("TNS", "Application dir does not exists. Partial Sync failed. appDir: " + appPath); + return; + } + + if (logger.isEnabled()) { + logger.write("Syncing sourceDir " + sourceDir.getAbsolutePath() + " with " + appDir.getAbsolutePath()); + } + + moveFiles(sourceDir, sourceDir.getAbsolutePath(), appDir.getAbsolutePath()); + } + + private void deleteRemovedFiles(File sourceDir, String sourceRootAbsolutePath, String targetRootAbsolutePath) { + if (!sourceDir.exists()) { + if (logger.isEnabled()) { + logger.write("Directory does not exist: " + sourceDir.getAbsolutePath()); + } + } + + File[] files = sourceDir.listFiles(); + + if (files != null) { + for (int i = 0; i < files.length; i++) { + File file = files[i]; + String targetFilePath = file.getAbsolutePath().replace(sourceRootAbsolutePath, targetRootAbsolutePath); + File targetFile = new File(targetFilePath); + if (file.isFile()) { + if (logger.isEnabled()) { + logger.write("Syncing removed file: " + file.getAbsolutePath().toString()); + } + + targetFile.delete(); + } else { + deleteRemovedFiles(file, sourceRootAbsolutePath, targetRootAbsolutePath); + + // this is done so empty folders, if any, are deleted after we're don deleting files. + if (targetFile.listFiles().length == 0) { + targetFile.delete(); + } + } + } + } + } + + private void executeRemovedSync(final Context context, final File sourceDir) { + String appPath = context.getFilesDir().getAbsolutePath() + "/app"; + deleteRemovedFiles(sourceDir, sourceDir.getAbsolutePath(), appPath); + } + + private boolean copyFile(String sourceFile, String destinationFile) { + FileInputStream fis = null; + FileOutputStream fos = null; + + try { + fis = new FileInputStream(sourceFile); + fos = new FileOutputStream(destinationFile, false); + + byte[] buffer = new byte[4096]; + int read = 0; + + while ((read = fis.read(buffer)) != -1) { + fos.write(buffer, 0, read); + } + } catch (FileNotFoundException e) { + logger.write("Error copying file " + sourceFile); + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + return false; + } catch (IOException e) { + logger.write("Error copying file " + sourceFile); + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + return false; + } finally { + try { + if (fis != null) { + fis.close(); + } + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + } + } + + return true; + } +} \ No newline at end of file diff --git a/platforms/android/test-app/app/src/debug/java/com/tns/NativeScriptSyncServiceSocketImpl.java b/platforms/android/test-app/app/src/debug/java/com/tns/NativeScriptSyncServiceSocketImpl.java new file mode 100644 index 000000000..989a8a86e --- /dev/null +++ b/platforms/android/test-app/app/src/debug/java/com/tns/NativeScriptSyncServiceSocketImpl.java @@ -0,0 +1,448 @@ +package com.tns; + +import android.content.Context; +import android.net.LocalServerSocket; +import android.net.LocalSocket; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.io.OutputStream; +import java.util.Arrays; + +public class NativeScriptSyncServiceSocketImpl { + private static String DEVICE_APP_DIR; + + private final Runtime runtime; + private static Logger logger; + private final Context context; + + private LocalServerSocketThread localServerThread; + private Thread localServerJavaThread; + + public NativeScriptSyncServiceSocketImpl(Runtime runtime, Logger logger, Context context) { + this.runtime = runtime; + NativeScriptSyncServiceSocketImpl.logger = logger; + this.context = context; + DEVICE_APP_DIR = this.context.getFilesDir().getAbsolutePath() + "/app"; + } + + private class LocalServerSocketThread implements Runnable { + + private volatile boolean running; + private final String name; + + private LiveSyncWorker livesyncWorker; + private LocalServerSocket deviceSystemSocket; + + public LocalServerSocketThread(String name) { + this.name = name; + this.running = false; + } + + public void stop() { + this.running = false; + try { + deviceSystemSocket.close(); + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + + public void run() { + running = true; + try { + deviceSystemSocket = new LocalServerSocket(this.name); + while (running) { + LocalSocket systemSocket = deviceSystemSocket.accept(); + livesyncWorker = new LiveSyncWorker(systemSocket); + Thread liveSyncThread = setUpLivesyncThread(); + liveSyncThread.start(); + } + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + catch (java.security.NoSuchAlgorithmException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + + private Thread setUpLivesyncThread() { + Thread livesyncThread = new Thread(livesyncWorker); + livesyncThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.write(String.format("%s(%s): %s", t.getName(), t.getId(), e.toString())); + } + }); + livesyncThread.setName("Livesync Thread"); + return livesyncThread; + } + + @Override + protected void finalize() throws Throwable { + deviceSystemSocket.close(); + } + } + + public void startServer() { + localServerThread = new LocalServerSocketThread(context.getPackageName() + "-livesync"); + localServerJavaThread = new Thread(localServerThread); + localServerJavaThread.setName("Livesync Server Thread"); + localServerJavaThread.start(); + } + + private class LiveSyncWorker implements Runnable { + public static final int OPERATION_BYTE_SIZE = 1; + public static final int HASH_BYTE_SIZE = 16; + public static final int LENGTH_BYTE_SIZE = 1; + public static final int OPERATION_ID_BYTE_SIZE = 32; + public static final int DELETE_FILE_OPERATION = 7; + public static final int CREATE_FILE_OPERATION = 8; + public static final int DO_SYNC_OPERATION = 9; + public static final int ERROR_REPORT_CODE = 1; + public static final int OPERATION_END_NO_REFRESH_REPORT_CODE = 3; + public static final int OPERATION_END_REPORT_CODE = 2; + public static final int REPORT_CODE_SIZE = 1; + public static final int DO_REFRESH_LENGTH = 1; + public static final int DO_REFRESH_VALUE = 1; + public static final String FILE_NAME = "fileName"; + public static final String FILE_NAME_LENGTH = FILE_NAME + "Length"; + public static final String OPERATION = "operation"; + public static final String FILE_CONTENT = "fileContent"; + public static final String FILE_CONTENT_LENGTH = FILE_CONTENT + "Length"; + public static final int DEFAULT_OPERATION = -1; + private static final String PROTOCOL_VERSION = "0.2.0"; + private byte[] handshakeMessage; + private final DigestInputStream input; + private Closeable livesyncSocket; + private OutputStream output; + + public LiveSyncWorker(LocalSocket systemSocket) throws IOException, java.security.NoSuchAlgorithmException { + this.livesyncSocket = systemSocket; + MessageDigest md = MessageDigest.getInstance("MD5"); + input = new DigestInputStream(systemSocket.getInputStream(), md); + output = systemSocket.getOutputStream(); + handshakeMessage = getHandshakeMessage(); + } + + public void run() { + try { + output.write(handshakeMessage); + output.flush(); + + } catch (IOException e) { + logger.write(String.format("Error while LiveSyncing: Client socket might be closed!", e.toString())); + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + try { + do { + int operation = getOperation(); + + if (operation == DELETE_FILE_OPERATION) { + + String fileName = getFileName(); + validateData(); + deleteRecursive(new File(DEVICE_APP_DIR, fileName)); + + } else if (operation == CREATE_FILE_OPERATION) { + + String fileName = getFileName(); + int contentLength = getFileContentLength(fileName); + validateData(); + byte[] content = getFileContent(fileName, contentLength); + validateData(); + createOrOverrideFile(fileName, content); + + } else if (operation == DO_SYNC_OPERATION) { + byte[] operationUid = readNextBytes(OPERATION_ID_BYTE_SIZE); + byte doRefresh = readNextBytes(DO_REFRESH_LENGTH)[0]; + int doRefreshInt = (int)doRefresh; + int operationReportCode; + + validateData(); + if(runtime != null && doRefreshInt == DO_REFRESH_VALUE) { + runtime.runScript(new File(NativeScriptSyncServiceSocketImpl.this.context.getFilesDir(), "internal/livesync.js"), false); + operationReportCode = OPERATION_END_REPORT_CODE; + } else { + operationReportCode = OPERATION_END_NO_REFRESH_REPORT_CODE; + } + + output.write(getReportMessageBytes(operationReportCode, operationUid)); + output.flush(); + + } else if (operation == DEFAULT_OPERATION) { + logger.write("LiveSync: input stream is empty!"); + break; + } else { + throw new IllegalArgumentException(String.format("\nLiveSync: Operation not recognised. Received operation is %s.", operation)); + } + + } while (true); + } catch (Exception e) { + String message = String.format("Error while LiveSyncing: %s", e.toString()); + this.closeWithError(message); + } catch (Throwable e) { + String message = String.format("%s(%s): Error while LiveSyncing.\nOriginal Exception: %s", Thread.currentThread().getName(), Thread.currentThread().getId(), e.toString()); + this.closeWithError(message); + } + + } + + private byte[] getErrorMessageBytes(String message) { + return this.getReportMessageBytes(ERROR_REPORT_CODE, message.getBytes()); + } + + private byte[] getReportMessageBytes(int reportType, byte[] messageBytes) { + byte[] reportBytes = new byte[]{(byte)reportType}; + byte[] combined = new byte[messageBytes.length + REPORT_CODE_SIZE]; + + System.arraycopy(reportBytes,0,combined, 0, REPORT_CODE_SIZE); + System.arraycopy(messageBytes,0,combined, REPORT_CODE_SIZE, messageBytes.length); + + return combined; + } + + private byte[] getHandshakeMessage() { + byte[] protocolVersionBytes = PROTOCOL_VERSION.getBytes(); + byte[] versionLength = new byte[]{(byte)protocolVersionBytes.length}; + byte[] packageNameBytes = context.getPackageName().getBytes(); + byte[] combined = new byte[protocolVersionBytes.length + packageNameBytes.length + versionLength.length]; + + System.arraycopy(versionLength,0,combined, 0, versionLength.length); + System.arraycopy(protocolVersionBytes,0,combined, versionLength.length, protocolVersionBytes.length); + System.arraycopy(packageNameBytes,0,combined,protocolVersionBytes.length + versionLength.length,packageNameBytes.length); + + return combined; + } + + private void validateData() throws IOException { + MessageDigest messageDigest = input.getMessageDigest(); + byte[] digest = messageDigest.digest(); + input.on(false); + byte[] inputMD5 = readNextBytes(HASH_BYTE_SIZE); + input.on(true); + + + if(!Arrays.equals(digest, inputMD5)){ + throw new IllegalStateException(String.format("\nLiveSync: Validation of data failed.\nComputed hash: %s\nOriginal hash: %s ", Arrays.toString(digest), Arrays.toString(inputMD5))); + } + } + + /* + * Tries to read operation input stream + * If the stream is empty, method returns -1 + * */ + private int getOperation() { + Integer operation; + byte[] operationBuff = null; + try { + operationBuff = readNextBytes(OPERATION_BYTE_SIZE); + if (operationBuff == null) { + return DEFAULT_OPERATION; + } + operation = Integer.parseInt(new String(operationBuff)); + + } catch (Exception e) { + if(operationBuff == null){ + operationBuff = new byte[]{}; + } + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. Bytes read: $s %s\nOriginal Exception: %s", OPERATION, Arrays.toString(operationBuff), e.toString())); + } + return operation; + } + + private String getFileName() { + byte[] fileNameBuffer; + int fileNameLength = -1; + + try { + fileNameLength = getLength(); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s. \nOriginal Exception: %s", FILE_NAME_LENGTH, e.toString())); + } + + if(fileNameLength <= 0) { + throw new IllegalStateException(String.format("\nLiveSync: File name length must be positive number or zero. Provided length: %s.", fileNameLength)); + } + + try { + fileNameBuffer = readNextBytes(fileNameLength); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse %s.\nOriginal Exception: %s", FILE_NAME, e.toString())); + } + + if (fileNameBuffer == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes.", FILE_NAME)); + } + + String fileName = new String(fileNameBuffer); + if (fileName.trim().length() < fileNameLength) { + logger.write(String.format("WARNING: %s parsed length is less than %s. We read less information than you specified!", FILE_NAME, FILE_NAME_LENGTH)); + } + + return fileName.trim(); + } + + private int getFileContentLength(String fileName) throws IllegalStateException { + int contentLength; + + try { + contentLength = getLength(); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to read %s for %s.\nOriginal Exception: %s", FILE_CONTENT_LENGTH, fileName, e.toString())); + } + + if(contentLength < 0){ + throw new IllegalStateException(String.format("\nLiveSync: Content length must be positive number or zero. Provided content length: %s.", contentLength)); + } + + return contentLength; + } + + private byte[] getFileContent(String fileName, int contentLength) throws IllegalStateException { + byte[] contentBuff = null; + + try { + if(contentLength > 0) { + contentBuff = readNextBytes(contentLength); + } else if(contentLength == 0){ + contentBuff = new byte[]{}; + } + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: failed to parse content of file: %s.\nOriginal Exception: %s", fileName, e.toString())); + } + + if (contentLength != 0 && contentBuff == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing %s bytes for file: %s. Did you send %s bytes?", FILE_CONTENT, fileName, contentLength)); + } + + return contentBuff; + } + + private int getLength() { + byte[] lengthBuffer; + int lengthInt; + + try { + byte lengthSize = readNextBytes(LENGTH_BYTE_SIZE)[0]; + //Cast signed byte to unsigned int + int lengthSizeInt = ((int) lengthSize) & 0xFF; + lengthBuffer = readNextBytes(lengthSizeInt); + + if (lengthBuffer == null) { + throw new IllegalStateException(String.format("\nLiveSync: Missing size length bytes.")); + } + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: Failed to read size length. \nOriginal Exception: %s", e.toString())); + } + + try { + lengthInt = Integer.valueOf(new String(lengthBuffer)); + } catch (Exception e) { + throw new IllegalStateException(String.format("\nLiveSync: Failed to parse size length. \nOriginal Exception: %s", e.toString())); + } + + return lengthInt; + } + + private void createOrOverrideFile(String fileName, byte[] content) throws IOException { + File fileToCreate = prepareFile(fileName); + try { + + fileToCreate.getParentFile().mkdirs(); + FileOutputStream fos = new FileOutputStream(fileToCreate.getCanonicalPath()); + if(runtime != null) { + runtime.lock(); + } + fos.write(content); + fos.close(); + + } catch (Exception e) { + throw new IOException(String.format("\nLiveSync: failed to write file: %s\nOriginal Exception: %s", fileName, e.toString())); + } finally { + if(runtime != null) { + runtime.unlock(); + } + } + } + + void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) + for (File child : fileOrDirectory.listFiles()) { + deleteRecursive(child); + } + + fileOrDirectory.delete(); + } + + private File prepareFile(String fileName) { + File fileToCreate = new File(DEVICE_APP_DIR, fileName); + if (fileToCreate.exists()) { + fileToCreate.delete(); + } + return fileToCreate; + } + + /* + * Reads next bites from input stream. Bytes read depend on passed parameter. + * */ + private byte[] readNextBytes(int size) throws IOException { + byte[] buffer = new byte[size]; + int bytesRead = 0; + int bufferWriteOffset = bytesRead; + try { + do { + + bytesRead = this.input.read(buffer, bufferWriteOffset, size); + if (bytesRead == -1) { + if (bufferWriteOffset == 0) { + return null; + } + break; + } + size -= bytesRead; + bufferWriteOffset += bytesRead; + } while (size > 0); + } catch (IOException e) { + String message = e.getMessage(); + if (message != null && message.equals("Try again")) { + throw new IllegalStateException("Error while LiveSyncing: Read operation timed out."); + } else { + throw e; + } + } + + return buffer; + } + + private void closeWithError(String message) { + try { + output.write(getErrorMessageBytes(message)); + output.flush(); + logger.write(message); + this.livesyncSocket.close(); + } catch (IOException e) { + if (com.tns.Runtime.isDebuggable()) { + e.printStackTrace(); + } + } + } + + @Override + protected void finalize() throws Throwable { + this.livesyncSocket.close(); + } + } +} diff --git a/platforms/android/test-app/app/src/debug/java/com/tns/tests/OverloadResolutionFixture.java b/platforms/android/test-app/app/src/debug/java/com/tns/tests/OverloadResolutionFixture.java new file mode 100644 index 000000000..8e8dfbb96 --- /dev/null +++ b/platforms/android/test-app/app/src/debug/java/com/tns/tests/OverloadResolutionFixture.java @@ -0,0 +1,81 @@ +package com.tns.tests; + +public final class OverloadResolutionFixture { + private OverloadResolutionFixture() { + } + + public static final class HttpUrl { + } + + public interface Marker { + } + + public static final class MarkerImpl implements Marker { + } + + public static final class Builder { + public String url(HttpUrl value) { + return "http-url"; + } + + public String url(java.net.URL value) { + return "java-url"; + } + + public String url(String value) { + return "string"; + } + } + + public static String pick(Object value) { + return "object"; + } + + public static String pick(HttpUrl value) { + return "http-url"; + } + + public static String pick(String value) { + return "string"; + } + + public static String text(Object value) { + return "object"; + } + + public static String text(CharSequence value) { + return "char-sequence"; + } + + public static String text(String value) { + return "string"; + } + + public static String marker(Object value) { + return "object"; + } + + public static String marker(Marker value) { + return "marker"; + } + + public static String numeric(int value) { + return "int"; + } + + public static String numeric(long value) { + return "long"; + } + + public static String numeric(double value) { + return "double"; + } + + public static String array(Object[] value) { + return "object-array"; + } + + public static String array(String[] value) { + return "string-array"; + } +} diff --git a/platforms/android/test-app/app/src/debug/res/drawable/button.xml b/platforms/android/test-app/app/src/debug/res/drawable/button.xml new file mode 100644 index 000000000..ec005653c --- /dev/null +++ b/platforms/android/test-app/app/src/debug/res/drawable/button.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/platforms/android/test-app/app/src/debug/res/drawable/button_accented.xml b/platforms/android/test-app/app/src/debug/res/drawable/button_accented.xml new file mode 100644 index 000000000..2d5065498 --- /dev/null +++ b/platforms/android/test-app/app/src/debug/res/drawable/button_accented.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/platforms/android/test-app/app/src/debug/res/layout/error_activity.xml b/platforms/android/test-app/app/src/debug/res/layout/error_activity.xml new file mode 100644 index 000000000..9e614f6f3 --- /dev/null +++ b/platforms/android/test-app/app/src/debug/res/layout/error_activity.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + diff --git a/platforms/android/test-app/app/src/debug/res/layout/exception_tab.xml b/platforms/android/test-app/app/src/debug/res/layout/exception_tab.xml new file mode 100644 index 000000000..4d3a545f4 --- /dev/null +++ b/platforms/android/test-app/app/src/debug/res/layout/exception_tab.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + +