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/ci.yml b/.github/workflows/ci.yml index 627aeb287..b0a5aa548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,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_trusted_release.yml b/.github/workflows/npm_trusted_release.yml index c446b32b5..c9808dbf0 100644 --- a/.github/workflows/npm_trusted_release.yml +++ b/.github/workflows/npm_trusted_release.yml @@ -1,30 +1,39 @@ 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 any combination of the NativeScript runtime npm packages via npm +# trusted publishing (OIDC): +# +# iOS @nativescript/ios-{v8,hermes,jsc,quickjs} +# Android @nativescript/android-{v8,hermes,jsc,quickjs,quickjs-ng,primjs} +# RN @nativescript/react-native +# +# Selection is a (platform, engines) pair: `platform` picks the family, +# `engines` narrows it to a comma-separated subset (or `all`). Every selected +# package becomes one matrix entry that is built on the runner it needs +# (macos for the Apple/RN packages, ubuntu for the Android ones) and published +# by an identical downstream job. # # 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. on: workflow_dispatch: inputs: - engine: - description: "Package to release (engine package, react-native, or 'all' for every iOS engine)" + platform: + description: "Package family to release" required: true type: choice - default: v8 + default: ios options: - - v8 - - hermes - - jsc - - quickjs + - ios + - android - react-native - all + engines: + description: "Comma-separated engines, or 'all'. iOS: v8|hermes|jsc|quickjs. Android: v8|v8-10|v8-11|v8-13|hermes|jsc|quickjs|quickjs-ng|primjs. Ignored for react-native." + required: false + type: string + default: all release-type: description: "Version bump (patch/minor/major publish to 'latest'; prerelease uses 'preid' as the dist-tag)" required: false @@ -56,7 +65,7 @@ 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.platform }}-${{ inputs.engines }} cancel-in-progress: false env: @@ -69,61 +78,198 @@ jobs: permissions: {} outputs: targets: ${{ steps.compute.outputs.targets }} + summary: ${{ steps.compute.outputs.summary }} steps: - name: Compute matrix id: compute env: - ENGINE: ${{ inputs.engine }} + PLATFORM: ${{ inputs.platform }} + ENGINES: ${{ inputs.engines }} 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 + node - >> "$GITHUB_OUTPUT" <<'NODE' + const platform = (process.env.PLATFORM || '').trim(); + const raw = (process.env.ENGINES || 'all').trim().toLowerCase(); + const tokens = raw === '' ? ['all'] : raw.split(/[\s,]+/).filter(Boolean); + const wantsAll = tokens.includes('all'); + + const IOS_ENGINES = ['v8', 'hermes', 'jsc', 'quickjs']; + + // Android engine slug -> the -Pengine value gradle expects. The v8-NN + // slugs all ship as @nativescript/android-v8; they only differ in which + // V8 drop is linked in. + const ANDROID_ENGINES = { + 'v8': 'V8-13', + 'v8-10': 'V8-10', + 'v8-11': 'V8-11', + 'v8-13': 'V8-13', + 'hermes': 'HERMES', + 'jsc': 'JSC', + 'quickjs': 'QUICKJS', + 'quickjs-ng': 'QUICKJS_NG', + 'primjs': 'PRIMJS', + }; + const ANDROID_DEFAULT = ['v8', 'hermes', 'jsc', 'quickjs', 'quickjs-ng', 'primjs']; + + const iosTarget = (engine) => ({ + key: `ios-${engine}`, + platform: 'ios', + engine, + runner: 'macos-26', + package_dir: `packages/ios-${engine}`, + package_name: `@nativescript/ios-${engine}`, + tarball_basename: `nativescript-ios-${engine}`, + npm_tag_target: `ios-${engine}`, + npm_tag_script: 'scripts/get-npm-tag.js', + artifact_dir: `packages/ios-${engine}/dist`, + gradle_engine: '', + }); + + const androidTarget = (engine) => { + const gradleEngine = ANDROID_ENGINES[engine]; + // v8-10 / v8-11 / v8-13 all publish as android-v8. + const pkgEngine = engine.startsWith('v8') ? 'v8' : engine; + return { + key: `android-${engine}`, + platform: 'android', + engine, + runner: 'ubuntu-latest', + package_dir: `packages/android-${pkgEngine}`, + package_name: `@nativescript/android-${pkgEngine}`, + tarball_basename: `nativescript-android-${pkgEngine}`, + npm_tag_target: `android-${pkgEngine}`, + npm_tag_script: 'platforms/android/scripts/get-npm-tag.js', + artifact_dir: `platforms/android/dist_${gradleEngine.toLowerCase()}`, + gradle_engine: gradleEngine, + }; + }; + + const rnTarget = () => ({ + key: 'react-native', + platform: 'react-native', + engine: '', + runner: 'macos-26', + package_dir: 'packages/react-native', + package_name: '@nativescript/react-native', + tarball_basename: 'nativescript-react-native', + npm_tag_target: 'react-native', + npm_tag_script: 'scripts/get-npm-tag.js', + artifact_dir: 'packages/react-native/dist', + gradle_engine: '', + }); + + const fail = (msg) => { console.error(msg); process.exit(1); }; + const targets = []; + + const selectIos = () => { + if (wantsAll) return IOS_ENGINES; + const picked = tokens.filter((t) => IOS_ENGINES.includes(t)); + if (platform === 'ios' && picked.length !== tokens.length) { + fail(`Unsupported iOS engine(s): ${tokens.filter((t) => !IOS_ENGINES.includes(t)).join(', ')}. Valid: ${IOS_ENGINES.join(', ')}.`); + } + return picked; + }; + + const selectAndroid = () => { + if (wantsAll) return ANDROID_DEFAULT; + const valid = Object.keys(ANDROID_ENGINES); + const picked = tokens.filter((t) => valid.includes(t)); + if (platform === 'android' && picked.length !== tokens.length) { + fail(`Unsupported Android engine(s): ${tokens.filter((t) => !valid.includes(t)).join(', ')}. Valid: ${valid.join(', ')}.`); + } + // Two v8-NN slugs would publish the same package twice. + const v8Slugs = picked.filter((t) => t.startsWith('v8')); + if (v8Slugs.length > 1) { + fail(`Pick a single V8 variant; ${v8Slugs.join(', ')} all publish @nativescript/android-v8.`); + } + return picked; + }; + + switch (platform) { + case 'ios': + targets.push(...selectIos().map(iosTarget)); + break; + case 'android': + targets.push(...selectAndroid().map(androidTarget)); + break; + case 'react-native': + targets.push(rnTarget()); + break; + case 'all': { + const ios = selectIos(); + const android = selectAndroid(); + // With an explicit engine list, react-native only joins in when it + // is named; `all` means literally everything. + const rn = wantsAll || tokens.includes('react-native'); + const unmatched = wantsAll + ? [] + : tokens.filter((t) => t !== 'react-native' && !ios.includes(t) && !android.includes(t)); + if (unmatched.length) { + fail(`Engine(s) valid for neither iOS nor Android: ${unmatched.join(', ')}.`); + } + targets.push(...ios.map(iosTarget), ...android.map(androidTarget)); + if (rn) targets.push(rnTarget()); + break; + } + default: + fail(`Unsupported platform: ${platform}`); + } + + if (!targets.length) { + fail(`No packages selected for platform="${platform}" engines="${raw}".`); + } + + console.log(`targets=${JSON.stringify(targets)}`); + console.log(`summary=${targets.map((t) => t.package_name).join(', ')}`); + NODE + - name: Show selection + env: + SUMMARY: ${{ steps.compute.outputs.summary }} + run: | + echo "Selected packages: $SUMMARY" build: - name: Build ${{ matrix.target }} + name: Build ${{ matrix.key }} needs: matrix - runs-on: macos-26 + runs-on: ${{ matrix.runner }} 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 + include: ${{ fromJson(needs.matrix.outputs.targets) }} steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 with: egress-policy: audit - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 + if: ${{ matrix.platform != 'android' }} with: xcode-version: ${{ env.XCODE_VERSION }} - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 - submodules: recursive + # The Android build fetches (and patches) the engine submodules it needs + # itself, via `npm run setup`. + submodules: ${{ matrix.platform == 'android' && 'false' || 'recursive' }} - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: node-version: 24 registry-url: "https://registry.npmjs.org" - name: Install Python + if: ${{ matrix.platform != 'android' }} uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: "3" - - name: Install Dependencies + - name: Set up JDK 17 + if: ${{ matrix.platform == 'android' }} + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + with: + java-version: "17" + distribution: "temurin" + - name: Install dependencies (Apple) + if: ${{ matrix.platform != 'android' }} run: | npm install python3 -m pip install --upgrade pip six @@ -137,6 +283,14 @@ jobs: sudo mkdir -p /usr/local/bin sudo ln -sf "$(command -v cmake)" /usr/local/bin/cmake fi + - name: Install dependencies (Android) + if: ${{ matrix.platform == 'android' }} + working-directory: platforms/android + run: npm install + - name: Set up engine submodules, patches, and jsparser + if: ${{ matrix.platform == 'android' }} + working-directory: platforms/android + run: npm run setup - name: Bump version id: bump shell: bash @@ -145,28 +299,20 @@ jobs: PACKAGE_VERSION: ${{ inputs.version }} PREID: ${{ inputs.preid }} NPM_TAG_OVERRIDE: ${{ inputs.npm-tag }} - TARGET: ${{ matrix.target }} + PLATFORM: ${{ matrix.platform }} + ENGINE: ${{ matrix.engine }} + PACKAGE_DIR: ${{ matrix.package_dir }} + PACKAGE_NAME: ${{ matrix.package_name }} + NPM_TAG_TARGET: ${{ matrix.npm_tag_target }} + NPM_TAG_SCRIPT: ${{ matrix.npm_tag_script }} run: | set -euo pipefail release_type="$RELEASE_TYPE" package_version="$PACKAGE_VERSION" preid="$PREID" npm_tag_override="$NPM_TAG_OVERRIDE" - target="$TARGET" - if [ "$target" = "react-native" ]; then - pkg_dir="packages/react-native" - package_name="@nativescript/react-native" - 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" - fi - pushd "$pkg_dir" >/dev/null + pushd "$PACKAGE_DIR" >/dev/null if [ -n "$package_version" ]; then npm version "$package_version" --no-git-tag-version >/dev/null elif [ "$release_type" = "prerelease" ]; then @@ -177,7 +323,7 @@ jobs: 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") + NPM_TAG=$(NPM_VERSION="$NPM_VERSION" node "./$NPM_TAG_SCRIPT" "$NPM_TAG_TARGET") if [ -n "$npm_tag_override" ]; then case "$npm_tag_override" in *[[:space:]]*) @@ -195,62 +341,87 @@ jobs: echo "Exact prerelease publishes must include a prerelease identifier (for example 9.0.0-preview.0)." >&2 exit 1 fi + + if [ "$PLATFORM" = "android" ]; then + # Gradle copies platforms/android/package.json into the dist dir and packs + # it, so the package identity has to be stamped there. + npm --prefix platforms/android pkg set name="$PACKAGE_NAME" version="$NPM_VERSION" + elif [ "$PLATFORM" = "ios" ]; then + # build_npm_ios.sh reads the variant it is packaging from the environment. + echo "IOS_VARIANT=ios-${ENGINE}" >> "$GITHUB_ENV" + 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 "Resolved $package_name@$NPM_VERSION (tag: $NPM_TAG)" - - name: Build iOS engine (--${{ matrix.target }}) - if: ${{ matrix.target != 'react-native' }} + echo "Resolved $PACKAGE_NAME@$NPM_VERSION (tag: $NPM_TAG)" + - name: Build iOS engine (--${{ matrix.engine }}) + if: ${{ matrix.platform == 'ios' }} env: - TARGET: ${{ matrix.target }} - run: ./scripts/build_all_ios.sh "--${TARGET}" + ENGINE: ${{ matrix.engine }} + run: ./scripts/build_all_ios.sh "--${ENGINE}" - name: Build @nativescript/react-native - if: ${{ matrix.target == 'react-native' }} + if: ${{ matrix.platform == 'react-native' }} run: | ./scripts/build_all_react_native.sh ./scripts/build_react_native_turbomodule.sh + - name: Grant execute permission for gradlew + if: ${{ matrix.platform == 'android' }} + working-directory: platforms/android + run: chmod +x gradlew + - name: Build Android runtime (-Pengine=${{ matrix.gradle_engine }}) + if: ${{ matrix.platform == 'android' }} + working-directory: platforms/android + env: + GRADLE_ENGINE: ${{ matrix.gradle_engine }} + run: ./gradlew -Pengine="${GRADLE_ENGINE}" - name: Record metadata shell: bash env: - TARGET: ${{ matrix.target }} - PACKAGE_DIR: ${{ steps.bump.outputs.PACKAGE_DIR }} - PACKAGE_NAME: ${{ steps.bump.outputs.PACKAGE_NAME }} + KEY: ${{ matrix.key }} + PLATFORM: ${{ matrix.platform }} + ENGINE: ${{ matrix.engine }} + ARTIFACT_DIR: ${{ matrix.artifact_dir }} + PACKAGE_NAME: ${{ matrix.package_name }} + TARBALL_BASENAME: ${{ matrix.tarball_basename }} 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" - mkdir -p "$package_dir/dist" - cat > "$package_dir/dist/release-meta.json" <&2 + ls -la "$ARTIFACT_DIR" || true + exit 1 + fi + cat > "${ARTIFACT_DIR}/release-meta.json" <&2 exit 1 @@ -311,12 +482,12 @@ jobs: NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }} PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }} TARBALL: ${{ steps.meta.outputs.TARBALL }} - TARGET: ${{ matrix.target }} + KEY: ${{ matrix.key }} DRY_RUN: ${{ inputs.dry-run }} NODE_AUTH_TOKEN: "" run: | set -euo pipefail - TARBALL_PATH="npm-package/${TARGET}/${TARBALL}" + TARBALL_PATH="npm-package/${KEY}/${TARBALL}" PUBLISH_ARGS=("$TARBALL_PATH" --tag "$NPM_TAG" --access public --provenance) if [ "$DRY_RUN" = "true" ]; then PUBLISH_ARGS+=(--dry-run) @@ -336,12 +507,12 @@ jobs: NPM_TAG: ${{ steps.meta.outputs.NPM_TAG }} PACKAGE_NAME: ${{ steps.meta.outputs.PACKAGE_NAME }} TARBALL: ${{ steps.meta.outputs.TARBALL }} - TARGET: ${{ matrix.target }} + KEY: ${{ matrix.key }} DRY_RUN: ${{ inputs.dry-run }} NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} run: | set -euo pipefail - TARBALL_PATH="npm-package/${TARGET}/${TARBALL}" + TARBALL_PATH="npm-package/${KEY}/${TARBALL}" PUBLISH_ARGS=("$TARBALL_PATH" --tag "$NPM_TAG" --access public --provenance) if [ "$DRY_RUN" = "true" ]; then PUBLISH_ARGS+=(--dry-run) @@ -361,22 +532,28 @@ jobs: steps: - name: Print summary env: - PACKAGE_SELECTION: ${{ inputs.engine }} + PLATFORM: ${{ inputs.platform }} + ENGINES: ${{ inputs.engines }} 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 }} + PACKAGES: ${{ needs.matrix.outputs.summary }} BUILD_RESULT: ${{ needs.build.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 "| Field | Value |" + echo "| --- | --- |" + echo "| Platform | $PLATFORM |" + echo "| Engines | $ENGINES |" + echo "| Packages | $PACKAGES |" + echo "| Release type | $RELEASE_TYPE |" + echo "| Exact version | ${PACKAGE_VERSION:-(none)} |" + echo "| Preid | $PREID |" + echo "| NPM tag override | ${NPM_TAG_OVERRIDE:-(none)} |" + echo "| Dry run | $DRY_RUN |" + echo "| Build result | $BUILD_RESULT |" + echo "| Publish result | $PUBLISH_RESULT |" + } | tee -a "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index d8af2d700..6ea16553c 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,33 @@ 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/ diff --git a/NativeScript/CMakeLists.txt b/NativeScript/CMakeLists.txt index b9386f462..a4a781895 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,86 @@ 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 + ffi/objc/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} + ffi/objc/v8/NativeApiV8.mm + ffi/objc/v8/NativeApiV8HostObjects.mm + ffi/objc/v8/NativeApiV8Runtime.mm + ffi/objc/v8/NativeApiV8Value.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} + ffi/objc/jsc/NativeApiJSC.mm + ffi/objc/jsc/NativeApiJSCHostObjects.mm + ffi/objc/jsc/NativeApiJSCRuntime.mm + ffi/objc/jsc/NativeApiJSCValue.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} + ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm + ffi/objc/quickjs/NativeApiQuickJS.mm + ffi/objc/quickjs/NativeApiQuickJSRuntime.mm + ffi/objc/quickjs/NativeApiQuickJSValue.mm ) set(SOURCE_FILES ${FFI_SHARED_SOURCE_FILES} - runtime/NativeScriptException.mm + runtime/apple/NativeScriptException.mm ) if(NS_EFFECTIVE_FFI_BACKEND STREQUAL "napi") @@ -287,140 +311,147 @@ 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 + # with the Android build. 0.11.0 replaced libbf with dtoa. + ../vendor/quickjs/source_ng/cutils.c + ../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 +513,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 +539,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 +693,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 +750,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/napi/callbackhandlers/CallbackHandlers.cpp b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp new file mode 100644 index 000000000..cd2763121 --- /dev/null +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.cpp @@ -0,0 +1,1714 @@ +// +// 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; + +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) { + + 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 ((entry != nullptr) && entry->getIsResolved()) { + 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); +} + +jobjectArray +CallbackHandlers::GetImplementedInterfaces(napi_env env, JEnv &jEnv, + napi_value implementationObject) { + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { + return CallbackHandlers::GetJavaStringArray(jEnv, 0); + } + + vector interfacesToImplement; + + 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 (int 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(".")); + + jstring value = jEnv.NewStringUTF(node.c_str()); + interfacesToImplement.push_back(value); + } + } + } + + 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) { + if (implementationObject == nullptr || napi_util::is_undefined(env, implementationObject)) { + return CallbackHandlers::GetJavaStringArray(jEnv, 0); + } + + vector 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 (int 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; + } + + napi_value method; + + NAPI_GUARD(napi_get_property(env, implementationObject, element, &method)) {} + + bool methodFound = napi_util::is_of_type(env, method, napi_function); + + if (methodFound) { + jstring value = jEnv.NewStringUTF(name.c_str()); + methodNames.push_back(value); + } + } + + 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; \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h new file mode 100644 index 000000000..39f4f5c01 --- /dev/null +++ b/NativeScript/ffi/jni/napi/callbackhandlers/CallbackHandlers.h @@ -0,0 +1,377 @@ +#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); + + 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); + + /* + * 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_ */ \ No newline at end of file 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..7f41e29c5 --- /dev/null +++ b/NativeScript/ffi/jni/napi/constants/Constants.h @@ -0,0 +1,33 @@ +#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 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" + +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..176b7b910 --- /dev/null +++ b/NativeScript/ffi/jni/napi/conversion/ArgConverter.cpp @@ -0,0 +1,347 @@ +#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) { + TypeLongOperationsCache *cache; + auto itFound = s_type_long_operations_cache.find(env); + if (itFound == s_type_long_operations_cache.end()) { + cache = new TypeLongOperationsCache; + s_type_long_operations_cache.emplace(env, cache); + } else { + cache = itFound->second; + } + + 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 itFound = s_type_long_operations_cache.find(env); + if (itFound != s_type_long_operations_cache.end()) { + if (itFound->second->LongNumberCtorFunc) { + NAPI_GUARD(napi_delete_reference(env, itFound->second->LongNumberCtorFunc)) {} + } + if (itFound->second->NanNumberObject) { + NAPI_GUARD(napi_delete_reference(env, itFound->second->NanNumberObject)) {} + } + delete itFound->second; + s_type_long_operations_cache.erase(itFound); + } +} + +robin_hood::unordered_map 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..a42a14cf2 --- /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 robin_hood::unordered_map 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..7b829c3dd --- /dev/null +++ b/NativeScript/ffi/jni/napi/jni/LRUCache.h @@ -0,0 +1,181 @@ +#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 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..433f328e9 --- /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..7836dfbfd --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.cpp @@ -0,0 +1,135 @@ +#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) return retType; + auto reader = MetadataNode::getMetadataReader(); + + if (type == NodeType::Method && !this->getReturnType().empty()) { + retType = MetadataReader::GetReturnType(this->returnType); + } + + retTypeParsed = true; + + return retType; +} + +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..6e708526c --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataEntry.h @@ -0,0 +1,116 @@ +#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; + 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..490caa7d1 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.cpp @@ -0,0 +1,2632 @@ +#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; +} + +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; + } + 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; + } + 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); + 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') { + if (entry.name != lastMethodName) { + entry.type = NodeType::Method; + 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); + + if (hasDot) { + extendName = argv[0]; + implementationObject = argv[1]; + } else { + bool validExtend = GetExtendLocation(env, extendLocation, isTypeScriptExtend); + 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 extendNameAndLocation = + extendLocation + ArgConverter::ConvertToString(env, extendName); + string fullClassName; + string baseClassName = node->m_name; + 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); + + auto cachedData = GetCachedExtendedClassData(env, fullExtendedName); + 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); + + ExtendedClassCacheData cacheData(napi_util::make_ref(env, extendFuncCtor), fullExtendedName, + node); + auto cache = GetMetadataNodeCache(env); + cache->ExtendedCtorFuncCache.emplace(fullExtendedName, cacheData); + + 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); + + 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; + entry = &first; + } + + while ((callbackData != nullptr) && (entry == nullptr)) { + auto &candidates = callbackData->candidates; + + className = &callbackData->node->m_name; + + // Iterates through all methods and finds the best match based on the number of arguments + auto found = false; + for (auto &c: candidates) { + found = (!c.isExtensionFunction && c.getParamCount() == argc) || + (c.isExtensionFunction && c.getParamCount() == argc + 1); + if (found) { + if (c.isExtensionFunction) { + className = &c.getDeclaringType(); + } + entry = &c; + DEBUG_WRITE("MetaDataEntry Method %s's signature is: %s", + entry->getName().c_str(), + entry->getSig().c_str()); + break; + } + } + + // Iterates through the parent class's methods to find a good match + if (!found) { + callbackData = callbackData->parent; + } + } + + + 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(); + } + napi_value result = CallbackHandlers::CallJavaMethod(env, jsThis, *className, methodName, entry, + isFromInterface, first.isStatic, info, + argc, argv, initialCallbackData->objectManager); +// 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); + 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; + } + } + 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..03e59890f --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataNode.h @@ -0,0 +1,356 @@ +#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: + 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 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; + }; + + static bool s_profilerEnabled; + +}; + +#endif //METADATA_NODE_H \ No newline at end of file diff --git a/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp new file mode 100644 index 000000000..af2020493 --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataReader.cpp @@ -0,0 +1,364 @@ +#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; + + 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..e30cfb1ea --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MetadataTreeNode.h @@ -0,0 +1,49 @@ +#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; + + 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..58bbbdd8a --- /dev/null +++ b/NativeScript/ffi/jni/napi/metadata/MethodCache.h @@ -0,0 +1,369 @@ +#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; + napi_get_named_property(env, value, PROP_KEY_NULL_NODE_NAME, &nullNode); + + if (!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); + + 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..a5c67014a --- /dev/null +++ b/NativeScript/ffi/jni/napi/objectmanager/ObjectManager.cpp @@ -0,0 +1,1096 @@ +#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(); +} + +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/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/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/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/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..e98ba0431 --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.h @@ -0,0 +1,26 @@ +#ifndef NATIVE_API_JSI_H +#define NATIVE_API_JSI_H + +#include + +#include "ffi/objc/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/ffi/objc/hermes/NativeApiJsi.mm b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm new file mode 100644 index 000000000..9b3e2f93d --- /dev/null +++ b/NativeScript/ffi/objc/hermes/NativeApiJsi.mm @@ -0,0 +1,284 @@ +#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 "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/NativeApiJSCHostObjects.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm new file mode 100644 index 000000000..3caee72cf --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCHostObjects.mm @@ -0,0 +1,231 @@ +#include "NativeApiJSCRuntime.h" +#include "../shared/NativeApiStackValueArray.h" + +#ifdef TARGET_ENGINE_JSC + +namespace nativescript { +namespace engine { + +namespace jscengine { + +JSClassRef hostClass(Runtime& runtime); +JSClassRef functionClass(Runtime& runtime); +void setFunctionPrototype(JSGlobalContextRef context, JSObjectRef function); + +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; + } + if (shouldDeferToNativeInstancePrototype(context, object, propertyName, + holder)) { + 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 { + return holder->hostObject->set(runtime, PropNameID(stringToUtf8(propertyName)), + Value::borrowed(runtime, value)); + } 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); + 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 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 = "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; +} + +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_)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC 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..c81f46e93 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.h @@ -0,0 +1,903 @@ +#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 NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace engine { + +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 bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +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; +} + +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); + } + if (selectorGroupFunctionClass != nullptr) { + JSClassRelease(selectorGroupFunctionClass); + } + } + + JSGlobalContextRef context = nullptr; + JSClassRef hostClass = nullptr; + JSClassRef functionClass = nullptr; + JSClassRef selectorGroupFunctionClass = 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; +}; + +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); + +} // 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_; } + + 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; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + 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_; +}; + +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()); + } + + // The wrapper for an ObjC instance. Same object as createFromHostObject + // builds, but marked so property reads defer to the JS prototype chain when + // it carries the name -- V8 gets that from its kNonMasking template; JSC's + // class callback runs before the prototype is consulted, so it has to ask. + 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 JSError(runtime, jscengine::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, jscengine::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 = jscengine::makeJSString(name); + JSValueRef exception = nullptr; + JSObjectSetProperty(runtime.context(), local(runtime), property, value.local(runtime), + kJSPropertyAttributeNone, &exception); + JSStringRelease(property); + if (exception != nullptr) { + throw JSError(runtime, jscengine::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, jscengine::valueToUtf8(runtime.context(), 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); + } + + 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); + + 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, jscengine::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, jscengine::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, jscengine::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(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 JSError(runtime, jscengine::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, jscengine::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, jscengine::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(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 JSError(runtime, jscengine::valueToUtf8(runtime.context(), 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 JSError(runtime, jscengine::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 engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC + +#endif // NATIVESCRIPT_FFI_JSC_NATIVE_API_JSC_RUNTIME_H diff --git a/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm similarity index 77% rename from NativeScript/ffi/jsc/NativeApiJSCRuntime.mm rename to NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm index 3396af697..da5aa2ce8 100644 --- a/NativeScript/ffi/jsc/NativeApiJSCRuntime.mm +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCRuntime.mm @@ -2,8 +2,8 @@ #ifdef TARGET_ENGINE_JSC -namespace facebook { -namespace jsi { +namespace nativescript { +namespace engine { Object Runtime::global() { return Object::fromValueStorage(Value(*this, JSContextGetGlobalObject(context())).storage_); @@ -13,18 +13,18 @@ const std::string& sourceURL) { JSStringRef source = JSStringCreateWithUTF8CString( buffer != nullptr ? std::string(buffer->data(), buffer->size()).c_str() : ""); - JSStringRef url = jscdirect::makeJSString(sourceURL); + 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 JSError(*this, jscdirect::valueToUtf8(context(), exception)); + throw JSError(*this, jscengine::valueToUtf8(context(), exception)); } return Value(*this, result); } -} // namespace jsi -} // namespace facebook +} // namespace engine +} // namespace nativescript #endif // TARGET_ENGINE_JSC 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/NativeApiJSCValue.mm b/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm new file mode 100644 index 000000000..f66913761 --- /dev/null +++ b/NativeScript/ffi/objc/jsc/NativeApiJSCValue.mm @@ -0,0 +1,108 @@ +#include "NativeApiJSCRuntime.h" + +#ifdef TARGET_ENGINE_JSC + +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 {}; } + +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)); +} + +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, jscengine::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 engine +} // namespace nativescript + +#endif // TARGET_ENGINE_JSC 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/napi/Class.mm b/NativeScript/ffi/objc/napi/Class.mm similarity index 100% rename from NativeScript/ffi/napi/Class.mm rename to NativeScript/ffi/objc/napi/Class.mm 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/napi/Enum.mm b/NativeScript/ffi/objc/napi/Enum.mm similarity index 100% rename from NativeScript/ffi/napi/Enum.mm rename to NativeScript/ffi/objc/napi/Enum.mm 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 97% rename from NativeScript/ffi/napi/ObjCBridge.h rename to NativeScript/ffi/objc/napi/ObjCBridge.h index 6e7eb19a0..7b6228334 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); @@ -330,16 +330,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 +343,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 +352,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 +361,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 +372,6 @@ class ObjCBridgeState { RecentObjectWrapperRef& replaced = recentObjectWrappers[nextRecentObjectWrapperSlot++ % kRecentObjectWrapperLimit]; - deleteRecentObjectWrapperRef(env, replaced); replaced = entry; } inline napi_value getRecentObjectWrapper(napi_env env, id object) { @@ -397,12 +387,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 +406,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; 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/napi/Object.mm b/NativeScript/ffi/objc/napi/Object.mm similarity index 100% rename from NativeScript/ffi/napi/Object.mm rename to NativeScript/ffi/objc/napi/Object.mm 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/NativeApiQuickJSHostObjects.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm new file mode 100644 index 000000000..15532e17f --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSHostObjects.mm @@ -0,0 +1,348 @@ +#include "NativeApiQuickJSRuntime.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; + +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 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) { + Runtime runtime(stateForContext(ctx)); + auto* holder = static_cast(JS_GetOpaque(obj, gHostClassId)); + if (holder == nullptr || holder->hostObject == nullptr) { + return JS_UNDEFINED; + } + try { + bool handledByPrototype = false; + JSValue prototypeResult = + nativePrototypeProperty(ctx, obj, atom, receiver, holder, + &handledByPrototype); + if (handledByPrototype) { + return prototypeResult; + } + + Value result = holder->hostObject->get(runtime, PropNameID(atomToUtf8(ctx, atom))); + if (!result.isUndefined()) { + return result.local(runtime); + } + return JS_UNDEFINED; + } 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 { + bool handled = holder->hostObject->set( + runtime, PropNameID(atomToUtf8(ctx, atom)), + Value::borrowed(runtime, value)); + return handled ? 1 : 0; + } 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; + } + 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 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 = "NativeScriptEngineHostObject"; + 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 = "NativeScriptEngineFunction"; + def.call = nativeFunctionCall; + def.finalizer = nativeFunctionFinalize; + JS_NewClass(rt, gFunctionClassId, &def); + JS_SetClassProto(runtime.context(), gFunctionClassId, JS_NewObject(runtime.context())); + state->functionClassRegistered = 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; +} + +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; +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS 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..f3daf70aa --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.h @@ -0,0 +1,805 @@ +#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 NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace engine { + +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 bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +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; +}; + +extern JSClassID gHostClassId; +extern JSClassID gFunctionClassId; + +std::shared_ptr stateForContext(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 + +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_; } + 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() : 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() && JS_IsBigInt(jsValue()); } + bool isSymbol() const { return isQuickJS() && JS_IsSymbol(jsValue()); } + + Object asObject(Runtime& runtime) const; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + 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_; +}; + +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()); + } + + 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(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 == 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); + } + 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); + 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); + 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, quickjsengine::currentExceptionMessage(runtime.context())); + } + 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, quickjsengine::currentExceptionMessage(runtime.context())); + } + 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( + 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; + } + 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( + 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_FFI_QUICKJS_NATIVE_API_QUICKJS_RUNTIME_H diff --git a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm similarity index 92% rename from NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm rename to NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm index 6a64b8e57..d38eb3ac6 100644 --- a/NativeScript/ffi/quickjs/NativeApiQuickJSRuntime.mm +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSRuntime.mm @@ -2,8 +2,8 @@ #ifdef TARGET_ENGINE_QUICKJS -namespace facebook { -namespace jsi { +namespace nativescript { +namespace engine { String BigInt::toString(Runtime& runtime, int) const { JSValue value = local(runtime); @@ -34,7 +34,7 @@ return value; } -} // namespace jsi -} // namespace facebook +} // namespace engine +} // namespace nativescript #endif // TARGET_ENGINE_QUICKJS 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/NativeApiQuickJSValue.mm b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm new file mode 100644 index 000000000..bb6852019 --- /dev/null +++ b/NativeScript/ffi/objc/quickjs/NativeApiQuickJSValue.mm @@ -0,0 +1,111 @@ +#include "NativeApiQuickJSRuntime.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 {}; } +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)); +} +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 engine +} // namespace nativescript + +#endif // TARGET_ENGINE_QUICKJS 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/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..a6c2044e8 --- /dev/null +++ b/NativeScript/ffi/objc/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/ffi/objc/shared/NativeApiStackValueArray.h b/NativeScript/ffi/objc/shared/NativeApiStackValueArray.h new file mode 100644 index 000000000..b91091ddd --- /dev/null +++ b/NativeScript/ffi/objc/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/ffi/objc/shared/PreparedSignatureDispatch.h b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h new file mode 100644 index 000000000..c941006fe --- /dev/null +++ b/NativeScript/ffi/objc/shared/PreparedSignatureDispatch.h @@ -0,0 +1,80 @@ +#ifndef NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H +#define NATIVESCRIPT_FFI_SHARED_PREPARED_SIGNATURE_DISPATCH_H + +#include "SignatureDispatchCore.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_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..229347a74 --- /dev/null +++ b/NativeScript/ffi/objc/shared/SignatureDispatchCore.h @@ -0,0 +1,298 @@ +#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" + +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 constexpr metagen::MDSectionOffset kNullMetadataSectionOffset = + static_cast(0xFFFFFFFFu >> 1); + +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 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/shared/Tasks.cpp b/NativeScript/ffi/objc/shared/Tasks.cpp similarity index 100% rename from NativeScript/ffi/shared/Tasks.cpp rename to NativeScript/ffi/objc/shared/Tasks.cpp diff --git a/NativeScript/ffi/shared/Tasks.h b/NativeScript/ffi/objc/shared/Tasks.h similarity index 100% rename from NativeScript/ffi/shared/Tasks.h rename to NativeScript/ffi/objc/shared/Tasks.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..d16a91f96 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/HostObject.mm @@ -0,0 +1,615 @@ +#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 == "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, "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..611747cf8 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Install.mm @@ -0,0 +1,1861 @@ +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'); + 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 (_) { + } +}) +)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..af32e2d3e --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/Invocation.mm @@ -0,0 +1,1764 @@ +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; +} + +bool isNSComparisonResultOrderingName(const std::string& enumName, + const std::string& member) { + if (enumName != "NSComparisonResult") { + return false; + } + return member == "Ascending" || member == "Same" || member == "Descending"; +} + +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; + } + + 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(); + } + + 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..5baecf609 --- /dev/null +++ b/NativeScript/ffi/objc/shared/bridge/ObjCBridge.mm @@ -0,0 +1,2425 @@ +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 { + const NativeApiBridge* bridge = nullptr; + 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.bridge == this && 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{ + this, key, generation, {}, true, false, expectedValidationKey}; + return Value::undefined(); + } + storedValue = entry->value; + cachedStringLike = entry->stringLikeNative; + cache[firstSlot] = RoundTripCacheEntry{ + this, 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 { + const NativeApiBridge* bridge = nullptr; + 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.bridge == this && 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{this, key, generation, property, {}, true}; + return Value::undefined(); + } + auto propertyIt = objectIt->second.find(property); + if (propertyIt == objectIt->second.end() || propertyIt->second == nullptr) { + cache[slot] = + ObjectExpandoCacheEntry{this, key, generation, property, {}, true}; + return Value::undefined(); + } + cache[slot] = ObjectExpandoCacheEntry{ + this, 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 { + const NativeApiBridge* bridge = nullptr; + 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.bridge == this && 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{ + this, cls, generation, property, nullptr, true}; + return nullptr; + } + auto propIt = classIt->second.find(property); + if (propIt == classIt->second.end()) { + cache[slot] = PropertyGetterCacheEntry{ + this, cls, generation, property, nullptr, true}; + return nullptr; + } + cache[slot] = + PropertyGetterCacheEntry{this, 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 += 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); + } + 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}; + 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/NativeApiV8HostObjects.mm b/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm new file mode 100644 index 000000000..97ebf2fe7 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8HostObjects.mm @@ -0,0 +1,386 @@ +#include "NativeApiV8Runtime.h" +#include "../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(); + v8::String::Utf8Value utf8(isolate, property); + if (*utf8 == nullptr) { + return v8::Intercepted::kNo; + } + Value result = holder->hostObject->get( + runtime, PropNameID(std::string(*utf8, utf8.length()))); + 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; + }, + // 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; + } + Runtime runtime(holder->state); + try { + bool handled = holder->hostObject->set( + runtime, PropNameID(propertyNameToUtf8(info.GetIsolate(), property)), + Value(runtime, value)); + return handled ? v8::Intercepted::kYes : v8::Intercepted::kNo; + } 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 { + 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, v8::kEmbedderDataTypeTagDefault)); + 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; + }, + // 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 { + 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()); +} + +// 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(); + v8::String::Utf8Value utf8(isolate, property); + if (*utf8 == nullptr) { + return v8::Intercepted::kNo; + } + Value result = holder->hostObject->get( + runtime, PropNameID(std::string(*utf8, utf8.length()))); + 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; + }, + // 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(); + v8::String::Utf8Value utf8(isolate, property); + if (*utf8 == nullptr) { + return v8::Intercepted::kNo; + } + bool handled = holder->hostObject->set( + runtime, PropNameID(std::string(*utf8, utf8.length())), + Value(runtime, value)); + return handled ? v8::Intercepted::kYes : v8::Intercepted::kNo; + } 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 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; + }, + // 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 { + 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::kNonMasking)); + 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 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_)); +} + +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 diff --git a/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm new file mode 100644 index 000000000..97ef47828 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Marshalling.mm @@ -0,0 +1,593 @@ +// 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()) { + if (state->nativeClassArgumentLast.nativeClass != Nil && + !state->nativeClassArgumentLast.value.IsEmpty() && + state->nativeClassArgumentLast.value.Get(runtime.isolate()) == value) { + return state->nativeClassArgumentLast.nativeClass; + } + for (auto& entry : state->nativeClassArgumentCache) { + if (entry.nativeClass != Nil && !entry.value.IsEmpty() && + entry.value.Get(runtime.isolate()) == value) { + state->nativeClassArgumentLast.value.Reset(runtime.isolate(), value); + state->nativeClassArgumentLast.nativeClass = entry.nativeClass; + return 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 = state->nativeSelectorArgumentLast.selector; + return true; + } + for (auto& entry : state->nativeSelectorArgumentCache) { + if (entry.selector != nullptr && !entry.value.IsEmpty() && + entry.value.Get(runtime.isolate()) == value) { + *result = 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..a7f2c32b0 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.h @@ -0,0 +1,819 @@ +#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 NativeApiClassBuilderProtocol +@end + +#ifdef EMBED_METADATA_SIZE +extern const unsigned char embedded_metadata[EMBED_METADATA_SIZE]; +#endif + +namespace nativescript { +namespace engine { + +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 bool set(Runtime& runtime, const PropNameID& name, const Value& value); + virtual std::vector getPropertyNames(Runtime& runtime); +}; + +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 = Nil; + for (auto& entry : nativeClassArgumentCache) { + entry.value.Reset(); + entry.nativeClass = Nil; + } + nativeSelectorArgumentLast.value.Reset(); + nativeSelectorArgumentLast.selector = nullptr; + for (auto& entry : nativeSelectorArgumentCache) { + entry.value.Reset(); + entry.selector = nullptr; + } + 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; + v8::Global hostObjectTemplate; + v8::Global nativeObjectTemplate; // kNonMasking for instances + std::vector> retainedNativeData; + struct NativeClassArgumentCacheEntry { + v8::Global value; + Class nativeClass = Nil; + }; + NativeClassArgumentCacheEntry nativeClassArgumentLast; + NativeClassArgumentCacheEntry nativeClassArgumentCache[4]; + size_t nativeClassArgumentCacheNext = 0; + struct NativeSelectorArgumentCacheEntry { + v8::Global value; + SEL 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(); } + + Kind kind = Kind::Undefined; + bool boolValue = false; + double numberValue = 0; + v8::Global value; + v8::Local borrowedValue; +}; + +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 V8 engine operation failed."; +} + +inline void throwV8Exception(v8::Isolate* isolate, const std::exception& exception) { + isolate->ThrowException(v8::Exception::Error(makeV8String(isolate, exception.what()))); +} + +} // 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(); } + 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 { + 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_->value.Reset(runtime.isolate(), value.borrowedValue_); + kind_ = v8engine::ValueStorage::Kind::V8; + return; + } + kind_ = value.kind_; + boolValue_ = value.boolValue_; + numberValue_ = value.numberValue_; + borrowedValue_ = value.borrowedValue_; + storage_ = value.storage_; + } + Value(Runtime& runtime, Value&& value) + : kind_(value.kind_), + boolValue_(value.boolValue_), + numberValue_(value.numberValue_), + 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); + + 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; + String asString(Runtime& runtime) const; + BigInt getBigInt(Runtime& runtime) const; + + v8::Local local(Runtime& runtime) const { + v8::Isolate* isolate = runtime.isolate(); + 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_->value.Reset(runtime.isolate(), value); + } + + static Value borrowed(Runtime&, v8::Local value) { + Value result; + result.kind_ = v8engine::ValueStorage::Kind::V8Borrowed; + result.borrowedValue_ = value; + return result; + } + + // 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.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; + v8::Local borrowedValue_; + std::shared_ptr storage_; +}; + +class Object { + public: + Object() = default; + explicit Object(Runtime& runtime) + : storage_(std::make_shared(v8engine::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), + 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::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, v8engine::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, v8engine::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, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + } + + bool hasProperty(Runtime& runtime, const char* name) const { + v8::TryCatch tryCatch(runtime.isolate()); + return local(runtime) + ->Has(runtime.context(), + v8engine::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 == 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); + } + + 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); + + 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, v8engine::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, v8engine::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, v8engine::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 { + 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_->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, v8engine::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, v8engine::currentExceptionMessage(runtime.isolate(), 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_->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, v8engine::currentExceptionMessage(runtime.isolate(), tryCatch)); + } + return String(runtime, result); + } + + v8::Local local(Runtime& runtime) const { + 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_->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 { + return Value::fromStorage(storage_); + } +}; +} // namespace engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 + +#endif // NATIVESCRIPT_FFI_V8_NATIVE_API_V8_RUNTIME_H diff --git a/NativeScript/ffi/v8/NativeApiV8Runtime.mm b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm similarity index 77% rename from NativeScript/ffi/v8/NativeApiV8Runtime.mm rename to NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm index 681a211b0..10f8e3d86 100644 --- a/NativeScript/ffi/v8/NativeApiV8Runtime.mm +++ b/NativeScript/ffi/objc/v8/NativeApiV8Runtime.mm @@ -2,8 +2,8 @@ #ifdef TARGET_ENGINE_V8 -namespace facebook { -namespace jsi { +namespace nativescript { +namespace engine { Object Runtime::global() { return Object::fromValueStorage(Value(*this, context()->Global()).storage_); @@ -17,20 +17,20 @@ v8::NewStringType::kNormal, buffer != nullptr ? static_cast(buffer->size()) : 0) .ToLocalChecked(); - v8::Local resourceName = v8direct::makeV8String(isolate(), sourceURL); + v8::Local resourceName = v8engine::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)); + throw JSError(*this, v8engine::currentExceptionMessage(isolate(), tryCatch)); } v8::Local result; if (!script->Run(context()).ToLocal(&result)) { - throw JSError(*this, v8direct::currentExceptionMessage(isolate(), tryCatch)); + throw JSError(*this, v8engine::currentExceptionMessage(isolate(), tryCatch)); } return Value(*this, result); } -} // namespace jsi -} // namespace facebook +} // namespace engine +} // namespace nativescript #endif // TARGET_ENGINE_V8 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/NativeApiV8Value.mm b/NativeScript/ffi/objc/v8/NativeApiV8Value.mm new file mode 100644 index 000000000..dab1e0271 --- /dev/null +++ b/NativeScript/ffi/objc/v8/NativeApiV8Value.mm @@ -0,0 +1,242 @@ +#include "NativeApiV8Runtime.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 {}; } + +String::String(Runtime& runtime, v8::Local value) + : storage_(std::make_shared(v8engine::ValueStorage::Kind::V8)) { + storage_->value.Reset(runtime.isolate(), value); +} + +String::operator Value() const { + return Value::fromStorage(storage_); +} + +Value::Value(Runtime&, const String& value) { + storage_ = value.storage_; + kind_ = storage_->kind; +} +Value::Value(Runtime&, const Object& object) { + storage_ = object.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Function& function) { + storage_ = function.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const Array& array) { + storage_ = array.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const ArrayBuffer& arrayBuffer) { + storage_ = arrayBuffer.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} +Value::Value(Runtime&, const BigInt& bigint) { + storage_ = bigint.storage_; + kind_ = storage_ ? storage_->kind : v8engine::ValueStorage::Kind::Undefined; +} + +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 = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return isolate != nullptr && !borrowedValue_.IsEmpty() + ? borrowedValue_->BooleanValue(isolate) + : false; + } + if (kind_ == v8engine::ValueStorage::Kind::V8 && storage_ && !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 (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 = v8::Isolate::GetCurrent(); + 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) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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 = v8::Isolate::GetCurrent(); + 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->value.Reset(runtime.isolate(), borrowedValue_); + } + return Object::fromValueStorage(std::move(s)); +} + +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, v8engine::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 engine +} // namespace nativescript + +#endif // TARGET_ENGINE_V8 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/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/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/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..1a8697ccb 100644 --- a/NativeScript/napi/jsc/jsr.cpp +++ b/NativeScript/napi/jsc/jsr.cpp @@ -1,91 +1,436 @@ #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__ +// Forces a full, synchronous collection. Declared in +// JavaScriptCore/ExtraSymbolsForTAPI.h and exported by the prebuilt +// libJavaScriptCore, but not reachable through any public header. +// +// Unlike JSGarbageCollect, this entry point does NOT take the VM's API lock in +// the vendored jsc-android build -- it jumps straight to Heap::collectNow, and +// Heap::requestCollection opens with +// +// RELEASE_ASSERT(vm().atomStringTable() == Thread::current().atomStringTable()) +// +// The VM's AtomStringTable is only installed on a thread by JSLock::lock, so +// the caller must already hold the API lock. See JscCollectSynchronously below +// for why that is not automatic inside a napi callback. +extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef); + +namespace { + +// JSC hands a property callback the context with the API lock still held, so +// this runs on the locked side and may force the collection. +JSValueRef JscSyncGcGetProperty(JSContextRef ctx, JSObjectRef, JSStringRef, + JSValueRef*) { + JSSynchronousGarbageCollectForDebugging(ctx); + return JSValueMakeUndefined(ctx); +} + +JSClassRef JscSyncGcClass() { + static JSClassRef cls = [] { + JSClassDefinition definition{kJSClassDefinitionEmpty}; + definition.className = "NativeScriptSynchronousGC"; + definition.getProperty = JscSyncGcGetProperty; + return JSClassCreate(&definition); + }(); + return cls; +} + +// Runs a full collection that has actually finished by the time it returns. +// +// The detour through a property read is not decoration. JSC's C API takes the +// VM's API lock inside each entry point, but JSCallbackObject wraps a +// callAsFunction callback in JSLock::DropAllLocks -- so for the whole body of a +// napi function callback (which is what global.gc() is) this thread holds no +// lock and does not have the VM's AtomStringTable installed. Calling +// JSSynchronousGarbageCollectForDebugging from there trips the RELEASE_ASSERT +// quoted above and aborts the process. +// +// Property callbacks are not wrapped in DropAllLocks, so entering through +// JSObjectGetProperty puts us back inside the lock, which is exactly the state +// the collector requires. Verified on-device: the same probe reports the VM's +// table installed in getProperty and initialize, and the thread's default table +// in callAsFunction. +void JscCollectSynchronously(JSGlobalContextRef context) { + JSObjectRef trigger = JSObjectMake(context, JscSyncGcClass(), nullptr); + JSStringRef name = JSStringCreateWithUTF8CString("collect"); + JSObjectGetProperty(context, trigger, name, nullptr); + JSStringRelease(name); +} + +} // namespace +#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_napi_env(napi_env* env, napi_runtime runtime) { - if (env == nullptr) return napi_invalid_arg; +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, 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 skip the + // collection, so unreachable objects need not be gone by the time it + // returns, and the timers spec "frees up resources after complete" + // fails. Retrying it does not help; repeated hints are still hints. +#ifdef __ANDROID__ + JscCollectSynchronously(env->context); +#else JSGarbageCollect(env->context); +#endif 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); - return napi_ok; +#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; } -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 -# , /